This commit is contained in:
jackbeeby
2024-12-05 18:20:27 +11:00
parent b8de0556ec
commit dc5eea1ad0
288 changed files with 101937 additions and 0 deletions

View File

@@ -0,0 +1,122 @@
import { verifyJWT } from "@saleor/app-sdk/verify-jwt";
import { logger, redactLogValue } from "../../lib/logger";
import { REQUIRED_SALEOR_PERMISSIONS } from "../jwt/consts";
import { middleware, procedure } from "./trpc-server";
import { checkTokenExpiration } from "@/modules/jwt/check-token-expiration";
import { saleorApp } from "@/saleor-app";
import { createClient } from "@/lib/create-graphq-client";
import {
JwtInvalidError,
JwtTokenExpiredError,
ReqMissingAppIdError,
ReqMissingAuthDataError,
ReqMissingSaleorApiUrlError,
ReqMissingTokenError,
} from "@/errors";
const attachAppToken = middleware(async ({ ctx, next }) => {
logger.debug("attachAppToken middleware");
if (!ctx.saleorApiUrl) {
logger.debug("ctx.saleorApiUrl not found, throwing");
throw new ReqMissingSaleorApiUrlError("Missing saleorApiUrl in request");
}
const authData = await saleorApp.apl.get(ctx.saleorApiUrl);
if (!authData) {
logger.debug("authData not found, throwing 401");
throw new ReqMissingAuthDataError("Missing authData in request");
}
return next({
ctx: {
appToken: authData.token,
saleorApiUrl: authData.saleorApiUrl,
appId: authData.appId,
},
});
});
const validateClientToken = middleware(async ({ ctx, next }) => {
logger.debug("validateClientToken middleware");
if (!ctx.token) {
throw new ReqMissingTokenError(
"Missing token in request. This middleware can be used only in frontend",
);
}
if (!ctx.appId) {
throw new ReqMissingAppIdError(
"Missing appId in request. This middleware can be used after auth is attached",
);
}
if (!ctx.saleorApiUrl) {
throw new ReqMissingSaleorApiUrlError(
"Missing saleorApiUrl in request. This middleware can be used after auth is attached",
);
}
logger.debug({ token: redactLogValue(ctx.token) }, "check if JWT token didn't expire");
const expired = checkTokenExpiration(ctx.token);
logger.debug({ expired }, "JWT token expiration check result");
if (expired) {
throw new JwtTokenExpiredError("Token expired");
}
try {
logger.debug({ token: redactLogValue(ctx.token) }, "trying to verify JWT token from frontend");
await verifyJWT({
appId: ctx.appId,
token: ctx.token,
saleorApiUrl: ctx.saleorApiUrl,
requiredPermissions: REQUIRED_SALEOR_PERMISSIONS,
});
} catch (e) {
logger.debug("JWT verification failed, throwing");
throw new JwtInvalidError("Invalid token", { cause: e });
}
return next({
ctx: {
...ctx,
saleorApiUrl: ctx.saleorApiUrl,
},
});
});
/**
* Construct common graphQL client and attach it to the context
*
* Can be used only if called from the frontend (react-query),
* otherwise jwks validation will fail (if createCaller used)
*
* TODO Rethink middleware composition to enable safe server-side router calls
*/
export const protectedClientProcedure = procedure
.use(attachAppToken)
.use(validateClientToken)
.use(async ({ ctx, next }) => {
const client = createClient(ctx.saleorApiUrl, async () =>
Promise.resolve({ token: ctx.appToken }),
);
const pinoLoggerInstance = logger.child({
saleorApiUrl: ctx.saleorApiUrl,
});
return next({
ctx: {
apiClient: client,
appToken: ctx.appToken,
saleorApiUrl: ctx.saleorApiUrl,
logger: pinoLoggerInstance,
},
});
});