mirror of
https://github.com/dadosfera/maestro.git
synced 2026-09-17 06:44:47 +00:00
173 lines
5.1 KiB
TypeScript
173 lines
5.1 KiB
TypeScript
import {
|
|
Injectable,
|
|
CanActivate,
|
|
OnApplicationBootstrap,
|
|
ExecutionContext,
|
|
Inject,
|
|
ForbiddenException,
|
|
} 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';
|
|
import { ApiKeyService } from 'src/modules/api-key/api-key.service';
|
|
|
|
@Injectable()
|
|
export class AuthenticationGuard
|
|
implements CanActivate, OnApplicationBootstrap
|
|
{
|
|
logger: DadosferaLogger;
|
|
|
|
pems: Map<string, string>;
|
|
|
|
constructor(
|
|
@Inject(DadosferaLogger)
|
|
dadosferaLogger: DadosferaLogger,
|
|
private reflector: Reflector,
|
|
private authClient: AuthClientService,
|
|
private apiKeyService: ApiKeyService
|
|
) {
|
|
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);
|
|
});
|
|
}
|
|
|
|
async canActivate(ctx: ExecutionContext): Promise<boolean> {
|
|
const authFunctions = this.reflector.getAllAndMerge<
|
|
AuthenticationFunction[]
|
|
>(AUTH_FUNCTION_KEY, [ctx.getClass(), ctx.getHandler()]);
|
|
const mustBeAuthenticated = authFunctions.length > 0;
|
|
|
|
if (!mustBeAuthenticated) {
|
|
// no need to be authenticated
|
|
return true;
|
|
}
|
|
|
|
const request = ctx.switchToHttp().getRequest();
|
|
const apiKey = request.get('X-api-key');
|
|
if (apiKey) {
|
|
const {
|
|
api_key
|
|
} = await this.apiKeyService.get(apiKey);
|
|
|
|
request.user = {
|
|
user_id: api_key.user_id,
|
|
username: api_key.username,
|
|
permissions: api_key.permissions,
|
|
customer_id: api_key.customer_id,
|
|
customer_name: api_key.customer_name,
|
|
customer_tier: api_key.customer_tier,
|
|
customer_modules: api_key.customer_modules,
|
|
access_token: apiKey,
|
|
};
|
|
|
|
return true;
|
|
}
|
|
|
|
const accessToken = this.validateToken(request, mustBeAuthenticated);
|
|
|
|
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;
|
|
}
|
|
|
|
// Bloquear outros customer de usar o maestor dedicado
|
|
const DEDICATED_PROXY = process.env.DEDICATED_PROXY || '';
|
|
if (DEDICATED_PROXY !== '' && DEDICATED_PROXY !== accessTokenPayload.customer_id) {
|
|
throw new ErrorBuilder(ErrorCodes.AUTH.FORBIDDEN);
|
|
}
|
|
|
|
// Bloquear o customer de acesso o maestro publico
|
|
const hasNetworkPolicyModule = accessTokenPayload.customer_modules.includes('network-policy');
|
|
if (hasNetworkPolicyModule && DEDICATED_PROXY === '') {
|
|
throw new ForbiddenException(ErrorCodes.AUTH.FORBIDDEN);
|
|
}
|
|
|
|
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,
|
|
customer_modules: accessTokenPayload.customer_modules,
|
|
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;
|
|
}
|
|
}
|