1
0
mirror of https://github.com/bitwarden/jslib synced 2025-12-15 15:53:51 +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';
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>;
logInSso: (
code: string,

View File

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

View File

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

View File

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

View File

@@ -50,4 +50,8 @@ export abstract class TokenRequest implements CaptchaProtectedRequest {
alterIdentityTokenHeaders(headers: Headers) {
// 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 { Utils } from "../misc/utils";
import { IdentityCaptchaResponse } from "../models/response/identityCaptchaResponse";
export class AuthService implements AuthServiceAbstraction {
email: string;
masterPasswordHash: string;
localMasterPasswordHash: string;
code: string;
codeVerifier: string;
ssoRedirectUrl: string;
clientId: string;
clientSecret: string;
captchaToken: string;
private localMasterPasswordHash: string;
private savedTokenRequest: ApiTokenRequest | PasswordTokenRequest | SsoTokenRequest;
private key: SymmetricCryptoKey;
@@ -65,7 +60,12 @@ export class AuthService implements AuthServiceAbstraction {
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();
const key = await this.makePreloginKey(masterPassword, email);
const hashedPassword = await this.cryptoService.hashPassword(masterPassword, key);
@@ -74,20 +74,32 @@ export class AuthService implements AuthServiceAbstraction {
key,
HashPurpose.LocalAuthorization
);
return await this.logInHelper(
const tokenRequest = new PasswordTokenRequest(
email,
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,
null,
null,
null,
null,
null,
key,
twoFactor,
captchaToken,
null
);
if (result.twoFactor) {
this.saveState(tokenRequest, key, localHashedPassword, result.twoFactorProviders);
}
return result;
}
async logInSso(
@@ -95,59 +107,84 @@ export class AuthService implements AuthServiceAbstraction {
codeVerifier: string,
redirectUrl: string,
orgId: string,
twoFactor?: TwoFactorData,
twoFactor?: TwoFactorData
): Promise<AuthResult> {
this.twoFactorService.clearSelectedProvider();
return await this.logInHelper(
null,
null,
null,
const tokenRequest = new SsoTokenRequest(
code,
codeVerifier,
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,
code,
null,
null,
twoFactor,
null,
orgId
);
if (result.twoFactor) {
this.saveState(tokenRequest, null, null, result.twoFactorProviders);
}
return result;
}
async logInApiKey(clientId: string, clientSecret: string, twoFactor?: TwoFactorData): Promise<AuthResult> {
async logInApiKey(
clientId: string,
clientSecret: string,
twoFactor?: TwoFactorData
): Promise<AuthResult> {
this.twoFactorService.clearSelectedProvider();
return await this.logInHelper(
null,
null,
const tokenRequest = new ApiTokenRequest(
clientId,
clientSecret,
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,
clientId,
clientSecret,
null,
twoFactor,
null,
null
);
if (result.twoFactor) {
this.saveState(tokenRequest, null, null, result.twoFactorProviders);
}
return result;
}
async logInTwoFactor(
twoFactor: TwoFactorData,
): Promise<AuthResult> {
return await this.logInHelper(
this.email,
this.masterPasswordHash,
async logInTwoFactor(twoFactor: TwoFactorData): Promise<AuthResult> {
this.savedTokenRequest.setTwoFactor(twoFactor);
const response = await this.apiService.postIdentityToken(this.savedTokenRequest);
return await this.processTokenResponse(
response,
(this.savedTokenRequest as PasswordTokenRequest).email,
this.localMasterPasswordHash,
this.code,
this.codeVerifier,
this.ssoRedirectUrl,
this.clientId,
this.clientSecret,
this.key,
twoFactor,
this.captchaToken,
null
(this.savedTokenRequest as SsoTokenRequest).code,
(this.savedTokenRequest as ApiTokenRequest).clientId,
(this.savedTokenRequest as ApiTokenRequest).clientSecret,
this.key
);
}
@@ -156,16 +193,20 @@ export class AuthService implements AuthServiceAbstraction {
this.messagingService.send("loggedOut");
}
// TODO
authingWithApiKey(): boolean {
return this.clientId != null && this.clientSecret != null;
return null;
// return this.clientId != null && this.clientSecret != null;
}
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 {
return this.email != null && this.masterPasswordHash != null;
return null;
// return this.email != null && this.masterPasswordHash != null;
}
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);
}
private async logInHelper(
private async processTokenResponse(
response: IdentityTokenResponse | IdentityTwoFactorResponse | IdentityCaptchaResponse,
email: string,
hashedPassword: string,
localHashedPassword: string,
code: string,
codeVerifier: string,
redirectUrl: string,
clientId: string,
clientSecret: string,
key: SymmetricCryptoKey,
twoFactor: TwoFactorData,
captchaToken?: string,
orgId?: string
): 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();
const result = new AuthResult();
@@ -224,19 +247,6 @@ export class AuthService implements AuthServiceAbstraction {
result.twoFactor = !!(response as any).twoFactorProviders2;
if (result.twoFactor) {
this.saveState(
email,
hashedPassword,
localHashedPassword,
code,
codeVerifier,
redirectUrl,
clientId,
clientSecret,
key,
(response as IdentityTwoFactorResponse).twoFactorProviders2
);
result.twoFactorProviders = (response as IdentityTwoFactorResponse).twoFactorProviders2;
return result;
}
@@ -292,19 +302,7 @@ export class AuthService implements AuthServiceAbstraction {
return new DeviceRequest(appId, this.platformUtilsService);
}
private async createTokenRequest(
email: string,
hashedPassword: string,
code: string,
codeVerifier: string,
redirectUrl: string,
clientId: string,
clientSecret: string,
twoFactor: TwoFactorData,
captchaToken: string
) {
const deviceRequest = await this.createDeviceRequest();
private async createTwoFactorData(twoFactor: TwoFactorData, email: string) {
if (twoFactor == null) {
const storedTwoFactorToken = await this.tokenService.getTwoFactorToken(email);
if (storedTwoFactorToken != null) {
@@ -312,38 +310,16 @@ export class AuthService implements AuthServiceAbstraction {
token: storedTwoFactorToken,
provider: TwoFactorProviderType.Remember,
remember: false,
}
};
} else {
twoFactor = {
token: null,
provider: null,
remember: false,
};
}
twoFactor = {
token: null,
provider: null,
remember: false,
};
}
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.");
}
return twoFactor;
}
private async saveAccountInformation(
@@ -425,39 +401,21 @@ export class AuthService implements AuthServiceAbstraction {
}
private saveState(
email: string,
hashedPassword: string,
localHashedPassword: string,
code: string,
codeVerifier: string,
redirectUrl: string,
clientId: string,
clientSecret: string,
tokenRequest: ApiTokenRequest | PasswordTokenRequest | SsoTokenRequest,
key: SymmetricCryptoKey,
localMasterPasswordHash: string,
twoFactorProviders: Map<TwoFactorProviderType, { [key: string]: string }>
) {
this.email = email;
this.masterPasswordHash = hashedPassword;
this.localMasterPasswordHash = localHashedPassword;
this.code = code;
this.codeVerifier = codeVerifier;
this.ssoRedirectUrl = redirectUrl;
this.clientId = clientId;
this.clientSecret = clientSecret;
this.savedTokenRequest = tokenRequest;
this.localMasterPasswordHash = localMasterPasswordHash;
this.key = this.setCryptoKeys ? key : null;
this.twoFactorService.setProviders(twoFactorProviders);
}
private clearState(): void {
this.savedTokenRequest = null;
this.key = null;
this.email = null;
this.masterPasswordHash = null;
this.localMasterPasswordHash = null;
this.code = null;
this.codeVerifier = null;
this.ssoRedirectUrl = null;
this.clientId = null;
this.clientSecret = null;
this.twoFactorService.clearProviders();
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 { HashPurpose } from "jslib-common/enums/hashPurpose";
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", () => {
let cryptoService: SubstituteOf<CryptoService>;
@@ -327,9 +329,9 @@ describe("Cipher Service", () => {
it("logIn: sends stored 2FA token to server", async () => {
commonSetup();
logInSetup();
logInSetup();
tokenService.getTwoFactorToken(email).resolves(twoFactorToken);
tokenService.getTwoFactorToken(email).resolves(twoFactorToken);
await authService.logIn(email, masterPassword);
@@ -351,9 +353,13 @@ describe("Cipher Service", () => {
it("logIn: sends 2FA token entered by user to server", async () => {
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(
Arg.is((actual) => {
@@ -371,15 +377,22 @@ describe("Cipher Service", () => {
);
});
it("logInTwoFactor: sends 2FA token to server when using Master Password", async () => {
commonSetup();
authService.email = email;
authService.masterPasswordHash = hashedPassword;
authService.localMasterPasswordHash = localHashedPassword;
const tokenRequest = new PasswordTokenRequest(email, hashedPassword, null, null, {
identifier: deviceId,
} 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(
Arg.is((actual) => {