diff --git a/packages/mana-core-nestjs-integration/package.json b/packages/mana-core-nestjs-integration/package.json index 73857fb9f..97f119a7d 100644 --- a/packages/mana-core-nestjs-integration/package.json +++ b/packages/mana-core-nestjs-integration/package.json @@ -9,6 +9,16 @@ "types": "./dist/index.d.ts", "import": "./dist/index.js", "require": "./dist/index.js" + }, + "./guards": { + "types": "./dist/guards/index.d.ts", + "import": "./dist/guards/index.js", + "require": "./dist/guards/index.js" + }, + "./decorators": { + "types": "./dist/decorators/index.d.ts", + "import": "./dist/decorators/index.js", + "require": "./dist/decorators/index.js" } }, "scripts": { diff --git a/packages/mana-core-nestjs-integration/src/decorators/index.ts b/packages/mana-core-nestjs-integration/src/decorators/index.ts new file mode 100644 index 000000000..83895afa6 --- /dev/null +++ b/packages/mana-core-nestjs-integration/src/decorators/index.ts @@ -0,0 +1,2 @@ +export { CurrentUser, JwtPayload } from './current-user.decorator'; +export { Public, IS_PUBLIC_KEY } from './public.decorator'; diff --git a/packages/mana-core-nestjs-integration/src/decorators/public.decorator.ts b/packages/mana-core-nestjs-integration/src/decorators/public.decorator.ts new file mode 100644 index 000000000..35d552c95 --- /dev/null +++ b/packages/mana-core-nestjs-integration/src/decorators/public.decorator.ts @@ -0,0 +1,8 @@ +import { SetMetadata } from '@nestjs/common'; + +export const IS_PUBLIC_KEY = 'isPublic'; + +/** + * Decorator to mark a route as public (no authentication required) + */ +export const Public = () => SetMetadata(IS_PUBLIC_KEY, true); diff --git a/packages/mana-core-nestjs-integration/src/guards/index.ts b/packages/mana-core-nestjs-integration/src/guards/index.ts new file mode 100644 index 000000000..e9e9bb171 --- /dev/null +++ b/packages/mana-core-nestjs-integration/src/guards/index.ts @@ -0,0 +1,2 @@ +export { AuthGuard } from './auth.guard'; +export { OptionalAuthGuard } from './optional-auth.guard'; diff --git a/packages/mana-core-nestjs-integration/src/guards/optional-auth.guard.ts b/packages/mana-core-nestjs-integration/src/guards/optional-auth.guard.ts new file mode 100644 index 000000000..339df2212 --- /dev/null +++ b/packages/mana-core-nestjs-integration/src/guards/optional-auth.guard.ts @@ -0,0 +1,76 @@ +import { + Injectable, + CanActivate, + ExecutionContext, + Inject, + Optional, +} from '@nestjs/common'; +import * as jwt from 'jsonwebtoken'; +import { MANA_CORE_OPTIONS } from '../mana-core.module'; +import { ManaCoreModuleOptions } from '../interfaces/mana-core-options.interface'; + +/** + * Optional auth guard - allows unauthenticated requests but still extracts user info if token is present + */ +@Injectable() +export class OptionalAuthGuard implements CanActivate { + constructor( + @Optional() + @Inject(MANA_CORE_OPTIONS) + private readonly options?: ManaCoreModuleOptions + ) {} + + async canActivate(context: ExecutionContext): Promise { + const request = context.switchToHttp().getRequest(); + const token = this.extractTokenFromHeader(request); + + if (!token) { + // No token - allow request but user will be undefined + request.user = null; + return true; + } + + try { + // Decode the token to extract user information + const decoded = jwt.decode(token) as jwt.JwtPayload | null; + + if (decoded && decoded.sub) { + // Attach user info to request + request.user = { + sub: decoded.sub, + email: decoded.email || '', + role: decoded.role || 'user', + app_id: decoded.app_id, + iat: decoded.iat, + exp: decoded.exp, + }; + + // Store raw token for downstream services + request.accessToken = token; + + if (this.options?.debug) { + console.log('[OptionalAuthGuard] User authenticated:', decoded.sub); + } + } else { + request.user = null; + } + } catch (error) { + if (this.options?.debug) { + console.error('[OptionalAuthGuard] Token decode failed:', error); + } + request.user = null; + } + + return true; + } + + private extractTokenFromHeader(request: any): string | undefined { + const authHeader = request.headers.authorization; + if (!authHeader) { + return undefined; + } + + const [type, token] = authHeader.split(' '); + return type === 'Bearer' ? token : undefined; + } +} diff --git a/packages/mana-core-nestjs-integration/src/index.ts b/packages/mana-core-nestjs-integration/src/index.ts index 3fae76370..9b7b04c84 100644 --- a/packages/mana-core-nestjs-integration/src/index.ts +++ b/packages/mana-core-nestjs-integration/src/index.ts @@ -10,9 +10,11 @@ export { // Guards export { AuthGuard } from './guards/auth.guard'; +export { OptionalAuthGuard } from './guards/optional-auth.guard'; // Decorators export { CurrentUser, JwtPayload } from './decorators/current-user.decorator'; +export { Public, IS_PUBLIC_KEY } from './decorators/public.decorator'; // Services export {