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,
|
||||
remember?: boolean) => Promise<AuthResult>;
|
||||
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,
|
||||
twoFactorProvider: TwoFactorProviderType, twoFactorToken: string, remember?: boolean) => Promise<AuthResult>;
|
||||
logInApiKeyComplete: (clientId: string, clientSecret: string, twoFactorProvider: TwoFactorProviderType,
|
||||
|
|
|
@ -151,14 +151,14 @@ export class AuthService implements AuthServiceAbstraction {
|
|||
}
|
||||
|
||||
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;
|
||||
const key = await this.makePreloginKey(masterPassword, email);
|
||||
const hashedPassword = await this.cryptoService.hashPassword(masterPassword, key);
|
||||
const localHashedPassword = await this.cryptoService.hashPassword(masterPassword, key,
|
||||
HashPurpose.LocalAuthorization);
|
||||
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,
|
||||
|
|
|
@ -6,6 +6,7 @@ import { TwoFactorProviderType } from 'jslib-common/enums/twoFactorProviderType'
|
|||
|
||||
import { AuthResult } from 'jslib-common/models/domain/authResult';
|
||||
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 { AuthService } from 'jslib-common/abstractions/auth.service';
|
||||
|
@ -30,6 +31,7 @@ export class LoginCommand {
|
|||
protected success: () => Promise<MessageResponse>;
|
||||
protected canInteract: boolean;
|
||||
protected clientId: string;
|
||||
protected clientSecret: string;
|
||||
|
||||
private ssoRedirectUri: string = null;
|
||||
|
||||
|
@ -51,32 +53,9 @@ export class LoginCommand {
|
|||
let clientSecret: string = null;
|
||||
|
||||
if (options.apikey != null) {
|
||||
const storedClientId: string = process.env.BW_CLIENTID;
|
||||
const storedClientSecret: string = process.env.BW_CLIENTSECRET;
|
||||
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;
|
||||
}
|
||||
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;
|
||||
}
|
||||
const apiIdentifiers = await this.apiIdentifiers();
|
||||
clientId = apiIdentifiers.clientId;
|
||||
clientSecret = apiIdentifiers.clientSecret;
|
||||
} else if (options.sso != null && this.canInteract) {
|
||||
const passwordOptions: any = {
|
||||
type: 'password',
|
||||
|
@ -156,7 +135,7 @@ export class LoginCommand {
|
|||
twoFactorMethod, twoFactorToken, false);
|
||||
} else {
|
||||
response = await this.authService.logInComplete(email, password, twoFactorMethod,
|
||||
twoFactorToken, false);
|
||||
twoFactorToken, false, this.clientSecret);
|
||||
}
|
||||
} else {
|
||||
if (clientId != null && clientSecret != null) {
|
||||
|
@ -167,8 +146,27 @@ export class LoginCommand {
|
|||
response = await this.authService.logIn(email, password);
|
||||
}
|
||||
if (response.captchaSiteKey) {
|
||||
return 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)');
|
||||
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/#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) {
|
||||
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> {
|
||||
return new Promise((resolve, reject) => {
|
||||
const callbackServer = http.createServer((req, res) => {
|
||||
|
|
Loading…
Reference in New Issue