mirror of
https://github.com/bitwarden/jslib
synced 2025-12-10 21:33:17 +00:00
Break tokenRequest into subclasses
This commit is contained in:
@@ -75,7 +75,6 @@ import { SendRequest } from '../models/request/sendRequest';
|
||||
import { SetPasswordRequest } from '../models/request/setPasswordRequest';
|
||||
import { StorageRequest } from '../models/request/storageRequest';
|
||||
import { TaxInfoUpdateRequest } from '../models/request/taxInfoUpdateRequest';
|
||||
import { TokenRequest } from '../models/request/tokenRequest';
|
||||
import { TwoFactorEmailRequest } from '../models/request/twoFactorEmailRequest';
|
||||
import { TwoFactorProviderRequest } from '../models/request/twoFactorProviderRequest';
|
||||
import { TwoFactorRecoveryRequest } from '../models/request/twoFactorRecoveryRequest';
|
||||
@@ -93,6 +92,10 @@ import { VerifyBankRequest } from '../models/request/verifyBankRequest';
|
||||
import { VerifyDeleteRecoverRequest } from '../models/request/verifyDeleteRecoverRequest';
|
||||
import { VerifyEmailRequest } from '../models/request/verifyEmailRequest';
|
||||
|
||||
import { ApiTokenRequest } from '../models/request/identityToken/apiTokenRequest';
|
||||
import { PasswordTokenRequest } from '../models/request/identityToken/passwordTokenRequest';
|
||||
import { SsoTokenRequest } from '../models/request/identityToken/ssoTokenRequest';
|
||||
|
||||
import { ApiKeyResponse } from '../models/response/apiKeyResponse';
|
||||
import { AttachmentResponse } from '../models/response/attachmentResponse';
|
||||
import { AttachmentUploadDataResponse } from '../models/response/attachmentUploadDataResponse';
|
||||
@@ -164,7 +167,8 @@ import { UserKeyResponse } from '../models/response/userKeyResponse';
|
||||
import { SendAccessView } from '../models/view/sendAccessView';
|
||||
|
||||
export abstract class ApiService {
|
||||
postIdentityToken: (request: TokenRequest) => Promise<IdentityTokenResponse | IdentityTwoFactorResponse | IdentityCaptchaResponse>;
|
||||
postIdentityToken: (request: PasswordTokenRequest | SsoTokenRequest | ApiTokenRequest) =>
|
||||
Promise<IdentityTokenResponse | IdentityTwoFactorResponse | IdentityCaptchaResponse>;
|
||||
refreshIdentityToken: () => Promise<any>;
|
||||
|
||||
getProfile: () => Promise<ProfileResponse>;
|
||||
|
||||
@@ -11,7 +11,7 @@ export abstract class TokenService {
|
||||
toggleTokens: () => Promise<any>;
|
||||
setTwoFactorToken: (token: string, email: string) => Promise<any>;
|
||||
getTwoFactorToken: (email: string) => Promise<string>;
|
||||
clearTwoFactorToken: (email: string) => Promise<any>;
|
||||
clearTwoFactorToken: () => Promise<any>;
|
||||
clearToken: (userId?: string) => Promise<any>;
|
||||
decodeToken: (token?: string) => any;
|
||||
getTokenExpirationDate: () => Promise<Date>;
|
||||
|
||||
29
common/src/models/request/identityToken/apiTokenRequest.ts
Normal file
29
common/src/models/request/identityToken/apiTokenRequest.ts
Normal file
@@ -0,0 +1,29 @@
|
||||
import { TokenRequest } from './tokenRequest';
|
||||
|
||||
import { TwoFactorProviderType } from '../../../enums/twoFactorProviderType';
|
||||
|
||||
import { DeviceRequest } from '../deviceRequest';
|
||||
|
||||
export class ApiTokenRequest extends TokenRequest {
|
||||
clientId: string;
|
||||
clientSecret: string;
|
||||
|
||||
constructor(clientId: string, clientSecret: string, public provider: TwoFactorProviderType,
|
||||
public token: string, public remember: boolean, public captchaResponse: string, device?: DeviceRequest) {
|
||||
super(provider, token, remember, captchaResponse, device);
|
||||
|
||||
this.clientId = clientId
|
||||
this.clientSecret = clientSecret;
|
||||
}
|
||||
|
||||
toIdentityToken(clientId: string) {
|
||||
const obj = super.toIdentityToken(clientId);
|
||||
|
||||
obj.clientId = this.clientId;
|
||||
obj.scope = clientId.startsWith('organization') ? 'api.organization' : 'api';
|
||||
obj.grant_type = 'client_credentials';
|
||||
obj.client_secret = this.clientSecret;
|
||||
|
||||
return obj;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
import { TokenRequest } from './tokenRequest';
|
||||
|
||||
import { TwoFactorProviderType } from '../../../enums/twoFactorProviderType';
|
||||
|
||||
import { DeviceRequest } from '../deviceRequest';
|
||||
|
||||
import { Utils } from '../../../misc/utils';
|
||||
|
||||
export class PasswordTokenRequest extends TokenRequest {
|
||||
email: string;
|
||||
masterPasswordHash: string;
|
||||
|
||||
constructor(email: string, masterPasswordHash: string, public provider: TwoFactorProviderType, public token: string,
|
||||
public remember: boolean, public captchaResponse: string, device?: DeviceRequest) {
|
||||
super(provider, token, remember, captchaResponse, device);
|
||||
|
||||
this.email = email;
|
||||
this.masterPasswordHash = masterPasswordHash;
|
||||
}
|
||||
|
||||
toIdentityToken(clientId: string) {
|
||||
const obj = super.toIdentityToken(clientId);
|
||||
|
||||
obj.grant_type = 'password';
|
||||
obj.username = this.email;
|
||||
obj.password = this.masterPasswordHash;
|
||||
|
||||
return obj;
|
||||
}
|
||||
|
||||
alterIdentityTokenHeaders(headers: Headers) {
|
||||
headers.set('Auth-Email', Utils.fromUtf8ToUrlB64(this.email));
|
||||
}
|
||||
}
|
||||
31
common/src/models/request/identityToken/ssoTokenRequest.ts
Normal file
31
common/src/models/request/identityToken/ssoTokenRequest.ts
Normal file
@@ -0,0 +1,31 @@
|
||||
import { TokenRequest } from './tokenRequest';
|
||||
|
||||
import { TwoFactorProviderType } from '../../../enums/twoFactorProviderType';
|
||||
|
||||
import { DeviceRequest } from '../deviceRequest';
|
||||
|
||||
export class SsoTokenRequest extends TokenRequest {
|
||||
code: string;
|
||||
codeVerifier: string;
|
||||
redirectUri: string;
|
||||
|
||||
constructor(code: string, codeVerifier: string, redirectUri: string, public provider: TwoFactorProviderType,
|
||||
public token: string, public remember: boolean, public captchaResponse: string, device?: DeviceRequest) {
|
||||
super(provider, token, remember, captchaResponse, device);
|
||||
|
||||
this.code = code;
|
||||
this.codeVerifier = codeVerifier;
|
||||
this.redirectUri = redirectUri;
|
||||
}
|
||||
|
||||
toIdentityToken(clientId: string) {
|
||||
const obj = super.toIdentityToken(clientId);
|
||||
|
||||
obj.grant_type = 'authorization_code';
|
||||
obj.code = this.code;
|
||||
obj.code_verifier = this.codeVerifier;
|
||||
obj.redirect_uri = this.redirectUri;
|
||||
|
||||
return obj;
|
||||
}
|
||||
}
|
||||
44
common/src/models/request/identityToken/tokenRequest.ts
Normal file
44
common/src/models/request/identityToken/tokenRequest.ts
Normal file
@@ -0,0 +1,44 @@
|
||||
import { TwoFactorProviderType } from '../../../enums/twoFactorProviderType';
|
||||
|
||||
import { CaptchaProtectedRequest } from '../captchaProtectedRequest';
|
||||
import { DeviceRequest } from '../deviceRequest';
|
||||
|
||||
export abstract class TokenRequest implements CaptchaProtectedRequest {
|
||||
device?: DeviceRequest;
|
||||
|
||||
constructor(public provider: TwoFactorProviderType, public token: string, public remember: boolean,
|
||||
public captchaResponse: string, device?: DeviceRequest) {
|
||||
this.device = device != null ? device : null;
|
||||
}
|
||||
|
||||
toIdentityToken(clientId: string) {
|
||||
const obj: any = {
|
||||
scope: 'api offline_access',
|
||||
client_id: clientId,
|
||||
};
|
||||
|
||||
if (this.device) {
|
||||
obj.deviceType = this.device.type;
|
||||
obj.deviceIdentifier = this.device.identifier;
|
||||
obj.deviceName = this.device.name;
|
||||
// no push tokens for browser apps yet
|
||||
// obj.devicePushToken = this.device.pushToken;
|
||||
}
|
||||
|
||||
if (this.token && this.provider != null) {
|
||||
obj.twoFactorToken = this.token;
|
||||
obj.twoFactorProvider = this.provider;
|
||||
obj.twoFactorRemember = this.remember ? '1' : '0';
|
||||
}
|
||||
|
||||
if (this.captchaResponse != null) {
|
||||
obj.captchaResponse = this.captchaResponse;
|
||||
}
|
||||
|
||||
return obj;
|
||||
}
|
||||
|
||||
alterIdentityTokenHeaders(headers: Headers) {
|
||||
// Implemented in subclass if required
|
||||
}
|
||||
}
|
||||
@@ -1,84 +0,0 @@
|
||||
import { TwoFactorProviderType } from '../../enums/twoFactorProviderType';
|
||||
|
||||
import { CaptchaProtectedRequest } from './captchaProtectedRequest';
|
||||
import { DeviceRequest } from './deviceRequest';
|
||||
|
||||
import { Utils } from '../../misc/utils';
|
||||
|
||||
export class TokenRequest implements CaptchaProtectedRequest {
|
||||
email: string;
|
||||
masterPasswordHash: string;
|
||||
code: string;
|
||||
codeVerifier: string;
|
||||
redirectUri: string;
|
||||
clientId: string;
|
||||
clientSecret: string;
|
||||
device?: DeviceRequest;
|
||||
|
||||
constructor(credentials: string[], codes: string[], clientIdClientSecret: string[], public provider: TwoFactorProviderType,
|
||||
public token: string, public remember: boolean, public captchaResponse: string, device?: DeviceRequest) {
|
||||
if (credentials != null && credentials.length > 1) {
|
||||
this.email = credentials[0];
|
||||
this.masterPasswordHash = credentials[1];
|
||||
} else if (codes != null && codes.length > 2) {
|
||||
this.code = codes[0];
|
||||
this.codeVerifier = codes[1];
|
||||
this.redirectUri = codes[2];
|
||||
} else if (clientIdClientSecret != null && clientIdClientSecret.length > 1) {
|
||||
this.clientId = clientIdClientSecret[0];
|
||||
this.clientSecret = clientIdClientSecret[1];
|
||||
}
|
||||
this.device = device != null ? device : null;
|
||||
}
|
||||
|
||||
toIdentityToken(clientId: string) {
|
||||
const obj: any = {
|
||||
scope: 'api offline_access',
|
||||
client_id: clientId,
|
||||
};
|
||||
|
||||
if (this.clientSecret != null) {
|
||||
obj.scope = clientId.startsWith('organization') ? 'api.organization' : 'api';
|
||||
obj.grant_type = 'client_credentials';
|
||||
obj.client_secret = this.clientSecret;
|
||||
} else if (this.masterPasswordHash != null && this.email != null) {
|
||||
obj.grant_type = 'password';
|
||||
obj.username = this.email;
|
||||
obj.password = this.masterPasswordHash;
|
||||
} else if (this.code != null && this.codeVerifier != null && this.redirectUri != null) {
|
||||
obj.grant_type = 'authorization_code';
|
||||
obj.code = this.code;
|
||||
obj.code_verifier = this.codeVerifier;
|
||||
obj.redirect_uri = this.redirectUri;
|
||||
} else {
|
||||
throw new Error('must provide credentials or codes');
|
||||
}
|
||||
|
||||
if (this.device) {
|
||||
obj.deviceType = this.device.type;
|
||||
obj.deviceIdentifier = this.device.identifier;
|
||||
obj.deviceName = this.device.name;
|
||||
// no push tokens for browser apps yet
|
||||
// obj.devicePushToken = this.device.pushToken;
|
||||
}
|
||||
|
||||
if (this.token && this.provider != null) {
|
||||
obj.twoFactorToken = this.token;
|
||||
obj.twoFactorProvider = this.provider;
|
||||
obj.twoFactorRemember = this.remember ? '1' : '0';
|
||||
}
|
||||
|
||||
if (this.captchaResponse != null) {
|
||||
obj.captchaResponse = this.captchaResponse;
|
||||
}
|
||||
|
||||
|
||||
return obj;
|
||||
}
|
||||
|
||||
alterIdentityTokenHeaders(headers: Headers) {
|
||||
if (this.clientSecret == null && this.masterPasswordHash != null && this.email != null) {
|
||||
headers.set('Auth-Email', Utils.fromUtf8ToUrlB64(this.email));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -76,7 +76,7 @@ import { SendRequest } from '../models/request/sendRequest';
|
||||
import { SetPasswordRequest } from '../models/request/setPasswordRequest';
|
||||
import { StorageRequest } from '../models/request/storageRequest';
|
||||
import { TaxInfoUpdateRequest } from '../models/request/taxInfoUpdateRequest';
|
||||
import { TokenRequest } from '../models/request/tokenRequest';
|
||||
import { TokenRequest } from '../models/request/identityToken/tokenRequest';
|
||||
import { TwoFactorEmailRequest } from '../models/request/twoFactorEmailRequest';
|
||||
import { TwoFactorProviderRequest } from '../models/request/twoFactorProviderRequest';
|
||||
import { TwoFactorRecoveryRequest } from '../models/request/twoFactorRecoveryRequest';
|
||||
@@ -209,7 +209,7 @@ export class ApiService implements ApiServiceAbstraction {
|
||||
}
|
||||
request.alterIdentityTokenHeaders(headers);
|
||||
const response = await this.fetch(new Request(this.environmentService.getIdentityUrl() + '/connect/token', {
|
||||
body: this.qsStringify(request.toIdentityToken(request.clientId ?? this.platformUtilsService.identityClientId)),
|
||||
body: this.qsStringify(request.toIdentityToken(this.platformUtilsService.identityClientId)),
|
||||
credentials: this.getCredentials(),
|
||||
cache: 'no-store',
|
||||
headers: headers,
|
||||
@@ -226,7 +226,7 @@ export class ApiService implements ApiServiceAbstraction {
|
||||
return new IdentityTokenResponse(responseJson);
|
||||
} else if (response.status === 400 && responseJson.TwoFactorProviders2 &&
|
||||
Object.keys(responseJson.TwoFactorProviders2).length) {
|
||||
await this.tokenService.clearTwoFactorToken(request.email);
|
||||
await this.tokenService.clearTwoFactorToken();
|
||||
return new IdentityTwoFactorResponse(responseJson);
|
||||
} else if (response.status === 400 && responseJson.HCaptcha_SiteKey &&
|
||||
Object.keys(responseJson.HCaptcha_SiteKey).length) {
|
||||
|
||||
@@ -11,7 +11,10 @@ import { DeviceRequest } from '../models/request/deviceRequest';
|
||||
import { KeyConnectorUserKeyRequest } from '../models/request/keyConnectorUserKeyRequest';
|
||||
import { KeysRequest } from '../models/request/keysRequest';
|
||||
import { PreloginRequest } from '../models/request/preloginRequest';
|
||||
import { TokenRequest } from '../models/request/tokenRequest';
|
||||
|
||||
import { ApiTokenRequest } from '../models/request/identityToken/apiTokenRequest';
|
||||
import { PasswordTokenRequest } from '../models/request/identityToken/passwordTokenRequest';
|
||||
import { SsoTokenRequest } from '../models/request/identityToken/ssoTokenRequest';
|
||||
|
||||
import { IdentityTokenResponse } from '../models/response/identityTokenResponse';
|
||||
import { IdentityTwoFactorResponse } from '../models/response/identityTwoFactorResponse';
|
||||
@@ -142,6 +145,7 @@ export class AuthService implements AuthServiceAbstraction {
|
||||
}
|
||||
return this.cryptoService.makeKey(masterPassword, email, kdf, kdfIterations);
|
||||
}
|
||||
|
||||
private async logInHelper(email: string, hashedPassword: string, localHashedPassword: string, code: string,
|
||||
codeVerifier: string, redirectUrl: string, clientId: string, clientSecret: string, key: SymmetricCryptoKey,
|
||||
twoFactorProvider?: TwoFactorProviderType, twoFactorToken?: string, remember?: boolean, captchaToken?: string,
|
||||
@@ -223,39 +227,31 @@ export class AuthService implements AuthServiceAbstraction {
|
||||
const storedTwoFactorToken = await this.tokenService.getTwoFactorToken(email);
|
||||
const deviceRequest = new DeviceRequest(appId, this.platformUtilsService);
|
||||
|
||||
let emailPassword: string[] = [];
|
||||
let codeCodeVerifier: string[] = [];
|
||||
let clientIdClientSecret: [string, string] = [null, null];
|
||||
let effectiveToken = null;
|
||||
let effectiveProvider = null;
|
||||
let effectiveRemember = false;
|
||||
|
||||
if (twoFactorToken != null && twoFactorProvider != null) {
|
||||
effectiveToken = twoFactorToken;
|
||||
effectiveProvider = twoFactorProvider;
|
||||
effectiveRemember = remember;
|
||||
} else if (storedTwoFactorToken != null) {
|
||||
effectiveToken = storedTwoFactorToken;
|
||||
effectiveProvider = TwoFactorProviderType.Remember;
|
||||
}
|
||||
|
||||
if (email != null && hashedPassword != null) {
|
||||
emailPassword = [email, hashedPassword];
|
||||
return new PasswordTokenRequest(email, hashedPassword, effectiveProvider, effectiveToken,
|
||||
effectiveRemember, captchaToken, deviceRequest);
|
||||
} else if (code != null && codeVerifier != null && redirectUrl != null) {
|
||||
return new SsoTokenRequest(code, codeVerifier, redirectUrl, effectiveProvider, effectiveToken,
|
||||
effectiveRemember, captchaToken, deviceRequest);
|
||||
} else if (clientId != null && clientSecret != null) {
|
||||
return new ApiTokenRequest(clientId, clientSecret, effectiveProvider, effectiveToken, effectiveRemember,
|
||||
captchaToken, deviceRequest);
|
||||
} else {
|
||||
emailPassword = null;
|
||||
throw new Error('No credentials provided.');
|
||||
}
|
||||
if (code != null && codeVerifier != null && redirectUrl != null) {
|
||||
codeCodeVerifier = [code, codeVerifier, redirectUrl];
|
||||
} else {
|
||||
codeCodeVerifier = null;
|
||||
}
|
||||
if (clientId != null && clientSecret != null) {
|
||||
clientIdClientSecret = [clientId, clientSecret];
|
||||
} else {
|
||||
clientIdClientSecret = null;
|
||||
}
|
||||
|
||||
let request: TokenRequest;
|
||||
if (twoFactorToken != null && twoFactorProvider != null) {
|
||||
request = new TokenRequest(emailPassword, codeCodeVerifier, clientIdClientSecret, twoFactorProvider,
|
||||
twoFactorToken, remember, captchaToken, deviceRequest);
|
||||
} else if (storedTwoFactorToken != null) {
|
||||
request = new TokenRequest(emailPassword, codeCodeVerifier, clientIdClientSecret,
|
||||
TwoFactorProviderType.Remember, storedTwoFactorToken, false, captchaToken, deviceRequest);
|
||||
} else {
|
||||
request = new TokenRequest(emailPassword, codeCodeVerifier, clientIdClientSecret, null,
|
||||
null, false, captchaToken, deviceRequest);
|
||||
}
|
||||
|
||||
return request;
|
||||
}
|
||||
|
||||
private async saveAccountInformation(tokenResponse: IdentityTokenResponse, clientId: string, clientSecret: string) {
|
||||
|
||||
@@ -28,6 +28,9 @@ 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 { SsoTokenRequest } from 'jslib-common/models/request/identityToken/ssoTokenRequest';
|
||||
import { ApiTokenRequest } from 'jslib-common/models/request/identityToken/apiTokenRequest';
|
||||
import { PasswordTokenRequest } from 'jslib-common/models/request/identityToken/passwordTokenRequest';
|
||||
|
||||
describe('Cipher Service', () => {
|
||||
let cryptoService: SubstituteOf<CryptoService>;
|
||||
@@ -178,13 +181,15 @@ describe('Cipher Service', () => {
|
||||
|
||||
// Assert
|
||||
// Api call:
|
||||
apiService.received(1).postIdentityToken(Arg.is(actual =>
|
||||
actual.email === email &&
|
||||
actual.masterPasswordHash === hashedPassword &&
|
||||
actual.device.identifier === deviceId &&
|
||||
actual.provider == null &&
|
||||
actual.token == null &&
|
||||
actual.captchaResponse == null));
|
||||
apiService.received(1).postIdentityToken(Arg.is(actual => {
|
||||
const passwordTokenRequest = actual as PasswordTokenRequest;
|
||||
return passwordTokenRequest.email === email &&
|
||||
passwordTokenRequest.masterPasswordHash === hashedPassword &&
|
||||
actual.device.identifier === deviceId &&
|
||||
actual.provider == null &&
|
||||
actual.token == null &&
|
||||
actual.captchaResponse == null
|
||||
}));
|
||||
|
||||
// Sets local environment:
|
||||
commonSuccessAssertions();
|
||||
@@ -302,14 +307,16 @@ describe('Cipher Service', () => {
|
||||
|
||||
await authService.logInTwoFactor(twoFactorProviderType, twoFactorToken, twoFactorRemember);
|
||||
|
||||
apiService.received(1).postIdentityToken(Arg.is(actual =>
|
||||
actual.email === email &&
|
||||
actual.masterPasswordHash === hashedPassword &&
|
||||
actual.device.identifier === deviceId &&
|
||||
actual.provider === twoFactorProviderType &&
|
||||
actual.token === twoFactorToken &&
|
||||
actual.remember === twoFactorRemember &&
|
||||
actual.captchaResponse == null));
|
||||
apiService.received(1).postIdentityToken(Arg.is(actual => {
|
||||
const passwordTokenRequest = actual as PasswordTokenRequest;
|
||||
return passwordTokenRequest.email === email &&
|
||||
passwordTokenRequest.masterPasswordHash === hashedPassword &&
|
||||
actual.device.identifier === deviceId &&
|
||||
actual.provider === twoFactorProviderType &&
|
||||
actual.token === twoFactorToken &&
|
||||
actual.remember === twoFactorRemember &&
|
||||
actual.captchaResponse == null
|
||||
}));
|
||||
});
|
||||
|
||||
// SSO
|
||||
@@ -325,14 +332,16 @@ describe('Cipher Service', () => {
|
||||
|
||||
// Assert
|
||||
// Api call:
|
||||
apiService.received(1).postIdentityToken(Arg.is(actual =>
|
||||
actual.code === ssoCode &&
|
||||
actual.codeVerifier === ssoCodeVerifier &&
|
||||
actual.redirectUri === ssoRedirectUrl &&
|
||||
actual.device.identifier === deviceId &&
|
||||
actual.provider == null &&
|
||||
actual.token == null &&
|
||||
actual.captchaResponse == null));
|
||||
apiService.received(1).postIdentityToken(Arg.is(actual => {
|
||||
const ssoTokenRequest = actual as SsoTokenRequest;
|
||||
return ssoTokenRequest.code === ssoCode &&
|
||||
ssoTokenRequest.codeVerifier === ssoCodeVerifier &&
|
||||
ssoTokenRequest.redirectUri === ssoRedirectUrl &&
|
||||
actual.device.identifier === deviceId &&
|
||||
actual.provider == null &&
|
||||
actual.token == null &&
|
||||
actual.captchaResponse == null
|
||||
}));
|
||||
|
||||
// Sets local environment:
|
||||
commonSuccessAssertions();
|
||||
@@ -427,13 +436,15 @@ describe('Cipher Service', () => {
|
||||
|
||||
const result = await authService.logInApiKey(apiClientId, apiClientSecret);
|
||||
|
||||
apiService.received(1).postIdentityToken(Arg.is(actual =>
|
||||
actual.clientId === apiClientId &&
|
||||
actual.clientSecret === apiClientSecret &&
|
||||
actual.device.identifier === deviceId &&
|
||||
actual.provider == null &&
|
||||
actual.token == null &&
|
||||
actual.captchaResponse == null));
|
||||
apiService.received(1).postIdentityToken(Arg.is(actual => {
|
||||
const apiTokenRequest = actual as ApiTokenRequest;
|
||||
return apiTokenRequest.clientId === apiClientId &&
|
||||
apiTokenRequest.clientSecret === apiClientSecret &&
|
||||
actual.device.identifier === deviceId &&
|
||||
actual.provider == null &&
|
||||
actual.token == null &&
|
||||
actual.captchaResponse == null
|
||||
}));
|
||||
|
||||
// Sets local environment:
|
||||
stateService.received(1).addAccount({
|
||||
|
||||
Reference in New Issue
Block a user