@jvhaile/loopback4-helper
Version:
Helper components and tools for loopback 4.
116 lines (96 loc) • 4.61 kB
text/typescript
import {AuthenticationBindings, AuthenticationMetadata, AuthenticationStrategy} from '@loopback/authentication';
import {Getter, service} from '@loopback/core';
import {HttpErrors, Request} from '@loopback/rest';
import {securityId, UserProfile} from '@loopback/security';
import {inject} from "@loopback/core";
import {AuthenticationService} from "./services/authentication.service";
import {RepositoryBindings} from "./keys";
import {BaseUserRepository} from "./repositories/base.user.repository";
import {BaseSessionRepository} from "./repositories/base.session.repository";
import {BaseClientRepository} from "./repositories/base.client.repository";
import {BaseClient} from "./models/base.client.model";
import {BaseSession} from "./models/base.session.model";
import {BaseUser} from "./models/base.user.model";
export type CurrentUser<U extends BaseUser = BaseUser, C extends BaseClient = BaseClient, S extends BaseSession = BaseSession> = {
[securityId]: string,
client: C,
user: U,
session: S,
}
export class AuthStrategy implements AuthenticationStrategy {
readonly name = 'remit';
constructor(
(AuthenticationService)
readonly authenticationService: AuthenticationService,
.getter(AuthenticationBindings.METADATA)
readonly getMetaData: Getter<AuthenticationMetadata>,
(RepositoryBindings.USER_REPOSITORY)
readonly userRepository: BaseUserRepository,
(RepositoryBindings.CLIENT_REPOSITORY)
readonly clientRepository: BaseClientRepository,
(RepositoryBindings.SESSION_REPOSITORY)
readonly sessionRepository: BaseSessionRepository,
) {
}
async getMeta(): Promise<AuthenticationMetadata | null> {
const meta: any = await this.getMetaData();
if (meta) {
if (meta.forEach) {
return meta.length ? meta[0] : null;
}
return meta;
}
return null;
}
async authenticate(request: Request): Promise<UserProfile | undefined> {
const apiKey = request.header('apiKey');
// @ts-ignore
const userAgent: UserAgent = request.headers['parsed-user-agent'];
const client = await this.validateClient(apiKey);
const meta = await this.getMeta();
if (meta?.options?.passUserAuth) return {
[securityId]: '',
client: client
};
const token = this.extractTokenFromHeader(request);
const session = await this.authenticationService.validateTokenAndGetSession(token, client, userAgent);
const user = await this.userRepository.findById(session.userId)
const allowedRoles = meta?.options?.allowedRoles ?? [];
if (allowedRoles && allowedRoles.length && !allowedRoles.includes(user.role ?? '')) {
throw new HttpErrors.Forbidden("Access denied, account is not authorized for this action!");
}
return {
[securityId]: session.id!,
client,
session,
user
};
}
async validateClient(apiKey: any): Promise<BaseClient> {
//todo client platform verification
if (!apiKey) throw new HttpErrors.Unauthorized(`API Key required.`);
if (typeof apiKey != "string") throw new HttpErrors.Unauthorized(`Invalid API Key format.`);
const client = await this.clientRepository.findOne({where: {apiKey}});
if (!client) throw new HttpErrors.Unauthorized(`Invalid API Key.`);
if (!client.active) throw new HttpErrors.Unauthorized(`API Key is disabled.`);
return client;
}
extractTokenFromHeader(request: Request): string {
if (!request.headers.authorization) {
throw new HttpErrors.Unauthorized(`Authorization header not found.`);
}
const authHeaderValue = request.headers.authorization;
if (!authHeaderValue.startsWith('Bearer')) {
throw new HttpErrors.Unauthorized(
`Authorization header is not of type 'Bearer'.`,
);
}
//split the string into 2 parts : 'Bearer ' and the `xxx.yyy.zzz`
const parts = authHeaderValue.split(' ');
if (parts.length !== 2)
throw new HttpErrors.Unauthorized(
`Authorization header value has too many parts. It must follow the pattern: 'Bearer xx.yy.zz' where xx.yy.zz is a valid JWT token.`,
);
return parts[1];
}
}