mirror of
https://github.com/bitwarden/browser
synced 2025-12-20 10:13:31 +00:00
Auth/ps 2298 reorg auth (#4564)
* Move auth service factories to Auth team * Move authentication componenets to Auth team * Move auth guard services to Auth team * Move Duo content script to Auth team * Move auth CLI commands to Auth team * Move Desktop Account components to Auth Team * Move Desktop guards to Auth team * Move two-factor provider images to Auth team * Move web Accounts components to Auth Team * Move web settings components to Auth Team * Move web two factor images to Auth Team * Fix missed import changes for Auth Team * Fix Linting errors * Fix missed CLI imports * Fix missed Desktop imports * Revert images move * Fix missed imports in Web * Move angular lib components to Auth Team * Move angular auth guards to Auth team * Move strategy specs to Auth team * Update .eslintignore for new paths * Move lib common abstractions to Auth team * Move services to Auth team * Move common lib enums to Auth team * Move webauthn iframe to Auth team * Move lib common domain models to Auth team * Move common lib requests to Auth team * Move response models to Auth team * Clean up whitelist * Move bit web components to Auth team * Move SSO and SCIM files to Auth team * Revert move SCIM to Auth team SCIM belongs to Admin Console team * Move captcha to Auth team * Move key connector to Auth team * Move emergency access to auth team * Delete extra file * linter fixes * Move kdf config to auth team * Fix whitelist * Fix duo autoformat * Complete two factor provider request move * Fix whitelist names * Fix login capitalization * Revert hint dependency reordering * Revert hint dependency reordering * Revert hint component This components is being picked up as a move between clients * Move web hint component to Auth team * Move new files to auth team * Fix desktop build * Fix browser build
This commit is contained in:
@@ -0,0 +1,5 @@
|
||||
import { SecretVerificationRequest } from "../models/request/secret-verification.request";
|
||||
|
||||
export abstract class AccountApiService {
|
||||
abstract deleteAccount(request: SecretVerificationRequest): Promise<void>;
|
||||
}
|
||||
5
libs/common/src/auth/abstractions/account-api.service.ts
Normal file
5
libs/common/src/auth/abstractions/account-api.service.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
import { Verification } from "../../types/verification";
|
||||
|
||||
export abstract class AccountApiService {
|
||||
abstract deleteAccount(verification: Verification): Promise<void>;
|
||||
}
|
||||
5
libs/common/src/auth/abstractions/account.service.ts
Normal file
5
libs/common/src/auth/abstractions/account.service.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
export abstract class AccountService {}
|
||||
|
||||
export abstract class InternalAccountService extends AccountService {
|
||||
abstract delete(): void;
|
||||
}
|
||||
47
libs/common/src/auth/abstractions/auth.service.ts
Normal file
47
libs/common/src/auth/abstractions/auth.service.ts
Normal file
@@ -0,0 +1,47 @@
|
||||
import { Observable } from "rxjs";
|
||||
|
||||
import { SymmetricCryptoKey } from "../../models/domain/symmetric-crypto-key";
|
||||
import { AuthRequestPushNotification } from "../../models/response/notification.response";
|
||||
import { AuthenticationStatus } from "../enums/authentication-status";
|
||||
import { AuthResult } from "../models/domain/auth-result";
|
||||
import {
|
||||
UserApiLogInCredentials,
|
||||
PasswordLogInCredentials,
|
||||
SsoLogInCredentials,
|
||||
PasswordlessLogInCredentials,
|
||||
} from "../models/domain/log-in-credentials";
|
||||
import { TokenTwoFactorRequest } from "../models/request/identity-token/token-two-factor.request";
|
||||
import { AuthRequestResponse } from "../models/response/auth-request.response";
|
||||
|
||||
export abstract class AuthService {
|
||||
masterPasswordHash: string;
|
||||
email: string;
|
||||
accessCode: string;
|
||||
authRequestId: string;
|
||||
|
||||
logIn: (
|
||||
credentials:
|
||||
| UserApiLogInCredentials
|
||||
| PasswordLogInCredentials
|
||||
| SsoLogInCredentials
|
||||
| PasswordlessLogInCredentials
|
||||
) => Promise<AuthResult>;
|
||||
logInTwoFactor: (
|
||||
twoFactor: TokenTwoFactorRequest,
|
||||
captchaResponse: string
|
||||
) => Promise<AuthResult>;
|
||||
logOut: (callback: () => void) => void;
|
||||
makePreloginKey: (masterPassword: string, email: string) => Promise<SymmetricCryptoKey>;
|
||||
authingWithUserApiKey: () => boolean;
|
||||
authingWithSso: () => boolean;
|
||||
authingWithPassword: () => boolean;
|
||||
authingWithPasswordless: () => boolean;
|
||||
getAuthStatus: (userId?: string) => Promise<AuthenticationStatus>;
|
||||
authResponsePushNotifiction: (notification: AuthRequestPushNotification) => Promise<any>;
|
||||
passwordlessLogin: (
|
||||
id: string,
|
||||
key: string,
|
||||
requestApproved: boolean
|
||||
) => Promise<AuthRequestResponse>;
|
||||
getPushNotifcationObs$: () => Observable<any>;
|
||||
}
|
||||
19
libs/common/src/auth/abstractions/key-connector.service.ts
Normal file
19
libs/common/src/auth/abstractions/key-connector.service.ts
Normal file
@@ -0,0 +1,19 @@
|
||||
import { Organization } from "../../models/domain/organization";
|
||||
import { IdentityTokenResponse } from "../models/response/identity-token.response";
|
||||
|
||||
export abstract class KeyConnectorService {
|
||||
getAndSetKey: (url?: string) => Promise<void>;
|
||||
getManagingOrganization: () => Promise<Organization>;
|
||||
getUsesKeyConnector: () => Promise<boolean>;
|
||||
migrateUser: () => Promise<void>;
|
||||
userNeedsMigration: () => Promise<boolean>;
|
||||
convertNewSsoUserToKeyConnector: (
|
||||
tokenResponse: IdentityTokenResponse,
|
||||
orgId: string
|
||||
) => Promise<void>;
|
||||
setUsesKeyConnector: (enabled: boolean) => Promise<void>;
|
||||
setConvertAccountRequired: (status: boolean) => Promise<void>;
|
||||
getConvertAccountRequired: () => Promise<boolean>;
|
||||
removeConvertAccountRequired: () => Promise<void>;
|
||||
clear: () => Promise<void>;
|
||||
}
|
||||
8
libs/common/src/auth/abstractions/login.service.ts
Normal file
8
libs/common/src/auth/abstractions/login.service.ts
Normal file
@@ -0,0 +1,8 @@
|
||||
export abstract class LoginService {
|
||||
getEmail: () => string;
|
||||
getRememberEmail: () => boolean;
|
||||
setEmail: (value: string) => void;
|
||||
setRememberEmail: (value: boolean) => void;
|
||||
clearValues: () => void;
|
||||
saveEmailSettings: () => Promise<void>;
|
||||
}
|
||||
31
libs/common/src/auth/abstractions/token.service.ts
Normal file
31
libs/common/src/auth/abstractions/token.service.ts
Normal file
@@ -0,0 +1,31 @@
|
||||
import { IdentityTokenResponse } from "../models/response/identity-token.response";
|
||||
|
||||
export abstract class TokenService {
|
||||
setTokens: (
|
||||
accessToken: string,
|
||||
refreshToken: string,
|
||||
clientIdClientSecret: [string, string]
|
||||
) => Promise<any>;
|
||||
setToken: (token: string) => Promise<any>;
|
||||
getToken: () => Promise<string>;
|
||||
setRefreshToken: (refreshToken: string) => Promise<any>;
|
||||
getRefreshToken: () => Promise<string>;
|
||||
setClientId: (clientId: string) => Promise<any>;
|
||||
getClientId: () => Promise<string>;
|
||||
setClientSecret: (clientSecret: string) => Promise<any>;
|
||||
getClientSecret: () => Promise<string>;
|
||||
setTwoFactorToken: (tokenResponse: IdentityTokenResponse) => Promise<any>;
|
||||
getTwoFactorToken: () => Promise<string>;
|
||||
clearTwoFactorToken: () => Promise<any>;
|
||||
clearToken: (userId?: string) => Promise<any>;
|
||||
decodeToken: (token?: string) => any;
|
||||
getTokenExpirationDate: () => Promise<Date>;
|
||||
tokenSecondsRemaining: (offsetSeconds?: number) => Promise<number>;
|
||||
tokenNeedsRefresh: (minutes?: number) => Promise<boolean>;
|
||||
getUserId: () => Promise<string>;
|
||||
getEmail: () => Promise<string>;
|
||||
getEmailVerified: () => Promise<boolean>;
|
||||
getName: () => Promise<string>;
|
||||
getIssuer: () => Promise<string>;
|
||||
getIsExternal: () => Promise<boolean>;
|
||||
}
|
||||
23
libs/common/src/auth/abstractions/two-factor.service.ts
Normal file
23
libs/common/src/auth/abstractions/two-factor.service.ts
Normal file
@@ -0,0 +1,23 @@
|
||||
import { TwoFactorProviderType } from "../enums/two-factor-provider-type";
|
||||
import { IdentityTwoFactorResponse } from "../models/response/identity-two-factor.response";
|
||||
|
||||
export interface TwoFactorProviderDetails {
|
||||
type: TwoFactorProviderType;
|
||||
name: string;
|
||||
description: string;
|
||||
priority: number;
|
||||
sort: number;
|
||||
premium: boolean;
|
||||
}
|
||||
|
||||
export abstract class TwoFactorService {
|
||||
init: () => void;
|
||||
getSupportedProviders: (win: Window) => TwoFactorProviderDetails[];
|
||||
getDefaultProvider: (webAuthnSupported: boolean) => TwoFactorProviderType;
|
||||
setSelectedProvider: (type: TwoFactorProviderType) => void;
|
||||
clearSelectedProvider: () => void;
|
||||
|
||||
setProviders: (response: IdentityTwoFactorResponse) => void;
|
||||
clearProviders: () => void;
|
||||
getProviders: () => Map<TwoFactorProviderType, { [key: string]: string }>;
|
||||
}
|
||||
37
libs/common/src/auth/captcha-iframe.ts
Normal file
37
libs/common/src/auth/captcha-iframe.ts
Normal file
@@ -0,0 +1,37 @@
|
||||
import { I18nService } from "../abstractions/i18n.service";
|
||||
import { IFrameComponent } from "../misc/iframe_component";
|
||||
|
||||
export class CaptchaIFrame extends IFrameComponent {
|
||||
constructor(
|
||||
win: Window,
|
||||
webVaultUrl: string,
|
||||
private i18nService: I18nService,
|
||||
successCallback: (message: string) => any,
|
||||
errorCallback: (message: string) => any,
|
||||
infoCallback: (message: string) => any
|
||||
) {
|
||||
super(
|
||||
win,
|
||||
webVaultUrl,
|
||||
"captcha-connector.html",
|
||||
"hcaptcha_iframe",
|
||||
successCallback,
|
||||
errorCallback,
|
||||
(message: string) => {
|
||||
const parsedMessage = JSON.parse(message);
|
||||
if (typeof parsedMessage !== "string") {
|
||||
this.iframe.height = parsedMessage.height.toString();
|
||||
this.iframe.width = parsedMessage.width.toString();
|
||||
} else {
|
||||
infoCallback(parsedMessage);
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
init(siteKey: string): void {
|
||||
super.initComponent(
|
||||
this.createParams({ siteKey: siteKey, locale: this.i18nService.translationLocale }, 1)
|
||||
);
|
||||
}
|
||||
}
|
||||
4
libs/common/src/auth/enums/auth-request-type.ts
Normal file
4
libs/common/src/auth/enums/auth-request-type.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
export enum AuthRequestType {
|
||||
AuthenticateAndUnlock = 0,
|
||||
Unlock = 1,
|
||||
}
|
||||
5
libs/common/src/auth/enums/authentication-status.ts
Normal file
5
libs/common/src/auth/enums/authentication-status.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
export enum AuthenticationStatus {
|
||||
LoggedOut = 0,
|
||||
Locked = 1,
|
||||
Unlocked = 2,
|
||||
}
|
||||
6
libs/common/src/auth/enums/authentication-type.ts
Normal file
6
libs/common/src/auth/enums/authentication-type.ts
Normal file
@@ -0,0 +1,6 @@
|
||||
export enum AuthenticationType {
|
||||
Password = 0,
|
||||
Sso = 1,
|
||||
UserApi = 2,
|
||||
Passwordless = 3,
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
export enum EmergencyAccessStatusType {
|
||||
Invited = 0,
|
||||
Accepted = 1,
|
||||
Confirmed = 2,
|
||||
RecoveryInitiated = 3,
|
||||
RecoveryApproved = 4,
|
||||
}
|
||||
4
libs/common/src/auth/enums/emergency-access-type.ts
Normal file
4
libs/common/src/auth/enums/emergency-access-type.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
export enum EmergencyAccessType {
|
||||
View = 0,
|
||||
Takeover = 1,
|
||||
}
|
||||
5
libs/common/src/auth/enums/organization-api-key-type.ts
Normal file
5
libs/common/src/auth/enums/organization-api-key-type.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
export enum OrganizationApiKeyType {
|
||||
Default = 0,
|
||||
BillingSync = 1,
|
||||
Scim = 2,
|
||||
}
|
||||
33
libs/common/src/auth/enums/sso.ts
Normal file
33
libs/common/src/auth/enums/sso.ts
Normal file
@@ -0,0 +1,33 @@
|
||||
export enum SsoType {
|
||||
None = 0,
|
||||
OpenIdConnect = 1,
|
||||
Saml2 = 2,
|
||||
}
|
||||
|
||||
export enum OpenIdConnectRedirectBehavior {
|
||||
RedirectGet = 0,
|
||||
FormPost = 1,
|
||||
}
|
||||
|
||||
export enum Saml2BindingType {
|
||||
HttpRedirect = 1,
|
||||
HttpPost = 2,
|
||||
}
|
||||
|
||||
export enum Saml2NameIdFormat {
|
||||
NotConfigured = 0,
|
||||
Unspecified = 1,
|
||||
EmailAddress = 2,
|
||||
X509SubjectName = 3,
|
||||
WindowsDomainQualifiedName = 4,
|
||||
KerberosPrincipalName = 5,
|
||||
EntityIdentifier = 6,
|
||||
Persistent = 7,
|
||||
Transient = 8,
|
||||
}
|
||||
|
||||
export enum Saml2SigningBehavior {
|
||||
IfIdpWantAuthnRequestsSigned = 0,
|
||||
Always = 1,
|
||||
Never = 3,
|
||||
}
|
||||
10
libs/common/src/auth/enums/two-factor-provider-type.ts
Normal file
10
libs/common/src/auth/enums/two-factor-provider-type.ts
Normal file
@@ -0,0 +1,10 @@
|
||||
export enum TwoFactorProviderType {
|
||||
Authenticator = 0,
|
||||
Email = 1,
|
||||
Duo = 2,
|
||||
Yubikey = 3,
|
||||
U2f = 4,
|
||||
Remember = 5,
|
||||
OrganizationDuo = 6,
|
||||
WebAuthn = 7,
|
||||
}
|
||||
4
libs/common/src/auth/enums/verification-type.ts
Normal file
4
libs/common/src/auth/enums/verification-type.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
export enum VerificationType {
|
||||
MasterPassword = 0,
|
||||
OTP = 1,
|
||||
}
|
||||
294
libs/common/src/auth/login-strategies/login.strategy.spec.ts
Normal file
294
libs/common/src/auth/login-strategies/login.strategy.spec.ts
Normal file
@@ -0,0 +1,294 @@
|
||||
import { mock, MockProxy } from "jest-mock-extended";
|
||||
|
||||
import { ApiService } from "../../abstractions/api.service";
|
||||
import { AppIdService } from "../../abstractions/appId.service";
|
||||
import { CryptoService } from "../../abstractions/crypto.service";
|
||||
import { LogService } from "../../abstractions/log.service";
|
||||
import { MessagingService } from "../../abstractions/messaging.service";
|
||||
import { PlatformUtilsService } from "../../abstractions/platformUtils.service";
|
||||
import { StateService } from "../../abstractions/state.service";
|
||||
import { Utils } from "../../misc/utils";
|
||||
import { Account, AccountProfile, AccountTokens } from "../../models/domain/account";
|
||||
import { EncString } from "../../models/domain/enc-string";
|
||||
import { AuthService } from "../abstractions/auth.service";
|
||||
import { TokenService } from "../abstractions/token.service";
|
||||
import { TwoFactorService } from "../abstractions/two-factor.service";
|
||||
import { TwoFactorProviderType } from "../enums/two-factor-provider-type";
|
||||
import { AuthResult } from "../models/domain/auth-result";
|
||||
import { PasswordLogInCredentials } from "../models/domain/log-in-credentials";
|
||||
import { PasswordTokenRequest } from "../models/request/identity-token/password-token.request";
|
||||
import { TokenTwoFactorRequest } from "../models/request/identity-token/token-two-factor.request";
|
||||
import { IdentityCaptchaResponse } from "../models/response/identity-captcha.response";
|
||||
import { IdentityTokenResponse } from "../models/response/identity-token.response";
|
||||
import { IdentityTwoFactorResponse } from "../models/response/identity-two-factor.response";
|
||||
|
||||
import { PasswordLogInStrategy } from "./password-login.strategy";
|
||||
|
||||
const email = "hello@world.com";
|
||||
const masterPassword = "password";
|
||||
|
||||
const deviceId = Utils.newGuid();
|
||||
const accessToken = "ACCESS_TOKEN";
|
||||
const refreshToken = "REFRESH_TOKEN";
|
||||
const encKey = "ENC_KEY";
|
||||
const privateKey = "PRIVATE_KEY";
|
||||
const captchaSiteKey = "CAPTCHA_SITE_KEY";
|
||||
const kdf = 0;
|
||||
const kdfIterations = 10000;
|
||||
const userId = Utils.newGuid();
|
||||
const masterPasswordHash = "MASTER_PASSWORD_HASH";
|
||||
const name = "NAME";
|
||||
|
||||
const decodedToken = {
|
||||
sub: userId,
|
||||
name: name,
|
||||
email: email,
|
||||
premium: false,
|
||||
};
|
||||
|
||||
const twoFactorProviderType = TwoFactorProviderType.Authenticator;
|
||||
const twoFactorToken = "TWO_FACTOR_TOKEN";
|
||||
const twoFactorRemember = true;
|
||||
|
||||
export function identityTokenResponseFactory() {
|
||||
return new IdentityTokenResponse({
|
||||
ForcePasswordReset: false,
|
||||
Kdf: kdf,
|
||||
KdfIterations: kdfIterations,
|
||||
Key: encKey,
|
||||
PrivateKey: privateKey,
|
||||
ResetMasterPassword: false,
|
||||
access_token: accessToken,
|
||||
expires_in: 3600,
|
||||
refresh_token: refreshToken,
|
||||
scope: "api offline_access",
|
||||
token_type: "Bearer",
|
||||
});
|
||||
}
|
||||
|
||||
describe("LogInStrategy", () => {
|
||||
let cryptoService: MockProxy<CryptoService>;
|
||||
let apiService: MockProxy<ApiService>;
|
||||
let tokenService: MockProxy<TokenService>;
|
||||
let appIdService: MockProxy<AppIdService>;
|
||||
let platformUtilsService: MockProxy<PlatformUtilsService>;
|
||||
let messagingService: MockProxy<MessagingService>;
|
||||
let logService: MockProxy<LogService>;
|
||||
let stateService: MockProxy<StateService>;
|
||||
let twoFactorService: MockProxy<TwoFactorService>;
|
||||
let authService: MockProxy<AuthService>;
|
||||
|
||||
let passwordLogInStrategy: PasswordLogInStrategy;
|
||||
let credentials: PasswordLogInCredentials;
|
||||
|
||||
beforeEach(async () => {
|
||||
cryptoService = mock<CryptoService>();
|
||||
apiService = mock<ApiService>();
|
||||
tokenService = mock<TokenService>();
|
||||
appIdService = mock<AppIdService>();
|
||||
platformUtilsService = mock<PlatformUtilsService>();
|
||||
messagingService = mock<MessagingService>();
|
||||
logService = mock<LogService>();
|
||||
stateService = mock<StateService>();
|
||||
twoFactorService = mock<TwoFactorService>();
|
||||
authService = mock<AuthService>();
|
||||
|
||||
appIdService.getAppId.mockResolvedValue(deviceId);
|
||||
tokenService.decodeToken.calledWith(accessToken).mockResolvedValue(decodedToken);
|
||||
|
||||
// The base class is abstract so we test it via PasswordLogInStrategy
|
||||
passwordLogInStrategy = new PasswordLogInStrategy(
|
||||
cryptoService,
|
||||
apiService,
|
||||
tokenService,
|
||||
appIdService,
|
||||
platformUtilsService,
|
||||
messagingService,
|
||||
logService,
|
||||
stateService,
|
||||
twoFactorService,
|
||||
authService
|
||||
);
|
||||
credentials = new PasswordLogInCredentials(email, masterPassword);
|
||||
});
|
||||
|
||||
describe("base class", () => {
|
||||
it("sets the local environment after a successful login", async () => {
|
||||
apiService.postIdentityToken.mockResolvedValue(identityTokenResponseFactory());
|
||||
|
||||
await passwordLogInStrategy.logIn(credentials);
|
||||
|
||||
expect(stateService.addAccount).toHaveBeenCalledWith(
|
||||
new Account({
|
||||
profile: {
|
||||
...new AccountProfile(),
|
||||
...{
|
||||
userId: userId,
|
||||
name: name,
|
||||
email: email,
|
||||
hasPremiumPersonally: false,
|
||||
kdfIterations: kdfIterations,
|
||||
kdfType: kdf,
|
||||
},
|
||||
},
|
||||
tokens: {
|
||||
...new AccountTokens(),
|
||||
...{
|
||||
accessToken: accessToken,
|
||||
refreshToken: refreshToken,
|
||||
},
|
||||
},
|
||||
})
|
||||
);
|
||||
expect(cryptoService.setEncKey).toHaveBeenCalledWith(encKey);
|
||||
expect(cryptoService.setEncPrivateKey).toHaveBeenCalledWith(privateKey);
|
||||
expect(messagingService.send).toHaveBeenCalledWith("loggedIn");
|
||||
});
|
||||
|
||||
it("builds AuthResult", async () => {
|
||||
const tokenResponse = identityTokenResponseFactory();
|
||||
tokenResponse.forcePasswordReset = true;
|
||||
tokenResponse.resetMasterPassword = true;
|
||||
|
||||
apiService.postIdentityToken.mockResolvedValue(tokenResponse);
|
||||
|
||||
const result = await passwordLogInStrategy.logIn(credentials);
|
||||
|
||||
expect(result).toEqual({
|
||||
forcePasswordReset: true,
|
||||
resetMasterPassword: true,
|
||||
twoFactorProviders: null,
|
||||
captchaSiteKey: "",
|
||||
} as AuthResult);
|
||||
});
|
||||
|
||||
it("rejects login if CAPTCHA is required", async () => {
|
||||
// Sample CAPTCHA response
|
||||
const tokenResponse = new IdentityCaptchaResponse({
|
||||
error: "invalid_grant",
|
||||
error_description: "Captcha required.",
|
||||
HCaptcha_SiteKey: captchaSiteKey,
|
||||
});
|
||||
|
||||
apiService.postIdentityToken.mockResolvedValue(tokenResponse);
|
||||
|
||||
const result = await passwordLogInStrategy.logIn(credentials);
|
||||
|
||||
expect(stateService.addAccount).not.toHaveBeenCalled();
|
||||
expect(messagingService.send).not.toHaveBeenCalled();
|
||||
|
||||
const expected = new AuthResult();
|
||||
expected.captchaSiteKey = captchaSiteKey;
|
||||
expect(result).toEqual(expected);
|
||||
});
|
||||
|
||||
it("makes a new public and private key for an old account", async () => {
|
||||
const tokenResponse = identityTokenResponseFactory();
|
||||
tokenResponse.privateKey = null;
|
||||
cryptoService.makeKeyPair.mockResolvedValue(["PUBLIC_KEY", new EncString("PRIVATE_KEY")]);
|
||||
|
||||
apiService.postIdentityToken.mockResolvedValue(tokenResponse);
|
||||
|
||||
await passwordLogInStrategy.logIn(credentials);
|
||||
|
||||
// User key must be set before the new RSA keypair is generated, otherwise we can't decrypt the EncKey
|
||||
expect(cryptoService.setKey).toHaveBeenCalled();
|
||||
expect(cryptoService.makeKeyPair).toHaveBeenCalled();
|
||||
expect(cryptoService.setKey.mock.invocationCallOrder[0]).toBeLessThan(
|
||||
cryptoService.makeKeyPair.mock.invocationCallOrder[0]
|
||||
);
|
||||
|
||||
expect(apiService.postAccountKeys).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe("Two-factor authentication", () => {
|
||||
it("rejects login if 2FA is required", async () => {
|
||||
// Sample response where TOTP 2FA required
|
||||
const tokenResponse = new IdentityTwoFactorResponse({
|
||||
TwoFactorProviders: ["0"],
|
||||
TwoFactorProviders2: { 0: null },
|
||||
error: "invalid_grant",
|
||||
error_description: "Two factor required.",
|
||||
});
|
||||
|
||||
apiService.postIdentityToken.mockResolvedValue(tokenResponse);
|
||||
|
||||
const result = await passwordLogInStrategy.logIn(credentials);
|
||||
|
||||
expect(stateService.addAccount).not.toHaveBeenCalled();
|
||||
expect(messagingService.send).not.toHaveBeenCalled();
|
||||
|
||||
const expected = new AuthResult();
|
||||
expected.twoFactorProviders = new Map<TwoFactorProviderType, { [key: string]: string }>();
|
||||
expected.twoFactorProviders.set(0, null);
|
||||
expect(result).toEqual(expected);
|
||||
});
|
||||
|
||||
it("sends stored 2FA token to server", async () => {
|
||||
tokenService.getTwoFactorToken.mockResolvedValue(twoFactorToken);
|
||||
apiService.postIdentityToken.mockResolvedValue(identityTokenResponseFactory());
|
||||
|
||||
await passwordLogInStrategy.logIn(credentials);
|
||||
|
||||
expect(apiService.postIdentityToken).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
twoFactor: {
|
||||
provider: TwoFactorProviderType.Remember,
|
||||
token: twoFactorToken,
|
||||
remember: false,
|
||||
} as TokenTwoFactorRequest,
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
it("sends 2FA token provided by user to server (single step)", async () => {
|
||||
// This occurs if the user enters the 2FA code as an argument in the CLI
|
||||
apiService.postIdentityToken.mockResolvedValue(identityTokenResponseFactory());
|
||||
credentials.twoFactor = new TokenTwoFactorRequest(
|
||||
twoFactorProviderType,
|
||||
twoFactorToken,
|
||||
twoFactorRemember
|
||||
);
|
||||
|
||||
await passwordLogInStrategy.logIn(credentials);
|
||||
|
||||
expect(apiService.postIdentityToken).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
twoFactor: {
|
||||
provider: twoFactorProviderType,
|
||||
token: twoFactorToken,
|
||||
remember: twoFactorRemember,
|
||||
} as TokenTwoFactorRequest,
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
it("sends 2FA token provided by user to server (two-step)", async () => {
|
||||
// Simulate a partially completed login
|
||||
passwordLogInStrategy.tokenRequest = new PasswordTokenRequest(
|
||||
email,
|
||||
masterPasswordHash,
|
||||
null,
|
||||
null
|
||||
);
|
||||
|
||||
apiService.postIdentityToken.mockResolvedValue(identityTokenResponseFactory());
|
||||
|
||||
await passwordLogInStrategy.logInTwoFactor(
|
||||
new TokenTwoFactorRequest(twoFactorProviderType, twoFactorToken, twoFactorRemember),
|
||||
null
|
||||
);
|
||||
|
||||
expect(apiService.postIdentityToken).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
twoFactor: {
|
||||
provider: twoFactorProviderType,
|
||||
token: twoFactorToken,
|
||||
remember: twoFactorRemember,
|
||||
} as TokenTwoFactorRequest,
|
||||
})
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
176
libs/common/src/auth/login-strategies/login.strategy.ts
Normal file
176
libs/common/src/auth/login-strategies/login.strategy.ts
Normal file
@@ -0,0 +1,176 @@
|
||||
import { ApiService } from "../../abstractions/api.service";
|
||||
import { AppIdService } from "../../abstractions/appId.service";
|
||||
import { CryptoService } from "../../abstractions/crypto.service";
|
||||
import { LogService } from "../../abstractions/log.service";
|
||||
import { MessagingService } from "../../abstractions/messaging.service";
|
||||
import { PlatformUtilsService } from "../../abstractions/platformUtils.service";
|
||||
import { StateService } from "../../abstractions/state.service";
|
||||
import { Account, AccountProfile, AccountTokens } from "../../models/domain/account";
|
||||
import { KeysRequest } from "../../models/request/keys.request";
|
||||
import { TokenService } from "../abstractions/token.service";
|
||||
import { TwoFactorService } from "../abstractions/two-factor.service";
|
||||
import { TwoFactorProviderType } from "../enums/two-factor-provider-type";
|
||||
import { AuthResult } from "../models/domain/auth-result";
|
||||
import {
|
||||
UserApiLogInCredentials,
|
||||
PasswordLogInCredentials,
|
||||
SsoLogInCredentials,
|
||||
PasswordlessLogInCredentials,
|
||||
} from "../models/domain/log-in-credentials";
|
||||
import { DeviceRequest } from "../models/request/identity-token/device.request";
|
||||
import { PasswordTokenRequest } from "../models/request/identity-token/password-token.request";
|
||||
import { SsoTokenRequest } from "../models/request/identity-token/sso-token.request";
|
||||
import { TokenTwoFactorRequest } from "../models/request/identity-token/token-two-factor.request";
|
||||
import { UserApiTokenRequest } from "../models/request/identity-token/user-api-token.request";
|
||||
import { IdentityCaptchaResponse } from "../models/response/identity-captcha.response";
|
||||
import { IdentityTokenResponse } from "../models/response/identity-token.response";
|
||||
import { IdentityTwoFactorResponse } from "../models/response/identity-two-factor.response";
|
||||
|
||||
export abstract class LogInStrategy {
|
||||
protected abstract tokenRequest: UserApiTokenRequest | PasswordTokenRequest | SsoTokenRequest;
|
||||
protected captchaBypassToken: string = null;
|
||||
|
||||
constructor(
|
||||
protected cryptoService: CryptoService,
|
||||
protected apiService: ApiService,
|
||||
protected tokenService: TokenService,
|
||||
protected appIdService: AppIdService,
|
||||
protected platformUtilsService: PlatformUtilsService,
|
||||
protected messagingService: MessagingService,
|
||||
protected logService: LogService,
|
||||
protected stateService: StateService,
|
||||
protected twoFactorService: TwoFactorService
|
||||
) {}
|
||||
|
||||
abstract logIn(
|
||||
credentials:
|
||||
| UserApiLogInCredentials
|
||||
| PasswordLogInCredentials
|
||||
| SsoLogInCredentials
|
||||
| PasswordlessLogInCredentials
|
||||
): Promise<AuthResult>;
|
||||
|
||||
// The user key comes from different sources depending on the login strategy
|
||||
protected abstract setUserKey(response: IdentityTokenResponse): Promise<void>;
|
||||
|
||||
async logInTwoFactor(
|
||||
twoFactor: TokenTwoFactorRequest,
|
||||
captchaResponse: string = null
|
||||
): Promise<AuthResult> {
|
||||
this.tokenRequest.setTwoFactor(twoFactor);
|
||||
return this.startLogIn();
|
||||
}
|
||||
|
||||
protected async startLogIn(): Promise<AuthResult> {
|
||||
this.twoFactorService.clearSelectedProvider();
|
||||
|
||||
const response = await this.apiService.postIdentityToken(this.tokenRequest);
|
||||
|
||||
if (response instanceof IdentityTwoFactorResponse) {
|
||||
return this.processTwoFactorResponse(response);
|
||||
} else if (response instanceof IdentityCaptchaResponse) {
|
||||
return this.processCaptchaResponse(response);
|
||||
} else if (response instanceof IdentityTokenResponse) {
|
||||
return this.processTokenResponse(response);
|
||||
}
|
||||
|
||||
throw new Error("Invalid response object.");
|
||||
}
|
||||
|
||||
protected async buildDeviceRequest() {
|
||||
const appId = await this.appIdService.getAppId();
|
||||
return new DeviceRequest(appId, this.platformUtilsService);
|
||||
}
|
||||
|
||||
protected async buildTwoFactor(userProvidedTwoFactor?: TokenTwoFactorRequest) {
|
||||
if (userProvidedTwoFactor != null) {
|
||||
return userProvidedTwoFactor;
|
||||
}
|
||||
|
||||
const storedTwoFactorToken = await this.tokenService.getTwoFactorToken();
|
||||
if (storedTwoFactorToken != null) {
|
||||
return new TokenTwoFactorRequest(TwoFactorProviderType.Remember, storedTwoFactorToken, false);
|
||||
}
|
||||
|
||||
return new TokenTwoFactorRequest();
|
||||
}
|
||||
|
||||
protected async saveAccountInformation(tokenResponse: IdentityTokenResponse) {
|
||||
const accountInformation = await this.tokenService.decodeToken(tokenResponse.accessToken);
|
||||
await this.stateService.addAccount(
|
||||
new Account({
|
||||
profile: {
|
||||
...new AccountProfile(),
|
||||
...{
|
||||
userId: accountInformation.sub,
|
||||
name: accountInformation.name,
|
||||
email: accountInformation.email,
|
||||
hasPremiumPersonally: accountInformation.premium,
|
||||
kdfIterations: tokenResponse.kdfIterations,
|
||||
kdfMemory: tokenResponse.kdfMemory,
|
||||
kdfParallelism: tokenResponse.kdfParallelism,
|
||||
kdfType: tokenResponse.kdf,
|
||||
},
|
||||
},
|
||||
tokens: {
|
||||
...new AccountTokens(),
|
||||
...{
|
||||
accessToken: tokenResponse.accessToken,
|
||||
refreshToken: tokenResponse.refreshToken,
|
||||
},
|
||||
},
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
protected async processTokenResponse(response: IdentityTokenResponse): Promise<AuthResult> {
|
||||
const result = new AuthResult();
|
||||
result.resetMasterPassword = response.resetMasterPassword;
|
||||
result.forcePasswordReset = response.forcePasswordReset;
|
||||
|
||||
await this.saveAccountInformation(response);
|
||||
|
||||
if (response.twoFactorToken != null) {
|
||||
await this.tokenService.setTwoFactorToken(response);
|
||||
}
|
||||
|
||||
await this.setUserKey(response);
|
||||
|
||||
// Must come after the user Key is set, otherwise createKeyPairForOldAccount will fail
|
||||
const newSsoUser = response.key == null;
|
||||
if (!newSsoUser) {
|
||||
await this.cryptoService.setEncKey(response.key);
|
||||
await this.cryptoService.setEncPrivateKey(
|
||||
response.privateKey ?? (await this.createKeyPairForOldAccount())
|
||||
);
|
||||
}
|
||||
|
||||
this.messagingService.send("loggedIn");
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private async processTwoFactorResponse(response: IdentityTwoFactorResponse): Promise<AuthResult> {
|
||||
const result = new AuthResult();
|
||||
result.twoFactorProviders = response.twoFactorProviders2;
|
||||
this.twoFactorService.setProviders(response);
|
||||
this.captchaBypassToken = response.captchaToken ?? null;
|
||||
return result;
|
||||
}
|
||||
|
||||
private async processCaptchaResponse(response: IdentityCaptchaResponse): Promise<AuthResult> {
|
||||
const result = new AuthResult();
|
||||
result.captchaSiteKey = response.siteKey;
|
||||
return result;
|
||||
}
|
||||
|
||||
private async createKeyPairForOldAccount() {
|
||||
try {
|
||||
const [publicKey, privateKey] = await this.cryptoService.makeKeyPair();
|
||||
await this.apiService.postAccountKeys(new KeysRequest(publicKey, privateKey.encryptedString));
|
||||
return privateKey.encryptedString;
|
||||
} catch (e) {
|
||||
this.logService.error(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,113 @@
|
||||
import { mock, MockProxy } from "jest-mock-extended";
|
||||
|
||||
import { ApiService } from "../../abstractions/api.service";
|
||||
import { AppIdService } from "../../abstractions/appId.service";
|
||||
import { CryptoService } from "../../abstractions/crypto.service";
|
||||
import { LogService } from "../../abstractions/log.service";
|
||||
import { MessagingService } from "../../abstractions/messaging.service";
|
||||
import { PlatformUtilsService } from "../../abstractions/platformUtils.service";
|
||||
import { StateService } from "../../abstractions/state.service";
|
||||
import { HashPurpose } from "../../enums/hashPurpose";
|
||||
import { Utils } from "../../misc/utils";
|
||||
import { SymmetricCryptoKey } from "../../models/domain/symmetric-crypto-key";
|
||||
import { AuthService } from "../abstractions/auth.service";
|
||||
import { TokenService } from "../abstractions/token.service";
|
||||
import { TwoFactorService } from "../abstractions/two-factor.service";
|
||||
import { PasswordLogInCredentials } from "../models/domain/log-in-credentials";
|
||||
|
||||
import { identityTokenResponseFactory } from "./login.strategy.spec";
|
||||
import { PasswordLogInStrategy } from "./password-login.strategy";
|
||||
|
||||
const email = "hello@world.com";
|
||||
const masterPassword = "password";
|
||||
const hashedPassword = "HASHED_PASSWORD";
|
||||
const localHashedPassword = "LOCAL_HASHED_PASSWORD";
|
||||
const preloginKey = new SymmetricCryptoKey(
|
||||
Utils.fromB64ToArray(
|
||||
"N2KWjlLpfi5uHjv+YcfUKIpZ1l+W+6HRensmIqD+BFYBf6N/dvFpJfWwYnVBdgFCK2tJTAIMLhqzIQQEUmGFgg=="
|
||||
)
|
||||
);
|
||||
const deviceId = Utils.newGuid();
|
||||
|
||||
describe("PasswordLogInStrategy", () => {
|
||||
let cryptoService: MockProxy<CryptoService>;
|
||||
let apiService: MockProxy<ApiService>;
|
||||
let tokenService: MockProxy<TokenService>;
|
||||
let appIdService: MockProxy<AppIdService>;
|
||||
let platformUtilsService: MockProxy<PlatformUtilsService>;
|
||||
let messagingService: MockProxy<MessagingService>;
|
||||
let logService: MockProxy<LogService>;
|
||||
let stateService: MockProxy<StateService>;
|
||||
let twoFactorService: MockProxy<TwoFactorService>;
|
||||
let authService: MockProxy<AuthService>;
|
||||
|
||||
let passwordLogInStrategy: PasswordLogInStrategy;
|
||||
let credentials: PasswordLogInCredentials;
|
||||
|
||||
beforeEach(async () => {
|
||||
cryptoService = mock<CryptoService>();
|
||||
apiService = mock<ApiService>();
|
||||
tokenService = mock<TokenService>();
|
||||
appIdService = mock<AppIdService>();
|
||||
platformUtilsService = mock<PlatformUtilsService>();
|
||||
messagingService = mock<MessagingService>();
|
||||
logService = mock<LogService>();
|
||||
stateService = mock<StateService>();
|
||||
twoFactorService = mock<TwoFactorService>();
|
||||
authService = mock<AuthService>();
|
||||
|
||||
appIdService.getAppId.mockResolvedValue(deviceId);
|
||||
tokenService.decodeToken.mockResolvedValue({});
|
||||
|
||||
authService.makePreloginKey.mockResolvedValue(preloginKey);
|
||||
|
||||
cryptoService.hashPassword
|
||||
.calledWith(masterPassword, expect.anything(), undefined)
|
||||
.mockResolvedValue(hashedPassword);
|
||||
cryptoService.hashPassword
|
||||
.calledWith(masterPassword, expect.anything(), HashPurpose.LocalAuthorization)
|
||||
.mockResolvedValue(localHashedPassword);
|
||||
|
||||
passwordLogInStrategy = new PasswordLogInStrategy(
|
||||
cryptoService,
|
||||
apiService,
|
||||
tokenService,
|
||||
appIdService,
|
||||
platformUtilsService,
|
||||
messagingService,
|
||||
logService,
|
||||
stateService,
|
||||
twoFactorService,
|
||||
authService
|
||||
);
|
||||
credentials = new PasswordLogInCredentials(email, masterPassword);
|
||||
|
||||
apiService.postIdentityToken.mockResolvedValue(identityTokenResponseFactory());
|
||||
});
|
||||
|
||||
it("sends master password credentials to the server", async () => {
|
||||
await passwordLogInStrategy.logIn(credentials);
|
||||
|
||||
expect(apiService.postIdentityToken).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
email: email,
|
||||
masterPasswordHash: hashedPassword,
|
||||
device: expect.objectContaining({
|
||||
identifier: deviceId,
|
||||
}),
|
||||
twoFactor: expect.objectContaining({
|
||||
provider: null,
|
||||
token: null,
|
||||
}),
|
||||
captchaResponse: undefined,
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
it("sets the local environment after a successful login", async () => {
|
||||
await passwordLogInStrategy.logIn(credentials);
|
||||
|
||||
expect(cryptoService.setKey).toHaveBeenCalledWith(preloginKey);
|
||||
expect(cryptoService.setKeyHash).toHaveBeenCalledWith(localHashedPassword);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,95 @@
|
||||
import { ApiService } from "../../abstractions/api.service";
|
||||
import { AppIdService } from "../../abstractions/appId.service";
|
||||
import { CryptoService } from "../../abstractions/crypto.service";
|
||||
import { LogService } from "../../abstractions/log.service";
|
||||
import { MessagingService } from "../../abstractions/messaging.service";
|
||||
import { PlatformUtilsService } from "../../abstractions/platformUtils.service";
|
||||
import { StateService } from "../../abstractions/state.service";
|
||||
import { HashPurpose } from "../../enums/hashPurpose";
|
||||
import { SymmetricCryptoKey } from "../../models/domain/symmetric-crypto-key";
|
||||
import { AuthService } from "../abstractions/auth.service";
|
||||
import { TokenService } from "../abstractions/token.service";
|
||||
import { TwoFactorService } from "../abstractions/two-factor.service";
|
||||
import { AuthResult } from "../models/domain/auth-result";
|
||||
import { PasswordLogInCredentials } from "../models/domain/log-in-credentials";
|
||||
import { PasswordTokenRequest } from "../models/request/identity-token/password-token.request";
|
||||
import { TokenTwoFactorRequest } from "../models/request/identity-token/token-two-factor.request";
|
||||
|
||||
import { LogInStrategy } from "./login.strategy";
|
||||
|
||||
export class PasswordLogInStrategy extends LogInStrategy {
|
||||
get email() {
|
||||
return this.tokenRequest.email;
|
||||
}
|
||||
|
||||
get masterPasswordHash() {
|
||||
return this.tokenRequest.masterPasswordHash;
|
||||
}
|
||||
|
||||
tokenRequest: PasswordTokenRequest;
|
||||
|
||||
private localHashedPassword: string;
|
||||
private key: SymmetricCryptoKey;
|
||||
|
||||
constructor(
|
||||
cryptoService: CryptoService,
|
||||
apiService: ApiService,
|
||||
tokenService: TokenService,
|
||||
appIdService: AppIdService,
|
||||
platformUtilsService: PlatformUtilsService,
|
||||
messagingService: MessagingService,
|
||||
logService: LogService,
|
||||
stateService: StateService,
|
||||
twoFactorService: TwoFactorService,
|
||||
private authService: AuthService
|
||||
) {
|
||||
super(
|
||||
cryptoService,
|
||||
apiService,
|
||||
tokenService,
|
||||
appIdService,
|
||||
platformUtilsService,
|
||||
messagingService,
|
||||
logService,
|
||||
stateService,
|
||||
twoFactorService
|
||||
);
|
||||
}
|
||||
|
||||
async setUserKey() {
|
||||
await this.cryptoService.setKey(this.key);
|
||||
await this.cryptoService.setKeyHash(this.localHashedPassword);
|
||||
}
|
||||
|
||||
async logInTwoFactor(
|
||||
twoFactor: TokenTwoFactorRequest,
|
||||
captchaResponse: string
|
||||
): Promise<AuthResult> {
|
||||
this.tokenRequest.captchaResponse = captchaResponse ?? this.captchaBypassToken;
|
||||
return super.logInTwoFactor(twoFactor);
|
||||
}
|
||||
|
||||
async logIn(credentials: PasswordLogInCredentials) {
|
||||
const { email, masterPassword, captchaToken, twoFactor } = credentials;
|
||||
|
||||
this.key = await this.authService.makePreloginKey(masterPassword, email);
|
||||
|
||||
// Hash the password early (before authentication) so we don't persist it in memory in plaintext
|
||||
this.localHashedPassword = await this.cryptoService.hashPassword(
|
||||
masterPassword,
|
||||
this.key,
|
||||
HashPurpose.LocalAuthorization
|
||||
);
|
||||
const hashedPassword = await this.cryptoService.hashPassword(masterPassword, this.key);
|
||||
|
||||
this.tokenRequest = new PasswordTokenRequest(
|
||||
email,
|
||||
hashedPassword,
|
||||
captchaToken,
|
||||
await this.buildTwoFactor(twoFactor),
|
||||
await this.buildDeviceRequest()
|
||||
);
|
||||
|
||||
return this.startLogIn();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
import { ApiService } from "../../abstractions/api.service";
|
||||
import { AppIdService } from "../../abstractions/appId.service";
|
||||
import { CryptoService } from "../../abstractions/crypto.service";
|
||||
import { LogService } from "../../abstractions/log.service";
|
||||
import { MessagingService } from "../../abstractions/messaging.service";
|
||||
import { PlatformUtilsService } from "../../abstractions/platformUtils.service";
|
||||
import { StateService } from "../../abstractions/state.service";
|
||||
import { AuthService } from "../abstractions/auth.service";
|
||||
import { TokenService } from "../abstractions/token.service";
|
||||
import { TwoFactorService } from "../abstractions/two-factor.service";
|
||||
import { AuthResult } from "../models/domain/auth-result";
|
||||
import { PasswordlessLogInCredentials } from "../models/domain/log-in-credentials";
|
||||
import { PasswordTokenRequest } from "../models/request/identity-token/password-token.request";
|
||||
import { TokenTwoFactorRequest } from "../models/request/identity-token/token-two-factor.request";
|
||||
|
||||
import { LogInStrategy } from "./login.strategy";
|
||||
|
||||
export class PasswordlessLogInStrategy extends LogInStrategy {
|
||||
get email() {
|
||||
return this.tokenRequest.email;
|
||||
}
|
||||
|
||||
get accessCode() {
|
||||
return this.passwordlessCredentials.accessCode;
|
||||
}
|
||||
|
||||
get authRequestId() {
|
||||
return this.passwordlessCredentials.authRequestId;
|
||||
}
|
||||
|
||||
tokenRequest: PasswordTokenRequest;
|
||||
private passwordlessCredentials: PasswordlessLogInCredentials;
|
||||
|
||||
constructor(
|
||||
cryptoService: CryptoService,
|
||||
apiService: ApiService,
|
||||
tokenService: TokenService,
|
||||
appIdService: AppIdService,
|
||||
platformUtilsService: PlatformUtilsService,
|
||||
messagingService: MessagingService,
|
||||
logService: LogService,
|
||||
stateService: StateService,
|
||||
twoFactorService: TwoFactorService,
|
||||
private authService: AuthService
|
||||
) {
|
||||
super(
|
||||
cryptoService,
|
||||
apiService,
|
||||
tokenService,
|
||||
appIdService,
|
||||
platformUtilsService,
|
||||
messagingService,
|
||||
logService,
|
||||
stateService,
|
||||
twoFactorService
|
||||
);
|
||||
}
|
||||
|
||||
async setUserKey() {
|
||||
await this.cryptoService.setKey(this.passwordlessCredentials.decKey);
|
||||
await this.cryptoService.setKeyHash(this.passwordlessCredentials.localPasswordHash);
|
||||
}
|
||||
|
||||
async logInTwoFactor(
|
||||
twoFactor: TokenTwoFactorRequest,
|
||||
captchaResponse: string
|
||||
): Promise<AuthResult> {
|
||||
this.tokenRequest.captchaResponse = captchaResponse ?? this.captchaBypassToken;
|
||||
return super.logInTwoFactor(twoFactor);
|
||||
}
|
||||
|
||||
async logIn(credentials: PasswordlessLogInCredentials) {
|
||||
this.passwordlessCredentials = credentials;
|
||||
|
||||
this.tokenRequest = new PasswordTokenRequest(
|
||||
credentials.email,
|
||||
credentials.accessCode,
|
||||
null,
|
||||
await this.buildTwoFactor(credentials.twoFactor),
|
||||
await this.buildDeviceRequest()
|
||||
);
|
||||
|
||||
this.tokenRequest.setPasswordlessAccessCode(credentials.authRequestId);
|
||||
return this.startLogIn();
|
||||
}
|
||||
}
|
||||
130
libs/common/src/auth/login-strategies/sso-login.strategy.spec.ts
Normal file
130
libs/common/src/auth/login-strategies/sso-login.strategy.spec.ts
Normal file
@@ -0,0 +1,130 @@
|
||||
import { mock, MockProxy } from "jest-mock-extended";
|
||||
|
||||
import { ApiService } from "../../abstractions/api.service";
|
||||
import { AppIdService } from "../../abstractions/appId.service";
|
||||
import { CryptoService } from "../../abstractions/crypto.service";
|
||||
import { LogService } from "../../abstractions/log.service";
|
||||
import { MessagingService } from "../../abstractions/messaging.service";
|
||||
import { PlatformUtilsService } from "../../abstractions/platformUtils.service";
|
||||
import { StateService } from "../../abstractions/state.service";
|
||||
import { Utils } from "../../misc/utils";
|
||||
import { KeyConnectorService } from "../abstractions/key-connector.service";
|
||||
import { TokenService } from "../abstractions/token.service";
|
||||
import { TwoFactorService } from "../abstractions/two-factor.service";
|
||||
import { SsoLogInCredentials } from "../models/domain/log-in-credentials";
|
||||
|
||||
import { identityTokenResponseFactory } from "./login.strategy.spec";
|
||||
import { SsoLogInStrategy } from "./sso-login.strategy";
|
||||
|
||||
describe("SsoLogInStrategy", () => {
|
||||
let cryptoService: MockProxy<CryptoService>;
|
||||
let apiService: MockProxy<ApiService>;
|
||||
let tokenService: MockProxy<TokenService>;
|
||||
let appIdService: MockProxy<AppIdService>;
|
||||
let platformUtilsService: MockProxy<PlatformUtilsService>;
|
||||
let messagingService: MockProxy<MessagingService>;
|
||||
let logService: MockProxy<LogService>;
|
||||
let stateService: MockProxy<StateService>;
|
||||
let twoFactorService: MockProxy<TwoFactorService>;
|
||||
let keyConnectorService: MockProxy<KeyConnectorService>;
|
||||
|
||||
let ssoLogInStrategy: SsoLogInStrategy;
|
||||
let credentials: SsoLogInCredentials;
|
||||
|
||||
const deviceId = Utils.newGuid();
|
||||
const keyConnectorUrl = "KEY_CONNECTOR_URL";
|
||||
|
||||
const ssoCode = "SSO_CODE";
|
||||
const ssoCodeVerifier = "SSO_CODE_VERIFIER";
|
||||
const ssoRedirectUrl = "SSO_REDIRECT_URL";
|
||||
const ssoOrgId = "SSO_ORG_ID";
|
||||
|
||||
beforeEach(async () => {
|
||||
cryptoService = mock<CryptoService>();
|
||||
apiService = mock<ApiService>();
|
||||
tokenService = mock<TokenService>();
|
||||
appIdService = mock<AppIdService>();
|
||||
platformUtilsService = mock<PlatformUtilsService>();
|
||||
messagingService = mock<MessagingService>();
|
||||
logService = mock<LogService>();
|
||||
stateService = mock<StateService>();
|
||||
twoFactorService = mock<TwoFactorService>();
|
||||
keyConnectorService = mock<KeyConnectorService>();
|
||||
|
||||
tokenService.getTwoFactorToken.mockResolvedValue(null);
|
||||
appIdService.getAppId.mockResolvedValue(deviceId);
|
||||
tokenService.decodeToken.mockResolvedValue({});
|
||||
|
||||
ssoLogInStrategy = new SsoLogInStrategy(
|
||||
cryptoService,
|
||||
apiService,
|
||||
tokenService,
|
||||
appIdService,
|
||||
platformUtilsService,
|
||||
messagingService,
|
||||
logService,
|
||||
stateService,
|
||||
twoFactorService,
|
||||
keyConnectorService
|
||||
);
|
||||
credentials = new SsoLogInCredentials(ssoCode, ssoCodeVerifier, ssoRedirectUrl, ssoOrgId);
|
||||
});
|
||||
|
||||
it("sends SSO information to server", async () => {
|
||||
apiService.postIdentityToken.mockResolvedValue(identityTokenResponseFactory());
|
||||
|
||||
await ssoLogInStrategy.logIn(credentials);
|
||||
|
||||
expect(apiService.postIdentityToken).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
code: ssoCode,
|
||||
codeVerifier: ssoCodeVerifier,
|
||||
redirectUri: ssoRedirectUrl,
|
||||
device: expect.objectContaining({
|
||||
identifier: deviceId,
|
||||
}),
|
||||
twoFactor: expect.objectContaining({
|
||||
provider: null,
|
||||
token: null,
|
||||
}),
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
it("does not set keys for new SSO user flow", async () => {
|
||||
const tokenResponse = identityTokenResponseFactory();
|
||||
tokenResponse.key = null;
|
||||
apiService.postIdentityToken.mockResolvedValue(tokenResponse);
|
||||
|
||||
await ssoLogInStrategy.logIn(credentials);
|
||||
|
||||
expect(cryptoService.setEncPrivateKey).not.toHaveBeenCalled();
|
||||
expect(cryptoService.setEncKey).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("gets and sets KeyConnector key for enrolled user", async () => {
|
||||
const tokenResponse = identityTokenResponseFactory();
|
||||
tokenResponse.keyConnectorUrl = keyConnectorUrl;
|
||||
|
||||
apiService.postIdentityToken.mockResolvedValue(tokenResponse);
|
||||
|
||||
await ssoLogInStrategy.logIn(credentials);
|
||||
|
||||
expect(keyConnectorService.getAndSetKey).toHaveBeenCalledWith(keyConnectorUrl);
|
||||
});
|
||||
|
||||
it("converts new SSO user to Key Connector on first login", async () => {
|
||||
const tokenResponse = identityTokenResponseFactory();
|
||||
tokenResponse.keyConnectorUrl = keyConnectorUrl;
|
||||
tokenResponse.key = null;
|
||||
|
||||
apiService.postIdentityToken.mockResolvedValue(tokenResponse);
|
||||
|
||||
await ssoLogInStrategy.logIn(credentials);
|
||||
|
||||
expect(keyConnectorService.convertNewSsoUserToKeyConnector).toHaveBeenCalledWith(
|
||||
tokenResponse,
|
||||
ssoOrgId
|
||||
);
|
||||
});
|
||||
});
|
||||
70
libs/common/src/auth/login-strategies/sso-login.strategy.ts
Normal file
70
libs/common/src/auth/login-strategies/sso-login.strategy.ts
Normal file
@@ -0,0 +1,70 @@
|
||||
import { ApiService } from "../../abstractions/api.service";
|
||||
import { AppIdService } from "../../abstractions/appId.service";
|
||||
import { CryptoService } from "../../abstractions/crypto.service";
|
||||
import { LogService } from "../../abstractions/log.service";
|
||||
import { MessagingService } from "../../abstractions/messaging.service";
|
||||
import { PlatformUtilsService } from "../../abstractions/platformUtils.service";
|
||||
import { StateService } from "../../abstractions/state.service";
|
||||
import { KeyConnectorService } from "../abstractions/key-connector.service";
|
||||
import { TokenService } from "../abstractions/token.service";
|
||||
import { TwoFactorService } from "../abstractions/two-factor.service";
|
||||
import { SsoLogInCredentials } from "../models/domain/log-in-credentials";
|
||||
import { SsoTokenRequest } from "../models/request/identity-token/sso-token.request";
|
||||
import { IdentityTokenResponse } from "../models/response/identity-token.response";
|
||||
|
||||
import { LogInStrategy } from "./login.strategy";
|
||||
|
||||
export class SsoLogInStrategy extends LogInStrategy {
|
||||
tokenRequest: SsoTokenRequest;
|
||||
orgId: string;
|
||||
|
||||
constructor(
|
||||
cryptoService: CryptoService,
|
||||
apiService: ApiService,
|
||||
tokenService: TokenService,
|
||||
appIdService: AppIdService,
|
||||
platformUtilsService: PlatformUtilsService,
|
||||
messagingService: MessagingService,
|
||||
logService: LogService,
|
||||
stateService: StateService,
|
||||
twoFactorService: TwoFactorService,
|
||||
private keyConnectorService: KeyConnectorService
|
||||
) {
|
||||
super(
|
||||
cryptoService,
|
||||
apiService,
|
||||
tokenService,
|
||||
appIdService,
|
||||
platformUtilsService,
|
||||
messagingService,
|
||||
logService,
|
||||
stateService,
|
||||
twoFactorService
|
||||
);
|
||||
}
|
||||
|
||||
async setUserKey(tokenResponse: IdentityTokenResponse) {
|
||||
const newSsoUser = tokenResponse.key == null;
|
||||
|
||||
if (tokenResponse.keyConnectorUrl != null) {
|
||||
if (!newSsoUser) {
|
||||
await this.keyConnectorService.getAndSetKey(tokenResponse.keyConnectorUrl);
|
||||
} else {
|
||||
await this.keyConnectorService.convertNewSsoUserToKeyConnector(tokenResponse, this.orgId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async logIn(credentials: SsoLogInCredentials) {
|
||||
this.orgId = credentials.orgId;
|
||||
this.tokenRequest = new SsoTokenRequest(
|
||||
credentials.code,
|
||||
credentials.codeVerifier,
|
||||
credentials.redirectUrl,
|
||||
await this.buildTwoFactor(credentials.twoFactor),
|
||||
await this.buildDeviceRequest()
|
||||
);
|
||||
|
||||
return this.startLogIn();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,115 @@
|
||||
import { mock, MockProxy } from "jest-mock-extended";
|
||||
|
||||
import { ApiService } from "../../abstractions/api.service";
|
||||
import { AppIdService } from "../../abstractions/appId.service";
|
||||
import { CryptoService } from "../../abstractions/crypto.service";
|
||||
import { EnvironmentService } from "../../abstractions/environment.service";
|
||||
import { LogService } from "../../abstractions/log.service";
|
||||
import { MessagingService } from "../../abstractions/messaging.service";
|
||||
import { PlatformUtilsService } from "../../abstractions/platformUtils.service";
|
||||
import { StateService } from "../../abstractions/state.service";
|
||||
import { Utils } from "../../misc/utils";
|
||||
import { KeyConnectorService } from "../abstractions/key-connector.service";
|
||||
import { TokenService } from "../abstractions/token.service";
|
||||
import { TwoFactorService } from "../abstractions/two-factor.service";
|
||||
import { UserApiLogInCredentials } from "../models/domain/log-in-credentials";
|
||||
|
||||
import { identityTokenResponseFactory } from "./login.strategy.spec";
|
||||
import { UserApiLogInStrategy } from "./user-api-login.strategy";
|
||||
|
||||
describe("UserApiLogInStrategy", () => {
|
||||
let cryptoService: MockProxy<CryptoService>;
|
||||
let apiService: MockProxy<ApiService>;
|
||||
let tokenService: MockProxy<TokenService>;
|
||||
let appIdService: MockProxy<AppIdService>;
|
||||
let platformUtilsService: MockProxy<PlatformUtilsService>;
|
||||
let messagingService: MockProxy<MessagingService>;
|
||||
let logService: MockProxy<LogService>;
|
||||
let stateService: MockProxy<StateService>;
|
||||
let twoFactorService: MockProxy<TwoFactorService>;
|
||||
let keyConnectorService: MockProxy<KeyConnectorService>;
|
||||
let environmentService: MockProxy<EnvironmentService>;
|
||||
|
||||
let apiLogInStrategy: UserApiLogInStrategy;
|
||||
let credentials: UserApiLogInCredentials;
|
||||
|
||||
const deviceId = Utils.newGuid();
|
||||
const keyConnectorUrl = "KEY_CONNECTOR_URL";
|
||||
const apiClientId = "API_CLIENT_ID";
|
||||
const apiClientSecret = "API_CLIENT_SECRET";
|
||||
|
||||
beforeEach(async () => {
|
||||
cryptoService = mock<CryptoService>();
|
||||
apiService = mock<ApiService>();
|
||||
tokenService = mock<TokenService>();
|
||||
appIdService = mock<AppIdService>();
|
||||
platformUtilsService = mock<PlatformUtilsService>();
|
||||
messagingService = mock<MessagingService>();
|
||||
logService = mock<LogService>();
|
||||
stateService = mock<StateService>();
|
||||
twoFactorService = mock<TwoFactorService>();
|
||||
keyConnectorService = mock<KeyConnectorService>();
|
||||
environmentService = mock<EnvironmentService>();
|
||||
|
||||
appIdService.getAppId.mockResolvedValue(deviceId);
|
||||
tokenService.getTwoFactorToken.mockResolvedValue(null);
|
||||
tokenService.decodeToken.mockResolvedValue({});
|
||||
|
||||
apiLogInStrategy = new UserApiLogInStrategy(
|
||||
cryptoService,
|
||||
apiService,
|
||||
tokenService,
|
||||
appIdService,
|
||||
platformUtilsService,
|
||||
messagingService,
|
||||
logService,
|
||||
stateService,
|
||||
twoFactorService,
|
||||
environmentService,
|
||||
keyConnectorService
|
||||
);
|
||||
|
||||
credentials = new UserApiLogInCredentials(apiClientId, apiClientSecret);
|
||||
});
|
||||
|
||||
it("sends api key credentials to the server", async () => {
|
||||
apiService.postIdentityToken.mockResolvedValue(identityTokenResponseFactory());
|
||||
await apiLogInStrategy.logIn(credentials);
|
||||
|
||||
expect(apiService.postIdentityToken).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
clientId: apiClientId,
|
||||
clientSecret: apiClientSecret,
|
||||
device: expect.objectContaining({
|
||||
identifier: deviceId,
|
||||
}),
|
||||
twoFactor: expect.objectContaining({
|
||||
provider: null,
|
||||
token: null,
|
||||
}),
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
it("sets the local environment after a successful login", async () => {
|
||||
apiService.postIdentityToken.mockResolvedValue(identityTokenResponseFactory());
|
||||
|
||||
await apiLogInStrategy.logIn(credentials);
|
||||
|
||||
expect(stateService.setApiKeyClientId).toHaveBeenCalledWith(apiClientId);
|
||||
expect(stateService.setApiKeyClientSecret).toHaveBeenCalledWith(apiClientSecret);
|
||||
expect(stateService.addAccount).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("gets and sets the Key Connector key from environmentUrl", async () => {
|
||||
const tokenResponse = identityTokenResponseFactory();
|
||||
tokenResponse.apiUseKeyConnector = true;
|
||||
|
||||
apiService.postIdentityToken.mockResolvedValue(tokenResponse);
|
||||
environmentService.getKeyConnectorUrl.mockReturnValue(keyConnectorUrl);
|
||||
|
||||
await apiLogInStrategy.logIn(credentials);
|
||||
|
||||
expect(keyConnectorService.getAndSetKey).toHaveBeenCalledWith(keyConnectorUrl);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,70 @@
|
||||
import { ApiService } from "../../abstractions/api.service";
|
||||
import { AppIdService } from "../../abstractions/appId.service";
|
||||
import { CryptoService } from "../../abstractions/crypto.service";
|
||||
import { EnvironmentService } from "../../abstractions/environment.service";
|
||||
import { LogService } from "../../abstractions/log.service";
|
||||
import { MessagingService } from "../../abstractions/messaging.service";
|
||||
import { PlatformUtilsService } from "../../abstractions/platformUtils.service";
|
||||
import { StateService } from "../../abstractions/state.service";
|
||||
import { TokenService } from "../../auth/abstractions/token.service";
|
||||
import { TwoFactorService } from "../../auth/abstractions/two-factor.service";
|
||||
import { KeyConnectorService } from "../abstractions/key-connector.service";
|
||||
import { UserApiLogInCredentials } from "../models/domain/log-in-credentials";
|
||||
import { UserApiTokenRequest } from "../models/request/identity-token/user-api-token.request";
|
||||
import { IdentityTokenResponse } from "../models/response/identity-token.response";
|
||||
|
||||
import { LogInStrategy } from "./login.strategy";
|
||||
|
||||
export class UserApiLogInStrategy extends LogInStrategy {
|
||||
tokenRequest: UserApiTokenRequest;
|
||||
|
||||
constructor(
|
||||
cryptoService: CryptoService,
|
||||
apiService: ApiService,
|
||||
tokenService: TokenService,
|
||||
appIdService: AppIdService,
|
||||
platformUtilsService: PlatformUtilsService,
|
||||
messagingService: MessagingService,
|
||||
logService: LogService,
|
||||
stateService: StateService,
|
||||
twoFactorService: TwoFactorService,
|
||||
private environmentService: EnvironmentService,
|
||||
private keyConnectorService: KeyConnectorService
|
||||
) {
|
||||
super(
|
||||
cryptoService,
|
||||
apiService,
|
||||
tokenService,
|
||||
appIdService,
|
||||
platformUtilsService,
|
||||
messagingService,
|
||||
logService,
|
||||
stateService,
|
||||
twoFactorService
|
||||
);
|
||||
}
|
||||
|
||||
async setUserKey(tokenResponse: IdentityTokenResponse) {
|
||||
if (tokenResponse.apiUseKeyConnector) {
|
||||
const keyConnectorUrl = this.environmentService.getKeyConnectorUrl();
|
||||
await this.keyConnectorService.getAndSetKey(keyConnectorUrl);
|
||||
}
|
||||
}
|
||||
|
||||
async logIn(credentials: UserApiLogInCredentials) {
|
||||
this.tokenRequest = new UserApiTokenRequest(
|
||||
credentials.clientId,
|
||||
credentials.clientSecret,
|
||||
await this.buildTwoFactor(),
|
||||
await this.buildDeviceRequest()
|
||||
);
|
||||
|
||||
return this.startLogIn();
|
||||
}
|
||||
|
||||
protected async saveAccountInformation(tokenResponse: IdentityTokenResponse) {
|
||||
await super.saveAccountInformation(tokenResponse);
|
||||
await this.stateService.setApiKeyClientId(this.tokenRequest.clientId);
|
||||
await this.stateService.setApiKeyClientSecret(this.tokenRequest.clientSecret);
|
||||
}
|
||||
}
|
||||
136
libs/common/src/auth/models/api/sso-config.api.ts
Normal file
136
libs/common/src/auth/models/api/sso-config.api.ts
Normal file
@@ -0,0 +1,136 @@
|
||||
import { BaseResponse } from "../../../models/response/base.response";
|
||||
import {
|
||||
OpenIdConnectRedirectBehavior,
|
||||
Saml2BindingType,
|
||||
Saml2NameIdFormat,
|
||||
Saml2SigningBehavior,
|
||||
SsoType,
|
||||
} from "../../enums/sso";
|
||||
import { SsoConfigView } from "../view/sso-config.view";
|
||||
|
||||
export class SsoConfigApi extends BaseResponse {
|
||||
static fromView(view: SsoConfigView, api = new SsoConfigApi()) {
|
||||
api.configType = view.configType;
|
||||
|
||||
api.keyConnectorEnabled = view.keyConnectorEnabled;
|
||||
api.keyConnectorUrl = view.keyConnectorUrl;
|
||||
|
||||
if (api.configType === SsoType.OpenIdConnect) {
|
||||
api.authority = view.openId.authority;
|
||||
api.clientId = view.openId.clientId;
|
||||
api.clientSecret = view.openId.clientSecret;
|
||||
api.metadataAddress = view.openId.metadataAddress;
|
||||
api.redirectBehavior = view.openId.redirectBehavior;
|
||||
api.getClaimsFromUserInfoEndpoint = view.openId.getClaimsFromUserInfoEndpoint;
|
||||
api.additionalScopes = view.openId.additionalScopes;
|
||||
api.additionalUserIdClaimTypes = view.openId.additionalUserIdClaimTypes;
|
||||
api.additionalEmailClaimTypes = view.openId.additionalEmailClaimTypes;
|
||||
api.additionalNameClaimTypes = view.openId.additionalNameClaimTypes;
|
||||
api.acrValues = view.openId.acrValues;
|
||||
api.expectedReturnAcrValue = view.openId.expectedReturnAcrValue;
|
||||
} else if (api.configType === SsoType.Saml2) {
|
||||
api.spNameIdFormat = view.saml.spNameIdFormat;
|
||||
api.spOutboundSigningAlgorithm = view.saml.spOutboundSigningAlgorithm;
|
||||
api.spSigningBehavior = view.saml.spSigningBehavior;
|
||||
api.spMinIncomingSigningAlgorithm = view.saml.spMinIncomingSigningAlgorithm;
|
||||
api.spWantAssertionsSigned = view.saml.spWantAssertionsSigned;
|
||||
api.spValidateCertificates = view.saml.spValidateCertificates;
|
||||
|
||||
api.idpEntityId = view.saml.idpEntityId;
|
||||
api.idpBindingType = view.saml.idpBindingType;
|
||||
api.idpSingleSignOnServiceUrl = view.saml.idpSingleSignOnServiceUrl;
|
||||
api.idpSingleLogoutServiceUrl = view.saml.idpSingleLogoutServiceUrl;
|
||||
api.idpX509PublicCert = view.saml.idpX509PublicCert;
|
||||
api.idpOutboundSigningAlgorithm = view.saml.idpOutboundSigningAlgorithm;
|
||||
api.idpAllowUnsolicitedAuthnResponse = view.saml.idpAllowUnsolicitedAuthnResponse;
|
||||
api.idpWantAuthnRequestsSigned = view.saml.idpWantAuthnRequestsSigned;
|
||||
|
||||
// Value is inverted in the api model (disable instead of allow)
|
||||
api.idpDisableOutboundLogoutRequests = !view.saml.idpAllowOutboundLogoutRequests;
|
||||
}
|
||||
|
||||
return api;
|
||||
}
|
||||
configType: SsoType;
|
||||
|
||||
keyConnectorEnabled: boolean;
|
||||
keyConnectorUrl: string;
|
||||
|
||||
// OpenId
|
||||
authority: string;
|
||||
clientId: string;
|
||||
clientSecret: string;
|
||||
metadataAddress: string;
|
||||
redirectBehavior: OpenIdConnectRedirectBehavior;
|
||||
getClaimsFromUserInfoEndpoint: boolean;
|
||||
additionalScopes: string;
|
||||
additionalUserIdClaimTypes: string;
|
||||
additionalEmailClaimTypes: string;
|
||||
additionalNameClaimTypes: string;
|
||||
acrValues: string;
|
||||
expectedReturnAcrValue: string;
|
||||
|
||||
// SAML
|
||||
spNameIdFormat: Saml2NameIdFormat;
|
||||
spOutboundSigningAlgorithm: string;
|
||||
spSigningBehavior: Saml2SigningBehavior;
|
||||
spMinIncomingSigningAlgorithm: string;
|
||||
spWantAssertionsSigned: boolean;
|
||||
spValidateCertificates: boolean;
|
||||
|
||||
idpEntityId: string;
|
||||
idpBindingType: Saml2BindingType;
|
||||
idpSingleSignOnServiceUrl: string;
|
||||
idpSingleLogoutServiceUrl: string;
|
||||
idpX509PublicCert: string;
|
||||
idpOutboundSigningAlgorithm: string;
|
||||
idpAllowUnsolicitedAuthnResponse: boolean;
|
||||
idpDisableOutboundLogoutRequests: boolean;
|
||||
idpWantAuthnRequestsSigned: boolean;
|
||||
|
||||
constructor(data: any = null) {
|
||||
super(data);
|
||||
if (data == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.configType = this.getResponseProperty("ConfigType");
|
||||
|
||||
this.keyConnectorEnabled = this.getResponseProperty("KeyConnectorEnabled");
|
||||
this.keyConnectorUrl = this.getResponseProperty("KeyConnectorUrl");
|
||||
|
||||
this.authority = this.getResponseProperty("Authority");
|
||||
this.clientId = this.getResponseProperty("ClientId");
|
||||
this.clientSecret = this.getResponseProperty("ClientSecret");
|
||||
this.metadataAddress = this.getResponseProperty("MetadataAddress");
|
||||
this.redirectBehavior = this.getResponseProperty("RedirectBehavior");
|
||||
this.getClaimsFromUserInfoEndpoint = this.getResponseProperty("GetClaimsFromUserInfoEndpoint");
|
||||
this.additionalScopes = this.getResponseProperty("AdditionalScopes");
|
||||
this.additionalUserIdClaimTypes = this.getResponseProperty("AdditionalUserIdClaimTypes");
|
||||
this.additionalEmailClaimTypes = this.getResponseProperty("AdditionalEmailClaimTypes");
|
||||
this.additionalNameClaimTypes = this.getResponseProperty("AdditionalNameClaimTypes");
|
||||
this.acrValues = this.getResponseProperty("AcrValues");
|
||||
this.expectedReturnAcrValue = this.getResponseProperty("ExpectedReturnAcrValue");
|
||||
|
||||
this.spNameIdFormat = this.getResponseProperty("SpNameIdFormat");
|
||||
this.spOutboundSigningAlgorithm = this.getResponseProperty("SpOutboundSigningAlgorithm");
|
||||
this.spSigningBehavior = this.getResponseProperty("SpSigningBehavior");
|
||||
this.spMinIncomingSigningAlgorithm = this.getResponseProperty("SpMinIncomingSigningAlgorithm");
|
||||
this.spWantAssertionsSigned = this.getResponseProperty("SpWantAssertionsSigned");
|
||||
this.spValidateCertificates = this.getResponseProperty("SpValidateCertificates");
|
||||
|
||||
this.idpEntityId = this.getResponseProperty("IdpEntityId");
|
||||
this.idpBindingType = this.getResponseProperty("IdpBindingType");
|
||||
this.idpSingleSignOnServiceUrl = this.getResponseProperty("IdpSingleSignOnServiceUrl");
|
||||
this.idpSingleLogoutServiceUrl = this.getResponseProperty("IdpSingleLogoutServiceUrl");
|
||||
this.idpX509PublicCert = this.getResponseProperty("IdpX509PublicCert");
|
||||
this.idpOutboundSigningAlgorithm = this.getResponseProperty("IdpOutboundSigningAlgorithm");
|
||||
this.idpAllowUnsolicitedAuthnResponse = this.getResponseProperty(
|
||||
"IdpAllowUnsolicitedAuthnResponse"
|
||||
);
|
||||
this.idpDisableOutboundLogoutRequests = this.getResponseProperty(
|
||||
"IdpDisableOutboundLogoutRequests"
|
||||
);
|
||||
this.idpWantAuthnRequestsSigned = this.getResponseProperty("IdpWantAuthnRequestsSigned");
|
||||
}
|
||||
}
|
||||
17
libs/common/src/auth/models/domain/auth-result.ts
Normal file
17
libs/common/src/auth/models/domain/auth-result.ts
Normal file
@@ -0,0 +1,17 @@
|
||||
import { Utils } from "../../../misc/utils";
|
||||
import { TwoFactorProviderType } from "../../enums/two-factor-provider-type";
|
||||
|
||||
export class AuthResult {
|
||||
captchaSiteKey = "";
|
||||
resetMasterPassword = false;
|
||||
forcePasswordReset = false;
|
||||
twoFactorProviders: Map<TwoFactorProviderType, { [key: string]: string }> = null;
|
||||
|
||||
get requiresCaptcha() {
|
||||
return !Utils.isNullOrWhitespace(this.captchaSiteKey);
|
||||
}
|
||||
|
||||
get requiresTwoFactor() {
|
||||
return this.twoFactorProviders != null;
|
||||
}
|
||||
}
|
||||
16
libs/common/src/auth/models/domain/environment-urls.ts
Normal file
16
libs/common/src/auth/models/domain/environment-urls.ts
Normal file
@@ -0,0 +1,16 @@
|
||||
import { Jsonify } from "type-fest";
|
||||
|
||||
export class EnvironmentUrls {
|
||||
base: string = null;
|
||||
api: string = null;
|
||||
identity: string = null;
|
||||
icons: string = null;
|
||||
notifications: string = null;
|
||||
events: string = null;
|
||||
webVault: string = null;
|
||||
keyConnector: string = null;
|
||||
|
||||
static fromJSON(obj: Jsonify<EnvironmentUrls>): EnvironmentUrls {
|
||||
return Object.assign(new EnvironmentUrls(), obj);
|
||||
}
|
||||
}
|
||||
11
libs/common/src/auth/models/domain/kdf-config.ts
Normal file
11
libs/common/src/auth/models/domain/kdf-config.ts
Normal file
@@ -0,0 +1,11 @@
|
||||
export class KdfConfig {
|
||||
iterations: number;
|
||||
memory?: number;
|
||||
parallelism?: number;
|
||||
|
||||
constructor(iterations: number, memory?: number, parallelism?: number) {
|
||||
this.iterations = iterations;
|
||||
this.memory = memory;
|
||||
this.parallelism = parallelism;
|
||||
}
|
||||
}
|
||||
45
libs/common/src/auth/models/domain/log-in-credentials.ts
Normal file
45
libs/common/src/auth/models/domain/log-in-credentials.ts
Normal file
@@ -0,0 +1,45 @@
|
||||
import { SymmetricCryptoKey } from "../../../models/domain/symmetric-crypto-key";
|
||||
import { AuthenticationType } from "../../enums/authentication-type";
|
||||
import { TokenTwoFactorRequest } from "../request/identity-token/token-two-factor.request";
|
||||
|
||||
export class PasswordLogInCredentials {
|
||||
readonly type = AuthenticationType.Password;
|
||||
|
||||
constructor(
|
||||
public email: string,
|
||||
public masterPassword: string,
|
||||
public captchaToken?: string,
|
||||
public twoFactor?: TokenTwoFactorRequest
|
||||
) {}
|
||||
}
|
||||
|
||||
export class SsoLogInCredentials {
|
||||
readonly type = AuthenticationType.Sso;
|
||||
|
||||
constructor(
|
||||
public code: string,
|
||||
public codeVerifier: string,
|
||||
public redirectUrl: string,
|
||||
public orgId: string,
|
||||
public twoFactor?: TokenTwoFactorRequest
|
||||
) {}
|
||||
}
|
||||
|
||||
export class UserApiLogInCredentials {
|
||||
readonly type = AuthenticationType.UserApi;
|
||||
|
||||
constructor(public clientId: string, public clientSecret: string) {}
|
||||
}
|
||||
|
||||
export class PasswordlessLogInCredentials {
|
||||
readonly type = AuthenticationType.Passwordless;
|
||||
|
||||
constructor(
|
||||
public email: string,
|
||||
public accessCode: string,
|
||||
public authRequestId: string,
|
||||
public decKey: SymmetricCryptoKey,
|
||||
public localPasswordHash: string,
|
||||
public twoFactor?: TokenTwoFactorRequest
|
||||
) {}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
export abstract class CaptchaProtectedRequest {
|
||||
captchaResponse: string = null;
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
export class DeviceVerificationRequest {
|
||||
unknownDeviceVerificationEnabled: boolean;
|
||||
|
||||
constructor(unknownDeviceVerificationEnabled: boolean) {
|
||||
this.unknownDeviceVerificationEnabled = unknownDeviceVerificationEnabled;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
import { SecretVerificationRequest } from "./secret-verification.request";
|
||||
|
||||
export class EmailTokenRequest extends SecretVerificationRequest {
|
||||
newEmail: string;
|
||||
masterPasswordHash: string;
|
||||
}
|
||||
7
libs/common/src/auth/models/request/email.request.ts
Normal file
7
libs/common/src/auth/models/request/email.request.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
import { EmailTokenRequest } from "./email-token.request";
|
||||
|
||||
export class EmailRequest extends EmailTokenRequest {
|
||||
newMasterPasswordHash: string;
|
||||
token: string;
|
||||
key: string;
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
export class EmergencyAccessAcceptRequest {
|
||||
token: string;
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
export class EmergencyAccessConfirmRequest {
|
||||
key: string;
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
import { EmergencyAccessType } from "../../enums/emergency-access-type";
|
||||
|
||||
export class EmergencyAccessInviteRequest {
|
||||
email: string;
|
||||
type: EmergencyAccessType;
|
||||
waitTimeDays: number;
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
export class EmergencyAccessPasswordRequest {
|
||||
newMasterPasswordHash: string;
|
||||
key: string;
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
import { EmergencyAccessType } from "../../enums/emergency-access-type";
|
||||
|
||||
export class EmergencyAccessUpdateRequest {
|
||||
type: EmergencyAccessType;
|
||||
waitTimeDays: number;
|
||||
keyEncrypted?: string;
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
import { PlatformUtilsService } from "../../../../abstractions/platformUtils.service";
|
||||
import { DeviceType } from "../../../../enums/deviceType";
|
||||
|
||||
export class DeviceRequest {
|
||||
type: DeviceType;
|
||||
name: string;
|
||||
identifier: string;
|
||||
pushToken?: string;
|
||||
|
||||
constructor(appId: string, platformUtilsService: PlatformUtilsService) {
|
||||
this.type = platformUtilsService.getDevice();
|
||||
this.name = platformUtilsService.getDeviceString();
|
||||
this.identifier = appId;
|
||||
this.pushToken = null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
import { ClientType } from "../../../../enums/clientType";
|
||||
import { Utils } from "../../../../misc/utils";
|
||||
import { CaptchaProtectedRequest } from "../captcha-protected.request";
|
||||
|
||||
import { DeviceRequest } from "./device.request";
|
||||
import { TokenTwoFactorRequest } from "./token-two-factor.request";
|
||||
import { TokenRequest } from "./token.request";
|
||||
|
||||
export class PasswordTokenRequest extends TokenRequest implements CaptchaProtectedRequest {
|
||||
constructor(
|
||||
public email: string,
|
||||
public masterPasswordHash: string,
|
||||
public captchaResponse: string,
|
||||
protected twoFactor: TokenTwoFactorRequest,
|
||||
device?: DeviceRequest
|
||||
) {
|
||||
super(twoFactor, device);
|
||||
}
|
||||
|
||||
toIdentityToken(clientId: ClientType) {
|
||||
const obj = super.toIdentityToken(clientId);
|
||||
|
||||
obj.grant_type = "password";
|
||||
obj.username = this.email;
|
||||
obj.password = this.masterPasswordHash;
|
||||
|
||||
if (this.captchaResponse != null) {
|
||||
obj.captchaResponse = this.captchaResponse;
|
||||
}
|
||||
|
||||
return obj;
|
||||
}
|
||||
|
||||
alterIdentityTokenHeaders(headers: Headers) {
|
||||
headers.set("Auth-Email", Utils.fromUtf8ToUrlB64(this.email));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
import { DeviceRequest } from "./device.request";
|
||||
import { TokenTwoFactorRequest } from "./token-two-factor.request";
|
||||
import { TokenRequest } from "./token.request";
|
||||
|
||||
export class SsoTokenRequest extends TokenRequest {
|
||||
constructor(
|
||||
public code: string,
|
||||
public codeVerifier: string,
|
||||
public redirectUri: string,
|
||||
protected twoFactor: TokenTwoFactorRequest,
|
||||
device?: DeviceRequest
|
||||
) {
|
||||
super(twoFactor, device);
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
import { TwoFactorProviderType } from "../../../enums/two-factor-provider-type";
|
||||
|
||||
export class TokenTwoFactorRequest {
|
||||
constructor(
|
||||
public provider: TwoFactorProviderType = null,
|
||||
public token: string = null,
|
||||
public remember: boolean = false
|
||||
) {}
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
import { DeviceRequest } from "./device.request";
|
||||
import { TokenTwoFactorRequest } from "./token-two-factor.request";
|
||||
|
||||
export abstract class TokenRequest {
|
||||
protected device?: DeviceRequest;
|
||||
protected passwordlessAuthRequest: string;
|
||||
|
||||
constructor(protected twoFactor: TokenTwoFactorRequest, device?: DeviceRequest) {
|
||||
this.device = device != null ? device : null;
|
||||
}
|
||||
|
||||
// eslint-disable-next-line
|
||||
alterIdentityTokenHeaders(headers: Headers) {
|
||||
// Implemented in subclass if required
|
||||
}
|
||||
|
||||
setTwoFactor(twoFactor: TokenTwoFactorRequest) {
|
||||
this.twoFactor = twoFactor;
|
||||
}
|
||||
|
||||
setPasswordlessAccessCode(accessCode: string) {
|
||||
this.passwordlessAuthRequest = accessCode;
|
||||
}
|
||||
|
||||
protected 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;
|
||||
}
|
||||
|
||||
//passswordless login
|
||||
if (this.passwordlessAuthRequest) {
|
||||
obj.authRequest = this.passwordlessAuthRequest;
|
||||
}
|
||||
|
||||
if (this.twoFactor) {
|
||||
if (this.twoFactor.token && this.twoFactor.provider != null) {
|
||||
obj.twoFactorToken = this.twoFactor.token;
|
||||
obj.twoFactorProvider = this.twoFactor.provider;
|
||||
obj.twoFactorRemember = this.twoFactor.remember ? "1" : "0";
|
||||
}
|
||||
}
|
||||
|
||||
return obj;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
import { DeviceRequest } from "./device.request";
|
||||
import { TokenTwoFactorRequest } from "./token-two-factor.request";
|
||||
import { TokenRequest } from "./token.request";
|
||||
|
||||
export class UserApiTokenRequest extends TokenRequest {
|
||||
constructor(
|
||||
public clientId: string,
|
||||
public clientSecret: string,
|
||||
protected twoFactor: TokenTwoFactorRequest,
|
||||
device?: DeviceRequest
|
||||
) {
|
||||
super(twoFactor, device);
|
||||
}
|
||||
|
||||
toIdentityToken() {
|
||||
const obj = super.toIdentityToken(this.clientId);
|
||||
|
||||
obj.scope = this.clientId.startsWith("organization") ? "api.organization" : "api";
|
||||
obj.grant_type = "client_credentials";
|
||||
obj.client_secret = this.clientSecret;
|
||||
|
||||
return obj;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
export class KeyConnectorUserKeyRequest {
|
||||
key: string;
|
||||
|
||||
constructor(key: string) {
|
||||
this.key = key;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
import { SsoConfigApi } from "../api/sso-config.api";
|
||||
|
||||
export class OrganizationSsoRequest {
|
||||
enabled = false;
|
||||
identifier: string;
|
||||
data: SsoConfigApi;
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
export class PasswordHintRequest {
|
||||
email: string;
|
||||
|
||||
constructor(email: string) {
|
||||
this.email = email;
|
||||
}
|
||||
}
|
||||
7
libs/common/src/auth/models/request/password.request.ts
Normal file
7
libs/common/src/auth/models/request/password.request.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
import { SecretVerificationRequest } from "./secret-verification.request";
|
||||
|
||||
export class PasswordRequest extends SecretVerificationRequest {
|
||||
newMasterPasswordHash: string;
|
||||
masterPasswordHint: string;
|
||||
key: string;
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
export class PasswordlessAuthRequest {
|
||||
constructor(
|
||||
readonly key: string,
|
||||
readonly masterPasswordHash: string,
|
||||
readonly deviceIdentifier: string,
|
||||
readonly requestApproved: boolean
|
||||
) {}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
import { AuthRequestType } from "../../enums/auth-request-type";
|
||||
|
||||
export class PasswordlessCreateAuthRequest {
|
||||
constructor(
|
||||
readonly email: string,
|
||||
readonly deviceIdentifier: string,
|
||||
readonly publicKey: string,
|
||||
readonly type: AuthRequestType,
|
||||
readonly accessCode: string,
|
||||
readonly fingerprintPhrase: string
|
||||
) {}
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
export class SecretVerificationRequest {
|
||||
masterPasswordHash: string;
|
||||
otp: string;
|
||||
authRequestAccessCode: string;
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
import { KdfType } from "../../../enums/kdfType";
|
||||
import { KeysRequest } from "../../../models/request/keys.request";
|
||||
import { KdfConfig } from "../domain/kdf-config";
|
||||
|
||||
export class SetKeyConnectorKeyRequest {
|
||||
key: string;
|
||||
keys: KeysRequest;
|
||||
kdf: KdfType;
|
||||
kdfIterations: number;
|
||||
kdfMemory?: number;
|
||||
kdfParallelism?: number;
|
||||
orgIdentifier: string;
|
||||
|
||||
constructor(
|
||||
key: string,
|
||||
kdf: KdfType,
|
||||
kdfConfig: KdfConfig,
|
||||
orgIdentifier: string,
|
||||
keys: KeysRequest
|
||||
) {
|
||||
this.key = key;
|
||||
this.kdf = kdf;
|
||||
this.kdfIterations = kdfConfig.iterations;
|
||||
this.kdfMemory = kdfConfig.memory;
|
||||
this.kdfParallelism = kdfConfig.parallelism;
|
||||
this.orgIdentifier = orgIdentifier;
|
||||
this.keys = keys;
|
||||
}
|
||||
}
|
||||
36
libs/common/src/auth/models/request/set-password.request.ts
Normal file
36
libs/common/src/auth/models/request/set-password.request.ts
Normal file
@@ -0,0 +1,36 @@
|
||||
import { KdfType } from "../../../enums/kdfType";
|
||||
import { KeysRequest } from "../../../models/request/keys.request";
|
||||
|
||||
export class SetPasswordRequest {
|
||||
masterPasswordHash: string;
|
||||
key: string;
|
||||
masterPasswordHint: string;
|
||||
keys: KeysRequest;
|
||||
kdf: KdfType;
|
||||
kdfIterations: number;
|
||||
kdfMemory?: number;
|
||||
kdfParallelism?: number;
|
||||
orgIdentifier: string;
|
||||
|
||||
constructor(
|
||||
masterPasswordHash: string,
|
||||
key: string,
|
||||
masterPasswordHint: string,
|
||||
orgIdentifier: string,
|
||||
keys: KeysRequest,
|
||||
kdf: KdfType,
|
||||
kdfIterations: number,
|
||||
kdfMemory?: number,
|
||||
kdfParallelism?: number
|
||||
) {
|
||||
this.masterPasswordHash = masterPasswordHash;
|
||||
this.key = key;
|
||||
this.masterPasswordHint = masterPasswordHint;
|
||||
this.kdf = kdf;
|
||||
this.kdfIterations = kdfIterations;
|
||||
this.kdfMemory = kdfMemory;
|
||||
this.kdfParallelism = kdfParallelism;
|
||||
this.orgIdentifier = orgIdentifier;
|
||||
this.keys = keys;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
import { SecretVerificationRequest } from "./secret-verification.request";
|
||||
|
||||
export class TwoFactorEmailRequest extends SecretVerificationRequest {
|
||||
email: string;
|
||||
deviceIdentifier: string;
|
||||
authRequestId: string;
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
import { TwoFactorProviderType } from "../../enums/two-factor-provider-type";
|
||||
|
||||
import { SecretVerificationRequest } from "./secret-verification.request";
|
||||
|
||||
export class TwoFactorProviderRequest extends SecretVerificationRequest {
|
||||
type: TwoFactorProviderType;
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
import { SecretVerificationRequest } from "./secret-verification.request";
|
||||
|
||||
export class TwoFactorRecoveryRequest extends SecretVerificationRequest {
|
||||
recoveryCode: string;
|
||||
email: string;
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
export class UpdateProfileRequest {
|
||||
name: string;
|
||||
masterPasswordHint: string;
|
||||
culture = "en-US"; // deprecated
|
||||
|
||||
constructor(name: string, masterPasswordHint: string) {
|
||||
this.name = name;
|
||||
this.masterPasswordHint = masterPasswordHint ? masterPasswordHint : null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
import { SecretVerificationRequest } from "./secret-verification.request";
|
||||
|
||||
export class UpdateTwoFactorAuthenticatorRequest extends SecretVerificationRequest {
|
||||
token: string;
|
||||
key: string;
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
import { SecretVerificationRequest } from "./secret-verification.request";
|
||||
|
||||
export class UpdateTwoFactorDuoRequest extends SecretVerificationRequest {
|
||||
integrationKey: string;
|
||||
secretKey: string;
|
||||
host: string;
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
import { SecretVerificationRequest } from "./secret-verification.request";
|
||||
|
||||
export class UpdateTwoFactorEmailRequest extends SecretVerificationRequest {
|
||||
token: string;
|
||||
email: string;
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
import { SecretVerificationRequest } from "./secret-verification.request";
|
||||
|
||||
export class UpdateTwoFactorWebAuthnDeleteRequest extends SecretVerificationRequest {
|
||||
id: number;
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
import { SecretVerificationRequest } from "./secret-verification.request";
|
||||
|
||||
export class UpdateTwoFactorWebAuthnRequest extends SecretVerificationRequest {
|
||||
deviceResponse: PublicKeyCredential;
|
||||
name: string;
|
||||
id: number;
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
import { SecretVerificationRequest } from "./secret-verification.request";
|
||||
|
||||
export class UpdateTwoFactorYubioOtpRequest extends SecretVerificationRequest {
|
||||
key1: string;
|
||||
key2: string;
|
||||
key3: string;
|
||||
key4: string;
|
||||
key5: string;
|
||||
nfc: boolean;
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
export class VerifyOTPRequest {
|
||||
OTP: string;
|
||||
|
||||
constructor(OTP: string) {
|
||||
this.OTP = OTP;
|
||||
}
|
||||
}
|
||||
12
libs/common/src/auth/models/response/api-key.response.ts
Normal file
12
libs/common/src/auth/models/response/api-key.response.ts
Normal file
@@ -0,0 +1,12 @@
|
||||
import { BaseResponse } from "../../../models/response/base.response";
|
||||
|
||||
export class ApiKeyResponse extends BaseResponse {
|
||||
apiKey: string;
|
||||
revisionDate: Date;
|
||||
|
||||
constructor(response: any) {
|
||||
super(response);
|
||||
this.apiKey = this.getResponseProperty("ApiKey");
|
||||
this.revisionDate = new Date(this.getResponseProperty("RevisionDate"));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
import { DeviceType } from "../../../enums/deviceType";
|
||||
import { BaseResponse } from "../../../models/response/base.response";
|
||||
|
||||
const RequestTimeOut = 60000 * 15; //15 Minutes
|
||||
|
||||
export class AuthRequestResponse extends BaseResponse {
|
||||
id: string;
|
||||
publicKey: string;
|
||||
requestDeviceType: DeviceType;
|
||||
requestIpAddress: string;
|
||||
key: string;
|
||||
masterPasswordHash: string;
|
||||
creationDate: string;
|
||||
requestApproved?: boolean;
|
||||
requestFingerprint?: string;
|
||||
responseDate?: string;
|
||||
isAnswered: boolean;
|
||||
isExpired: boolean;
|
||||
|
||||
constructor(response: any) {
|
||||
super(response);
|
||||
this.id = this.getResponseProperty("Id");
|
||||
this.publicKey = this.getResponseProperty("PublicKey");
|
||||
this.requestDeviceType = this.getResponseProperty("RequestDeviceType");
|
||||
this.requestIpAddress = this.getResponseProperty("RequestIpAddress");
|
||||
this.key = this.getResponseProperty("Key");
|
||||
this.masterPasswordHash = this.getResponseProperty("MasterPasswordHash");
|
||||
this.creationDate = this.getResponseProperty("CreationDate");
|
||||
this.requestApproved = this.getResponseProperty("RequestApproved");
|
||||
this.requestFingerprint = this.getResponseProperty("RequestFingerprint");
|
||||
this.responseDate = this.getResponseProperty("ResponseDate");
|
||||
|
||||
const requestDate = new Date(this.creationDate);
|
||||
const requestDateUTC = Date.UTC(
|
||||
requestDate.getUTCFullYear(),
|
||||
requestDate.getUTCMonth(),
|
||||
requestDate.getDate(),
|
||||
requestDate.getUTCHours(),
|
||||
requestDate.getUTCMinutes(),
|
||||
requestDate.getUTCSeconds(),
|
||||
requestDate.getUTCMilliseconds()
|
||||
);
|
||||
|
||||
const dateNow = new Date(Date.now());
|
||||
const dateNowUTC = Date.UTC(
|
||||
dateNow.getUTCFullYear(),
|
||||
dateNow.getUTCMonth(),
|
||||
dateNow.getDate(),
|
||||
dateNow.getUTCHours(),
|
||||
dateNow.getUTCMinutes(),
|
||||
dateNow.getUTCSeconds(),
|
||||
dateNow.getUTCMilliseconds()
|
||||
);
|
||||
|
||||
this.isExpired = dateNowUTC - requestDateUTC >= RequestTimeOut;
|
||||
this.isAnswered = this.requestApproved != null && this.responseDate != null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
export interface ICaptchaProtectedResponse {
|
||||
captchaBypassToken: string;
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
import { BaseResponse } from "../../../models/response/base.response";
|
||||
|
||||
export class DeviceVerificationResponse extends BaseResponse {
|
||||
isDeviceVerificationSectionEnabled: boolean;
|
||||
unknownDeviceVerificationEnabled: boolean;
|
||||
|
||||
constructor(response: any) {
|
||||
super(response);
|
||||
this.isDeviceVerificationSectionEnabled = this.getResponseProperty(
|
||||
"IsDeviceVerificationSectionEnabled"
|
||||
);
|
||||
this.unknownDeviceVerificationEnabled = this.getResponseProperty(
|
||||
"UnknownDeviceVerificationEnabled"
|
||||
);
|
||||
}
|
||||
}
|
||||
19
libs/common/src/auth/models/response/device.response.ts
Normal file
19
libs/common/src/auth/models/response/device.response.ts
Normal file
@@ -0,0 +1,19 @@
|
||||
import { DeviceType } from "../../../enums/deviceType";
|
||||
import { BaseResponse } from "../../../models/response/base.response";
|
||||
|
||||
export class DeviceResponse extends BaseResponse {
|
||||
id: string;
|
||||
name: number;
|
||||
identifier: string;
|
||||
type: DeviceType;
|
||||
creationDate: string;
|
||||
|
||||
constructor(response: any) {
|
||||
super(response);
|
||||
this.id = this.getResponseProperty("Id");
|
||||
this.name = this.getResponseProperty("Name");
|
||||
this.identifier = this.getResponseProperty("Identifier");
|
||||
this.type = this.getResponseProperty("Type");
|
||||
this.creationDate = this.getResponseProperty("CreationDate");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,89 @@
|
||||
import { KdfType } from "../../../enums/kdfType";
|
||||
import { BaseResponse } from "../../../models/response/base.response";
|
||||
import { CipherResponse } from "../../../vault/models/response/cipher.response";
|
||||
import { EmergencyAccessStatusType } from "../../enums/emergency-access-status-type";
|
||||
import { EmergencyAccessType } from "../../enums/emergency-access-type";
|
||||
|
||||
export class EmergencyAccessGranteeDetailsResponse extends BaseResponse {
|
||||
id: string;
|
||||
granteeId: string;
|
||||
name: string;
|
||||
email: string;
|
||||
type: EmergencyAccessType;
|
||||
status: EmergencyAccessStatusType;
|
||||
waitTimeDays: number;
|
||||
creationDate: string;
|
||||
avatarColor: string;
|
||||
|
||||
constructor(response: any) {
|
||||
super(response);
|
||||
this.id = this.getResponseProperty("Id");
|
||||
this.granteeId = this.getResponseProperty("GranteeId");
|
||||
this.name = this.getResponseProperty("Name");
|
||||
this.email = this.getResponseProperty("Email");
|
||||
this.type = this.getResponseProperty("Type");
|
||||
this.status = this.getResponseProperty("Status");
|
||||
this.waitTimeDays = this.getResponseProperty("WaitTimeDays");
|
||||
this.creationDate = this.getResponseProperty("CreationDate");
|
||||
this.avatarColor = this.getResponseProperty("AvatarColor");
|
||||
}
|
||||
}
|
||||
|
||||
export class EmergencyAccessGrantorDetailsResponse extends BaseResponse {
|
||||
id: string;
|
||||
grantorId: string;
|
||||
name: string;
|
||||
email: string;
|
||||
type: EmergencyAccessType;
|
||||
status: EmergencyAccessStatusType;
|
||||
waitTimeDays: number;
|
||||
creationDate: string;
|
||||
avatarColor: string;
|
||||
|
||||
constructor(response: any) {
|
||||
super(response);
|
||||
this.id = this.getResponseProperty("Id");
|
||||
this.grantorId = this.getResponseProperty("GrantorId");
|
||||
this.name = this.getResponseProperty("Name");
|
||||
this.email = this.getResponseProperty("Email");
|
||||
this.type = this.getResponseProperty("Type");
|
||||
this.status = this.getResponseProperty("Status");
|
||||
this.waitTimeDays = this.getResponseProperty("WaitTimeDays");
|
||||
this.creationDate = this.getResponseProperty("CreationDate");
|
||||
this.avatarColor = this.getResponseProperty("AvatarColor");
|
||||
}
|
||||
}
|
||||
|
||||
export class EmergencyAccessTakeoverResponse extends BaseResponse {
|
||||
keyEncrypted: string;
|
||||
kdf: KdfType;
|
||||
kdfIterations: number;
|
||||
kdfMemory?: number;
|
||||
kdfParallelism?: number;
|
||||
|
||||
constructor(response: any) {
|
||||
super(response);
|
||||
|
||||
this.keyEncrypted = this.getResponseProperty("KeyEncrypted");
|
||||
this.kdf = this.getResponseProperty("Kdf");
|
||||
this.kdfIterations = this.getResponseProperty("KdfIterations");
|
||||
this.kdfMemory = this.getResponseProperty("KdfMemory");
|
||||
this.kdfParallelism = this.getResponseProperty("KdfParallelism");
|
||||
}
|
||||
}
|
||||
|
||||
export class EmergencyAccessViewResponse extends BaseResponse {
|
||||
keyEncrypted: string;
|
||||
ciphers: CipherResponse[] = [];
|
||||
|
||||
constructor(response: any) {
|
||||
super(response);
|
||||
|
||||
this.keyEncrypted = this.getResponseProperty("KeyEncrypted");
|
||||
|
||||
const ciphers = this.getResponseProperty("Ciphers");
|
||||
if (ciphers != null) {
|
||||
this.ciphers = ciphers.map((c: any) => new CipherResponse(c));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
import { BaseResponse } from "../../../models/response/base.response";
|
||||
|
||||
export class IdentityCaptchaResponse extends BaseResponse {
|
||||
siteKey: string;
|
||||
|
||||
constructor(response: any) {
|
||||
super(response);
|
||||
this.siteKey = this.getResponseProperty("HCaptcha_SiteKey");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
import { KdfType } from "../../../enums/kdfType";
|
||||
import { BaseResponse } from "../../../models/response/base.response";
|
||||
|
||||
export class IdentityTokenResponse extends BaseResponse {
|
||||
accessToken: string;
|
||||
expiresIn: number;
|
||||
refreshToken: string;
|
||||
tokenType: string;
|
||||
|
||||
resetMasterPassword: boolean;
|
||||
privateKey: string;
|
||||
key: string;
|
||||
twoFactorToken: string;
|
||||
kdf: KdfType;
|
||||
kdfIterations: number;
|
||||
kdfMemory?: number;
|
||||
kdfParallelism?: number;
|
||||
forcePasswordReset: boolean;
|
||||
apiUseKeyConnector: boolean;
|
||||
keyConnectorUrl: string;
|
||||
|
||||
constructor(response: any) {
|
||||
super(response);
|
||||
this.accessToken = response.access_token;
|
||||
this.expiresIn = response.expires_in;
|
||||
this.refreshToken = response.refresh_token;
|
||||
this.tokenType = response.token_type;
|
||||
|
||||
this.resetMasterPassword = this.getResponseProperty("ResetMasterPassword");
|
||||
this.privateKey = this.getResponseProperty("PrivateKey");
|
||||
this.key = this.getResponseProperty("Key");
|
||||
this.twoFactorToken = this.getResponseProperty("TwoFactorToken");
|
||||
this.kdf = this.getResponseProperty("Kdf");
|
||||
this.kdfIterations = this.getResponseProperty("KdfIterations");
|
||||
this.kdfMemory = this.getResponseProperty("KdfMemory");
|
||||
this.kdfParallelism = this.getResponseProperty("KdfParallelism");
|
||||
this.forcePasswordReset = this.getResponseProperty("ForcePasswordReset");
|
||||
this.apiUseKeyConnector = this.getResponseProperty("ApiUseKeyConnector");
|
||||
this.keyConnectorUrl = this.getResponseProperty("KeyConnectorUrl");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
import { BaseResponse } from "../../../models/response/base.response";
|
||||
import { TwoFactorProviderType } from "../../enums/two-factor-provider-type";
|
||||
|
||||
export class IdentityTwoFactorResponse extends BaseResponse {
|
||||
twoFactorProviders: TwoFactorProviderType[];
|
||||
twoFactorProviders2 = new Map<TwoFactorProviderType, { [key: string]: string }>();
|
||||
captchaToken: string;
|
||||
|
||||
constructor(response: any) {
|
||||
super(response);
|
||||
this.captchaToken = this.getResponseProperty("CaptchaBypassToken");
|
||||
this.twoFactorProviders = this.getResponseProperty("TwoFactorProviders");
|
||||
const twoFactorProviders2 = this.getResponseProperty("TwoFactorProviders2");
|
||||
if (twoFactorProviders2 != null) {
|
||||
for (const prop in twoFactorProviders2) {
|
||||
// eslint-disable-next-line
|
||||
if (twoFactorProviders2.hasOwnProperty(prop)) {
|
||||
this.twoFactorProviders2.set(parseInt(prop, null), twoFactorProviders2[prop]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
import { BaseResponse } from "../../../models/response/base.response";
|
||||
|
||||
export class KeyConnectorUserKeyResponse extends BaseResponse {
|
||||
key: string;
|
||||
|
||||
constructor(response: any) {
|
||||
super(response);
|
||||
this.key = this.getResponseProperty("Key");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
import { BaseResponse } from "../../../models/response/base.response";
|
||||
import { SsoConfigApi } from "../api/sso-config.api";
|
||||
|
||||
export class OrganizationSsoResponse extends BaseResponse {
|
||||
enabled: boolean;
|
||||
identifier: string;
|
||||
data: SsoConfigApi;
|
||||
urls: SsoUrls;
|
||||
|
||||
constructor(response: any) {
|
||||
super(response);
|
||||
this.enabled = this.getResponseProperty("Enabled");
|
||||
this.identifier = this.getResponseProperty("Identifier");
|
||||
this.data =
|
||||
this.getResponseProperty("Data") != null
|
||||
? new SsoConfigApi(this.getResponseProperty("Data"))
|
||||
: null;
|
||||
this.urls = new SsoUrls(this.getResponseProperty("Urls"));
|
||||
}
|
||||
}
|
||||
|
||||
class SsoUrls extends BaseResponse {
|
||||
callbackPath: string;
|
||||
signedOutCallbackPath: string;
|
||||
spEntityId: string;
|
||||
spMetadataUrl: string;
|
||||
spAcsUrl: string;
|
||||
|
||||
constructor(response: any) {
|
||||
super(response);
|
||||
this.callbackPath = this.getResponseProperty("CallbackPath");
|
||||
this.signedOutCallbackPath = this.getResponseProperty("SignedOutCallbackPath");
|
||||
this.spEntityId = this.getResponseProperty("SpEntityId");
|
||||
this.spMetadataUrl = this.getResponseProperty("SpMetadataUrl");
|
||||
this.spAcsUrl = this.getResponseProperty("SpAcsUrl");
|
||||
}
|
||||
}
|
||||
17
libs/common/src/auth/models/response/prelogin.response.ts
Normal file
17
libs/common/src/auth/models/response/prelogin.response.ts
Normal file
@@ -0,0 +1,17 @@
|
||||
import { KdfType } from "../../../enums/kdfType";
|
||||
import { BaseResponse } from "../../../models/response/base.response";
|
||||
|
||||
export class PreloginResponse extends BaseResponse {
|
||||
kdf: KdfType;
|
||||
kdfIterations: number;
|
||||
kdfMemory?: number;
|
||||
kdfParallelism?: number;
|
||||
|
||||
constructor(response: any) {
|
||||
super(response);
|
||||
this.kdf = this.getResponseProperty("Kdf");
|
||||
this.kdfIterations = this.getResponseProperty("KdfIterations");
|
||||
this.kdfMemory = this.getResponseProperty("KdfMemory");
|
||||
this.kdfParallelism = this.getResponseProperty("KdfParallelism");
|
||||
}
|
||||
}
|
||||
12
libs/common/src/auth/models/response/register.response.ts
Normal file
12
libs/common/src/auth/models/response/register.response.ts
Normal file
@@ -0,0 +1,12 @@
|
||||
import { BaseResponse } from "../../../models/response/base.response";
|
||||
|
||||
import { ICaptchaProtectedResponse } from "./captcha-protected.response";
|
||||
|
||||
export class RegisterResponse extends BaseResponse implements ICaptchaProtectedResponse {
|
||||
captchaBypassToken: string;
|
||||
|
||||
constructor(response: any) {
|
||||
super(response);
|
||||
this.captchaBypassToken = this.getResponseProperty("CaptchaBypassToken");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
import { BaseResponse } from "../../../models/response/base.response";
|
||||
|
||||
export class SsoPreValidateResponse extends BaseResponse {
|
||||
token: string;
|
||||
|
||||
constructor(response: any) {
|
||||
super(response);
|
||||
this.token = this.getResponseProperty("Token");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
import { BaseResponse } from "../../../models/response/base.response";
|
||||
|
||||
export class TwoFactorAuthenticatorResponse extends BaseResponse {
|
||||
enabled: boolean;
|
||||
key: string;
|
||||
|
||||
constructor(response: any) {
|
||||
super(response);
|
||||
this.enabled = this.getResponseProperty("Enabled");
|
||||
this.key = this.getResponseProperty("Key");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
import { BaseResponse } from "../../../models/response/base.response";
|
||||
|
||||
export class TwoFactorDuoResponse extends BaseResponse {
|
||||
enabled: boolean;
|
||||
host: string;
|
||||
secretKey: string;
|
||||
integrationKey: string;
|
||||
|
||||
constructor(response: any) {
|
||||
super(response);
|
||||
this.enabled = this.getResponseProperty("Enabled");
|
||||
this.host = this.getResponseProperty("Host");
|
||||
this.secretKey = this.getResponseProperty("SecretKey");
|
||||
this.integrationKey = this.getResponseProperty("IntegrationKey");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
import { BaseResponse } from "../../../models/response/base.response";
|
||||
|
||||
export class TwoFactorEmailResponse extends BaseResponse {
|
||||
enabled: boolean;
|
||||
email: string;
|
||||
|
||||
constructor(response: any) {
|
||||
super(response);
|
||||
this.enabled = this.getResponseProperty("Enabled");
|
||||
this.email = this.getResponseProperty("Email");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
import { BaseResponse } from "../../../models/response/base.response";
|
||||
import { TwoFactorProviderType } from "../../enums/two-factor-provider-type";
|
||||
|
||||
export class TwoFactorProviderResponse extends BaseResponse {
|
||||
enabled: boolean;
|
||||
type: TwoFactorProviderType;
|
||||
|
||||
constructor(response: any) {
|
||||
super(response);
|
||||
this.enabled = this.getResponseProperty("Enabled");
|
||||
this.type = this.getResponseProperty("Type");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
import { BaseResponse } from "../../../models/response/base.response";
|
||||
|
||||
export class TwoFactorRecoverResponse extends BaseResponse {
|
||||
code: string;
|
||||
|
||||
constructor(response: any) {
|
||||
super(response);
|
||||
this.code = this.getResponseProperty("Code");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
import { Utils } from "../../../misc/utils";
|
||||
import { BaseResponse } from "../../../models/response/base.response";
|
||||
|
||||
export class TwoFactorWebAuthnResponse extends BaseResponse {
|
||||
enabled: boolean;
|
||||
keys: KeyResponse[];
|
||||
|
||||
constructor(response: any) {
|
||||
super(response);
|
||||
this.enabled = this.getResponseProperty("Enabled");
|
||||
const keys = this.getResponseProperty("Keys");
|
||||
this.keys = keys == null ? null : keys.map((k: any) => new KeyResponse(k));
|
||||
}
|
||||
}
|
||||
|
||||
export class KeyResponse extends BaseResponse {
|
||||
name: string;
|
||||
id: number;
|
||||
migrated: boolean;
|
||||
|
||||
constructor(response: any) {
|
||||
super(response);
|
||||
this.name = this.getResponseProperty("Name");
|
||||
this.id = this.getResponseProperty("Id");
|
||||
this.migrated = this.getResponseProperty("Migrated");
|
||||
}
|
||||
}
|
||||
|
||||
export class ChallengeResponse extends BaseResponse implements PublicKeyCredentialCreationOptions {
|
||||
attestation?: AttestationConveyancePreference;
|
||||
authenticatorSelection?: AuthenticatorSelectionCriteria;
|
||||
challenge: BufferSource;
|
||||
excludeCredentials?: PublicKeyCredentialDescriptor[];
|
||||
extensions?: AuthenticationExtensionsClientInputs;
|
||||
pubKeyCredParams: PublicKeyCredentialParameters[];
|
||||
rp: PublicKeyCredentialRpEntity;
|
||||
timeout?: number;
|
||||
user: PublicKeyCredentialUserEntity;
|
||||
|
||||
constructor(response: any) {
|
||||
super(response);
|
||||
this.attestation = this.getResponseProperty("attestation");
|
||||
this.authenticatorSelection = this.getResponseProperty("authenticatorSelection");
|
||||
this.challenge = Utils.fromUrlB64ToArray(this.getResponseProperty("challenge"));
|
||||
this.excludeCredentials = this.getResponseProperty("excludeCredentials").map((c: any) => {
|
||||
c.id = Utils.fromUrlB64ToArray(c.id).buffer;
|
||||
return c;
|
||||
});
|
||||
this.extensions = this.getResponseProperty("extensions");
|
||||
this.pubKeyCredParams = this.getResponseProperty("pubKeyCredParams");
|
||||
this.rp = this.getResponseProperty("rp");
|
||||
this.timeout = this.getResponseProperty("timeout");
|
||||
|
||||
const user = this.getResponseProperty("user");
|
||||
user.id = Utils.fromUrlB64ToArray(user.id);
|
||||
|
||||
this.user = user;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
import { BaseResponse } from "../../../models/response/base.response";
|
||||
|
||||
export class TwoFactorYubiKeyResponse extends BaseResponse {
|
||||
enabled: boolean;
|
||||
key1: string;
|
||||
key2: string;
|
||||
key3: string;
|
||||
key4: string;
|
||||
key5: string;
|
||||
nfc: boolean;
|
||||
|
||||
constructor(response: any) {
|
||||
super(response);
|
||||
this.enabled = this.getResponseProperty("Enabled");
|
||||
this.key1 = this.getResponseProperty("Key1");
|
||||
this.key2 = this.getResponseProperty("Key2");
|
||||
this.key3 = this.getResponseProperty("Key3");
|
||||
this.key4 = this.getResponseProperty("Key4");
|
||||
this.key5 = this.getResponseProperty("Key5");
|
||||
this.nfc = this.getResponseProperty("Nfc");
|
||||
}
|
||||
}
|
||||
103
libs/common/src/auth/models/view/sso-config.view.ts
Normal file
103
libs/common/src/auth/models/view/sso-config.view.ts
Normal file
@@ -0,0 +1,103 @@
|
||||
import { View } from "../../../models/view/view";
|
||||
import {
|
||||
OpenIdConnectRedirectBehavior,
|
||||
Saml2BindingType,
|
||||
Saml2NameIdFormat,
|
||||
Saml2SigningBehavior,
|
||||
SsoType,
|
||||
} from "../../enums/sso";
|
||||
import { SsoConfigApi } from "../api/sso-config.api";
|
||||
|
||||
export class SsoConfigView extends View {
|
||||
configType: SsoType;
|
||||
|
||||
keyConnectorEnabled: boolean;
|
||||
keyConnectorUrl: string;
|
||||
|
||||
openId: {
|
||||
authority: string;
|
||||
clientId: string;
|
||||
clientSecret: string;
|
||||
metadataAddress: string;
|
||||
redirectBehavior: OpenIdConnectRedirectBehavior;
|
||||
getClaimsFromUserInfoEndpoint: boolean;
|
||||
additionalScopes: string;
|
||||
additionalUserIdClaimTypes: string;
|
||||
additionalEmailClaimTypes: string;
|
||||
additionalNameClaimTypes: string;
|
||||
acrValues: string;
|
||||
expectedReturnAcrValue: string;
|
||||
};
|
||||
|
||||
saml: {
|
||||
spNameIdFormat: Saml2NameIdFormat;
|
||||
spOutboundSigningAlgorithm: string;
|
||||
spSigningBehavior: Saml2SigningBehavior;
|
||||
spMinIncomingSigningAlgorithm: string;
|
||||
spWantAssertionsSigned: boolean;
|
||||
spValidateCertificates: boolean;
|
||||
|
||||
idpEntityId: string;
|
||||
idpBindingType: Saml2BindingType;
|
||||
idpSingleSignOnServiceUrl: string;
|
||||
idpSingleLogoutServiceUrl: string;
|
||||
idpX509PublicCert: string;
|
||||
idpOutboundSigningAlgorithm: string;
|
||||
idpAllowUnsolicitedAuthnResponse: boolean;
|
||||
idpAllowOutboundLogoutRequests: boolean;
|
||||
idpWantAuthnRequestsSigned: boolean;
|
||||
};
|
||||
|
||||
constructor(api: SsoConfigApi) {
|
||||
super();
|
||||
if (api == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.configType = api.configType;
|
||||
|
||||
this.keyConnectorEnabled = api.keyConnectorEnabled;
|
||||
this.keyConnectorUrl = api.keyConnectorUrl;
|
||||
|
||||
if (this.configType === SsoType.OpenIdConnect) {
|
||||
this.openId = {
|
||||
authority: api.authority,
|
||||
clientId: api.clientId,
|
||||
clientSecret: api.clientSecret,
|
||||
metadataAddress: api.metadataAddress,
|
||||
redirectBehavior: api.redirectBehavior,
|
||||
getClaimsFromUserInfoEndpoint: api.getClaimsFromUserInfoEndpoint,
|
||||
additionalScopes: api.additionalScopes,
|
||||
additionalUserIdClaimTypes: api.additionalUserIdClaimTypes,
|
||||
additionalEmailClaimTypes: api.additionalEmailClaimTypes,
|
||||
additionalNameClaimTypes: api.additionalNameClaimTypes,
|
||||
acrValues: api.acrValues,
|
||||
expectedReturnAcrValue: api.expectedReturnAcrValue,
|
||||
};
|
||||
} else if (this.configType === SsoType.Saml2) {
|
||||
this.saml = {
|
||||
spNameIdFormat: api.spNameIdFormat,
|
||||
spOutboundSigningAlgorithm: api.spOutboundSigningAlgorithm,
|
||||
spSigningBehavior: api.spSigningBehavior,
|
||||
spMinIncomingSigningAlgorithm: api.spMinIncomingSigningAlgorithm,
|
||||
spWantAssertionsSigned: api.spWantAssertionsSigned,
|
||||
spValidateCertificates: api.spValidateCertificates,
|
||||
|
||||
idpEntityId: api.idpEntityId,
|
||||
idpBindingType: api.idpBindingType,
|
||||
idpSingleSignOnServiceUrl: api.idpSingleSignOnServiceUrl,
|
||||
idpSingleLogoutServiceUrl: api.idpSingleLogoutServiceUrl,
|
||||
idpX509PublicCert: api.idpX509PublicCert,
|
||||
idpOutboundSigningAlgorithm: api.idpOutboundSigningAlgorithm,
|
||||
idpAllowUnsolicitedAuthnResponse: api.idpAllowUnsolicitedAuthnResponse,
|
||||
idpWantAuthnRequestsSigned: api.idpWantAuthnRequestsSigned,
|
||||
|
||||
// Value is inverted in the view model (allow instead of disable)
|
||||
idpAllowOutboundLogoutRequests:
|
||||
api.idpDisableOutboundLogoutRequests == null
|
||||
? null
|
||||
: !api.idpDisableOutboundLogoutRequests,
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
26
libs/common/src/auth/services/account-api.service.ts
Normal file
26
libs/common/src/auth/services/account-api.service.ts
Normal file
@@ -0,0 +1,26 @@
|
||||
import { ApiService } from "../../abstractions/api.service";
|
||||
import { LogService } from "../../abstractions/log.service";
|
||||
import { UserVerificationService } from "../../abstractions/userVerification/userVerification.service.abstraction";
|
||||
import { Verification } from "../../types/verification";
|
||||
import { AccountApiService } from "../abstractions/account-api.service";
|
||||
import { InternalAccountService } from "../abstractions/account.service";
|
||||
|
||||
export class AccountApiServiceImplementation implements AccountApiService {
|
||||
constructor(
|
||||
private apiService: ApiService,
|
||||
private userVerificationService: UserVerificationService,
|
||||
private logService: LogService,
|
||||
private accountService: InternalAccountService
|
||||
) {}
|
||||
|
||||
async deleteAccount(verification: Verification): Promise<void> {
|
||||
try {
|
||||
const verificationRequest = await this.userVerificationService.buildRequest(verification);
|
||||
await this.apiService.send("DELETE", "/accounts", verificationRequest, true, false);
|
||||
this.accountService.delete();
|
||||
} catch (e) {
|
||||
this.logService.error(e);
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
}
|
||||
16
libs/common/src/auth/services/account.service.ts
Normal file
16
libs/common/src/auth/services/account.service.ts
Normal file
@@ -0,0 +1,16 @@
|
||||
import { LogService } from "../../abstractions/log.service";
|
||||
import { MessagingService } from "../../abstractions/messaging.service";
|
||||
import { InternalAccountService } from "../../auth/abstractions/account.service";
|
||||
|
||||
export class AccountServiceImplementation implements InternalAccountService {
|
||||
constructor(private messagingService: MessagingService, private logService: LogService) {}
|
||||
|
||||
async delete(): Promise<void> {
|
||||
try {
|
||||
this.messagingService.send("logout");
|
||||
} catch (e) {
|
||||
this.logService.error(e);
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
}
|
||||
334
libs/common/src/auth/services/auth.service.ts
Normal file
334
libs/common/src/auth/services/auth.service.ts
Normal file
@@ -0,0 +1,334 @@
|
||||
import { Observable, Subject } from "rxjs";
|
||||
|
||||
import { ApiService } from "../../abstractions/api.service";
|
||||
import { AppIdService } from "../../abstractions/appId.service";
|
||||
import { CryptoService } from "../../abstractions/crypto.service";
|
||||
import { EncryptService } from "../../abstractions/encrypt.service";
|
||||
import { EnvironmentService } from "../../abstractions/environment.service";
|
||||
import { I18nService } from "../../abstractions/i18n.service";
|
||||
import { LogService } from "../../abstractions/log.service";
|
||||
import { MessagingService } from "../../abstractions/messaging.service";
|
||||
import { PlatformUtilsService } from "../../abstractions/platformUtils.service";
|
||||
import { StateService } from "../../abstractions/state.service";
|
||||
import { KdfType } from "../../enums/kdfType";
|
||||
import { KeySuffixOptions } from "../../enums/keySuffixOptions";
|
||||
import { Utils } from "../../misc/utils";
|
||||
import { SymmetricCryptoKey } from "../../models/domain/symmetric-crypto-key";
|
||||
import { PreloginRequest } from "../../models/request/prelogin.request";
|
||||
import { ErrorResponse } from "../../models/response/error.response";
|
||||
import { AuthRequestPushNotification } from "../../models/response/notification.response";
|
||||
import { AuthService as AuthServiceAbstraction } from "../abstractions/auth.service";
|
||||
import { KeyConnectorService } from "../abstractions/key-connector.service";
|
||||
import { TokenService } from "../abstractions/token.service";
|
||||
import { TwoFactorService } from "../abstractions/two-factor.service";
|
||||
import { AuthenticationStatus } from "../enums/authentication-status";
|
||||
import { AuthenticationType } from "../enums/authentication-type";
|
||||
import { PasswordLogInStrategy } from "../login-strategies/password-login.strategy";
|
||||
import { PasswordlessLogInStrategy } from "../login-strategies/passwordless-login.strategy";
|
||||
import { SsoLogInStrategy } from "../login-strategies/sso-login.strategy";
|
||||
import { UserApiLogInStrategy } from "../login-strategies/user-api-login.strategy";
|
||||
import { AuthResult } from "../models/domain/auth-result";
|
||||
import { KdfConfig } from "../models/domain/kdf-config";
|
||||
import {
|
||||
UserApiLogInCredentials,
|
||||
PasswordLogInCredentials,
|
||||
SsoLogInCredentials,
|
||||
PasswordlessLogInCredentials,
|
||||
} from "../models/domain/log-in-credentials";
|
||||
import { TokenTwoFactorRequest } from "../models/request/identity-token/token-two-factor.request";
|
||||
import { PasswordlessAuthRequest } from "../models/request/passwordless-auth.request";
|
||||
import { AuthRequestResponse } from "../models/response/auth-request.response";
|
||||
|
||||
const sessionTimeoutLength = 2 * 60 * 1000; // 2 minutes
|
||||
|
||||
export class AuthService implements AuthServiceAbstraction {
|
||||
get email(): string {
|
||||
if (
|
||||
this.logInStrategy instanceof PasswordLogInStrategy ||
|
||||
this.logInStrategy instanceof PasswordlessLogInStrategy
|
||||
) {
|
||||
return this.logInStrategy.email;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
get masterPasswordHash(): string {
|
||||
return this.logInStrategy instanceof PasswordLogInStrategy
|
||||
? this.logInStrategy.masterPasswordHash
|
||||
: null;
|
||||
}
|
||||
|
||||
get accessCode(): string {
|
||||
return this.logInStrategy instanceof PasswordlessLogInStrategy
|
||||
? this.logInStrategy.accessCode
|
||||
: null;
|
||||
}
|
||||
|
||||
get authRequestId(): string {
|
||||
return this.logInStrategy instanceof PasswordlessLogInStrategy
|
||||
? this.logInStrategy.authRequestId
|
||||
: null;
|
||||
}
|
||||
|
||||
private logInStrategy:
|
||||
| UserApiLogInStrategy
|
||||
| PasswordLogInStrategy
|
||||
| SsoLogInStrategy
|
||||
| PasswordlessLogInStrategy;
|
||||
private sessionTimeout: any;
|
||||
|
||||
private pushNotificationSubject = new Subject<string>();
|
||||
|
||||
constructor(
|
||||
protected cryptoService: CryptoService,
|
||||
protected apiService: ApiService,
|
||||
protected tokenService: TokenService,
|
||||
protected appIdService: AppIdService,
|
||||
protected platformUtilsService: PlatformUtilsService,
|
||||
protected messagingService: MessagingService,
|
||||
protected logService: LogService,
|
||||
protected keyConnectorService: KeyConnectorService,
|
||||
protected environmentService: EnvironmentService,
|
||||
protected stateService: StateService,
|
||||
protected twoFactorService: TwoFactorService,
|
||||
protected i18nService: I18nService,
|
||||
protected encryptService: EncryptService
|
||||
) {}
|
||||
|
||||
async logIn(
|
||||
credentials:
|
||||
| UserApiLogInCredentials
|
||||
| PasswordLogInCredentials
|
||||
| SsoLogInCredentials
|
||||
| PasswordlessLogInCredentials
|
||||
): Promise<AuthResult> {
|
||||
this.clearState();
|
||||
|
||||
let strategy:
|
||||
| UserApiLogInStrategy
|
||||
| PasswordLogInStrategy
|
||||
| SsoLogInStrategy
|
||||
| PasswordlessLogInStrategy;
|
||||
|
||||
switch (credentials.type) {
|
||||
case AuthenticationType.Password:
|
||||
strategy = new PasswordLogInStrategy(
|
||||
this.cryptoService,
|
||||
this.apiService,
|
||||
this.tokenService,
|
||||
this.appIdService,
|
||||
this.platformUtilsService,
|
||||
this.messagingService,
|
||||
this.logService,
|
||||
this.stateService,
|
||||
this.twoFactorService,
|
||||
this
|
||||
);
|
||||
break;
|
||||
case AuthenticationType.Sso:
|
||||
strategy = new SsoLogInStrategy(
|
||||
this.cryptoService,
|
||||
this.apiService,
|
||||
this.tokenService,
|
||||
this.appIdService,
|
||||
this.platformUtilsService,
|
||||
this.messagingService,
|
||||
this.logService,
|
||||
this.stateService,
|
||||
this.twoFactorService,
|
||||
this.keyConnectorService
|
||||
);
|
||||
break;
|
||||
case AuthenticationType.UserApi:
|
||||
strategy = new UserApiLogInStrategy(
|
||||
this.cryptoService,
|
||||
this.apiService,
|
||||
this.tokenService,
|
||||
this.appIdService,
|
||||
this.platformUtilsService,
|
||||
this.messagingService,
|
||||
this.logService,
|
||||
this.stateService,
|
||||
this.twoFactorService,
|
||||
this.environmentService,
|
||||
this.keyConnectorService
|
||||
);
|
||||
break;
|
||||
case AuthenticationType.Passwordless:
|
||||
strategy = new PasswordlessLogInStrategy(
|
||||
this.cryptoService,
|
||||
this.apiService,
|
||||
this.tokenService,
|
||||
this.appIdService,
|
||||
this.platformUtilsService,
|
||||
this.messagingService,
|
||||
this.logService,
|
||||
this.stateService,
|
||||
this.twoFactorService,
|
||||
this
|
||||
);
|
||||
break;
|
||||
}
|
||||
|
||||
const result = await strategy.logIn(credentials as any);
|
||||
|
||||
if (result?.requiresTwoFactor) {
|
||||
this.saveState(strategy);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
async logInTwoFactor(
|
||||
twoFactor: TokenTwoFactorRequest,
|
||||
captchaResponse: string
|
||||
): Promise<AuthResult> {
|
||||
if (this.logInStrategy == null) {
|
||||
throw new Error(this.i18nService.t("sessionTimeout"));
|
||||
}
|
||||
|
||||
try {
|
||||
const result = await this.logInStrategy.logInTwoFactor(twoFactor, captchaResponse);
|
||||
|
||||
// Only clear state if 2FA token has been accepted, otherwise we need to be able to try again
|
||||
if (!result.requiresTwoFactor && !result.requiresCaptcha) {
|
||||
this.clearState();
|
||||
}
|
||||
return result;
|
||||
} catch (e) {
|
||||
// API exceptions are okay, but if there are any unhandled client-side errors then clear state to be safe
|
||||
if (!(e instanceof ErrorResponse)) {
|
||||
this.clearState();
|
||||
}
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
logOut(callback: () => void) {
|
||||
callback();
|
||||
this.messagingService.send("loggedOut");
|
||||
}
|
||||
|
||||
authingWithUserApiKey(): boolean {
|
||||
return this.logInStrategy instanceof UserApiLogInStrategy;
|
||||
}
|
||||
|
||||
authingWithSso(): boolean {
|
||||
return this.logInStrategy instanceof SsoLogInStrategy;
|
||||
}
|
||||
|
||||
authingWithPassword(): boolean {
|
||||
return this.logInStrategy instanceof PasswordLogInStrategy;
|
||||
}
|
||||
|
||||
authingWithPasswordless(): boolean {
|
||||
return this.logInStrategy instanceof PasswordlessLogInStrategy;
|
||||
}
|
||||
|
||||
async getAuthStatus(userId?: string): Promise<AuthenticationStatus> {
|
||||
const isAuthenticated = await this.stateService.getIsAuthenticated({ userId: userId });
|
||||
if (!isAuthenticated) {
|
||||
return AuthenticationStatus.LoggedOut;
|
||||
}
|
||||
|
||||
// Keys aren't stored for a device that is locked or logged out
|
||||
// Make sure we're logged in before checking this, otherwise we could mix up those states
|
||||
const neverLock =
|
||||
(await this.cryptoService.hasKeyStored(KeySuffixOptions.Auto, userId)) &&
|
||||
!(await this.stateService.getEverBeenUnlocked({ userId: userId }));
|
||||
if (neverLock) {
|
||||
// TODO: This also _sets_ the key so when we check memory in the next line it finds a key.
|
||||
// We should refactor here.
|
||||
await this.cryptoService.getKey(KeySuffixOptions.Auto, userId);
|
||||
}
|
||||
|
||||
const hasKeyInMemory = await this.cryptoService.hasKeyInMemory(userId);
|
||||
if (!hasKeyInMemory) {
|
||||
return AuthenticationStatus.Locked;
|
||||
}
|
||||
|
||||
return AuthenticationStatus.Unlocked;
|
||||
}
|
||||
|
||||
async makePreloginKey(masterPassword: string, email: string): Promise<SymmetricCryptoKey> {
|
||||
email = email.trim().toLowerCase();
|
||||
let kdf: KdfType = null;
|
||||
let kdfConfig: KdfConfig = null;
|
||||
try {
|
||||
const preloginResponse = await this.apiService.postPrelogin(new PreloginRequest(email));
|
||||
if (preloginResponse != null) {
|
||||
kdf = preloginResponse.kdf;
|
||||
kdfConfig = new KdfConfig(
|
||||
preloginResponse.kdfIterations,
|
||||
preloginResponse.kdfMemory,
|
||||
preloginResponse.kdfParallelism
|
||||
);
|
||||
}
|
||||
} catch (e) {
|
||||
if (e == null || e.statusCode !== 404) {
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
return this.cryptoService.makeKey(masterPassword, email, kdf, kdfConfig);
|
||||
}
|
||||
|
||||
async authResponsePushNotifiction(notification: AuthRequestPushNotification): Promise<any> {
|
||||
this.pushNotificationSubject.next(notification.id);
|
||||
}
|
||||
|
||||
getPushNotifcationObs$(): Observable<any> {
|
||||
return this.pushNotificationSubject.asObservable();
|
||||
}
|
||||
|
||||
async passwordlessLogin(
|
||||
id: string,
|
||||
key: string,
|
||||
requestApproved: boolean
|
||||
): Promise<AuthRequestResponse> {
|
||||
const pubKey = Utils.fromB64ToArray(key);
|
||||
const encryptedKey = await this.cryptoService.rsaEncrypt(
|
||||
(
|
||||
await this.cryptoService.getKey()
|
||||
).encKey,
|
||||
pubKey.buffer
|
||||
);
|
||||
const encryptedMasterPassword = await this.cryptoService.rsaEncrypt(
|
||||
Utils.fromUtf8ToArray(await this.stateService.getKeyHash()),
|
||||
pubKey.buffer
|
||||
);
|
||||
const request = new PasswordlessAuthRequest(
|
||||
encryptedKey.encryptedString,
|
||||
encryptedMasterPassword.encryptedString,
|
||||
await this.appIdService.getAppId(),
|
||||
requestApproved
|
||||
);
|
||||
return await this.apiService.putAuthRequest(id, request);
|
||||
}
|
||||
|
||||
private saveState(
|
||||
strategy:
|
||||
| UserApiLogInStrategy
|
||||
| PasswordLogInStrategy
|
||||
| SsoLogInStrategy
|
||||
| PasswordlessLogInStrategy
|
||||
) {
|
||||
this.logInStrategy = strategy;
|
||||
this.startSessionTimeout();
|
||||
}
|
||||
|
||||
private clearState() {
|
||||
this.logInStrategy = null;
|
||||
this.clearSessionTimeout();
|
||||
}
|
||||
|
||||
private startSessionTimeout() {
|
||||
this.clearSessionTimeout();
|
||||
this.sessionTimeout = setTimeout(() => this.clearState(), sessionTimeoutLength);
|
||||
}
|
||||
|
||||
private clearSessionTimeout() {
|
||||
if (this.sessionTimeout != null) {
|
||||
clearTimeout(this.sessionTimeout);
|
||||
}
|
||||
}
|
||||
}
|
||||
144
libs/common/src/auth/services/key-connector.service.ts
Normal file
144
libs/common/src/auth/services/key-connector.service.ts
Normal file
@@ -0,0 +1,144 @@
|
||||
import { ApiService } from "../../abstractions/api.service";
|
||||
import { CryptoService } from "../../abstractions/crypto.service";
|
||||
import { CryptoFunctionService } from "../../abstractions/cryptoFunction.service";
|
||||
import { LogService } from "../../abstractions/log.service";
|
||||
import { OrganizationService } from "../../abstractions/organization/organization.service.abstraction";
|
||||
import { StateService } from "../../abstractions/state.service";
|
||||
import { OrganizationUserType } from "../../enums/organizationUserType";
|
||||
import { Utils } from "../../misc/utils";
|
||||
import { SymmetricCryptoKey } from "../../models/domain/symmetric-crypto-key";
|
||||
import { KeysRequest } from "../../models/request/keys.request";
|
||||
import { KeyConnectorService as KeyConnectorServiceAbstraction } from "../abstractions/key-connector.service";
|
||||
import { TokenService } from "../abstractions/token.service";
|
||||
import { KdfConfig } from "../models/domain/kdf-config";
|
||||
import { KeyConnectorUserKeyRequest } from "../models/request/key-connector-user-key.request";
|
||||
import { SetKeyConnectorKeyRequest } from "../models/request/set-key-connector-key.request";
|
||||
import { IdentityTokenResponse } from "../models/response/identity-token.response";
|
||||
|
||||
export class KeyConnectorService implements KeyConnectorServiceAbstraction {
|
||||
constructor(
|
||||
private stateService: StateService,
|
||||
private cryptoService: CryptoService,
|
||||
private apiService: ApiService,
|
||||
private tokenService: TokenService,
|
||||
private logService: LogService,
|
||||
private organizationService: OrganizationService,
|
||||
private cryptoFunctionService: CryptoFunctionService,
|
||||
private logoutCallback: (expired: boolean, userId?: string) => void
|
||||
) {}
|
||||
|
||||
setUsesKeyConnector(usesKeyConnector: boolean) {
|
||||
return this.stateService.setUsesKeyConnector(usesKeyConnector);
|
||||
}
|
||||
|
||||
async getUsesKeyConnector(): Promise<boolean> {
|
||||
return await this.stateService.getUsesKeyConnector();
|
||||
}
|
||||
|
||||
async userNeedsMigration() {
|
||||
const loggedInUsingSso = await this.tokenService.getIsExternal();
|
||||
const requiredByOrganization = (await this.getManagingOrganization()) != null;
|
||||
const userIsNotUsingKeyConnector = !(await this.getUsesKeyConnector());
|
||||
|
||||
return loggedInUsingSso && requiredByOrganization && userIsNotUsingKeyConnector;
|
||||
}
|
||||
|
||||
async migrateUser() {
|
||||
const organization = await this.getManagingOrganization();
|
||||
const key = await this.cryptoService.getKey();
|
||||
const keyConnectorRequest = new KeyConnectorUserKeyRequest(key.encKeyB64);
|
||||
|
||||
try {
|
||||
await this.apiService.postUserKeyToKeyConnector(
|
||||
organization.keyConnectorUrl,
|
||||
keyConnectorRequest
|
||||
);
|
||||
} catch (e) {
|
||||
this.handleKeyConnectorError(e);
|
||||
}
|
||||
|
||||
await this.apiService.postConvertToKeyConnector();
|
||||
}
|
||||
|
||||
async getAndSetKey(url: string) {
|
||||
try {
|
||||
const userKeyResponse = await this.apiService.getUserKeyFromKeyConnector(url);
|
||||
const keyArr = Utils.fromB64ToArray(userKeyResponse.key);
|
||||
const k = new SymmetricCryptoKey(keyArr);
|
||||
await this.cryptoService.setKey(k);
|
||||
} catch (e) {
|
||||
this.handleKeyConnectorError(e);
|
||||
}
|
||||
}
|
||||
|
||||
async getManagingOrganization() {
|
||||
const orgs = await this.organizationService.getAll();
|
||||
return orgs.find(
|
||||
(o) =>
|
||||
o.keyConnectorEnabled &&
|
||||
o.type !== OrganizationUserType.Admin &&
|
||||
o.type !== OrganizationUserType.Owner &&
|
||||
!o.isProviderUser
|
||||
);
|
||||
}
|
||||
|
||||
async convertNewSsoUserToKeyConnector(tokenResponse: IdentityTokenResponse, orgId: string) {
|
||||
const { kdf, kdfIterations, kdfMemory, kdfParallelism, keyConnectorUrl } = tokenResponse;
|
||||
const password = await this.cryptoFunctionService.randomBytes(64);
|
||||
const kdfConfig = new KdfConfig(kdfIterations, kdfMemory, kdfParallelism);
|
||||
|
||||
const k = await this.cryptoService.makeKey(
|
||||
Utils.fromBufferToB64(password),
|
||||
await this.tokenService.getEmail(),
|
||||
kdf,
|
||||
kdfConfig
|
||||
);
|
||||
const keyConnectorRequest = new KeyConnectorUserKeyRequest(k.encKeyB64);
|
||||
await this.cryptoService.setKey(k);
|
||||
|
||||
const encKey = await this.cryptoService.makeEncKey(k);
|
||||
await this.cryptoService.setEncKey(encKey[1].encryptedString);
|
||||
|
||||
const [pubKey, privKey] = await this.cryptoService.makeKeyPair();
|
||||
|
||||
try {
|
||||
await this.apiService.postUserKeyToKeyConnector(keyConnectorUrl, keyConnectorRequest);
|
||||
} catch (e) {
|
||||
this.handleKeyConnectorError(e);
|
||||
}
|
||||
|
||||
const keys = new KeysRequest(pubKey, privKey.encryptedString);
|
||||
const setPasswordRequest = new SetKeyConnectorKeyRequest(
|
||||
encKey[1].encryptedString,
|
||||
kdf,
|
||||
kdfConfig,
|
||||
orgId,
|
||||
keys
|
||||
);
|
||||
await this.apiService.postSetKeyConnectorKey(setPasswordRequest);
|
||||
}
|
||||
|
||||
async setConvertAccountRequired(status: boolean) {
|
||||
await this.stateService.setConvertAccountToKeyConnector(status);
|
||||
}
|
||||
|
||||
async getConvertAccountRequired(): Promise<boolean> {
|
||||
return await this.stateService.getConvertAccountToKeyConnector();
|
||||
}
|
||||
|
||||
async removeConvertAccountRequired() {
|
||||
await this.stateService.setConvertAccountToKeyConnector(null);
|
||||
}
|
||||
|
||||
async clear() {
|
||||
await this.removeConvertAccountRequired();
|
||||
}
|
||||
|
||||
private handleKeyConnectorError(e: any) {
|
||||
this.logService.error(e);
|
||||
if (this.logoutCallback != null) {
|
||||
this.logoutCallback(false);
|
||||
}
|
||||
throw new Error("Key Connector error");
|
||||
}
|
||||
}
|
||||
35
libs/common/src/auth/services/login.service.ts
Normal file
35
libs/common/src/auth/services/login.service.ts
Normal file
@@ -0,0 +1,35 @@
|
||||
import { StateService } from "../../abstractions/state.service";
|
||||
import { LoginService as LoginServiceAbstraction } from "../abstractions/login.service";
|
||||
|
||||
export class LoginService implements LoginServiceAbstraction {
|
||||
private _email: string;
|
||||
private _rememberEmail: boolean;
|
||||
|
||||
constructor(private stateService: StateService) {}
|
||||
|
||||
getEmail() {
|
||||
return this._email;
|
||||
}
|
||||
|
||||
getRememberEmail() {
|
||||
return this._rememberEmail;
|
||||
}
|
||||
|
||||
setEmail(value: string) {
|
||||
this._email = value;
|
||||
}
|
||||
|
||||
setRememberEmail(value: boolean) {
|
||||
this._rememberEmail = value;
|
||||
}
|
||||
|
||||
clearValues() {
|
||||
this._email = null;
|
||||
this._rememberEmail = null;
|
||||
}
|
||||
|
||||
async saveEmailSettings() {
|
||||
await this.stateService.setRememberedEmail(this._rememberEmail ? this._email : null);
|
||||
this.clearValues();
|
||||
}
|
||||
}
|
||||
181
libs/common/src/auth/services/token.service.ts
Normal file
181
libs/common/src/auth/services/token.service.ts
Normal file
@@ -0,0 +1,181 @@
|
||||
import { StateService } from "../../abstractions/state.service";
|
||||
import { Utils } from "../../misc/utils";
|
||||
import { TokenService as TokenServiceAbstraction } from "../abstractions/token.service";
|
||||
import { IdentityTokenResponse } from "../models/response/identity-token.response";
|
||||
|
||||
export class TokenService implements TokenServiceAbstraction {
|
||||
static decodeToken(token: string): Promise<any> {
|
||||
if (token == null) {
|
||||
throw new Error("Token not provided.");
|
||||
}
|
||||
|
||||
const parts = token.split(".");
|
||||
if (parts.length !== 3) {
|
||||
throw new Error("JWT must have 3 parts");
|
||||
}
|
||||
|
||||
const decoded = Utils.fromUrlB64ToUtf8(parts[1]);
|
||||
if (decoded == null) {
|
||||
throw new Error("Cannot decode the token");
|
||||
}
|
||||
|
||||
const decodedToken = JSON.parse(decoded);
|
||||
return decodedToken;
|
||||
}
|
||||
|
||||
constructor(private stateService: StateService) {}
|
||||
|
||||
async setTokens(
|
||||
accessToken: string,
|
||||
refreshToken: string,
|
||||
clientIdClientSecret: [string, string]
|
||||
): Promise<any> {
|
||||
await this.setToken(accessToken);
|
||||
await this.setRefreshToken(refreshToken);
|
||||
if (clientIdClientSecret != null) {
|
||||
await this.setClientId(clientIdClientSecret[0]);
|
||||
await this.setClientSecret(clientIdClientSecret[1]);
|
||||
}
|
||||
}
|
||||
|
||||
async setClientId(clientId: string): Promise<any> {
|
||||
return await this.stateService.setApiKeyClientId(clientId);
|
||||
}
|
||||
|
||||
async getClientId(): Promise<string> {
|
||||
return await this.stateService.getApiKeyClientId();
|
||||
}
|
||||
|
||||
async setClientSecret(clientSecret: string): Promise<any> {
|
||||
return await this.stateService.setApiKeyClientSecret(clientSecret);
|
||||
}
|
||||
|
||||
async getClientSecret(): Promise<string> {
|
||||
return await this.stateService.getApiKeyClientSecret();
|
||||
}
|
||||
|
||||
async setToken(token: string): Promise<void> {
|
||||
await this.stateService.setAccessToken(token);
|
||||
}
|
||||
|
||||
async getToken(): Promise<string> {
|
||||
return await this.stateService.getAccessToken();
|
||||
}
|
||||
|
||||
async setRefreshToken(refreshToken: string): Promise<any> {
|
||||
return await this.stateService.setRefreshToken(refreshToken);
|
||||
}
|
||||
|
||||
async getRefreshToken(): Promise<string> {
|
||||
return await this.stateService.getRefreshToken();
|
||||
}
|
||||
|
||||
async setTwoFactorToken(tokenResponse: IdentityTokenResponse): Promise<any> {
|
||||
return await this.stateService.setTwoFactorToken(tokenResponse.twoFactorToken);
|
||||
}
|
||||
|
||||
async getTwoFactorToken(): Promise<string> {
|
||||
return await this.stateService.getTwoFactorToken();
|
||||
}
|
||||
|
||||
async clearTwoFactorToken(): Promise<any> {
|
||||
return await this.stateService.setTwoFactorToken(null);
|
||||
}
|
||||
|
||||
async clearToken(userId?: string): Promise<any> {
|
||||
await this.stateService.setAccessToken(null, { userId: userId });
|
||||
await this.stateService.setRefreshToken(null, { userId: userId });
|
||||
await this.stateService.setApiKeyClientId(null, { userId: userId });
|
||||
await this.stateService.setApiKeyClientSecret(null, { userId: userId });
|
||||
}
|
||||
|
||||
// jwthelper methods
|
||||
// ref https://github.com/auth0/angular-jwt/blob/master/src/angularJwt/services/jwt.js
|
||||
|
||||
async decodeToken(token?: string): Promise<any> {
|
||||
token = token ?? (await this.stateService.getAccessToken());
|
||||
|
||||
if (token == null) {
|
||||
throw new Error("Token not found.");
|
||||
}
|
||||
|
||||
return TokenService.decodeToken(token);
|
||||
}
|
||||
|
||||
async getTokenExpirationDate(): Promise<Date> {
|
||||
const decoded = await this.decodeToken();
|
||||
if (typeof decoded.exp === "undefined") {
|
||||
return null;
|
||||
}
|
||||
|
||||
const d = new Date(0); // The 0 here is the key, which sets the date to the epoch
|
||||
d.setUTCSeconds(decoded.exp);
|
||||
return d;
|
||||
}
|
||||
|
||||
async tokenSecondsRemaining(offsetSeconds = 0): Promise<number> {
|
||||
const d = await this.getTokenExpirationDate();
|
||||
if (d == null) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
const msRemaining = d.valueOf() - (new Date().valueOf() + offsetSeconds * 1000);
|
||||
return Math.round(msRemaining / 1000);
|
||||
}
|
||||
|
||||
async tokenNeedsRefresh(minutes = 5): Promise<boolean> {
|
||||
const sRemaining = await this.tokenSecondsRemaining();
|
||||
return sRemaining < 60 * minutes;
|
||||
}
|
||||
|
||||
async getUserId(): Promise<string> {
|
||||
const decoded = await this.decodeToken();
|
||||
if (typeof decoded.sub === "undefined") {
|
||||
throw new Error("No user id found");
|
||||
}
|
||||
|
||||
return decoded.sub as string;
|
||||
}
|
||||
|
||||
async getEmail(): Promise<string> {
|
||||
const decoded = await this.decodeToken();
|
||||
if (typeof decoded.email === "undefined") {
|
||||
throw new Error("No email found");
|
||||
}
|
||||
|
||||
return decoded.email as string;
|
||||
}
|
||||
|
||||
async getEmailVerified(): Promise<boolean> {
|
||||
const decoded = await this.decodeToken();
|
||||
if (typeof decoded.email_verified === "undefined") {
|
||||
throw new Error("No email verification found");
|
||||
}
|
||||
|
||||
return decoded.email_verified as boolean;
|
||||
}
|
||||
|
||||
async getName(): Promise<string> {
|
||||
const decoded = await this.decodeToken();
|
||||
if (typeof decoded.name === "undefined") {
|
||||
return null;
|
||||
}
|
||||
|
||||
return decoded.name as string;
|
||||
}
|
||||
|
||||
async getIssuer(): Promise<string> {
|
||||
const decoded = await this.decodeToken();
|
||||
if (typeof decoded.iss === "undefined") {
|
||||
throw new Error("No issuer found");
|
||||
}
|
||||
|
||||
return decoded.iss as string;
|
||||
}
|
||||
|
||||
async getIsExternal(): Promise<boolean> {
|
||||
const decoded = await this.decodeToken();
|
||||
|
||||
return Array.isArray(decoded.amr) && decoded.amr.includes("external");
|
||||
}
|
||||
}
|
||||
186
libs/common/src/auth/services/two-factor.service.ts
Normal file
186
libs/common/src/auth/services/two-factor.service.ts
Normal file
@@ -0,0 +1,186 @@
|
||||
import { I18nService } from "../../abstractions/i18n.service";
|
||||
import { PlatformUtilsService } from "../../abstractions/platformUtils.service";
|
||||
import {
|
||||
TwoFactorProviderDetails,
|
||||
TwoFactorService as TwoFactorServiceAbstraction,
|
||||
} from "../abstractions/two-factor.service";
|
||||
import { TwoFactorProviderType } from "../enums/two-factor-provider-type";
|
||||
import { IdentityTwoFactorResponse } from "../models/response/identity-two-factor.response";
|
||||
|
||||
export const TwoFactorProviders: Partial<Record<TwoFactorProviderType, TwoFactorProviderDetails>> =
|
||||
{
|
||||
[TwoFactorProviderType.Authenticator]: {
|
||||
type: TwoFactorProviderType.Authenticator,
|
||||
name: null as string,
|
||||
description: null as string,
|
||||
priority: 1,
|
||||
sort: 1,
|
||||
premium: false,
|
||||
},
|
||||
[TwoFactorProviderType.Yubikey]: {
|
||||
type: TwoFactorProviderType.Yubikey,
|
||||
name: null as string,
|
||||
description: null as string,
|
||||
priority: 3,
|
||||
sort: 2,
|
||||
premium: true,
|
||||
},
|
||||
[TwoFactorProviderType.Duo]: {
|
||||
type: TwoFactorProviderType.Duo,
|
||||
name: "Duo",
|
||||
description: null as string,
|
||||
priority: 2,
|
||||
sort: 3,
|
||||
premium: true,
|
||||
},
|
||||
[TwoFactorProviderType.OrganizationDuo]: {
|
||||
type: TwoFactorProviderType.OrganizationDuo,
|
||||
name: "Duo (Organization)",
|
||||
description: null as string,
|
||||
priority: 10,
|
||||
sort: 4,
|
||||
premium: false,
|
||||
},
|
||||
[TwoFactorProviderType.Email]: {
|
||||
type: TwoFactorProviderType.Email,
|
||||
name: null as string,
|
||||
description: null as string,
|
||||
priority: 0,
|
||||
sort: 6,
|
||||
premium: false,
|
||||
},
|
||||
[TwoFactorProviderType.WebAuthn]: {
|
||||
type: TwoFactorProviderType.WebAuthn,
|
||||
name: null as string,
|
||||
description: null as string,
|
||||
priority: 4,
|
||||
sort: 5,
|
||||
premium: true,
|
||||
},
|
||||
};
|
||||
|
||||
export class TwoFactorService implements TwoFactorServiceAbstraction {
|
||||
private twoFactorProvidersData: Map<TwoFactorProviderType, { [key: string]: string }>;
|
||||
private selectedTwoFactorProviderType: TwoFactorProviderType = null;
|
||||
|
||||
constructor(
|
||||
private i18nService: I18nService,
|
||||
private platformUtilsService: PlatformUtilsService
|
||||
) {}
|
||||
|
||||
init() {
|
||||
TwoFactorProviders[TwoFactorProviderType.Email].name = this.i18nService.t("emailTitle");
|
||||
TwoFactorProviders[TwoFactorProviderType.Email].description = this.i18nService.t("emailDesc");
|
||||
|
||||
TwoFactorProviders[TwoFactorProviderType.Authenticator].name =
|
||||
this.i18nService.t("authenticatorAppTitle");
|
||||
TwoFactorProviders[TwoFactorProviderType.Authenticator].description =
|
||||
this.i18nService.t("authenticatorAppDesc");
|
||||
|
||||
TwoFactorProviders[TwoFactorProviderType.Duo].description = this.i18nService.t("duoDesc");
|
||||
|
||||
TwoFactorProviders[TwoFactorProviderType.OrganizationDuo].name =
|
||||
"Duo (" + this.i18nService.t("organization") + ")";
|
||||
TwoFactorProviders[TwoFactorProviderType.OrganizationDuo].description =
|
||||
this.i18nService.t("duoOrganizationDesc");
|
||||
|
||||
TwoFactorProviders[TwoFactorProviderType.WebAuthn].name = this.i18nService.t("webAuthnTitle");
|
||||
TwoFactorProviders[TwoFactorProviderType.WebAuthn].description =
|
||||
this.i18nService.t("webAuthnDesc");
|
||||
|
||||
TwoFactorProviders[TwoFactorProviderType.Yubikey].name = this.i18nService.t("yubiKeyTitle");
|
||||
TwoFactorProviders[TwoFactorProviderType.Yubikey].description =
|
||||
this.i18nService.t("yubiKeyDesc");
|
||||
}
|
||||
|
||||
getSupportedProviders(win: Window): TwoFactorProviderDetails[] {
|
||||
const providers: any[] = [];
|
||||
if (this.twoFactorProvidersData == null) {
|
||||
return providers;
|
||||
}
|
||||
|
||||
if (
|
||||
this.twoFactorProvidersData.has(TwoFactorProviderType.OrganizationDuo) &&
|
||||
this.platformUtilsService.supportsDuo()
|
||||
) {
|
||||
providers.push(TwoFactorProviders[TwoFactorProviderType.OrganizationDuo]);
|
||||
}
|
||||
|
||||
if (this.twoFactorProvidersData.has(TwoFactorProviderType.Authenticator)) {
|
||||
providers.push(TwoFactorProviders[TwoFactorProviderType.Authenticator]);
|
||||
}
|
||||
|
||||
if (this.twoFactorProvidersData.has(TwoFactorProviderType.Yubikey)) {
|
||||
providers.push(TwoFactorProviders[TwoFactorProviderType.Yubikey]);
|
||||
}
|
||||
|
||||
if (
|
||||
this.twoFactorProvidersData.has(TwoFactorProviderType.Duo) &&
|
||||
this.platformUtilsService.supportsDuo()
|
||||
) {
|
||||
providers.push(TwoFactorProviders[TwoFactorProviderType.Duo]);
|
||||
}
|
||||
|
||||
if (
|
||||
this.twoFactorProvidersData.has(TwoFactorProviderType.WebAuthn) &&
|
||||
this.platformUtilsService.supportsWebAuthn(win)
|
||||
) {
|
||||
providers.push(TwoFactorProviders[TwoFactorProviderType.WebAuthn]);
|
||||
}
|
||||
|
||||
if (this.twoFactorProvidersData.has(TwoFactorProviderType.Email)) {
|
||||
providers.push(TwoFactorProviders[TwoFactorProviderType.Email]);
|
||||
}
|
||||
|
||||
return providers;
|
||||
}
|
||||
|
||||
getDefaultProvider(webAuthnSupported: boolean): TwoFactorProviderType {
|
||||
if (this.twoFactorProvidersData == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (
|
||||
this.selectedTwoFactorProviderType != null &&
|
||||
this.twoFactorProvidersData.has(this.selectedTwoFactorProviderType)
|
||||
) {
|
||||
return this.selectedTwoFactorProviderType;
|
||||
}
|
||||
|
||||
let providerType: TwoFactorProviderType = null;
|
||||
let providerPriority = -1;
|
||||
this.twoFactorProvidersData.forEach((_value, type) => {
|
||||
const provider = (TwoFactorProviders as any)[type];
|
||||
if (provider != null && provider.priority > providerPriority) {
|
||||
if (type === TwoFactorProviderType.WebAuthn && !webAuthnSupported) {
|
||||
return;
|
||||
}
|
||||
|
||||
providerType = type;
|
||||
providerPriority = provider.priority;
|
||||
}
|
||||
});
|
||||
|
||||
return providerType;
|
||||
}
|
||||
|
||||
setSelectedProvider(type: TwoFactorProviderType) {
|
||||
this.selectedTwoFactorProviderType = type;
|
||||
}
|
||||
|
||||
clearSelectedProvider() {
|
||||
this.selectedTwoFactorProviderType = null;
|
||||
}
|
||||
|
||||
setProviders(response: IdentityTwoFactorResponse) {
|
||||
this.twoFactorProvidersData = response.twoFactorProviders2;
|
||||
}
|
||||
|
||||
clearProviders() {
|
||||
this.twoFactorProvidersData = null;
|
||||
}
|
||||
|
||||
getProviders() {
|
||||
return this.twoFactorProvidersData;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
import { ApiService } from "../../../abstractions/api.service";
|
||||
import { UserVerificationApiServiceAbstraction } from "../../../abstractions/userVerification/userVerification-api.service.abstraction";
|
||||
import { VerifyOTPRequest } from "../../models/request/verify-otp.request";
|
||||
|
||||
export class UserVerificationApiService implements UserVerificationApiServiceAbstraction {
|
||||
constructor(private apiService: ApiService) {}
|
||||
|
||||
postAccountVerifyOTP(request: VerifyOTPRequest): Promise<void> {
|
||||
return this.apiService.send("POST", "/accounts/verify-otp", request, true, false);
|
||||
}
|
||||
async postAccountRequestOTP(): Promise<void> {
|
||||
return this.apiService.send("POST", "/accounts/request-otp", null, true, false);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
import { CryptoService } from "../../../abstractions/crypto.service";
|
||||
import { I18nService } from "../../../abstractions/i18n.service";
|
||||
import { UserVerificationApiServiceAbstraction } from "../../../abstractions/userVerification/userVerification-api.service.abstraction";
|
||||
import { UserVerificationService as UserVerificationServiceAbstraction } from "../../../abstractions/userVerification/userVerification.service.abstraction";
|
||||
import { Verification } from "../../../types/verification";
|
||||
import { VerificationType } from "../../enums/verification-type";
|
||||
import { SecretVerificationRequest } from "../../models/request/secret-verification.request";
|
||||
import { VerifyOTPRequest } from "../../models/request/verify-otp.request";
|
||||
|
||||
/**
|
||||
* Used for general-purpose user verification throughout the app.
|
||||
* Use it to verify the input collected by UserVerificationComponent.
|
||||
*/
|
||||
export class UserVerificationService implements UserVerificationServiceAbstraction {
|
||||
constructor(
|
||||
private cryptoService: CryptoService,
|
||||
private i18nService: I18nService,
|
||||
private userVerificationApiService: UserVerificationApiServiceAbstraction
|
||||
) {}
|
||||
|
||||
/**
|
||||
* Create a new request model to be used for server-side verification
|
||||
* @param verification User-supplied verification data (Master Password or OTP)
|
||||
* @param requestClass The request model to create
|
||||
* @param alreadyHashed Whether the master password is already hashed
|
||||
*/
|
||||
async buildRequest<T extends SecretVerificationRequest>(
|
||||
verification: Verification,
|
||||
requestClass?: new () => T,
|
||||
alreadyHashed?: boolean
|
||||
) {
|
||||
this.validateInput(verification);
|
||||
|
||||
const request =
|
||||
requestClass != null ? new requestClass() : (new SecretVerificationRequest() as T);
|
||||
|
||||
if (verification.type === VerificationType.OTP) {
|
||||
request.otp = verification.secret;
|
||||
} else {
|
||||
request.masterPasswordHash = alreadyHashed
|
||||
? verification.secret
|
||||
: await this.cryptoService.hashPassword(verification.secret, null);
|
||||
}
|
||||
|
||||
return request;
|
||||
}
|
||||
|
||||
/**
|
||||
* Used to verify the Master Password client-side, or send the OTP to the server for verification (with no other data)
|
||||
* Generally used for client-side verification only.
|
||||
* @param verification User-supplied verification data (Master Password or OTP)
|
||||
*/
|
||||
async verifyUser(verification: Verification): Promise<boolean> {
|
||||
this.validateInput(verification);
|
||||
|
||||
if (verification.type === VerificationType.OTP) {
|
||||
const request = new VerifyOTPRequest(verification.secret);
|
||||
try {
|
||||
await this.userVerificationApiService.postAccountVerifyOTP(request);
|
||||
} catch (e) {
|
||||
throw new Error(this.i18nService.t("invalidVerificationCode"));
|
||||
}
|
||||
} else {
|
||||
const passwordValid = await this.cryptoService.compareAndUpdateKeyHash(
|
||||
verification.secret,
|
||||
null
|
||||
);
|
||||
if (!passwordValid) {
|
||||
throw new Error(this.i18nService.t("invalidMasterPassword"));
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
async requestOTP() {
|
||||
await this.userVerificationApiService.postAccountRequestOTP();
|
||||
}
|
||||
|
||||
private validateInput(verification: Verification) {
|
||||
if (verification?.secret == null || verification.secret === "") {
|
||||
if (verification.type === VerificationType.OTP) {
|
||||
throw new Error(this.i18nService.t("verificationCodeRequired"));
|
||||
} else {
|
||||
throw new Error(this.i18nService.t("masterPasswordRequired"));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
12
libs/common/src/auth/types/auth-response.ts
Normal file
12
libs/common/src/auth/types/auth-response.ts
Normal file
@@ -0,0 +1,12 @@
|
||||
import { VerificationType } from "../enums/verification-type";
|
||||
|
||||
import { TwoFactorResponse } from "./two-factor-response";
|
||||
|
||||
export type AuthResponseBase = {
|
||||
secret: string;
|
||||
verificationType: VerificationType;
|
||||
};
|
||||
|
||||
export type AuthResponse<T extends TwoFactorResponse> = AuthResponseBase & {
|
||||
response: T;
|
||||
};
|
||||
14
libs/common/src/auth/types/two-factor-response.ts
Normal file
14
libs/common/src/auth/types/two-factor-response.ts
Normal file
@@ -0,0 +1,14 @@
|
||||
import { TwoFactorAuthenticatorResponse } from "../models/response/two-factor-authenticator.response";
|
||||
import { TwoFactorDuoResponse } from "../models/response/two-factor-duo.response";
|
||||
import { TwoFactorEmailResponse } from "../models/response/two-factor-email.response";
|
||||
import { TwoFactorRecoverResponse } from "../models/response/two-factor-recover.response";
|
||||
import { TwoFactorWebAuthnResponse } from "../models/response/two-factor-web-authn.response";
|
||||
import { TwoFactorYubiKeyResponse } from "../models/response/two-factor-yubi-key.response";
|
||||
|
||||
export type TwoFactorResponse =
|
||||
| TwoFactorRecoverResponse
|
||||
| TwoFactorDuoResponse
|
||||
| TwoFactorEmailResponse
|
||||
| TwoFactorWebAuthnResponse
|
||||
| TwoFactorAuthenticatorResponse
|
||||
| TwoFactorYubiKeyResponse;
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user