Add protected handlers for next (#121)
* Add protected handlers for next * Remove unneeded awaits
This commit is contained in:
parent
d8b22bf583
commit
a0a19550c2
10 changed files with 439 additions and 3 deletions
43
src/get-app-id.ts
Normal file
43
src/get-app-id.ts
Normal file
|
@ -0,0 +1,43 @@
|
|||
import { AuthData } from "./APL";
|
||||
import { createDebug } from "./debug";
|
||||
|
||||
const debug = createDebug("getAppId");
|
||||
|
||||
type GetIdResponseType = {
|
||||
data?: {
|
||||
app?: {
|
||||
id: string;
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
export const getAppId = async (authData: AuthData): Promise<string | undefined> => {
|
||||
try {
|
||||
const response = await fetch(`https://${authData.domain}/graphql/`, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
Authorization: `Bearer ${authData.token}`,
|
||||
},
|
||||
body: JSON.stringify({
|
||||
query: `
|
||||
{
|
||||
app{
|
||||
id
|
||||
}
|
||||
}
|
||||
`,
|
||||
}),
|
||||
});
|
||||
if (response.status !== 200) {
|
||||
debug(`Could not get the app ID: Saleor API has response code ${response.status}`);
|
||||
return undefined;
|
||||
}
|
||||
const body = (await response.json()) as GetIdResponseType;
|
||||
const appId = body.data?.app?.id;
|
||||
return appId;
|
||||
} catch (e) {
|
||||
debug("Could not get the app ID: %O", e);
|
||||
return undefined;
|
||||
}
|
||||
};
|
57
src/handlers/next/create-protected-handler.ts
Normal file
57
src/handlers/next/create-protected-handler.ts
Normal file
|
@ -0,0 +1,57 @@
|
|||
import { NextApiHandler, NextApiRequest, NextApiResponse } from "next";
|
||||
|
||||
import { APL } from "../../APL";
|
||||
import { createDebug } from "../../debug";
|
||||
import { ProtectedHandlerContext } from "./process-async-saleor-webhook";
|
||||
import {
|
||||
processSaleorProtectedHandler,
|
||||
ProtectedHandlerError,
|
||||
SaleorProtectedHandlerError,
|
||||
} from "./process-protected-handler";
|
||||
|
||||
const debug = createDebug("ProtectedHandler");
|
||||
|
||||
export const ProtectedHandlerErrorCodeMap: Record<SaleorProtectedHandlerError, number> = {
|
||||
OTHER: 500,
|
||||
MISSING_HOST_HEADER: 400,
|
||||
MISSING_DOMAIN_HEADER: 400,
|
||||
NOT_REGISTERED: 401,
|
||||
JWT_VERIFICATION_FAILED: 401,
|
||||
NO_APP_ID: 401,
|
||||
MISSING_AUTHORIZATION_BEARER_HEADER: 400,
|
||||
};
|
||||
|
||||
export type NextProtectedApiHandler<TResp = unknown> = (
|
||||
req: NextApiRequest,
|
||||
res: NextApiResponse<TResp>,
|
||||
ctx: ProtectedHandlerContext
|
||||
) => unknown | Promise<unknown>;
|
||||
|
||||
/**
|
||||
* Wraps provided function, to ensure incoming request comes from Saleor Dashboard.
|
||||
* Also provides additional `context` object containing request properties.
|
||||
*/
|
||||
export const createProtectedHandler =
|
||||
(handlerFn: NextProtectedApiHandler, apl: APL): NextApiHandler =>
|
||||
(req, res) => {
|
||||
debug("Protected handler called");
|
||||
processSaleorProtectedHandler({
|
||||
req,
|
||||
apl,
|
||||
})
|
||||
.then(async (context) => {
|
||||
debug("Incoming request validated. Call handlerFn");
|
||||
return handlerFn(req, res, context);
|
||||
})
|
||||
.catch((e) => {
|
||||
debug("Unexpected error during processing the request");
|
||||
|
||||
if (e instanceof ProtectedHandlerError) {
|
||||
debug(`Validation error: ${e.message}`);
|
||||
res.status(ProtectedHandlerErrorCodeMap[e.errorType] || 400).end();
|
||||
return;
|
||||
}
|
||||
debug("Unexpected error: %O", e);
|
||||
res.status(500).end();
|
||||
});
|
||||
};
|
|
@ -1,3 +1,5 @@
|
|||
export * from "./create-app-register-handler";
|
||||
export * from "./create-manifest-handler";
|
||||
export * from "./create-protected-handler";
|
||||
export * from "./process-protected-handler";
|
||||
export * from "./saleor-async-webhook";
|
||||
|
|
|
@ -43,6 +43,11 @@ export type WebhookContext<T> = {
|
|||
authData: AuthData;
|
||||
};
|
||||
|
||||
export type ProtectedHandlerContext = {
|
||||
baseUrl: string;
|
||||
authData: AuthData;
|
||||
};
|
||||
|
||||
interface ProcessSaleorWebhookArgs {
|
||||
req: NextApiRequest;
|
||||
apl: APL;
|
||||
|
|
125
src/handlers/next/process-protected-handler.test.ts
Normal file
125
src/handlers/next/process-protected-handler.test.ts
Normal file
|
@ -0,0 +1,125 @@
|
|||
import { NextApiRequest } from "next/types";
|
||||
import { createMocks } from "node-mocks-http";
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
||||
|
||||
import { APL } from "../../APL";
|
||||
import { getAppId } from "../../get-app-id";
|
||||
import { verifyJWT } from "../../verify-jwt";
|
||||
import { processSaleorProtectedHandler } from "./process-protected-handler";
|
||||
|
||||
const validToken =
|
||||
"eyJ0eXAiOiJKV1QiLCJhbGciOiJFUzI1NiIsImtpZCI6Ijk4ZTEzNDk4YmM5NThjM2QyNzk2NjY5Zjk0NzYxMzZkIn0.eyJpYXQiOjE2NjkxOTE4NDUsIm93bmVyIjoic2FsZW9yIiwiaXNzIjoiZGVtby5ldS5zYWxlb3IuY2xvdWQiLCJleHAiOjE2NjkyNzgyNDUsInRva2VuIjoic2JsRmVrWnVCSUdXIiwiZW1haWwiOiJhZG1pbkBleGFtcGxlLmNvbSIsInR5cGUiOiJ0aGlyZHBhcnR5IiwidXNlcl9pZCI6IlZYTmxjam95TWc9PSIsImlzX3N0YWZmIjp0cnVlLCJhcHAiOiJRWEJ3T2pJM05RPT0iLCJwZXJtaXNzaW9ucyI6W10sInVzZXJfcGVybWlzc2lvbnMiOlsiTUFOQUdFX1BBR0VfVFlQRVNfQU5EX0FUVFJJQlVURVMiLCJNQU5BR0VfUFJPRFVDVF9UWVBFU19BTkRfQVRUUklCVVRFUyIsIk1BTkFHRV9ESVNDT1VOVFMiLCJNQU5BR0VfUExVR0lOUyIsIk1BTkFHRV9TVEFGRiIsIk1BTkFHRV9QUk9EVUNUUyIsIk1BTkFHRV9TSElQUElORyIsIk1BTkFHRV9UUkFOU0xBVElPTlMiLCJNQU5BR0VfT0JTRVJWQUJJTElUWSIsIk1BTkFHRV9VU0VSUyIsIk1BTkFHRV9BUFBTIiwiTUFOQUdFX0NIQU5ORUxTIiwiTUFOQUdFX0dJRlRfQ0FSRCIsIkhBTkRMRV9QQVlNRU5UUyIsIklNUEVSU09OQVRFX1VTRVIiLCJNQU5BR0VfU0VUVElOR1MiLCJNQU5BR0VfUEFHRVMiLCJNQU5BR0VfTUVOVVMiLCJNQU5BR0VfQ0hFQ0tPVVRTIiwiSEFORExFX0NIRUNLT1VUUyIsIk1BTkFHRV9PUkRFUlMiXX0.PUyvuUlDvUBXMGSaexusdlkY5wF83M8tsjefVXOknaKuVgLbafvLOgx78YGVB4kdAybC7O3Yjs7IIFOzz5U80Q";
|
||||
|
||||
const validAppId = "QXBwOjI3NQ==";
|
||||
|
||||
vi.mock("./../../get-app-id", () => ({
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
getAppId: vi.fn(),
|
||||
}));
|
||||
|
||||
vi.mock("./../../verify-jwt", () => ({
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
verifyJWT: vi.fn(),
|
||||
}));
|
||||
|
||||
describe("processSaleorProtectedHandler", () => {
|
||||
let mockRequest: NextApiRequest;
|
||||
|
||||
const mockAPL: APL = {
|
||||
get: async (domain: string) =>
|
||||
domain === "example.com"
|
||||
? {
|
||||
domain: "example.com",
|
||||
token: "mock-token",
|
||||
}
|
||||
: undefined,
|
||||
set: vi.fn(),
|
||||
delete: vi.fn(),
|
||||
getAll: vi.fn(),
|
||||
isReady: vi.fn(),
|
||||
isConfigured: vi.fn(),
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
// Create request method which passes all the tests
|
||||
const { req } = createMocks({
|
||||
headers: {
|
||||
host: "some-saleor-host.cloud",
|
||||
"x-forwarded-proto": "https",
|
||||
"saleor-domain": "example.com",
|
||||
"saleor-event": "product_updated",
|
||||
"saleor-signature": "mocked_signature",
|
||||
"authorization-bearer": validToken,
|
||||
},
|
||||
method: "POST",
|
||||
});
|
||||
mockRequest = req;
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
vi.resetAllMocks();
|
||||
});
|
||||
|
||||
it("Process valid request", async () => {
|
||||
vi.mocked(getAppId).mockResolvedValue(validAppId);
|
||||
vi.mocked(verifyJWT).mockResolvedValue();
|
||||
|
||||
expect(await processSaleorProtectedHandler({ apl: mockAPL, req: mockRequest })).toStrictEqual({
|
||||
authData: {
|
||||
domain: "example.com",
|
||||
token: "mock-token",
|
||||
},
|
||||
baseUrl: "https://some-saleor-host.cloud",
|
||||
});
|
||||
});
|
||||
|
||||
it("Throw error when app ID can't be fetched", async () => {
|
||||
vi.mocked(getAppId).mockResolvedValue("");
|
||||
|
||||
await expect(processSaleorProtectedHandler({ apl: mockAPL, req: mockRequest })).rejects.toThrow(
|
||||
"Could not get the app ID from the domain example.com"
|
||||
);
|
||||
});
|
||||
|
||||
it("Throw error when domain header is missing", async () => {
|
||||
vi.mocked(getAppId).mockResolvedValue(validAppId);
|
||||
vi.mocked(verifyJWT).mockResolvedValue();
|
||||
|
||||
delete mockRequest.headers["saleor-domain"];
|
||||
|
||||
await expect(processSaleorProtectedHandler({ apl: mockAPL, req: mockRequest })).rejects.toThrow(
|
||||
"Missing saleor-domain header"
|
||||
);
|
||||
});
|
||||
|
||||
it("Throw error when token header is missing", async () => {
|
||||
vi.mocked(getAppId).mockResolvedValue(validAppId);
|
||||
vi.mocked(verifyJWT).mockResolvedValue();
|
||||
|
||||
delete mockRequest.headers["authorization-bearer"];
|
||||
|
||||
await expect(processSaleorProtectedHandler({ apl: mockAPL, req: mockRequest })).rejects.toThrow(
|
||||
"Missing authorization-bearer header"
|
||||
);
|
||||
});
|
||||
|
||||
it("Throw error when APL has no auth data for the given domain", async () => {
|
||||
vi.mocked(getAppId).mockResolvedValue(validAppId);
|
||||
vi.mocked(verifyJWT).mockResolvedValue();
|
||||
|
||||
mockRequest.headers["saleor-domain"] = "wrong.example.com";
|
||||
|
||||
await expect(processSaleorProtectedHandler({ apl: mockAPL, req: mockRequest })).rejects.toThrow(
|
||||
"Can't find auth data for domain wrong.example.com. Please register the application"
|
||||
);
|
||||
});
|
||||
|
||||
it("Throw error when token verification fails", async () => {
|
||||
vi.mocked(getAppId).mockResolvedValue(validAppId);
|
||||
vi.mocked(verifyJWT).mockRejectedValue("Verification error");
|
||||
|
||||
await expect(processSaleorProtectedHandler({ apl: mockAPL, req: mockRequest })).rejects.toThrow(
|
||||
"JWT verification failed: "
|
||||
);
|
||||
});
|
||||
});
|
106
src/handlers/next/process-protected-handler.ts
Normal file
106
src/handlers/next/process-protected-handler.ts
Normal file
|
@ -0,0 +1,106 @@
|
|||
import { NextApiRequest } from "next";
|
||||
|
||||
import { APL } from "../../APL";
|
||||
import { AuthData } from "../../APL/apl";
|
||||
import { createDebug } from "../../debug";
|
||||
import { getAppId } from "../../get-app-id";
|
||||
import { getBaseUrl, getSaleorHeaders } from "../../headers";
|
||||
import { verifyJWT } from "../../verify-jwt";
|
||||
|
||||
const debug = createDebug("processProtectedHandler");
|
||||
|
||||
export type SaleorProtectedHandlerError =
|
||||
| "OTHER"
|
||||
| "MISSING_HOST_HEADER"
|
||||
| "MISSING_DOMAIN_HEADER"
|
||||
| "MISSING_AUTHORIZATION_BEARER_HEADER"
|
||||
| "NOT_REGISTERED"
|
||||
| "JWT_VERIFICATION_FAILED"
|
||||
| "NO_APP_ID";
|
||||
|
||||
export class ProtectedHandlerError extends Error {
|
||||
errorType: SaleorProtectedHandlerError = "OTHER";
|
||||
|
||||
constructor(message: string, errorType: SaleorProtectedHandlerError) {
|
||||
super(message);
|
||||
if (errorType) {
|
||||
this.errorType = errorType;
|
||||
}
|
||||
Object.setPrototypeOf(this, ProtectedHandlerError.prototype);
|
||||
}
|
||||
}
|
||||
|
||||
export type ProtectedHandlerContext = {
|
||||
baseUrl: string;
|
||||
authData: AuthData;
|
||||
};
|
||||
|
||||
interface ProcessSaleorProtectedHandlerArgs {
|
||||
req: NextApiRequest;
|
||||
apl: APL;
|
||||
}
|
||||
|
||||
type ProcessAsyncSaleorProtectedHandler = (
|
||||
props: ProcessSaleorProtectedHandlerArgs
|
||||
) => Promise<ProtectedHandlerContext>;
|
||||
|
||||
/**
|
||||
* Perform security checks on given request and return ProtectedHandlerContext object.
|
||||
* In case of validation issues, instance of the ProtectedHandlerError will be thrown.
|
||||
*/
|
||||
export const processSaleorProtectedHandler: ProcessAsyncSaleorProtectedHandler = async ({
|
||||
req,
|
||||
apl,
|
||||
}: ProcessSaleorProtectedHandlerArgs): Promise<ProtectedHandlerContext> => {
|
||||
debug("Request processing started");
|
||||
const { domain, authorizationBearer: token } = getSaleorHeaders(req.headers);
|
||||
|
||||
const baseUrl = getBaseUrl(req.headers);
|
||||
if (!baseUrl) {
|
||||
debug("Missing host header");
|
||||
throw new ProtectedHandlerError("Missing host header", "MISSING_HOST_HEADER");
|
||||
}
|
||||
|
||||
if (!domain) {
|
||||
debug("Missing saleor-domain header");
|
||||
throw new ProtectedHandlerError("Missing saleor-domain header", "MISSING_DOMAIN_HEADER");
|
||||
}
|
||||
|
||||
if (!token) {
|
||||
debug("Missing authorization-bearer header");
|
||||
throw new ProtectedHandlerError(
|
||||
"Missing authorization-bearer header",
|
||||
"MISSING_AUTHORIZATION_BEARER_HEADER"
|
||||
);
|
||||
}
|
||||
|
||||
// Check if domain has been registered in the APL
|
||||
const authData = await apl.get(domain);
|
||||
if (!authData) {
|
||||
debug("APL didn't found auth data for domain %s", domain);
|
||||
throw new ProtectedHandlerError(
|
||||
`Can't find auth data for domain ${domain}. Please register the application`,
|
||||
"NOT_REGISTERED"
|
||||
);
|
||||
}
|
||||
|
||||
const appId = await getAppId(authData);
|
||||
if (!appId) {
|
||||
debug("Could not get the app ID.");
|
||||
throw new ProtectedHandlerError(
|
||||
`Could not get the app ID from the domain ${domain}`,
|
||||
"NO_APP_ID"
|
||||
);
|
||||
}
|
||||
|
||||
try {
|
||||
await verifyJWT({ appId, token, domain });
|
||||
} catch (e) {
|
||||
throw new ProtectedHandlerError("JWT verification failed: ", "JWT_VERIFICATION_FAILED");
|
||||
}
|
||||
|
||||
return {
|
||||
baseUrl,
|
||||
authData,
|
||||
};
|
||||
};
|
|
@ -34,7 +34,7 @@ type WebhookManifestConfiguration =
|
|||
| WebhookManifestConfigurationWithAst
|
||||
| WebhookManifestConfigurationWithQuery;
|
||||
|
||||
export const ErrorCodeMap: Record<SaleorWebhookError, number> = {
|
||||
export const AsyncWebhookErrorCodeMap: Record<SaleorWebhookError, number> = {
|
||||
OTHER: 500,
|
||||
MISSING_HOST_HEADER: 400,
|
||||
MISSING_DOMAIN_HEADER: 400,
|
||||
|
@ -145,7 +145,7 @@ export class SaleorAsyncWebhook<TPayload = unknown> {
|
|||
|
||||
if (e instanceof WebhookError) {
|
||||
debug(`Validation error: ${e.message}`);
|
||||
res.status(ErrorCodeMap[e.errorType] || 400).end();
|
||||
res.status(AsyncWebhookErrorCodeMap[e.errorType] || 400).end();
|
||||
return;
|
||||
}
|
||||
debug("Unexpected error: %O", e);
|
||||
|
|
|
@ -12,6 +12,7 @@ const dropFileExtension = (filename: string) => path.parse(filename).name;
|
|||
export const inferWebhooks = async (
|
||||
baseURL: string,
|
||||
webhooksPath: string,
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
generatedGraphQL: any
|
||||
) => {
|
||||
let entries;
|
||||
|
@ -38,6 +39,7 @@ export const inferWebhooks = async (
|
|||
const statement = `${camelCaseName}SubscriptionDocument`;
|
||||
let query: string;
|
||||
if (statement in generatedGraphQL) {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
query = print((generatedGraphQL as any)[statement]);
|
||||
} else {
|
||||
throw Error("Subscription not found.");
|
||||
|
|
44
src/verify-jwt.test.ts
Normal file
44
src/verify-jwt.test.ts
Normal file
|
@ -0,0 +1,44 @@
|
|||
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
||||
|
||||
import { verifyJWT } from "./verify-jwt";
|
||||
|
||||
const validToken =
|
||||
"eyJ0eXAiOiJKV1QiLCJhbGciOiJFUzI1NiIsImtpZCI6Ijk4ZTEzNDk4YmM5NThjM2QyNzk2NjY5Zjk0NzYxMzZkIn0.eyJpYXQiOjE2NjkxOTE4NDUsIm93bmVyIjoic2FsZW9yIiwiaXNzIjoiZGVtby5ldS5zYWxlb3IuY2xvdWQiLCJleHAiOjE2NjkyNzgyNDUsInRva2VuIjoic2JsRmVrWnVCSUdXIiwiZW1haWwiOiJhZG1pbkBleGFtcGxlLmNvbSIsInR5cGUiOiJ0aGlyZHBhcnR5IiwidXNlcl9pZCI6IlZYTmxjam95TWc9PSIsImlzX3N0YWZmIjp0cnVlLCJhcHAiOiJRWEJ3T2pJM05RPT0iLCJwZXJtaXNzaW9ucyI6W10sInVzZXJfcGVybWlzc2lvbnMiOlsiTUFOQUdFX1BBR0VfVFlQRVNfQU5EX0FUVFJJQlVURVMiLCJNQU5BR0VfUFJPRFVDVF9UWVBFU19BTkRfQVRUUklCVVRFUyIsIk1BTkFHRV9ESVNDT1VOVFMiLCJNQU5BR0VfUExVR0lOUyIsIk1BTkFHRV9TVEFGRiIsIk1BTkFHRV9QUk9EVUNUUyIsIk1BTkFHRV9TSElQUElORyIsIk1BTkFHRV9UUkFOU0xBVElPTlMiLCJNQU5BR0VfT0JTRVJWQUJJTElUWSIsIk1BTkFHRV9VU0VSUyIsIk1BTkFHRV9BUFBTIiwiTUFOQUdFX0NIQU5ORUxTIiwiTUFOQUdFX0dJRlRfQ0FSRCIsIkhBTkRMRV9QQVlNRU5UUyIsIklNUEVSU09OQVRFX1VTRVIiLCJNQU5BR0VfU0VUVElOR1MiLCJNQU5BR0VfUEFHRVMiLCJNQU5BR0VfTUVOVVMiLCJNQU5BR0VfQ0hFQ0tPVVRTIiwiSEFORExFX0NIRUNLT1VUUyIsIk1BTkFHRV9PUkRFUlMiXX0.PUyvuUlDvUBXMGSaexusdlkY5wF83M8tsjefVXOknaKuVgLbafvLOgx78YGVB4kdAybC7O3Yjs7IIFOzz5U80Q";
|
||||
|
||||
const validDomain = "demo.eu.saleor.cloud";
|
||||
|
||||
const validAppId = "QXBwOjI3NQ==";
|
||||
|
||||
describe("verifyJWT", () => {
|
||||
beforeEach(() => {
|
||||
vi.mock("jose", async () => {
|
||||
const original = await vi.importActual("jose");
|
||||
return {
|
||||
// @ts-ignore
|
||||
...original,
|
||||
createRemoteJWKSet: vi.fn().mockImplementation(() => ""),
|
||||
jwtVerify: vi.fn().mockImplementation(() => ""),
|
||||
};
|
||||
});
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
vi.restoreAllMocks();
|
||||
});
|
||||
|
||||
it("Process valid request", async () => {
|
||||
await verifyJWT({ appId: validAppId, domain: validDomain, token: validToken });
|
||||
});
|
||||
|
||||
it("Throw error on decode issue", async () => {
|
||||
await expect(
|
||||
verifyJWT({ appId: validAppId, domain: validDomain, token: "wrong_token" })
|
||||
).rejects.toThrow("JWT verification failed: Could not decode authorization token.");
|
||||
});
|
||||
|
||||
it("Throw error on app ID missmatch", async () => {
|
||||
await expect(
|
||||
verifyJWT({ appId: "wrong_id", domain: validDomain, token: validToken })
|
||||
).rejects.toThrow("JWT verification failed: Token's app property is different than app ID.");
|
||||
});
|
||||
});
|
52
src/verify-jwt.ts
Normal file
52
src/verify-jwt.ts
Normal file
|
@ -0,0 +1,52 @@
|
|||
import * as jose from "jose";
|
||||
|
||||
import { createDebug } from "./debug";
|
||||
import { getJwksUrl } from "./urls";
|
||||
|
||||
const debug = createDebug("verify-jwt");
|
||||
|
||||
export interface DashboardTokenPayload extends jose.JWTPayload {
|
||||
app: string;
|
||||
}
|
||||
|
||||
export interface verifyJWTArguments {
|
||||
appId: string;
|
||||
domain: string;
|
||||
token: string;
|
||||
}
|
||||
|
||||
export const verifyJWT = async ({ domain, token, appId }: verifyJWTArguments) => {
|
||||
let tokenClaims: DashboardTokenPayload;
|
||||
const ERROR_MESSAGE = "JWT verification failed:";
|
||||
|
||||
try {
|
||||
tokenClaims = jose.decodeJwt(token as string) as DashboardTokenPayload;
|
||||
debug("Token Claims decoded from jwt");
|
||||
} catch (e) {
|
||||
debug("Token Claims could not be decoded from JWT, will respond with Bad Request");
|
||||
throw new Error(`${ERROR_MESSAGE} Could not decode authorization token.`);
|
||||
}
|
||||
|
||||
if (tokenClaims.app !== appId) {
|
||||
debug(
|
||||
"Resolved App ID value from token to be different than in request, will respond with Bad Request"
|
||||
);
|
||||
|
||||
throw new Error(`${ERROR_MESSAGE} Token's app property is different than app ID.`);
|
||||
}
|
||||
|
||||
try {
|
||||
debug("Trying to create JWKS");
|
||||
|
||||
const JWKS = jose.createRemoteJWKSet(new URL(getJwksUrl(domain)));
|
||||
debug("Trying to compare JWKS with token");
|
||||
await jose.jwtVerify(token, JWKS);
|
||||
} catch (e) {
|
||||
debug("Failure: %s", e);
|
||||
debug("Will return with Bad Request");
|
||||
|
||||
console.error(e);
|
||||
|
||||
throw new Error(`${ERROR_MESSAGE} JWT signature verification failed.`);
|
||||
}
|
||||
};
|
Loading…
Reference in a new issue