import { createRemoteJWKSet, jwtVerify, type JWTPayload } from "jose"; import { GraphQLError } from "graphql"; import type { FastifyRequest } from "fastify"; const LOGTO_JWKS_URL = process.env.LOGTO_JWKS_URL || "https://auth.optovia.ru/oidc/jwks"; const LOGTO_ISSUER = process.env.LOGTO_ISSUER || "https://auth.optovia.ru/oidc"; const LOGTO_KYC_AUDIENCE = process.env.LOGTO_KYC_AUDIENCE || "https://kyc.optovia.ru"; const jwks = createRemoteJWKSet(new URL(LOGTO_JWKS_URL)); export interface AuthContext { userId?: string; scopes: string[]; isM2M?: boolean; } function getBearerToken(req: FastifyRequest): string | null { const auth = req.headers.authorization || ""; if (!auth.startsWith("Bearer ")) return null; const token = auth.slice(7); if (!token || token === "undefined") return null; return token; } function scopesFromPayload(payload: JWTPayload): string[] { const scope = payload.scope; if (!scope) return []; if (typeof scope === "string") return scope.split(" "); if (Array.isArray(scope)) return scope as string[]; return []; } export async function publicContext(req: FastifyRequest): Promise { const token = getBearerToken(req); if (!token) return { scopes: [] }; const { payload } = await jwtVerify(token, jwks, { issuer: LOGTO_ISSUER, audience: LOGTO_KYC_AUDIENCE, }); const scopes = scopesFromPayload(payload); if (!scopes.includes("teams:user")) { throw new GraphQLError("Unauthorized", { extensions: { code: "UNAUTHENTICATED" }, }); } return { userId: payload.sub, scopes }; } export async function userContext(req: FastifyRequest): Promise { const token = getBearerToken(req); if (!token) { throw new GraphQLError("Unauthorized", { extensions: { code: "UNAUTHENTICATED" }, }); } const { payload } = await jwtVerify(token, jwks, { issuer: LOGTO_ISSUER, audience: LOGTO_KYC_AUDIENCE, }); const scopes = scopesFromPayload(payload); if (!scopes.includes("teams:user")) { throw new GraphQLError("Unauthorized", { extensions: { code: "UNAUTHENTICATED" }, }); } return { userId: payload.sub, scopes }; } export async function m2mContext(): Promise { return { scopes: [], isM2M: true }; }