Use apikey client secret as captcha validation (#454)
* Use apikey client secret as captcha validation * Linter fixes
This commit is contained in:
parent
26e8b48deb
commit
c5f236c2e4
|
@ -20,7 +20,7 @@ export abstract class AuthService {
|
||||||
logInTwoFactor: (twoFactorProvider: TwoFactorProviderType, twoFactorToken: string,
|
logInTwoFactor: (twoFactorProvider: TwoFactorProviderType, twoFactorToken: string,
|
||||||
remember?: boolean) => Promise<AuthResult>;
|
remember?: boolean) => Promise<AuthResult>;
|
||||||
logInComplete: (email: string, masterPassword: string, twoFactorProvider: TwoFactorProviderType,
|
logInComplete: (email: string, masterPassword: string, twoFactorProvider: TwoFactorProviderType,
|
||||||
twoFactorToken: string, remember?: boolean) => Promise<AuthResult>;
|
twoFactorToken: string, remember?: boolean, captchaToken?: string) => Promise<AuthResult>;
|
||||||
logInSsoComplete: (code: string, codeVerifier: string, redirectUrl: string,
|
logInSsoComplete: (code: string, codeVerifier: string, redirectUrl: string,
|
||||||
twoFactorProvider: TwoFactorProviderType, twoFactorToken: string, remember?: boolean) => Promise<AuthResult>;
|
twoFactorProvider: TwoFactorProviderType, twoFactorToken: string, remember?: boolean) => Promise<AuthResult>;
|
||||||
logInApiKeyComplete: (clientId: string, clientSecret: string, twoFactorProvider: TwoFactorProviderType,
|
logInApiKeyComplete: (clientId: string, clientSecret: string, twoFactorProvider: TwoFactorProviderType,
|
||||||
|
|
|
@ -151,14 +151,14 @@ export class AuthService implements AuthServiceAbstraction {
|
||||||
}
|
}
|
||||||
|
|
||||||
async logInComplete(email: string, masterPassword: string, twoFactorProvider: TwoFactorProviderType,
|
async logInComplete(email: string, masterPassword: string, twoFactorProvider: TwoFactorProviderType,
|
||||||
twoFactorToken: string, remember?: boolean): Promise<AuthResult> {
|
twoFactorToken: string, remember?: boolean, captchaToken?: string): Promise<AuthResult> {
|
||||||
this.selectedTwoFactorProviderType = null;
|
this.selectedTwoFactorProviderType = null;
|
||||||
const key = await this.makePreloginKey(masterPassword, email);
|
const key = await this.makePreloginKey(masterPassword, email);
|
||||||
const hashedPassword = await this.cryptoService.hashPassword(masterPassword, key);
|
const hashedPassword = await this.cryptoService.hashPassword(masterPassword, key);
|
||||||
const localHashedPassword = await this.cryptoService.hashPassword(masterPassword, key,
|
const localHashedPassword = await this.cryptoService.hashPassword(masterPassword, key,
|
||||||
HashPurpose.LocalAuthorization);
|
HashPurpose.LocalAuthorization);
|
||||||
return await this.logInHelper(email, hashedPassword, localHashedPassword, null, null, null, null, null, key,
|
return await this.logInHelper(email, hashedPassword, localHashedPassword, null, null, null, null, null, key,
|
||||||
twoFactorProvider, twoFactorToken, remember);
|
twoFactorProvider, twoFactorToken, remember, captchaToken);
|
||||||
}
|
}
|
||||||
|
|
||||||
async logInSsoComplete(code: string, codeVerifier: string, redirectUrl: string,
|
async logInSsoComplete(code: string, codeVerifier: string, redirectUrl: string,
|
||||||
|
|
|
@ -6,6 +6,7 @@ import { TwoFactorProviderType } from 'jslib-common/enums/twoFactorProviderType'
|
||||||
|
|
||||||
import { AuthResult } from 'jslib-common/models/domain/authResult';
|
import { AuthResult } from 'jslib-common/models/domain/authResult';
|
||||||
import { TwoFactorEmailRequest } from 'jslib-common/models/request/twoFactorEmailRequest';
|
import { TwoFactorEmailRequest } from 'jslib-common/models/request/twoFactorEmailRequest';
|
||||||
|
import { ErrorResponse } from 'jslib-common/models/response/errorResponse';
|
||||||
|
|
||||||
import { ApiService } from 'jslib-common/abstractions/api.service';
|
import { ApiService } from 'jslib-common/abstractions/api.service';
|
||||||
import { AuthService } from 'jslib-common/abstractions/auth.service';
|
import { AuthService } from 'jslib-common/abstractions/auth.service';
|
||||||
|
@ -30,6 +31,7 @@ export class LoginCommand {
|
||||||
protected success: () => Promise<MessageResponse>;
|
protected success: () => Promise<MessageResponse>;
|
||||||
protected canInteract: boolean;
|
protected canInteract: boolean;
|
||||||
protected clientId: string;
|
protected clientId: string;
|
||||||
|
protected clientSecret: string;
|
||||||
|
|
||||||
private ssoRedirectUri: string = null;
|
private ssoRedirectUri: string = null;
|
||||||
|
|
||||||
|
@ -51,32 +53,9 @@ export class LoginCommand {
|
||||||
let clientSecret: string = null;
|
let clientSecret: string = null;
|
||||||
|
|
||||||
if (options.apikey != null) {
|
if (options.apikey != null) {
|
||||||
const storedClientId: string = process.env.BW_CLIENTID;
|
const apiIdentifiers = await this.apiIdentifiers();
|
||||||
const storedClientSecret: string = process.env.BW_CLIENTSECRET;
|
clientId = apiIdentifiers.clientId;
|
||||||
if (storedClientId == null) {
|
clientSecret = apiIdentifiers.clientSecret;
|
||||||
if (this.canInteract) {
|
|
||||||
const answer: inquirer.Answers = await inquirer.createPromptModule({ output: process.stderr })({
|
|
||||||
type: 'input',
|
|
||||||
name: 'clientId',
|
|
||||||
message: 'client_id:',
|
|
||||||
});
|
|
||||||
clientId = answer.clientId;
|
|
||||||
} else {
|
|
||||||
clientId = null;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
clientId = storedClientId;
|
|
||||||
}
|
|
||||||
if (this.canInteract && storedClientSecret == null) {
|
|
||||||
const answer: inquirer.Answers = await inquirer.createPromptModule({ output: process.stderr })({
|
|
||||||
type: 'input',
|
|
||||||
name: 'clientSecret',
|
|
||||||
message: 'client_secret:',
|
|
||||||
});
|
|
||||||
clientSecret = answer.clientSecret;
|
|
||||||
} else {
|
|
||||||
clientSecret = storedClientSecret;
|
|
||||||
}
|
|
||||||
} else if (options.sso != null && this.canInteract) {
|
} else if (options.sso != null && this.canInteract) {
|
||||||
const passwordOptions: any = {
|
const passwordOptions: any = {
|
||||||
type: 'password',
|
type: 'password',
|
||||||
|
@ -156,7 +135,7 @@ export class LoginCommand {
|
||||||
twoFactorMethod, twoFactorToken, false);
|
twoFactorMethod, twoFactorToken, false);
|
||||||
} else {
|
} else {
|
||||||
response = await this.authService.logInComplete(email, password, twoFactorMethod,
|
response = await this.authService.logInComplete(email, password, twoFactorMethod,
|
||||||
twoFactorToken, false);
|
twoFactorToken, false, this.clientSecret);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (clientId != null && clientSecret != null) {
|
if (clientId != null && clientSecret != null) {
|
||||||
|
@ -167,8 +146,27 @@ export class LoginCommand {
|
||||||
response = await this.authService.logIn(email, password);
|
response = await this.authService.logIn(email, password);
|
||||||
}
|
}
|
||||||
if (response.captchaSiteKey) {
|
if (response.captchaSiteKey) {
|
||||||
return Response.badRequest('Your authentication request appears to be coming from a bot\n' +
|
const badCaptcha = Response.badRequest('Your authentication request appears to be coming from a bot\n' +
|
||||||
'Please log in using your API key (https://bitwarden.com/help/article/cli/#using-an-api-key)');
|
'Please use your API key to validate this request and ensure BW_CLIENTSECRET is correct, if set\n' +
|
||||||
|
'(https://bitwarden.com/help/article/cli/#using-an-api-key)');
|
||||||
|
|
||||||
|
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) {
|
if (response.twoFactor) {
|
||||||
let selectedProvider: any = null;
|
let selectedProvider: any = null;
|
||||||
|
@ -258,6 +256,55 @@ export class LoginCommand {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async apiClientId(): Promise<string> {
|
||||||
|
let clientId: string = null;
|
||||||
|
|
||||||
|
const storedClientId: string = process.env.BW_CLIENTID;
|
||||||
|
if (storedClientId == null) {
|
||||||
|
if (this.canInteract) {
|
||||||
|
const answer: inquirer.Answers = await inquirer.createPromptModule({ output: process.stderr })({
|
||||||
|
type: 'input',
|
||||||
|
name: 'clientId',
|
||||||
|
message: 'client_id:',
|
||||||
|
});
|
||||||
|
clientId = answer.clientId;
|
||||||
|
} else {
|
||||||
|
clientId = null;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
clientId = storedClientId;
|
||||||
|
}
|
||||||
|
|
||||||
|
return clientId;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async apiClientSecret(isAdditionalAuthentication: boolean = false): Promise<string> {
|
||||||
|
const additionalAuthenticationMessage = 'Additional authentication required.\nAPI key ';
|
||||||
|
let clientSecret: string = null;
|
||||||
|
|
||||||
|
const storedClientSecret: string = this.clientSecret || process.env.BW_CLIENTSECRET;
|
||||||
|
if (this.canInteract && storedClientSecret == null) {
|
||||||
|
const answer: inquirer.Answers = await inquirer.createPromptModule({ output: process.stderr })({
|
||||||
|
type: 'input',
|
||||||
|
name: 'clientSecret',
|
||||||
|
message: (isAdditionalAuthentication ? additionalAuthenticationMessage : '') + 'client_secret:',
|
||||||
|
|
||||||
|
});
|
||||||
|
clientSecret = answer.clientSecret;
|
||||||
|
} else {
|
||||||
|
clientSecret = storedClientSecret;
|
||||||
|
}
|
||||||
|
|
||||||
|
return clientSecret;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async apiIdentifiers(): Promise<{ clientId: string, clientSecret: string; }> {
|
||||||
|
return {
|
||||||
|
clientId: await this.apiClientId(),
|
||||||
|
clientSecret: await this.apiClientSecret(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
private async getSsoCode(codeChallenge: string, state: string): Promise<string> {
|
private async getSsoCode(codeChallenge: string, state: string): Promise<string> {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
const callbackServer = http.createServer((req, res) => {
|
const callbackServer = http.createServer((req, res) => {
|
||||||
|
|
Loading…
Reference in New Issue