2023-02-07 19:25:36 +00:00
|
|
|
import { verifyJWT } from "@saleor/app-sdk/verify-jwt";
|
|
|
|
import { middleware, procedure } from "./trpc-server";
|
2023-04-17 10:39:13 +00:00
|
|
|
import { saleorApp } from "../../saleor-app";
|
2023-02-07 19:25:36 +00:00
|
|
|
import { TRPCError } from "@trpc/server";
|
|
|
|
import { ProtectedHandlerError } from "@saleor/app-sdk/handlers/next";
|
2023-06-19 13:59:27 +00:00
|
|
|
import { createGraphQLClient, logger } from "@saleor/apps-shared";
|
2023-02-07 19:25:36 +00:00
|
|
|
|
|
|
|
const attachAppToken = middleware(async ({ ctx, next }) => {
|
|
|
|
logger.debug("attachAppToken middleware");
|
|
|
|
|
|
|
|
if (!ctx.saleorApiUrl) {
|
|
|
|
logger.debug("ctx.saleorApiUrl not found, throwing");
|
|
|
|
|
|
|
|
throw new TRPCError({
|
|
|
|
code: "BAD_REQUEST",
|
|
|
|
message: "Missing saleorApiUrl in request",
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
const authData = await saleorApp.apl.get(ctx.saleorApiUrl);
|
|
|
|
|
|
|
|
if (!authData) {
|
|
|
|
logger.debug("authData not found, throwing 401");
|
|
|
|
|
|
|
|
throw new TRPCError({
|
|
|
|
code: "UNAUTHORIZED",
|
|
|
|
message: "Missing auth data",
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
return next({
|
|
|
|
ctx: {
|
|
|
|
appToken: authData.token,
|
|
|
|
saleorApiUrl: authData.saleorApiUrl,
|
|
|
|
appId: authData.appId,
|
|
|
|
},
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
const validateClientToken = middleware(async ({ ctx, next, meta }) => {
|
|
|
|
logger.debug(
|
|
|
|
{
|
|
|
|
permissions: meta?.requiredClientPermissions,
|
|
|
|
},
|
|
|
|
"Calling validateClientToken middleware with permissions required"
|
|
|
|
);
|
|
|
|
|
|
|
|
if (!ctx.token) {
|
|
|
|
throw new TRPCError({
|
|
|
|
code: "INTERNAL_SERVER_ERROR",
|
|
|
|
message: "Missing token in request. This middleware can be used only in frontend",
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!ctx.appId) {
|
|
|
|
throw new TRPCError({
|
|
|
|
code: "INTERNAL_SERVER_ERROR",
|
|
|
|
message: "Missing appId in request. This middleware can be used after auth is attached",
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!ctx.saleorApiUrl) {
|
|
|
|
throw new TRPCError({
|
|
|
|
code: "INTERNAL_SERVER_ERROR",
|
|
|
|
message:
|
|
|
|
"Missing saleorApiUrl in request. This middleware can be used after auth is attached",
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
try {
|
|
|
|
logger.debug("trying to verify JWT token from frontend");
|
|
|
|
logger.debug({ token: ctx.token ? `${ctx.token[0]}...` : undefined });
|
|
|
|
|
|
|
|
await verifyJWT({
|
|
|
|
appId: ctx.appId,
|
|
|
|
token: ctx.token,
|
|
|
|
saleorApiUrl: ctx.saleorApiUrl,
|
|
|
|
requiredPermissions: meta?.requiredClientPermissions ?? [],
|
|
|
|
});
|
|
|
|
} catch (e) {
|
|
|
|
logger.debug("JWT verification failed, throwing");
|
|
|
|
throw new ProtectedHandlerError("JWT verification failed: ", "JWT_VERIFICATION_FAILED");
|
|
|
|
}
|
|
|
|
|
|
|
|
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 }) => {
|
2023-06-19 13:59:27 +00:00
|
|
|
const client = createGraphQLClient({
|
|
|
|
saleorApiUrl: ctx.saleorApiUrl,
|
|
|
|
token: ctx.token,
|
|
|
|
});
|
2023-02-07 19:25:36 +00:00
|
|
|
|
|
|
|
return next({
|
|
|
|
ctx: {
|
|
|
|
apiClient: client,
|
|
|
|
appToken: ctx.appToken,
|
|
|
|
saleorApiUrl: ctx.saleorApiUrl,
|
|
|
|
},
|
|
|
|
});
|
|
|
|
});
|