@point3/logto-module
Version:
포인트3 내부 logto Authentication 모듈입니다
96 lines (77 loc) • 3.37 kB
text/typescript
import {
Injectable,
CanActivate,
ExecutionContext,
UnauthorizedException,
InternalServerErrorException,
Inject,
HttpStatus
} from '@nestjs/common';
import { GqlExecutionContext } from '@nestjs/graphql';
import { Reflector } from '@nestjs/core';
import { IncomingHttpHeaders } from 'http';
import { errors } from 'jose';
import { p3Values } from 'point3-common-tool';
import { LogtoTokenVerifier, LogtoTokenVerifierToken } from '../token';
import { ConfigService } from '@nestjs/config';
export const LogtoTokenGuardToken = Symbol('LogtoTokenGuard');
()
export class LogtoTokenGuard implements CanActivate {
private reflector: Reflector = new Reflector();
constructor(
(LogtoTokenVerifierToken)
private tokenVerifier: LogtoTokenVerifier,
private configService: ConfigService
) { }
async canActivate(context: ExecutionContext): Promise<boolean> {
if (this.configService.get<string>('NODE_ENV') === 'local') {
return true;
}
//매타데이터에서 필요한 스코프와 역할을 가져온다.
const requiredScopes = this.reflector.get<string[]>('requiredScopes', context.getHandler());
const requiredRoles = this.reflector.get<string[]>('requiredRoles', context.getHandler());
const request = this.getRequest(context);
//헤더에서 베어러 토큰을 추출한다
try {
const bearerToken = this.extractBearerTokenFrom(request.headers);
const result = await this.tokenVerifier.verifyToken(bearerToken, requiredScopes, requiredRoles);
// request.user에 사용자 정보를 추가한다.
request.user = {
userId: result.sub,
managerId: p3Values.Guid.parse(result.managerId),
clientId: result.clientId ? p3Values.Guid.parse(result.clientId) : undefined,
}
return true;
} catch (error) {
if (error instanceof UnauthorizedException) throw error;
if (error instanceof errors.JOSEError) throw new UnauthorizedException(error);
if (error instanceof Error) throw new InternalServerErrorException("요청을 처리하지 못하였습니다.", `${HttpStatus.INTERNAL_SERVER_ERROR}`);
throw new UnauthorizedException("접근이 허용되지 않습니다.");
}
}
/**
* Extracts the Bearer token from the authorization header.
* @param headers - The incoming HTTP headers.
* @returns The extracted token.
* @throws UnauthorizedException if the authorization header is missing or invalid.
*/
private extractBearerTokenFrom(headers: IncomingHttpHeaders): string {
const bearerTokenIdentifier = 'Bearer';
if (!headers.authorization) {
throw new UnauthorizedException('Authorization header is missing');
}
if (!headers.authorization.startsWith(bearerTokenIdentifier)) {
throw new UnauthorizedException('Authorization token type not supported');
}
return headers.authorization.slice(bearerTokenIdentifier.length + 1);
};
private getRequest(context: ExecutionContext): any {
// Works for both REST and GraphQL
if (context.getType<'http' | 'graphql'>() === 'graphql') {
const gqlCtx = GqlExecutionContext.create(context);
// depends on what you return from GraphQLModule context: ({ req }) => ({ req })
return gqlCtx.getContext().req;
}
return context.switchToHttp().getRequest();
}
}