mirror of
https://github.com/dadosfera/maestro.git
synced 2026-09-11 10:14:47 +00:00
137 lines
3.9 KiB
TypeScript
137 lines
3.9 KiB
TypeScript
import {
|
|
Injectable,
|
|
CanActivate,
|
|
OnApplicationBootstrap,
|
|
ExecutionContext,
|
|
Inject,
|
|
} from '@nestjs/common';
|
|
import { Reflector } from '@nestjs/core';
|
|
import assert from 'assert';
|
|
import jwt from 'jsonwebtoken';
|
|
import { DadosferaLogger } from '@dadosfera/dadosfera-logs';
|
|
import { AuthClientService } from '../modules/auth/auth.service';
|
|
import {
|
|
AuthenticationFunction,
|
|
AUTH_FUNCTION_KEY,
|
|
} from '../decorators/authentication.decorator';
|
|
import { RequestUser } from '../decorators/user.decorator';
|
|
import ErrorBuilder from '../utils/ErrorBuilder';
|
|
import ErrorCodes from '../utils/errorCodes';
|
|
|
|
@Injectable()
|
|
export class AuthenticationGuard
|
|
implements CanActivate, OnApplicationBootstrap
|
|
{
|
|
logger: DadosferaLogger;
|
|
|
|
pems: Map<string, string>;
|
|
|
|
constructor(
|
|
@Inject(DadosferaLogger)
|
|
dadosferaLogger: DadosferaLogger,
|
|
private reflector: Reflector,
|
|
private authClient: AuthClientService,
|
|
) {
|
|
this.pems = new Map();
|
|
this.logger = dadosferaLogger.logger;
|
|
}
|
|
|
|
async onApplicationBootstrap() {
|
|
return this.loadDucJWKS();
|
|
}
|
|
|
|
private async loadDucJWKS() {
|
|
const { keys } = await this.authClient.getPublicKeys();
|
|
|
|
keys.forEach((key) => {
|
|
this.pems.set(key.kid, key.pem);
|
|
});
|
|
}
|
|
|
|
canActivate(ctx: ExecutionContext): boolean {
|
|
const authFunctions = this.reflector.getAllAndMerge<
|
|
AuthenticationFunction[]
|
|
>(AUTH_FUNCTION_KEY, [ctx.getClass(), ctx.getHandler()]);
|
|
const mustBeAuthenticated = authFunctions.length > 0;
|
|
|
|
const request = ctx.switchToHttp().getRequest();
|
|
const accessToken = this.validateToken(request, mustBeAuthenticated);
|
|
|
|
if (!mustBeAuthenticated) {
|
|
// no need to be authenticated
|
|
return true;
|
|
}
|
|
|
|
if (!accessToken) {
|
|
// couldn't load valid token
|
|
throw new ErrorBuilder(ErrorCodes.AUTH.UNAUTHORIZED);
|
|
}
|
|
|
|
// every authentication function must return true to authenticate
|
|
if (!authFunctions.every((func) => func(request, accessToken))) {
|
|
throw new ErrorBuilder(ErrorCodes.AUTH.FORBIDDEN);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
private validateToken(
|
|
request,
|
|
mustBeAuthenticated: boolean,
|
|
): RequestUser | false {
|
|
const accessToken = request.get('Authorization');
|
|
let accessTokenPayload: RequestUser;
|
|
|
|
// If the user isn't authenticated, an error will occurr anywhere here.
|
|
// Fancy error avoidance isn't performed by purpose, such as avoiding to access null values.
|
|
try {
|
|
const accessTokenDecoded: any = jwt.decode(accessToken, {
|
|
complete: true,
|
|
});
|
|
|
|
/*assert(
|
|
accessTokenDecoded.payload.iss === 'duc',
|
|
'token should be issued by DUC',
|
|
);*/
|
|
|
|
assert(
|
|
accessTokenDecoded?.payload?.token_use === 'access',
|
|
'should be an access token',
|
|
);
|
|
|
|
const { kid } = accessTokenDecoded.header;
|
|
const pemValue: string = this.pems.get(kid);
|
|
|
|
jwt.verify(accessToken, pemValue);
|
|
accessTokenPayload = accessTokenDecoded.payload;
|
|
} catch (err) {
|
|
// log errors if authentication is required
|
|
if (mustBeAuthenticated) {
|
|
this.logger.error('Unable to verify duc access token\n' + err?.stack);
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
request.accessTokenPayload = accessTokenPayload;
|
|
request.user = {
|
|
user_id: accessTokenPayload.user_id,
|
|
username: accessTokenPayload.username,
|
|
permissions: accessTokenPayload.permissions,
|
|
customer_id: accessTokenPayload.customer_id,
|
|
customer_name: accessTokenPayload.customer_name,
|
|
customer_tier: accessTokenPayload.customer_tier,
|
|
access_token: accessToken,
|
|
};
|
|
// TODO: for backwards compatibility. remove in the future
|
|
request.body.info = {
|
|
user_id: accessTokenPayload.user_id,
|
|
customer_id: accessTokenPayload.customer_id,
|
|
customer: accessTokenPayload.customer_name,
|
|
customer_tier: accessTokenPayload.customer_tier,
|
|
};
|
|
|
|
return accessTokenPayload;
|
|
}
|
|
}
|