authorization presenter
This commit is contained in:
parent
1d2e7da673
commit
7dc6e7795f
|
@ -21,7 +21,7 @@ import { Authentication } from '../../domain/entities/authentication';
|
||||||
import { Username } from '../../domain/entities/username';
|
import { Username } from '../../domain/entities/username';
|
||||||
import { ValidateAuthenticationQuery } from '../../queries/validate-authentication.query';
|
import { ValidateAuthenticationQuery } from '../../queries/validate-authentication.query';
|
||||||
import { AuthenticationPresenter } from './authentication.presenter';
|
import { AuthenticationPresenter } from './authentication.presenter';
|
||||||
import { RpcValidationPipe } from './rpc.validation-pipe';
|
import { RpcValidationPipe } from '../../../../utils/pipes/rpc.validation-pipe';
|
||||||
import { UsernamePresenter } from './username.presenter';
|
import { UsernamePresenter } from './username.presenter';
|
||||||
|
|
||||||
@UsePipes(
|
@UsePipes(
|
||||||
|
@ -43,10 +43,14 @@ export class AuthenticationController {
|
||||||
data: ValidateAuthenticationRequest,
|
data: ValidateAuthenticationRequest,
|
||||||
): Promise<AuthenticationPresenter> {
|
): Promise<AuthenticationPresenter> {
|
||||||
try {
|
try {
|
||||||
const auth: Authentication = await this._queryBus.execute(
|
const authentication: Authentication = await this._queryBus.execute(
|
||||||
new ValidateAuthenticationQuery(data.username, data.password),
|
new ValidateAuthenticationQuery(data.username, data.password),
|
||||||
);
|
);
|
||||||
return this._mapper.map(auth, Authentication, AuthenticationPresenter);
|
return this._mapper.map(
|
||||||
|
authentication,
|
||||||
|
Authentication,
|
||||||
|
AuthenticationPresenter,
|
||||||
|
);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
throw new RpcException({
|
throw new RpcException({
|
||||||
code: 7,
|
code: 7,
|
||||||
|
@ -60,10 +64,14 @@ export class AuthenticationController {
|
||||||
data: CreateAuthenticationRequest,
|
data: CreateAuthenticationRequest,
|
||||||
): Promise<AuthenticationPresenter> {
|
): Promise<AuthenticationPresenter> {
|
||||||
try {
|
try {
|
||||||
const auth: Authentication = await this._commandBus.execute(
|
const authentication: Authentication = await this._commandBus.execute(
|
||||||
new CreateAuthenticationCommand(data),
|
new CreateAuthenticationCommand(data),
|
||||||
);
|
);
|
||||||
return this._mapper.map(auth, Authentication, AuthenticationPresenter);
|
return this._mapper.map(
|
||||||
|
authentication,
|
||||||
|
Authentication,
|
||||||
|
AuthenticationPresenter,
|
||||||
|
);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
if (e instanceof DatabaseException) {
|
if (e instanceof DatabaseException) {
|
||||||
if (e.message.includes('Already exists')) {
|
if (e.message.includes('Already exists')) {
|
||||||
|
@ -135,11 +143,15 @@ export class AuthenticationController {
|
||||||
data: UpdatePasswordRequest,
|
data: UpdatePasswordRequest,
|
||||||
): Promise<AuthenticationPresenter> {
|
): Promise<AuthenticationPresenter> {
|
||||||
try {
|
try {
|
||||||
const auth: Authentication = await this._commandBus.execute(
|
const authentication: Authentication = await this._commandBus.execute(
|
||||||
new UpdatePasswordCommand(data),
|
new UpdatePasswordCommand(data),
|
||||||
);
|
);
|
||||||
|
|
||||||
return this._mapper.map(auth, Authentication, AuthenticationPresenter);
|
return this._mapper.map(
|
||||||
|
authentication,
|
||||||
|
Authentication,
|
||||||
|
AuthenticationPresenter,
|
||||||
|
);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
throw new RpcException({
|
throw new RpcException({
|
||||||
code: 7,
|
code: 7,
|
||||||
|
|
|
@ -1,23 +1,38 @@
|
||||||
import { Controller } from '@nestjs/common';
|
import { Mapper } from '@automapper/core';
|
||||||
|
import { InjectMapper } from '@automapper/nestjs';
|
||||||
|
import { Controller, UsePipes } from '@nestjs/common';
|
||||||
import { QueryBus } from '@nestjs/cqrs';
|
import { QueryBus } from '@nestjs/cqrs';
|
||||||
import { GrpcMethod, RpcException } from '@nestjs/microservices';
|
import { GrpcMethod, RpcException } from '@nestjs/microservices';
|
||||||
|
import { RpcValidationPipe } from 'src/utils/pipes/rpc.validation-pipe';
|
||||||
import { DecisionRequest } from '../../domain/dtos/decision.request';
|
import { DecisionRequest } from '../../domain/dtos/decision.request';
|
||||||
|
import { Authorization } from '../../domain/entities/authorization';
|
||||||
import { DecisionQuery } from '../../queries/decision.query';
|
import { DecisionQuery } from '../../queries/decision.query';
|
||||||
import { DecisionResult } from '../secondaries/decision-result';
|
import { AuthorizationPresenter } from './authorization.presenter';
|
||||||
|
|
||||||
|
@UsePipes(
|
||||||
|
new RpcValidationPipe({
|
||||||
|
whitelist: true,
|
||||||
|
forbidUnknownValues: false,
|
||||||
|
}),
|
||||||
|
)
|
||||||
@Controller()
|
@Controller()
|
||||||
export class AuthorizationController {
|
export class AuthorizationController {
|
||||||
constructor(private readonly _queryBus: QueryBus) {}
|
constructor(
|
||||||
|
private readonly _queryBus: QueryBus,
|
||||||
|
@InjectMapper() private readonly _mapper: Mapper,
|
||||||
|
) {}
|
||||||
|
|
||||||
@GrpcMethod('AuthorizationService', 'Decide')
|
@GrpcMethod('AuthorizationService', 'Decide')
|
||||||
async decide(data: DecisionRequest): Promise<DecisionResult> {
|
async decide(data: DecisionRequest): Promise<AuthorizationPresenter> {
|
||||||
try {
|
try {
|
||||||
const decision: boolean = await this._queryBus.execute(
|
const authorization: Authorization = await this._queryBus.execute(
|
||||||
new DecisionQuery(data.uuid, data.domain, data.action, data.context),
|
new DecisionQuery(data.uuid, data.domain, data.action, data.context),
|
||||||
);
|
);
|
||||||
return {
|
return this._mapper.map(
|
||||||
allow: decision,
|
authorization,
|
||||||
};
|
Authorization,
|
||||||
|
AuthorizationPresenter,
|
||||||
|
);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
throw new RpcException({
|
throw new RpcException({
|
||||||
code: 7,
|
code: 7,
|
||||||
|
|
|
@ -0,0 +1,6 @@
|
||||||
|
import { AutoMap } from '@automapper/classes';
|
||||||
|
|
||||||
|
export class AuthorizationPresenter {
|
||||||
|
@AutoMap()
|
||||||
|
allow: boolean;
|
||||||
|
}
|
|
@ -7,6 +7,7 @@ import { Domain } from '../../domain/dtos/domain.enum';
|
||||||
import { IMakeDecision } from '../../domain/interfaces/decision-maker';
|
import { IMakeDecision } from '../../domain/interfaces/decision-maker';
|
||||||
import { ContextItem } from '../../domain/dtos/context-item';
|
import { ContextItem } from '../../domain/dtos/context-item';
|
||||||
import { Decision } from './decision';
|
import { Decision } from './decision';
|
||||||
|
import { Authorization } from '../../domain/entities/authorization';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class OpaDecisionMaker extends IMakeDecision {
|
export class OpaDecisionMaker extends IMakeDecision {
|
||||||
|
@ -22,7 +23,7 @@ export class OpaDecisionMaker extends IMakeDecision {
|
||||||
domain: Domain,
|
domain: Domain,
|
||||||
action: Action,
|
action: Action,
|
||||||
context: Array<ContextItem>,
|
context: Array<ContextItem>,
|
||||||
): Promise<boolean> {
|
): Promise<Authorization> {
|
||||||
const { data } = await lastValueFrom(
|
const { data } = await lastValueFrom(
|
||||||
this._httpService.post<Decision>(
|
this._httpService.post<Decision>(
|
||||||
this._configService.get<string>('OPA_URL') + domain + '/' + action,
|
this._configService.get<string>('OPA_URL') + domain + '/' + action,
|
||||||
|
@ -34,6 +35,6 @@ export class OpaDecisionMaker extends IMakeDecision {
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
return data.result.allow;
|
return new Authorization(data.result.allow);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,11 +5,12 @@ import { DatabaseModule } from '../database/database.module';
|
||||||
import { AuthorizationController } from './adapters/primaries/authorization.controller';
|
import { AuthorizationController } from './adapters/primaries/authorization.controller';
|
||||||
import { OpaDecisionMaker } from './adapters/secondaries/opa.decision-maker';
|
import { OpaDecisionMaker } from './adapters/secondaries/opa.decision-maker';
|
||||||
import { DecisionUseCase } from './domain/usecases/decision.usecase';
|
import { DecisionUseCase } from './domain/usecases/decision.usecase';
|
||||||
|
import { AuthorizationProfile } from './mappers/authorization.profile';
|
||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
imports: [DatabaseModule, CqrsModule, HttpModule],
|
imports: [DatabaseModule, CqrsModule, HttpModule],
|
||||||
exports: [],
|
exports: [],
|
||||||
controllers: [AuthorizationController],
|
controllers: [AuthorizationController],
|
||||||
providers: [OpaDecisionMaker, DecisionUseCase],
|
providers: [OpaDecisionMaker, DecisionUseCase, AuthorizationProfile],
|
||||||
})
|
})
|
||||||
export class AuthorizationModule {}
|
export class AuthorizationModule {}
|
||||||
|
|
|
@ -0,0 +1,10 @@
|
||||||
|
import { AutoMap } from '@automapper/classes';
|
||||||
|
|
||||||
|
export class Authorization {
|
||||||
|
@AutoMap()
|
||||||
|
allow: boolean;
|
||||||
|
|
||||||
|
constructor(allow: boolean) {
|
||||||
|
this.allow = allow;
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,6 +1,7 @@
|
||||||
import { Injectable } from '@nestjs/common';
|
import { Injectable } from '@nestjs/common';
|
||||||
import { Action } from '../dtos/action.enum';
|
import { Action } from '../dtos/action.enum';
|
||||||
import { Domain } from '../dtos/domain.enum';
|
import { Domain } from '../dtos/domain.enum';
|
||||||
|
import { Authorization } from '../entities/authorization';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export abstract class IMakeDecision {
|
export abstract class IMakeDecision {
|
||||||
|
@ -9,5 +10,5 @@ export abstract class IMakeDecision {
|
||||||
domain: Domain,
|
domain: Domain,
|
||||||
action: Action,
|
action: Action,
|
||||||
context: Array<{ name: string; value: string }>,
|
context: Array<{ name: string; value: string }>,
|
||||||
): Promise<boolean>;
|
): Promise<Authorization>;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,12 +1,13 @@
|
||||||
import { QueryHandler } from '@nestjs/cqrs';
|
import { QueryHandler } from '@nestjs/cqrs';
|
||||||
import { OpaDecisionMaker } from '../../adapters/secondaries/opa.decision-maker';
|
import { OpaDecisionMaker } from '../../adapters/secondaries/opa.decision-maker';
|
||||||
import { DecisionQuery } from '../../queries/decision.query';
|
import { DecisionQuery } from '../../queries/decision.query';
|
||||||
|
import { Authorization } from '../entities/authorization';
|
||||||
|
|
||||||
@QueryHandler(DecisionQuery)
|
@QueryHandler(DecisionQuery)
|
||||||
export class DecisionUseCase {
|
export class DecisionUseCase {
|
||||||
constructor(private readonly _decisionMaker: OpaDecisionMaker) {}
|
constructor(private readonly _decisionMaker: OpaDecisionMaker) {}
|
||||||
|
|
||||||
async execute(decisionQuery: DecisionQuery): Promise<boolean> {
|
async execute(decisionQuery: DecisionQuery): Promise<Authorization> {
|
||||||
return this._decisionMaker.decide(
|
return this._decisionMaker.decide(
|
||||||
decisionQuery.uuid,
|
decisionQuery.uuid,
|
||||||
decisionQuery.domain,
|
decisionQuery.domain,
|
||||||
|
|
|
@ -0,0 +1,18 @@
|
||||||
|
import { createMap, Mapper } from '@automapper/core';
|
||||||
|
import { AutomapperProfile, InjectMapper } from '@automapper/nestjs';
|
||||||
|
import { Injectable } from '@nestjs/common';
|
||||||
|
import { AuthorizationPresenter } from '../adapters/primaries/authorization.presenter';
|
||||||
|
import { Authorization } from '../domain/entities/authorization';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class AuthorizationProfile extends AutomapperProfile {
|
||||||
|
constructor(@InjectMapper() mapper: Mapper) {
|
||||||
|
super(mapper);
|
||||||
|
}
|
||||||
|
|
||||||
|
override get profile() {
|
||||||
|
return (mapper: any) => {
|
||||||
|
createMap(mapper, Authorization, AuthorizationPresenter);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
|
@ -7,6 +7,7 @@ import { ContextItem } from '../../domain/dtos/context-item';
|
||||||
import { DecisionRequest } from '../../domain/dtos/decision.request';
|
import { DecisionRequest } from '../../domain/dtos/decision.request';
|
||||||
import { Domain } from '../../domain/dtos/domain.enum';
|
import { Domain } from '../../domain/dtos/domain.enum';
|
||||||
import { DecisionUseCase } from '../../domain/usecases/decision.usecase';
|
import { DecisionUseCase } from '../../domain/usecases/decision.usecase';
|
||||||
|
import { AuthorizationProfile } from '../../mappers/authorization.profile';
|
||||||
import { DecisionQuery } from '../../queries/decision.query';
|
import { DecisionQuery } from '../../queries/decision.query';
|
||||||
|
|
||||||
const mockOpaDecisionMaker = {
|
const mockOpaDecisionMaker = {
|
||||||
|
@ -25,6 +26,7 @@ describe('DecisionUseCase', () => {
|
||||||
useValue: mockOpaDecisionMaker,
|
useValue: mockOpaDecisionMaker,
|
||||||
},
|
},
|
||||||
DecisionUseCase,
|
DecisionUseCase,
|
||||||
|
AuthorizationProfile,
|
||||||
],
|
],
|
||||||
}).compile();
|
}).compile();
|
||||||
|
|
||||||
|
|
|
@ -66,23 +66,23 @@ describe('OpaDecisionMaker', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('execute', () => {
|
describe('execute', () => {
|
||||||
it('should return a truthy decision', async () => {
|
it('should return a truthy authorization', async () => {
|
||||||
const decision = await opaDecisionMaker.decide(
|
const authorization = await opaDecisionMaker.decide(
|
||||||
'bb281075-1b98-4456-89d6-c643d3044a91',
|
'bb281075-1b98-4456-89d6-c643d3044a91',
|
||||||
Domain.user,
|
Domain.user,
|
||||||
Action.read,
|
Action.read,
|
||||||
[],
|
[],
|
||||||
);
|
);
|
||||||
expect(decision).toBeTruthy();
|
expect(authorization.allow).toBeTruthy();
|
||||||
});
|
});
|
||||||
it('should return a falsy decision', async () => {
|
it('should return a falsy authorization', async () => {
|
||||||
const decision = await opaDecisionMaker.decide(
|
const authorization = await opaDecisionMaker.decide(
|
||||||
'bb281075-1b98-4456-89d6-c643d3044a91',
|
'bb281075-1b98-4456-89d6-c643d3044a91',
|
||||||
Domain.user,
|
Domain.user,
|
||||||
Action.read,
|
Action.read,
|
||||||
[],
|
[],
|
||||||
);
|
);
|
||||||
expect(decision).toBeFalsy();
|
expect(authorization.allow).toBeFalsy();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
Loading…
Reference in New Issue