permission-common: refactor batched requests

Signed-off-by: Vincenzo Scamporlino <vincenzos@spotify.com>
This commit is contained in:
Vincenzo Scamporlino
2025-04-10 16:05:59 +02:00
parent a5656fd532
commit ec42d827c2
@@ -55,6 +55,12 @@ const authorizePermissionResponseSchema: z.ZodSchema<AuthorizePermissionResponse
.or(z.literal(AuthorizeResult.DENY)),
});
const authorizePermissionResponseBatchSchema = z.object({
result: z.array(
z.literal(AuthorizeResult.ALLOW).or(z.literal(AuthorizeResult.DENY)),
),
});
const queryPermissionResponseSchema: z.ZodSchema<QueryPermissionResponse> =
z.union([
z.object({
@@ -129,6 +135,10 @@ export class PermissionClient implements PermissionEvaluator {
requests: AuthorizePermissionRequest[],
options?: PermissionClientRequestOptions,
): Promise<AuthorizePermissionResponse[]> {
if (!this.enabled) {
return requests.map(_ => ({ result: AuthorizeResult.ALLOW as const }));
}
if (this.enableBatchedRequests) {
return this.makeBatchedRequest(requests, options);
}
@@ -147,6 +157,10 @@ export class PermissionClient implements PermissionEvaluator {
queries: QueryPermissionRequest[],
options?: PermissionClientRequestOptions,
): Promise<QueryPermissionResponse[]> {
if (!this.enabled) {
return queries.map(_ => ({ result: AuthorizeResult.ALLOW as const }));
}
return this.makeRequest(queries, queryPermissionResponseSchema, options);
}
@@ -155,10 +169,6 @@ export class PermissionClient implements PermissionEvaluator {
itemSchema: z.ZodSchema<TResult>,
options?: AuthorizeRequestOptions,
) {
if (!this.enabled) {
return queries.map(_ => ({ result: AuthorizeResult.ALLOW as const }));
}
const request: PermissionMessageBatch<TQuery> = {
items: queries.map(query => ({
id: uuid.v4(),
@@ -166,25 +176,11 @@ export class PermissionClient implements PermissionEvaluator {
})),
};
const permissionApi = await this.discovery.getBaseUrl('permission');
const response = await fetch(`${permissionApi}/authorize`, {
method: 'POST',
body: JSON.stringify(request),
headers: {
...this.getAuthorizationHeader(options?.token),
'content-type': 'application/json',
},
});
if (!response.ok) {
throw await ResponseError.fromResponse(response);
}
const responseBody = await response.json();
const parsedResponse = responseSchema(
const parsedResponse = await this.makeRawRequest(
request,
itemSchema,
new Set(request.items.map(({ id }) => id)),
).parse(responseBody);
options,
);
const responsesById = parsedResponse.items.reduce((acc, r) => {
acc[r.id] = r;
@@ -198,10 +194,6 @@ export class PermissionClient implements PermissionEvaluator {
queries: AuthorizePermissionRequest[],
options?: AuthorizeRequestOptions,
) {
if (!this.enabled) {
return queries.map(_ => ({ result: AuthorizeResult.ALLOW as const }));
}
const request: Record<string, BatchedAuthorizePermissionRequest> = {};
for (const query of queries) {
@@ -218,30 +210,11 @@ export class PermissionClient implements PermissionEvaluator {
}
}
const rawRequest = { items: Object.values(request) };
const permissionApi = await this.discovery.getBaseUrl('permission');
const response = await fetch(`${permissionApi}/authorize`, {
method: 'POST',
body: JSON.stringify(rawRequest),
headers: {
...this.getAuthorizationHeader(options?.token),
'content-type': 'application/json',
},
});
if (!response.ok) {
throw await ResponseError.fromResponse(response);
}
const responseBody = await response.json();
const parsedResponse = responseSchema(
z.object({
result: z.array(
z.literal(AuthorizeResult.ALLOW).or(z.literal(AuthorizeResult.DENY)),
),
}),
new Set(rawRequest.items.map(({ id }) => id)),
).parse(responseBody);
const parsedResponse = await this.makeRawRequest(
{ items: Object.values(request) },
authorizePermissionResponseBatchSchema,
options,
);
return queries.map(query => {
const { id } = request[query.permission.name];
@@ -253,6 +226,32 @@ export class PermissionClient implements PermissionEvaluator {
});
}
private async makeRawRequest<TQuery, TResult>(
request: PermissionMessageBatch<TQuery>,
itemSchema: z.ZodSchema<TResult>,
options?: AuthorizeRequestOptions,
) {
const permissionApi = await this.discovery.getBaseUrl('permission');
const response = await fetch(`${permissionApi}/authorize`, {
method: 'POST',
body: JSON.stringify(request),
headers: {
...this.getAuthorizationHeader(options?.token),
'content-type': 'application/json',
},
});
if (!response.ok) {
throw await ResponseError.fromResponse(response);
}
const responseBody = await response.json();
return responseSchema(
itemSchema,
new Set(request.items.map(({ id }) => id)),
).parse(responseBody);
}
private getAuthorizationHeader(token?: string): Record<string, string> {
return token ? { Authorization: `Bearer ${token}` } : {};
}