1
0
mirror of https://github.com/bitwarden/browser synced 2025-12-11 22:03:36 +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

@@ -5,6 +5,11 @@ import * as inquirer from "inquirer";
import { TwoFactorProviderType } from "jslib-common/enums/twoFactorProviderType";
import { AuthResult } from "jslib-common/models/domain/authResult";
import {
ApiLogInCredentials,
PasswordLogInCredentials,
SsoLogInCredentials,
} from "jslib-common/models/domain/logInCredentials";
import { TwoFactorEmailRequest } from "jslib-common/models/request/twoFactorEmailRequest";
import { ErrorResponse } from "jslib-common/models/response/errorResponse";
@@ -18,6 +23,7 @@ import { PasswordGenerationService } from "jslib-common/abstractions/passwordGen
import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service";
import { PolicyService } from "jslib-common/abstractions/policy.service";
import { StateService } from "jslib-common/abstractions/state.service";
import { TwoFactorService } from "jslib-common/abstractions/twoFactor.service";
import { Response } from "../models/response";
@@ -28,6 +34,8 @@ import { MessageResponse } from "../models/response/messageResponse";
import { NodeUtils } from "jslib-common/misc/nodeUtils";
import { Utils } from "jslib-common/misc/utils";
import Separator from "inquirer/lib/objects/separator";
// tslint:disable-next-line
const open = require("open");
@@ -53,6 +61,7 @@ export class LoginCommand {
protected stateService: StateService,
protected cryptoService: CryptoService,
protected policyService: PolicyService,
protected twoFactorService: TwoFactorService,
clientId: string
) {
this.clientId = clientId;
@@ -143,163 +152,146 @@ export class LoginCommand {
return Response.error("Invalid two-step login method.");
}
const twoFactor =
twoFactorToken == null
? null
: {
provider: twoFactorMethod,
token: twoFactorToken,
remember: false,
};
try {
if (this.validatedParams != null) {
await this.validatedParams();
}
let response: AuthResult = null;
if (twoFactorToken != null && twoFactorMethod != null) {
if (clientId != null && clientSecret != null) {
response = await this.authService.logInApiKeyComplete(
clientId,
clientSecret,
twoFactorMethod,
twoFactorToken,
false
);
} else if (ssoCode != null && ssoCodeVerifier != null) {
response = await this.authService.logInSsoComplete(
if (clientId != null && clientSecret != null) {
response = await this.authService.logIn(new ApiLogInCredentials(clientId, clientSecret));
} else if (ssoCode != null && ssoCodeVerifier != null) {
response = await this.authService.logIn(
new SsoLogInCredentials(
ssoCode,
ssoCodeVerifier,
this.ssoRedirectUri,
twoFactorMethod,
twoFactorToken,
false
);
} else {
response = await this.authService.logInComplete(
email,
password,
twoFactorMethod,
twoFactorToken,
false,
this.clientSecret
);
}
orgIdentifier,
twoFactor
)
);
} else {
if (clientId != null && clientSecret != null) {
response = await this.authService.logInApiKey(clientId, clientSecret);
} else if (ssoCode != null && ssoCodeVerifier != null) {
response = await this.authService.logInSso(
ssoCode,
ssoCodeVerifier,
this.ssoRedirectUri,
orgIdentifier
response = await this.authService.logIn(
new PasswordLogInCredentials(email, password, null, twoFactor)
);
}
if (response.captchaSiteKey) {
const badCaptcha = Response.badRequest(
"Your authentication request appears to be coming from a bot\n" +
"Please use your API key to validate this request and ensure BW_CLIENTSECRET is correct, if set.\n" +
"(https://bitwarden.com/help/article/cli-auth-challenges)"
);
try {
const captchaClientSecret = await this.apiClientSecret(true);
if (Utils.isNullOrWhitespace(captchaClientSecret)) {
return badCaptcha;
}
const secondResponse = await this.authService.logIn(
new PasswordLogInCredentials(email, password, captchaClientSecret, {
provider: twoFactorMethod,
token: twoFactorToken,
remember: false,
})
);
} else {
response = await this.authService.logIn(email, password);
}
if (response.captchaSiteKey) {
const badCaptcha = Response.badRequest(
"Your authentication request appears to be coming from a bot\n" +
"Please use your API key to validate this request and ensure BW_CLIENTSECRET is correct, if set.\n" +
"(https://bitwarden.com/help/article/cli-auth-challenges)"
);
try {
const captchaClientSecret = await this.apiClientSecret(true);
if (Utils.isNullOrWhitespace(captchaClientSecret)) {
return badCaptcha;
}
const secondResponse = await this.authService.logInComplete(
email,
password,
twoFactorMethod,
twoFactorToken,
false,
captchaClientSecret
);
response = secondResponse;
} catch (e) {
if (
(e instanceof ErrorResponse || e.constructor.name === "ErrorResponse") &&
(e as ErrorResponse).message.includes("Captcha is invalid")
) {
return badCaptcha;
} else {
throw e;
}
}
}
if (response.twoFactor) {
let selectedProvider: any = null;
const twoFactorProviders = this.authService.getSupportedTwoFactorProviders(null);
if (twoFactorProviders.length === 0) {
return Response.badRequest("No providers available for this client.");
}
if (twoFactorMethod != null) {
try {
selectedProvider = twoFactorProviders.filter((p) => p.type === twoFactorMethod)[0];
} catch (e) {
return Response.error("Invalid two-step login method.");
}
}
if (selectedProvider == null) {
if (twoFactorProviders.length === 1) {
selectedProvider = twoFactorProviders[0];
} else if (this.canInteract) {
const twoFactorOptions = twoFactorProviders.map((p) => p.name);
twoFactorOptions.push(new inquirer.Separator());
twoFactorOptions.push("Cancel");
const answer: inquirer.Answers = await inquirer.createPromptModule({
output: process.stderr,
})({
type: "list",
name: "method",
message: "Two-step login method:",
choices: twoFactorOptions,
});
const i = twoFactorOptions.indexOf(answer.method);
if (i === twoFactorOptions.length - 1) {
return Response.error("Login failed.");
}
selectedProvider = twoFactorProviders[i];
}
if (selectedProvider == null) {
return Response.error("Login failed. No provider selected.");
}
}
response = secondResponse;
} catch (e) {
if (
twoFactorToken == null &&
response.twoFactorProviders.size > 1 &&
selectedProvider.type === TwoFactorProviderType.Email
(e instanceof ErrorResponse || e.constructor.name === "ErrorResponse") &&
(e as ErrorResponse).message.includes("Captcha is invalid")
) {
const emailReq = new TwoFactorEmailRequest();
emailReq.email = this.authService.email;
emailReq.masterPasswordHash = this.authService.masterPasswordHash;
await this.apiService.postTwoFactorEmail(emailReq);
return badCaptcha;
} else {
throw e;
}
if (twoFactorToken == null) {
if (this.canInteract) {
const answer: inquirer.Answers = await inquirer.createPromptModule({
output: process.stderr,
})({
type: "input",
name: "token",
message: "Two-step login code:",
});
twoFactorToken = answer.token;
}
if (twoFactorToken == null || twoFactorToken === "") {
return Response.badRequest("Code is required.");
}
}
response = await this.authService.logInTwoFactor(
selectedProvider.type,
twoFactorToken,
false
);
}
}
if (response.requiresTwoFactor) {
let selectedProvider: any = null;
const twoFactorProviders = this.twoFactorService.getSupportedProviders(null);
if (twoFactorProviders.length === 0) {
return Response.badRequest("No providers available for this client.");
}
if (response.twoFactor) {
if (twoFactorMethod != null) {
try {
selectedProvider = twoFactorProviders.filter((p) => p.type === twoFactorMethod)[0];
} catch (e) {
return Response.error("Invalid two-step login method.");
}
}
if (selectedProvider == null) {
if (twoFactorProviders.length === 1) {
selectedProvider = twoFactorProviders[0];
} else if (this.canInteract) {
const twoFactorOptions: (string | Separator)[] = twoFactorProviders.map((p) => p.name);
twoFactorOptions.push(new inquirer.Separator());
twoFactorOptions.push("Cancel");
const answer: inquirer.Answers = await inquirer.createPromptModule({
output: process.stderr,
})({
type: "list",
name: "method",
message: "Two-step login method:",
choices: twoFactorOptions,
});
const i = twoFactorOptions.indexOf(answer.method);
if (i === twoFactorOptions.length - 1) {
return Response.error("Login failed.");
}
selectedProvider = twoFactorProviders[i];
}
if (selectedProvider == null) {
return Response.error("Login failed. No provider selected.");
}
}
if (
twoFactorToken == null &&
response.twoFactorProviders.size > 1 &&
selectedProvider.type === TwoFactorProviderType.Email
) {
const emailReq = new TwoFactorEmailRequest();
emailReq.email = this.authService.email;
emailReq.masterPasswordHash = this.authService.masterPasswordHash;
await this.apiService.postTwoFactorEmail(emailReq);
}
if (twoFactorToken == null) {
if (this.canInteract) {
const answer: inquirer.Answers = await inquirer.createPromptModule({
output: process.stderr,
})({
type: "input",
name: "token",
message: "Two-step login code:",
});
twoFactorToken = answer.token;
}
if (twoFactorToken == null || twoFactorToken === "") {
return Response.badRequest("Code is required.");
}
}
response = await this.authService.logInTwoFactor({
provider: selectedProvider.type,
token: twoFactorToken,
remember: false,
});
}
if (response.requiresTwoFactor) {
return Response.error("Login failed.");
}