Press n or j to go to the next uncovered block, b, p or k for the previous block.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 | 1x 1x 1x 1x 1x 1x 1x 1x | import { AuthenticationBindings, AuthenticationMetadata, AuthenticationStrategy } from '@loopback/authentication';
import { inject } from '@loopback/core';
import { HttpErrors, RedirectRoute, Request, } from '@loopback/rest';
import { AuthenticationStrategyBindings, HmacAuthenticationStrategyOptions } from './keys';
import { securityId } from '@loopback/security';
export class AuthError extends HttpErrors.Unauthorized {
constructor(message: string) {
super(message);
this.code = 'ERR_HMAC_AUTH_INVALID';
Error.captureStackTrace(this, this.constructor);
}
}
export interface HmacPrincipal extends RedirectRoute {
keyId: string;
nonce: string;
created: number;
signature: string;
url: string;
method: string;
options: HmacAuthenticationStrategyOptions
}
export class HmacAuthenticationStrategy implements AuthenticationStrategy {
name = 'hmac-auth';
@inject(AuthenticationStrategyBindings.HMAC_DEFAULT_OPTIONS)
options: HmacAuthenticationStrategyOptions;
constructor(
@inject(AuthenticationBindings.METADATA)
private metadata: AuthenticationMetadata[],
) { }
/**
* Authenticate request
* @param request The request
*/
async authenticate(request: Request): Promise<HmacPrincipal> {
return this.extractCredentials(request);
}
/**
* Extract a token from the request
* @param request The request
*/
private extractCredentials(request: Request): HmacPrincipal {
const { headers, url, method } = request;
if (!headers.authorization) {
throw new AuthError(`Authorization header not found.`);
}
const authHeaderValue = headers.authorization;
if (!authHeaderValue.startsWith('Signature')) {
throw new AuthError(`Authorization header is not of type 'Signature'.`);
}
const parts = authHeaderValue.split(' ');
if (parts.length !== 2) {
throw new AuthError(
`Authorization header value has too many parts. It must follow the pattern: 'Signature keyId="xxx",nonce="yyy",created="zzz",signature="zzz"' is a valid HMAC signature.`,
);
}
return this.extractCredentialsFromParts(parts[1], url, method);
}
private extractCredentialsFromParts(
part: string,
url: string,
method: string,
): HmacPrincipal {
const [keyIdValue, nonceValue, createdValue, signatureValue] = part.split(',');
const keyIdMatched = this.extractValueFromPart(keyIdValue, 'keyId');
const nonceMatched = this.extractValueFromPart(nonceValue, 'nonce');
const createdMatched = this.extractValueFromPart(createdValue, 'created');
const signatureMatched = this.extractValueFromPart(signatureValue, 'signature');
if (isNaN(+createdMatched[1])) {
throw new AuthError(`created was not a valid unix timestamp`);
}
const timeDiff = Math.floor(Date.now() / 1000) - Math.floor(+createdMatched[1] / 1000);
if (timeDiff > this.options.maxInterval) {
throw new AuthError(`The time difference between generated and requested time is too great`);
}
return {
[securityId]: keyIdMatched[1],
keyId: keyIdMatched[1],
nonce: nonceMatched[1],
created: +createdMatched[1],
signature: signatureMatched[1],
url,
method,
options: this.options
} as unknown as HmacPrincipal;
}
private extractValueFromPart(part: string, name: string): RegExpMatchArray {
const regex = new RegExp(`^${name}="(.{1,})"$`, 'g');
const matched = regex.exec(part);
if (!Array.isArray(matched) || matched.length !== 2 || typeof matched[1] !== 'string') {
throw new AuthError(`${name} was not present in header`);
}
return matched;
}
}
|