1
0
mirror of https://github.com/bitwarden/jslib synced 2025-12-18 09:13:19 +00:00

Lift tokenRequest and api call to request methods

Also reduce amount of persistent state (WIP)
This commit is contained in:
Thomas Rittson
2021-12-20 09:20:46 +10:00
parent bb04c5bf86
commit 208e88800b
7 changed files with 134 additions and 174 deletions

View File

@@ -3,14 +3,6 @@ import { SymmetricCryptoKey } from "../models/domain/symmetricCryptoKey";
import { TwoFactorData } from '../models/request/identityToken/tokenRequest'; import { TwoFactorData } from '../models/request/identityToken/tokenRequest';
export abstract class AuthService { export abstract class AuthService {
email: string;
masterPasswordHash: string;
code: string;
codeVerifier: string;
ssoRedirectUrl: string;
clientId: string;
clientSecret: string;
logIn: (email: string, masterPassword: string, twoFactor?: TwoFactorData, captchaToken?: string) => Promise<AuthResult>; logIn: (email: string, masterPassword: string, twoFactor?: TwoFactorData, captchaToken?: string) => Promise<AuthResult>;
logInSso: ( logInSso: (
code: string, code: string,

View File

@@ -4,8 +4,8 @@ import { DeviceRequest } from "../deviceRequest";
export class ApiTokenRequest extends TokenRequest { export class ApiTokenRequest extends TokenRequest {
constructor( constructor(
private clientId: string, public clientId: string,
private clientSecret: string, public clientSecret: string,
protected twoFactor: TwoFactorData, protected twoFactor: TwoFactorData,
captchaResponse: string, captchaResponse: string,
device?: DeviceRequest device?: DeviceRequest

View File

@@ -6,16 +6,13 @@ import { Utils } from "../../../misc/utils";
export class PasswordTokenRequest extends TokenRequest { export class PasswordTokenRequest extends TokenRequest {
constructor( constructor(
private email: string, public email: string,
private masterPasswordHash: string, public masterPasswordHash: string,
protected twoFactor: TwoFactorData, protected twoFactor: TwoFactorData,
captchaResponse: string, captchaResponse: string,
device?: DeviceRequest device?: DeviceRequest
) { ) {
super(twoFactor, captchaResponse, device); super(twoFactor, captchaResponse, device);
this.email = email;
this.masterPasswordHash = masterPasswordHash;
} }
toIdentityToken(clientId: string) { toIdentityToken(clientId: string) {

View File

@@ -4,18 +4,14 @@ import { DeviceRequest } from "../deviceRequest";
export class SsoTokenRequest extends TokenRequest { export class SsoTokenRequest extends TokenRequest {
constructor( constructor(
private code: string, public code: string,
private codeVerifier: string, public codeVerifier: string,
private redirectUri: string, public redirectUri: string,
protected twoFactor: TwoFactorData, protected twoFactor: TwoFactorData,
captchaResponse: string, captchaResponse: string,
device?: DeviceRequest device?: DeviceRequest
) { ) {
super(twoFactor, captchaResponse, device); super(twoFactor, captchaResponse, device);
this.code = code;
this.codeVerifier = codeVerifier;
this.redirectUri = redirectUri;
} }
toIdentityToken(clientId: string) { toIdentityToken(clientId: string) {

View File

@@ -50,4 +50,8 @@ export abstract class TokenRequest implements CaptchaProtectedRequest {
alterIdentityTokenHeaders(headers: Headers) { alterIdentityTokenHeaders(headers: Headers) {
// Implemented in subclass if required // Implemented in subclass if required
} }
setTwoFactor(twoFactor: TwoFactorData) {
this.twoFactor = twoFactor;
}
} }

View File

@@ -35,17 +35,12 @@ import { TokenService } from "../abstractions/token.service";
import { TwoFactorService } from "../abstractions/twoFactor.service"; import { TwoFactorService } from "../abstractions/twoFactor.service";
import { Utils } from "../misc/utils"; import { Utils } from "../misc/utils";
import { IdentityCaptchaResponse } from "../models/response/identityCaptchaResponse";
export class AuthService implements AuthServiceAbstraction { export class AuthService implements AuthServiceAbstraction {
email: string; private localMasterPasswordHash: string;
masterPasswordHash: string;
localMasterPasswordHash: string; private savedTokenRequest: ApiTokenRequest | PasswordTokenRequest | SsoTokenRequest;
code: string;
codeVerifier: string;
ssoRedirectUrl: string;
clientId: string;
clientSecret: string;
captchaToken: string;
private key: SymmetricCryptoKey; private key: SymmetricCryptoKey;
@@ -65,7 +60,12 @@ export class AuthService implements AuthServiceAbstraction {
private setCryptoKeys = true private setCryptoKeys = true
) {} ) {}
async logIn(email: string, masterPassword: string, twoFactor?: TwoFactorData, captchaToken?: string): Promise<AuthResult> { async logIn(
email: string,
masterPassword: string,
twoFactor?: TwoFactorData,
captchaToken?: string
): Promise<AuthResult> {
this.twoFactorService.clearSelectedProvider(); this.twoFactorService.clearSelectedProvider();
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);
@@ -74,20 +74,32 @@ export class AuthService implements AuthServiceAbstraction {
key, key,
HashPurpose.LocalAuthorization HashPurpose.LocalAuthorization
); );
return await this.logInHelper(
const tokenRequest = new PasswordTokenRequest(
email, email,
hashedPassword, hashedPassword,
await this.createTwoFactorData(twoFactor, email),
captchaToken,
await this.createDeviceRequest()
);
const response = await this.apiService.postIdentityToken(tokenRequest);
const result = await this.processTokenResponse(
response,
email,
localHashedPassword, localHashedPassword,
null, null,
null, null,
null, null,
null,
null,
key, key,
twoFactor,
captchaToken,
null null
); );
if (result.twoFactor) {
this.saveState(tokenRequest, key, localHashedPassword, result.twoFactorProviders);
}
return result;
} }
async logInSso( async logInSso(
@@ -95,59 +107,84 @@ export class AuthService implements AuthServiceAbstraction {
codeVerifier: string, codeVerifier: string,
redirectUrl: string, redirectUrl: string,
orgId: string, orgId: string,
twoFactor?: TwoFactorData, twoFactor?: TwoFactorData
): Promise<AuthResult> { ): Promise<AuthResult> {
this.twoFactorService.clearSelectedProvider(); this.twoFactorService.clearSelectedProvider();
return await this.logInHelper(
null, const tokenRequest = new SsoTokenRequest(
null,
null,
code, code,
codeVerifier, codeVerifier,
redirectUrl, redirectUrl,
await this.createTwoFactorData(twoFactor, null),
null,
await this.createDeviceRequest()
);
const response = await this.apiService.postIdentityToken(tokenRequest);
const result = await this.processTokenResponse(
response,
null, null,
null, null,
code,
null,
null, null,
twoFactor,
null, null,
orgId orgId
); );
if (result.twoFactor) {
this.saveState(tokenRequest, null, null, result.twoFactorProviders);
} }
async logInApiKey(clientId: string, clientSecret: string, twoFactor?: TwoFactorData): Promise<AuthResult> { return result;
}
async logInApiKey(
clientId: string,
clientSecret: string,
twoFactor?: TwoFactorData
): Promise<AuthResult> {
this.twoFactorService.clearSelectedProvider(); this.twoFactorService.clearSelectedProvider();
return await this.logInHelper(
null, const tokenRequest = new ApiTokenRequest(
null, clientId,
clientSecret,
await this.createTwoFactorData(twoFactor, null),
null, null,
await this.createDeviceRequest()
);
const response = await this.apiService.postIdentityToken(tokenRequest);
const result = await this.processTokenResponse(
response,
null, null,
null, null,
null, null,
clientId, clientId,
clientSecret, clientSecret,
null, null,
twoFactor,
null,
null null
); );
if (result.twoFactor) {
this.saveState(tokenRequest, null, null, result.twoFactorProviders);
} }
async logInTwoFactor( return result;
twoFactor: TwoFactorData, }
): Promise<AuthResult> {
return await this.logInHelper( async logInTwoFactor(twoFactor: TwoFactorData): Promise<AuthResult> {
this.email, this.savedTokenRequest.setTwoFactor(twoFactor);
this.masterPasswordHash, const response = await this.apiService.postIdentityToken(this.savedTokenRequest);
return await this.processTokenResponse(
response,
(this.savedTokenRequest as PasswordTokenRequest).email,
this.localMasterPasswordHash, this.localMasterPasswordHash,
this.code, (this.savedTokenRequest as SsoTokenRequest).code,
this.codeVerifier, (this.savedTokenRequest as ApiTokenRequest).clientId,
this.ssoRedirectUrl, (this.savedTokenRequest as ApiTokenRequest).clientSecret,
this.clientId, this.key
this.clientSecret,
this.key,
twoFactor,
this.captchaToken,
null
); );
} }
@@ -156,16 +193,20 @@ export class AuthService implements AuthServiceAbstraction {
this.messagingService.send("loggedOut"); this.messagingService.send("loggedOut");
} }
// TODO
authingWithApiKey(): boolean { authingWithApiKey(): boolean {
return this.clientId != null && this.clientSecret != null; return null;
// return this.clientId != null && this.clientSecret != null;
} }
authingWithSso(): boolean { authingWithSso(): boolean {
return this.code != null && this.codeVerifier != null && this.ssoRedirectUrl != null; return null;
// return this.code != null && this.codeVerifier != null && this.ssoRedirectUrl != null;
} }
authingWithPassword(): boolean { authingWithPassword(): boolean {
return this.email != null && this.masterPasswordHash != null; return null;
// return this.email != null && this.masterPasswordHash != null;
} }
async makePreloginKey(masterPassword: string, email: string): Promise<SymmetricCryptoKey> { async makePreloginKey(masterPassword: string, email: string): Promise<SymmetricCryptoKey> {
@@ -186,34 +227,16 @@ export class AuthService implements AuthServiceAbstraction {
return this.cryptoService.makeKey(masterPassword, email, kdf, kdfIterations); return this.cryptoService.makeKey(masterPassword, email, kdf, kdfIterations);
} }
private async logInHelper( private async processTokenResponse(
response: IdentityTokenResponse | IdentityTwoFactorResponse | IdentityCaptchaResponse,
email: string, email: string,
hashedPassword: string,
localHashedPassword: string, localHashedPassword: string,
code: string, code: string,
codeVerifier: string,
redirectUrl: string,
clientId: string, clientId: string,
clientSecret: string, clientSecret: string,
key: SymmetricCryptoKey, key: SymmetricCryptoKey,
twoFactor: TwoFactorData,
captchaToken?: string,
orgId?: string orgId?: string
): Promise<AuthResult> { ): Promise<AuthResult> {
const request = await this.createTokenRequest(
email,
hashedPassword,
code,
codeVerifier,
redirectUrl,
clientId,
clientSecret,
twoFactor,
captchaToken
);
const response = await this.apiService.postIdentityToken(request);
this.clearState(); this.clearState();
const result = new AuthResult(); const result = new AuthResult();
@@ -224,19 +247,6 @@ export class AuthService implements AuthServiceAbstraction {
result.twoFactor = !!(response as any).twoFactorProviders2; result.twoFactor = !!(response as any).twoFactorProviders2;
if (result.twoFactor) { if (result.twoFactor) {
this.saveState(
email,
hashedPassword,
localHashedPassword,
code,
codeVerifier,
redirectUrl,
clientId,
clientSecret,
key,
(response as IdentityTwoFactorResponse).twoFactorProviders2
);
result.twoFactorProviders = (response as IdentityTwoFactorResponse).twoFactorProviders2; result.twoFactorProviders = (response as IdentityTwoFactorResponse).twoFactorProviders2;
return result; return result;
} }
@@ -292,19 +302,7 @@ export class AuthService implements AuthServiceAbstraction {
return new DeviceRequest(appId, this.platformUtilsService); return new DeviceRequest(appId, this.platformUtilsService);
} }
private async createTokenRequest( private async createTwoFactorData(twoFactor: TwoFactorData, email: string) {
email: string,
hashedPassword: string,
code: string,
codeVerifier: string,
redirectUrl: string,
clientId: string,
clientSecret: string,
twoFactor: TwoFactorData,
captchaToken: string
) {
const deviceRequest = await this.createDeviceRequest();
if (twoFactor == null) { if (twoFactor == null) {
const storedTwoFactorToken = await this.tokenService.getTwoFactorToken(email); const storedTwoFactorToken = await this.tokenService.getTwoFactorToken(email);
if (storedTwoFactorToken != null) { if (storedTwoFactorToken != null) {
@@ -312,7 +310,7 @@ export class AuthService implements AuthServiceAbstraction {
token: storedTwoFactorToken, token: storedTwoFactorToken,
provider: TwoFactorProviderType.Remember, provider: TwoFactorProviderType.Remember,
remember: false, remember: false,
} };
} else { } else {
twoFactor = { twoFactor = {
token: null, token: null,
@@ -321,29 +319,7 @@ export class AuthService implements AuthServiceAbstraction {
}; };
} }
} }
return twoFactor;
if (email != null && hashedPassword != null) {
return new PasswordTokenRequest(
email,
hashedPassword,
twoFactor,
captchaToken,
deviceRequest
);
} else if (code != null && codeVerifier != null && redirectUrl != null) {
return new SsoTokenRequest(
code,
codeVerifier,
redirectUrl,
twoFactor,
captchaToken,
deviceRequest
);
} else if (clientId != null && clientSecret != null) {
return new ApiTokenRequest(clientId, clientSecret, twoFactor, captchaToken, deviceRequest);
} else {
throw new Error("No credentials provided.");
}
} }
private async saveAccountInformation( private async saveAccountInformation(
@@ -425,39 +401,21 @@ export class AuthService implements AuthServiceAbstraction {
} }
private saveState( private saveState(
email: string, tokenRequest: ApiTokenRequest | PasswordTokenRequest | SsoTokenRequest,
hashedPassword: string,
localHashedPassword: string,
code: string,
codeVerifier: string,
redirectUrl: string,
clientId: string,
clientSecret: string,
key: SymmetricCryptoKey, key: SymmetricCryptoKey,
localMasterPasswordHash: string,
twoFactorProviders: Map<TwoFactorProviderType, { [key: string]: string }> twoFactorProviders: Map<TwoFactorProviderType, { [key: string]: string }>
) { ) {
this.email = email; this.savedTokenRequest = tokenRequest;
this.masterPasswordHash = hashedPassword; this.localMasterPasswordHash = localMasterPasswordHash;
this.localMasterPasswordHash = localHashedPassword;
this.code = code;
this.codeVerifier = codeVerifier;
this.ssoRedirectUrl = redirectUrl;
this.clientId = clientId;
this.clientSecret = clientSecret;
this.key = this.setCryptoKeys ? key : null; this.key = this.setCryptoKeys ? key : null;
this.twoFactorService.setProviders(twoFactorProviders); this.twoFactorService.setProviders(twoFactorProviders);
} }
private clearState(): void { private clearState(): void {
this.savedTokenRequest = null;
this.key = null; this.key = null;
this.email = null;
this.masterPasswordHash = null;
this.localMasterPasswordHash = null; this.localMasterPasswordHash = null;
this.code = null;
this.codeVerifier = null;
this.ssoRedirectUrl = null;
this.clientId = null;
this.clientSecret = null;
this.twoFactorService.clearProviders(); this.twoFactorService.clearProviders();
this.twoFactorService.clearSelectedProvider(); this.twoFactorService.clearSelectedProvider();
} }

View File

@@ -26,6 +26,8 @@ import { IdentityTokenResponse } from "jslib-common/models/response/identityToke
import { TwoFactorService } from "jslib-common/abstractions/twoFactor.service"; import { TwoFactorService } from "jslib-common/abstractions/twoFactor.service";
import { HashPurpose } from "jslib-common/enums/hashPurpose"; import { HashPurpose } from "jslib-common/enums/hashPurpose";
import { TwoFactorProviderType } from "jslib-common/enums/twoFactorProviderType"; import { TwoFactorProviderType } from "jslib-common/enums/twoFactorProviderType";
import { PasswordTokenRequest } from "jslib-common/models/request/identityToken/passwordTokenRequest";
import { DeviceRequest } from "jslib-common/models/request/deviceRequest";
describe("Cipher Service", () => { describe("Cipher Service", () => {
let cryptoService: SubstituteOf<CryptoService>; let cryptoService: SubstituteOf<CryptoService>;
@@ -353,7 +355,11 @@ describe("Cipher Service", () => {
commonSetup(); commonSetup();
logInSetup(); logInSetup();
await authService.logIn(email, masterPassword, { provider: twoFactorProviderType, token: twoFactorToken, remember: twoFactorRemember }); await authService.logIn(email, masterPassword, {
provider: twoFactorProviderType,
token: twoFactorToken,
remember: twoFactorRemember,
});
apiService.received(1).postIdentityToken( apiService.received(1).postIdentityToken(
Arg.is((actual) => { Arg.is((actual) => {
@@ -371,15 +377,22 @@ describe("Cipher Service", () => {
); );
}); });
it("logInTwoFactor: sends 2FA token to server when using Master Password", async () => { it("logInTwoFactor: sends 2FA token to server when using Master Password", async () => {
commonSetup(); commonSetup();
authService.email = email; const tokenRequest = new PasswordTokenRequest(email, hashedPassword, null, null, {
authService.masterPasswordHash = hashedPassword; identifier: deviceId,
authService.localMasterPasswordHash = localHashedPassword; } as DeviceRequest);
await authService.logInTwoFactor({ provider: twoFactorProviderType, token: twoFactorToken, remember: twoFactorRemember }); (authService as any).localMasterPasswordHash = localHashedPassword;
(authService as any).email = email;
(authService as any).savedTokenRequest = tokenRequest;
await authService.logInTwoFactor({
provider: twoFactorProviderType,
token: twoFactorToken,
remember: twoFactorRemember,
});
apiService.received(1).postIdentityToken( apiService.received(1).postIdentityToken(
Arg.is((actual) => { Arg.is((actual) => {