Improve register handler errors API (#201)

This commit is contained in:
Lukasz Ostrowski 2023-02-28 13:54:58 +01:00 committed by GitHub
parent 5a93a166ab
commit a939281e98
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 69 additions and 52 deletions

View file

@ -0,0 +1,5 @@
---
"@saleor/app-sdk": minor
---
Register handler hooks will now respond with errors parsable by the dashboard. "Body" in error was removed, so client code can provide message and status.

View file

@ -67,7 +67,8 @@ export default createAppRegisterHandler({
allowedSaleorUrls: ["https://your-saleor.saleor.cloud/graphql/"], // optional, see options below allowedSaleorUrls: ["https://your-saleor.saleor.cloud/graphql/"], // optional, see options below
async onRequestVerified(req, { authData, respondWithError }) { async onRequestVerified(req, { authData, respondWithError }) {
await doSomethingAndBlockInstallation(authData.token).catch((err) => { await doSomethingAndBlockInstallation(authData.token).catch((err) => {
throw respondWithError({ body: "Error, installation will fail" }); // Return this method to break installation flow and show error in the Dashboard
return respondWithError({ message: "Error, installation will fail" });
}); });
}, },
}); });
@ -93,7 +94,7 @@ export type CreateAppRegisterHandlerOptions = {
authToken?: string; authToken?: string;
saleorDomain?: string; saleorDomain?: string;
saleorApiUrl?: string; saleorApiUrl?: string;
respondWithError: ({ status, message, body }) => never; // should throw respondWithError: ({ status, message }) => never; // will throw
} }
): Promise<void>; ): Promise<void>;
/** /**
@ -104,7 +105,7 @@ export type CreateAppRegisterHandlerOptions = {
request: Request, request: Request,
context: { context: {
authData: AuthData; authData: AuthData;
respondWithError: ({ status, message, body }) => never; // should throw respondWithError: ({ status, message }) => never; // will throw
} }
): Promise<void>; ): Promise<void>;
/** /**
@ -115,7 +116,7 @@ export type CreateAppRegisterHandlerOptions = {
request: Request, request: Request,
context: { context: {
authData: AuthData; authData: AuthData;
respondWithError: ({ status, message, body }) => never; // should throw respondWithError: ({ status, message }) => never; // will throw
} }
): Promise<void>; ): Promise<void>;
/** /**
@ -127,7 +128,7 @@ export type CreateAppRegisterHandlerOptions = {
context: { context: {
authData: AuthData; authData: AuthData;
error: unknown; error: unknown;
respondWithError: ({ status, message, body }) => never; // should throw respondWithError: ({ status, message }) => never; // will throw
} }
): Promise<void>; ): Promise<void>;
}; };

View file

@ -219,13 +219,12 @@ describe("create-app-register-handler", () => {
context: { context: {
respondWithError(params: { status: number; body: string; message: string }): Error; respondWithError(params: { status: number; body: string; message: string }): Error;
} }
) => { ) =>
throw context.respondWithError({ context.respondWithError({
status: 401, status: 401,
body: "test", body: "test",
message: "test message", message: "test message",
}); })
}
); );
const { res, req } = createMocks({ const { res, req } = createMocks({
@ -253,7 +252,13 @@ describe("create-app-register-handler", () => {
await handler(req, res); await handler(req, res);
expect(res._getStatusCode()).toBe(401); expect(res._getStatusCode()).toBe(401);
expect(res._getData()).toBe("test"); expect(res._getData()).toEqual({
success: false,
error: {
code: "REGISTER_HANDLER_HOOK_ERROR",
message: "test message",
},
});
}); });
}); });
}); });

View file

@ -16,33 +16,49 @@ const debug = createDebug("createAppRegisterHandler");
type HookCallbackErrorParams = { type HookCallbackErrorParams = {
status?: number; status?: number;
body?: object;
message?: string; message?: string;
}; };
class RegisterCallbackError extends Error { class RegisterCallbackError extends Error {
public status = 500; public status = 500;
public body: object = {};
constructor(errorParams: HookCallbackErrorParams) { constructor(errorParams: HookCallbackErrorParams) {
super(errorParams.message); super(errorParams.message);
if (errorParams.status) { if (errorParams.status) {
this.status = errorParams.status; this.status = errorParams.status;
} }
if (errorParams.body) {
this.body = errorParams.body;
}
} }
} }
const createCallbackError = (params: HookCallbackErrorParams) => new RegisterCallbackError(params); const createCallbackError = (params: HookCallbackErrorParams) => {
throw new RegisterCallbackError(params);
};
export type RegisterHandlerResponseBody = {
success: boolean;
error?: {
code?: string;
message?: string;
};
};
export const createRegisterHandlerResponseBody = (
success: boolean,
error?: RegisterHandlerResponseBody["error"]
): RegisterHandlerResponseBody => ({
success,
error,
});
const handleHookError = (e: RegisterCallbackError | unknown) => { const handleHookError = (e: RegisterCallbackError | unknown) => {
if (e instanceof RegisterCallbackError) { if (e instanceof RegisterCallbackError) {
return new Response(e.body, { status: e.status }); return new Response(
createRegisterHandlerResponseBody(false, {
code: "REGISTER_HANDLER_HOOK_ERROR",
message: e.message,
}),
{ status: e.status }
);
} }
return Response.InternalServerError("Error during app installation"); return Response.InternalServerError("Error during app installation");
}; };
@ -141,27 +157,24 @@ export const createAppRegisterHandler = ({
if (!validateAllowSaleorUrls(saleorApiUrl, allowedSaleorUrls)) { if (!validateAllowSaleorUrls(saleorApiUrl, allowedSaleorUrls)) {
debug("Validation of URL %s against allowSaleorUrls param resolves to false, throwing"); debug("Validation of URL %s against allowSaleorUrls param resolves to false, throwing");
return Response.Forbidden({ return Response.Forbidden(
success: false, createRegisterHandlerResponseBody(false, {
error: {
code: "SALEOR_URL_PROHIBITED", code: "SALEOR_URL_PROHIBITED",
message: "This app expects to be installed only in allowed saleor instances", message: "This app expects to be installed only in allowed saleor instances",
}, })
}); );
} }
const { configured: aplConfigured } = await apl.isConfigured(); const { configured: aplConfigured } = await apl.isConfigured();
if (!aplConfigured) { if (!aplConfigured) {
debug("The APL has not been configured"); debug("The APL has not been configured");
return new Response( return new Response(
{ createRegisterHandlerResponseBody(false, {
success: false, code: "APL_NOT_CONFIGURED",
error: { message: "APL_NOT_CONFIGURED. App is configured properly. Check APL docs for help.",
code: "APL_NOT_CONFIGURED", }),
message: "APL_NOT_CONFIGURED. App is configured properly. Check APL docs for help.",
},
},
{ {
status: 503, status: 503,
} }
@ -172,14 +185,11 @@ export const createAppRegisterHandler = ({
const appId = await getAppId({ saleorApiUrl, token: authToken }); const appId = await getAppId({ saleorApiUrl, token: authToken });
if (!appId) { if (!appId) {
return new Response( return new Response(
{ createRegisterHandlerResponseBody(false, {
success: false, code: "UNKNOWN_APP_ID",
error: { message:
code: "UNKNOWN_APP_ID", "The auth data given during registration request could not be used to fetch app ID.",
message: }),
"The auth data given during registration request could not be used to fetch app ID.",
},
},
{ {
status: 401, status: 401,
} }
@ -190,13 +200,10 @@ export const createAppRegisterHandler = ({
const jwks = await fetchRemoteJwks(saleorApiUrl); const jwks = await fetchRemoteJwks(saleorApiUrl);
if (!jwks) { if (!jwks) {
return new Response( return new Response(
{ createRegisterHandlerResponseBody(false, {
success: false, code: "JWKS_NOT_AVAILABLE",
error: { message: "Can't fetch the remote JWKS.",
code: "JWKS_NOT_AVAILABLE", }),
message: "Can't fetch the remote JWKS.",
},
},
{ {
status: 401, status: 401,
} }
@ -262,17 +269,16 @@ export const createAppRegisterHandler = ({
} }
} }
return Response.InternalServerError({ return Response.InternalServerError(
success: false, createRegisterHandlerResponseBody(false, {
error: {
message: "Registration failed: could not save the auth data.", message: "Registration failed: could not save the auth data.",
}, })
}); );
} }
debug("Register complete"); debug("Register complete");
return Response.OK({ success: true }); return Response.OK(createRegisterHandlerResponseBody(true));
}; };
return toNextHandler([ return toNextHandler([