1
0
mirror of https://github.com/bitwarden/jslib synced 2025-12-17 16:53:20 +00:00

[Tech debt] Refactor authService and remove LogInHelper (#588)

* Use different strategy classes for different types of login
* General refactor and cleanup of auth logic
* Create subclasses for different types of login credentials
* Create subclasses for different types of tokenRequests
* Create TwoFactorService, move code out of authService
* refactor base CLI commands to use new interface
This commit is contained in:
Thomas Rittson
2022-02-01 09:51:32 +10:00
committed by GitHub
parent 92a65b7b36
commit aa2bdd00be
31 changed files with 1798 additions and 920 deletions

View File

@@ -1,9 +1,18 @@
import { TwoFactorProviderType } from "../../enums/twoFactorProviderType";
import { Utils } from "../../misc/utils";
export class AuthResult {
twoFactor: boolean = false;
captchaSiteKey: string = "";
resetMasterPassword: boolean = false;
forcePasswordReset: boolean = false;
twoFactorProviders: Map<TwoFactorProviderType, { [key: string]: string }> = null;
get requiresCaptcha() {
return !Utils.isNullOrWhitespace(this.captchaSiteKey);
}
get requiresTwoFactor() {
return this.twoFactorProviders != null;
}
}

View File

@@ -0,0 +1,24 @@
import { TokenRequestTwoFactor } from "../request/identityToken/tokenRequest";
export class PasswordLogInCredentials {
constructor(
public email: string,
public masterPassword: string,
public captchaToken?: string,
public twoFactor?: TokenRequestTwoFactor
) {}
}
export class SsoLogInCredentials {
constructor(
public code: string,
public codeVerifier: string,
public redirectUrl: string,
public orgId: string,
public twoFactor?: TokenRequestTwoFactor
) {}
}
export class ApiLogInCredentials {
constructor(public clientId: string, public clientSecret: string) {}
}

View File

@@ -0,0 +1,24 @@
import { TokenRequest, TokenRequestTwoFactor } from "./tokenRequest";
import { DeviceRequest } from "../deviceRequest";
export class ApiTokenRequest extends TokenRequest {
constructor(
public clientId: string,
public clientSecret: string,
protected twoFactor: TokenRequestTwoFactor,
device?: DeviceRequest
) {
super(twoFactor, device);
}
toIdentityToken() {
const obj = super.toIdentityToken(this.clientId);
obj.scope = this.clientId.startsWith("organization") ? "api.organization" : "api";
obj.grant_type = "client_credentials";
obj.client_secret = this.clientSecret;
return obj;
}
}

View File

@@ -0,0 +1,36 @@
import { TokenRequest, TokenRequestTwoFactor } from "./tokenRequest";
import { CaptchaProtectedRequest } from "../captchaProtectedRequest";
import { DeviceRequest } from "../deviceRequest";
import { Utils } from "../../../misc/utils";
export class PasswordTokenRequest extends TokenRequest implements CaptchaProtectedRequest {
constructor(
public email: string,
public masterPasswordHash: string,
public captchaResponse: string,
protected twoFactor: TokenRequestTwoFactor,
device?: DeviceRequest
) {
super(twoFactor, device);
}
toIdentityToken(clientId: string) {
const obj = super.toIdentityToken(clientId);
obj.grant_type = "password";
obj.username = this.email;
obj.password = this.masterPasswordHash;
if (this.captchaResponse != null) {
obj.captchaResponse = this.captchaResponse;
}
return obj;
}
alterIdentityTokenHeaders(headers: Headers) {
headers.set("Auth-Email", Utils.fromUtf8ToUrlB64(this.email));
}
}

View File

@@ -0,0 +1,26 @@
import { TokenRequest, TokenRequestTwoFactor } from "./tokenRequest";
import { DeviceRequest } from "../deviceRequest";
export class SsoTokenRequest extends TokenRequest {
constructor(
public code: string,
public codeVerifier: string,
public redirectUri: string,
protected twoFactor: TokenRequestTwoFactor,
device?: DeviceRequest
) {
super(twoFactor, device);
}
toIdentityToken(clientId: string) {
const obj = super.toIdentityToken(clientId);
obj.grant_type = "authorization_code";
obj.code = this.code;
obj.code_verifier = this.codeVerifier;
obj.redirect_uri = this.redirectUri;
return obj;
}
}

View File

@@ -0,0 +1,48 @@
import { TwoFactorProviderType } from "../../../enums/twoFactorProviderType";
import { DeviceRequest } from "../deviceRequest";
export interface TokenRequestTwoFactor {
provider: TwoFactorProviderType;
token: string;
remember: boolean;
}
export abstract class TokenRequest {
protected device?: DeviceRequest;
constructor(protected twoFactor: TokenRequestTwoFactor, device?: DeviceRequest) {
this.device = device != null ? device : null;
}
alterIdentityTokenHeaders(headers: Headers) {
// Implemented in subclass if required
}
setTwoFactor(twoFactor: TokenRequestTwoFactor) {
this.twoFactor = twoFactor;
}
protected toIdentityToken(clientId: string) {
const obj: any = {
scope: "api offline_access",
client_id: clientId,
};
if (this.device) {
obj.deviceType = this.device.type;
obj.deviceIdentifier = this.device.identifier;
obj.deviceName = this.device.name;
// no push tokens for browser apps yet
// obj.devicePushToken = this.device.pushToken;
}
if (this.twoFactor.token && this.twoFactor.provider != null) {
obj.twoFactorToken = this.twoFactor.token;
obj.twoFactorProvider = this.twoFactor.provider;
obj.twoFactorRemember = this.twoFactor.remember ? "1" : "0";
}
return obj;
}
}

View File

@@ -1,91 +0,0 @@
import { TwoFactorProviderType } from "../../enums/twoFactorProviderType";
import { CaptchaProtectedRequest } from "./captchaProtectedRequest";
import { DeviceRequest } from "./deviceRequest";
import { Utils } from "../../misc/utils";
export class TokenRequest implements CaptchaProtectedRequest {
email: string;
masterPasswordHash: string;
code: string;
codeVerifier: string;
redirectUri: string;
clientId: string;
clientSecret: string;
device?: DeviceRequest;
constructor(
credentials: string[],
codes: string[],
clientIdClientSecret: string[],
public provider: TwoFactorProviderType,
public token: string,
public remember: boolean,
public captchaResponse: string,
device?: DeviceRequest
) {
if (credentials != null && credentials.length > 1) {
this.email = credentials[0];
this.masterPasswordHash = credentials[1];
} else if (codes != null && codes.length > 2) {
this.code = codes[0];
this.codeVerifier = codes[1];
this.redirectUri = codes[2];
} else if (clientIdClientSecret != null && clientIdClientSecret.length > 1) {
this.clientId = clientIdClientSecret[0];
this.clientSecret = clientIdClientSecret[1];
}
this.device = device != null ? device : null;
}
toIdentityToken(clientId: string) {
const obj: any = {
scope: "api offline_access",
client_id: clientId,
};
if (this.clientSecret != null) {
obj.scope = clientId.startsWith("organization") ? "api.organization" : "api";
obj.grant_type = "client_credentials";
obj.client_secret = this.clientSecret;
} else if (this.masterPasswordHash != null && this.email != null) {
obj.grant_type = "password";
obj.username = this.email;
obj.password = this.masterPasswordHash;
} else if (this.code != null && this.codeVerifier != null && this.redirectUri != null) {
obj.grant_type = "authorization_code";
obj.code = this.code;
obj.code_verifier = this.codeVerifier;
obj.redirect_uri = this.redirectUri;
} else {
throw new Error("must provide credentials or codes");
}
if (this.device) {
obj.deviceType = this.device.type;
obj.deviceIdentifier = this.device.identifier;
obj.deviceName = this.device.name;
// no push tokens for browser apps yet
// obj.devicePushToken = this.device.pushToken;
}
if (this.token && this.provider != null) {
obj.twoFactorToken = this.token;
obj.twoFactorProvider = this.provider;
obj.twoFactorRemember = this.remember ? "1" : "0";
}
if (this.captchaResponse != null) {
obj.captchaResponse = this.captchaResponse;
}
return obj;
}
alterIdentityTokenHeaders(headers: Headers) {
if (this.clientSecret == null && this.masterPasswordHash != null && this.email != null) {
headers.set("Auth-Email", Utils.fromUtf8ToUrlB64(this.email));
}
}
}