Advertisement
Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- import { FastifyInstance, FastifyRequest, FastifyReply } from "fastify";
- import { createRemoteJWKSet, jwtVerify } from "jose";
- import fp from "fastify-plugin";
- import {
- createErrorResponse,
- InternalMessageCode,
- MessageCode,
- } from "../types/api-resource";
- interface LogtoFastifyOptions {
- jwksUrl: string;
- issuer: string;
- audience: string;
- }
- export interface LogtoUser {
- sub: string;
- scope: string;
- user_id: string | null;
- is_staff: boolean;
- role: string;
- role_id: string;
- site_id: string;
- [key: string]: any;
- }
- declare module "fastify" {
- interface FastifyRequest {
- logtoUser?: LogtoUser;
- user?: {
- id: string | null;
- logtoId: string;
- email: string | undefined;
- isStaff: boolean;
- claims: LogtoUser;
- role: string;
- role_id: string;
- site_id: string | undefined;
- };
- }
- }
- const extractBearerTokenFromHeaders = (authorization?: string) => {
- if (!authorization) {
- throw new Error("Authorization header is missing");
- }
- if (!authorization.startsWith("Bearer ")) {
- throw new Error("Authorization header is not in the Bearer scheme");
- }
- return authorization.slice(7); // The length of 'Bearer ' is 7
- };
- export const logtoFastifyPlugin = fp(
- async (fastify: FastifyInstance, options: LogtoFastifyOptions) => {
- const { jwksUrl, issuer, audience } = options;
- // Generate a JWKS using jwks_uri obtained from the Logto server
- const jwks = createRemoteJWKSet(new URL(jwksUrl));
- // Decorator to verify access token
- fastify.decorate(
- "verifyLogtoToken",
- async (request: FastifyRequest, reply: FastifyReply) => {
- try {
- const token = extractBearerTokenFromHeaders(
- request.headers.authorization
- );
- // Log token details for debugging (remove in production)
- console.log(
- `Verifying token with issuer: ${issuer}, audience: ${audience}`
- );
- const { payload } = await jwtVerify(token, jwks, {
- issuer,
- audience,
- clockTolerance: "10 hours",
- });
- // Add user info to request
- request.logtoUser = payload as LogtoUser;
- return true;
- } catch (error) {
- // Provide more detailed error information
- console.error("Token verification failed:", error);
- let errorMessage = "Invalid token";
- if (error instanceof Error) {
- // More specific error messages based on error type
- if (error.message.includes("expired")) {
- errorMessage = "Token has expired";
- } else if (error.message.includes("audience")) {
- errorMessage = "Invalid token audience";
- } else if (error.message.includes("issuer")) {
- errorMessage = "Invalid token issuer";
- } else {
- errorMessage = error.message;
- }
- }
- return reply
- .status(401)
- .send(
- createErrorResponse(
- error instanceof Error ? error.message : "Invalid token",
- MessageCode.UNAUTHORIZED,
- 401,
- InternalMessageCode.UNAUTHORIZED_REQUEST
- )
- );
- }
- }
- );
- // Helper to check if user has required scopes
- fastify.decorate("requireScopes", (scopes: string[]) => {
- return async (request: FastifyRequest, reply: FastifyReply) => {
- if (!request.logtoUser) {
- reply
- .status(401)
- .send(
- createErrorResponse(
- "Unauthorized",
- MessageCode.UNAUTHORIZED,
- 401,
- InternalMessageCode.UNAUTHORIZED_REQUEST
- )
- );
- return false;
- }
- const userScopes = request.logtoUser.scope?.split(" ") || [];
- const hasRequiredScopes = scopes.every((scope) =>
- userScopes.includes(scope)
- );
- if (!hasRequiredScopes) {
- reply
- .status(403)
- .send(
- createErrorResponse(
- "Insufficient permissions",
- MessageCode.FORBIDDEN,
- 403,
- InternalMessageCode.FORBIDDEN_REQUEST
- )
- );
- return false;
- }
- return true;
- };
- });
- // Helper to check if user has required roles
- fastify.decorate("requireRoles", (roles: string[]) => {
- return async (request: FastifyRequest, reply: FastifyReply) => {
- if (!request.logtoUser) {
- reply
- .status(401)
- .send(
- createErrorResponse(
- "Unauthorized",
- MessageCode.UNAUTHORIZED,
- 401,
- InternalMessageCode.UNAUTHORIZED_REQUEST
- )
- );
- return false;
- }
- // Change this line to use the single role property
- const userRole = request.logtoUser.role;
- // Check if the user's role is in the allowed roles array
- const hasRequiredRoles = roles.includes(userRole);
- if (!hasRequiredRoles) {
- reply
- .status(403)
- .send(
- createErrorResponse(
- "Insufficient permissions",
- MessageCode.FORBIDDEN,
- 403,
- InternalMessageCode.FORBIDDEN_REQUEST
- )
- );
- return false;
- }
- return true;
- };
- });
- // Helper to get claims from JWT
- fastify.decorate("getLogtoUserClaims", (request: FastifyRequest) => {
- if (!request.logtoUser) {
- return null;
- }
- return request.logtoUser;
- });
- // Helper to get a specific claim from JWT
- fastify.decorate(
- "getLogtoUserClaim",
- (request: FastifyRequest, claimName: string) => {
- if (!request.logtoUser) {
- return null;
- }
- return request.logtoUser[claimName];
- }
- );
- }
- );
- // Middleware to verify token
- export const verifyLogtoToken = async (
- request: FastifyRequest,
- reply: FastifyReply
- ) => {
- return request.server.verifyLogtoToken(request, reply);
- };
- // Middleware to require specific scopes
- export const requireScopes = (scopes: string[]) => {
- return async (request: FastifyRequest, reply: FastifyReply) => {
- return request.server.requireScopes(scopes)(request, reply);
- };
- };
- // Middleware to require specific roles
- export const requireRoles = (roles: string[]) => {
- return async (request: FastifyRequest, reply: FastifyReply) => {
- return request.server.requireRoles(roles)(request, reply);
- };
- };
- // Helper to get all claims from JWT
- export const getLogtoUserClaims = (request: FastifyRequest) => {
- return request.server.getLogtoUserClaims(request);
- };
- // Helper to get a specific claim from JWT
- export const getLogtoUserClaim = (
- request: FastifyRequest,
- claimName: string
- ) => {
- return request.server.getLogtoUserClaim(request, claimName);
- };
- export const extractLogtoUserClaims = async (
- request: FastifyRequest,
- reply: FastifyReply
- ) => {
- // Skip if no user is authenticated
- if (!request.logtoUser) {
- return;
- }
- // Extract common claims and add them directly to the request
- request.user = {
- id: request.logtoUser.user_id,
- logtoId: request.logtoUser.sub,
- email: request.logtoUser.email,
- isStaff: request.logtoUser.is_staff || false,
- role: request.logtoUser.role,
- role_id: request.logtoUser.role_id,
- site_id: request.logtoUser.site_id,
- claims: request.logtoUser,
- };
- };
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement