Skip to content

Commit

Permalink
feat(component): add a new strategy for otp
Browse files Browse the repository at this point in the history
added a new 2-factor authentication strategy
  • Loading branch information
AnkurBansalSF committed Apr 6, 2022
1 parent 401097c commit 801621c
Show file tree
Hide file tree
Showing 12 changed files with 127 additions and 1 deletion.
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@
"@loopback/rest": "^11.1.1"
},
"dependencies": {
"@artsy/passport-local-with-otp": "^0.3.1",
"@exlinc/keycloak-passport": "^1.0.2",
"@loopback/context": "^4.1.1",
"@loopback/core": "^3.1.1",
Expand Down
5 changes: 5 additions & 0 deletions src/component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@ import {
LocalPasswordVerifyProvider,
ResourceOwnerPasswordStrategyFactoryProvider,
ResourceOwnerVerifyProvider,
PassportOtpStrategyFactoryProvider,
OtpVerifyProvider,
} from './strategies';
import {Strategies} from './strategies/keys';

Expand All @@ -47,6 +49,8 @@ export class AuthenticationComponent implements Component {
// Strategy function factories
[Strategies.Passport.LOCAL_STRATEGY_FACTORY.key]:
LocalPasswordStrategyFactoryProvider,
[Strategies.Passport.OTP_AUTH_STRATEGY_FACTORY.key]:
PassportOtpStrategyFactoryProvider,
[Strategies.Passport.CLIENT_PASSWORD_STRATEGY_FACTORY.key]:
ClientPasswordStrategyFactoryProvider,
[Strategies.Passport.BEARER_STRATEGY_FACTORY.key]:
Expand All @@ -71,6 +75,7 @@ export class AuthenticationComponent implements Component {
ClientPasswordVerifyProvider,
[Strategies.Passport.LOCAL_PASSWORD_VERIFIER.key]:
LocalPasswordVerifyProvider,
[Strategies.Passport.OTP_VERIFIER.key]: OtpVerifyProvider,
[Strategies.Passport.BEARER_TOKEN_VERIFIER.key]:
BearerTokenVerifyProvider,
[Strategies.Passport.RESOURCE_OWNER_PASSWORD_VERIFIER.key]:
Expand Down
10 changes: 10 additions & 0 deletions src/strategies/keys.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,16 @@ export namespace Strategies {
'sf.passport.verifier.localPassword',
);

// Passport-local-with-otp startegy
export const OTP_AUTH_STRATEGY_FACTORY =
BindingKey.create<LocalPasswordStrategyFactory>(
'sf.passport.strategyFactory.otpAuth',
);
export const OTP_VERIFIER =
BindingKey.create<VerifyFunction.LocalPasswordFn>(
'sf.passport.verifier.otpAuth',
);

// Passport-oauth2-client-password strategy
export const CLIENT_PASSWORD_STRATEGY_FACTORY =
BindingKey.create<ClientPasswordStrategyFactory>(
Expand Down
1 change: 1 addition & 0 deletions src/strategies/passport/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,4 @@ export * from './passport-azure-ad';
export * from './passport-insta-oauth2';
export * from './passport-apple-oauth2';
export * from './passport-facebook-oauth2';
export * from './passport-local-with-otp';
2 changes: 2 additions & 0 deletions src/strategies/passport/passport-local-with-otp/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export * from './otp-strategy-factory.provider';
export * from './otp-verify.provider';
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import {inject, Provider} from '@loopback/core';
import {HttpErrors} from '@loopback/rest';
import {AuthErrorKeys} from '../../../error-keys';
import {Strategies} from '../../keys';
import {Otp, VerifyFunction} from '../../types';
export const PassportOtpStrategy = require('@artsy/passport-local-with-otp');

export interface PassportOtpStrategyFactory {
(
options: Otp.StrategyOptions,
verifierPassed?: VerifyFunction.OtpAuthFn,
): typeof PassportOtpStrategy;
}

export class PassportOtpStrategyFactoryProvider
implements Provider<PassportOtpStrategyFactory>
{
constructor(
@inject(Strategies.Passport.OTP_VERIFIER)
private readonly verifierOtp: VerifyFunction.OtpAuthFn,
) {}

value(): PassportOtpStrategyFactory {
return (options, verifier) =>
this.getPassportOtpStrategyVerifier(options, verifier);
}

getPassportOtpStrategyVerifier(
options?: Otp.StrategyOptions,
verifierPassed?: VerifyFunction.OtpAuthFn,
): typeof PassportOtpStrategy {
const verifyFn = verifierPassed ?? this.verifierOtp;
return new PassportOtpStrategy(
options,
async (
username: string,
password: string,
otp: string,
cb: Otp.VerifyCallback,
) => {
try {
const user = await verifyFn(username, password, otp, cb);
if (!user) {
throw new HttpErrors.Unauthorized(AuthErrorKeys.InvalidCredentials);
}
cb(undefined, user);
} catch (err) {
cb(err);
}
},
);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import {Provider} from '@loopback/context';
import {HttpErrors} from '@loopback/rest';

import {VerifyFunction} from '../../types';

export class OtpVerifyProvider implements Provider<VerifyFunction.OtpAuthFn> {
constructor() {}

value(): VerifyFunction.OtpAuthFn {
return async (username: string, password: string, otp: string) => {
throw new HttpErrors.NotImplemented(
`VerifyFunction.OtpAuthFn is not implemented`,
);
};
}
}
1 change: 1 addition & 0 deletions src/strategies/types/index.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
export * from './types';
export * from './keycloak.types';
export * from './otp-auth.types';
19 changes: 19 additions & 0 deletions src/strategies/types/otp-auth.types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
export namespace Otp {
export interface StrategyOptions {
skipTotpVerification: string;
usernameField: string;
passwordField: string;
codeField: string;
window: number;
otpField: string;
passReqToCallback: boolean;
}

export type VerifyCallback = (
err?: string | Error,
// eslint-disable-next-line @typescript-eslint/no-explicit-any
user?: any,
// eslint-disable-next-line @typescript-eslint/no-explicit-any
info?: any,
) => void;
}
9 changes: 9 additions & 0 deletions src/strategies/types/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,15 @@ export namespace VerifyFunction {
(username: string, password: string, req?: Request): Promise<T | null>;
}

export interface OtpAuthFn<T = IAuthUser> extends GenericAuthFn<T> {
(
username: string,
password: string,
otp: string,
req?: Request,
): Promise<T | null>;
}

export interface BearerFn<T = IAuthUser> extends GenericAuthFn<T> {
(token: string, req?: Request): Promise<T | null>;
}
Expand Down
10 changes: 9 additions & 1 deletion src/strategies/user-auth-strategy.provider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,9 @@ import {
InstagramAuthStrategyFactory,
KeycloakStrategyFactory,
FacebookAuthStrategyFactory,
PassportOtpStrategyFactory,
} from './passport';
import {Keycloak, VerifyFunction} from './types';
import {Keycloak, Otp, VerifyFunction} from './types';

interface ExtendedStrategyOption extends FacebookStrategy.StrategyOption {
passReqToCallback?: false;
Expand All @@ -38,6 +39,8 @@ export class AuthStrategyProvider implements Provider<Strategy | undefined> {
private readonly metadata: AuthenticationMetadata,
@inject(Strategies.Passport.LOCAL_STRATEGY_FACTORY)
private readonly getLocalStrategyVerifier: LocalPasswordStrategyFactory,
@inject(Strategies.Passport.OTP_AUTH_STRATEGY_FACTORY)
private readonly getOtpVerifier: PassportOtpStrategyFactory,
@inject(Strategies.Passport.BEARER_STRATEGY_FACTORY)
private readonly getBearerStrategyVerifier: BearerStrategyFactory,
@inject(Strategies.Passport.RESOURCE_OWNER_STRATEGY_FACTORY)
Expand Down Expand Up @@ -129,6 +132,11 @@ export class AuthStrategyProvider implements Provider<Strategy | undefined> {
| ExtendedStrategyOption,
verifier as VerifyFunction.FacebookAuthFn,
);
} else if (name === STRATEGY.LOCAL_WITH_OTP) {
return this.getOtpVerifier(
this.metadata.options as Otp.StrategyOptions,
verifier as VerifyFunction.OtpAuthFn,
);
} else {
return Promise.reject(`The strategy ${name} is not available.`);
}
Expand Down
1 change: 1 addition & 0 deletions src/strategy-name.enum.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,5 @@ export const enum STRATEGY {
FACEBOOK_OAUTH2 = 'Facebook Oauth 2.0',
AZURE_AD = 'Azure AD',
KEYCLOAK = 'keycloak',
LOCAL_WITH_OTP = 'local with otp',
}

0 comments on commit 801621c

Please sign in to comment.