mirror of
https://github.com/bitwarden/browser
synced 2025-12-19 01:33:33 +00:00
[SG-168] Passwordless login web MVP (#3424)
* passwordless login page redesign * passwordless login page redesign * restyled login form to use tailwind * restyled login form to use tailwind * moved texts on login device template to locales * made reactive form changes for clients * added request model * made more changes * added implmentation to auth request api * fixed refrencing issue * renamed model property * Added resend notification functionality * Added new file * login with device first draft * login with device first draft * login with device first draft * login with device first draft * connection to anonymous hub * connection to anonymous hub * refactored confirm login response * removed comment * cleaned up login * changed uptyped form builder * changed uptyped form builder * [SG-168] Update login strategy with passwordless login credentials. * [SG-168] Removed logs. Changed inputs for passwordless logic strategy. Removed tokenRequestPasswordless it is using the same as password. * code cleanup * code cleanup * removed login with device from self hosted * fixed PR comments * added module for login * fixed post request bug * added feature flag * added feature flag * added feature flag Co-authored-by: André Bispo <abispo@bitwarden.com>
This commit is contained in:
60
libs/common/src/services/anonymousHub.service.ts
Normal file
60
libs/common/src/services/anonymousHub.service.ts
Normal file
@@ -0,0 +1,60 @@
|
||||
import { Injectable } from "@angular/core";
|
||||
import {
|
||||
HttpTransportType,
|
||||
HubConnection,
|
||||
HubConnectionBuilder,
|
||||
IHubProtocol,
|
||||
} from "@microsoft/signalr";
|
||||
import { MessagePackHubProtocol } from "@microsoft/signalr-protocol-msgpack";
|
||||
|
||||
import { AnonymousHubService as AnonymousHubServiceAbstraction } from "../abstractions/anonymousHub.service";
|
||||
import { AuthService } from "../abstractions/auth.service";
|
||||
import { EnvironmentService } from "../abstractions/environment.service";
|
||||
import { LogService } from "../abstractions/log.service";
|
||||
|
||||
import {
|
||||
AuthRequestPushNotification,
|
||||
NotificationResponse,
|
||||
} from "./../models/response/notificationResponse";
|
||||
|
||||
@Injectable()
|
||||
export class AnonymousHubService implements AnonymousHubServiceAbstraction {
|
||||
private anonHubConnection: HubConnection;
|
||||
private url: string;
|
||||
|
||||
constructor(
|
||||
private environmentService: EnvironmentService,
|
||||
private authService: AuthService,
|
||||
private logService: LogService
|
||||
) {}
|
||||
|
||||
async createHubConnection(token: string) {
|
||||
this.url = this.environmentService.getNotificationsUrl();
|
||||
|
||||
this.anonHubConnection = new HubConnectionBuilder()
|
||||
.withUrl(this.url + "/anonymousHub?Token=" + token, {
|
||||
skipNegotiation: true,
|
||||
transport: HttpTransportType.WebSockets,
|
||||
})
|
||||
.withHubProtocol(new MessagePackHubProtocol() as IHubProtocol)
|
||||
.build();
|
||||
|
||||
this.anonHubConnection.start().catch((error) => this.logService.error(error));
|
||||
|
||||
this.anonHubConnection.on("AuthRequestResponseRecieved", (data: any) => {
|
||||
this.ProcessNotification(new NotificationResponse(data));
|
||||
});
|
||||
}
|
||||
|
||||
stopHubConnection() {
|
||||
if (this.anonHubConnection) {
|
||||
this.anonHubConnection.stop();
|
||||
}
|
||||
}
|
||||
|
||||
private async ProcessNotification(notification: NotificationResponse) {
|
||||
await this.authService.authResponsePushNotifiction(
|
||||
notification.payload as AuthRequestPushNotification
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -54,6 +54,7 @@ import { OrganizationUserUpdateGroupsRequest } from "../models/request/organizat
|
||||
import { OrganizationUserUpdateRequest } from "../models/request/organizationUserUpdateRequest";
|
||||
import { PasswordHintRequest } from "../models/request/passwordHintRequest";
|
||||
import { PasswordRequest } from "../models/request/passwordRequest";
|
||||
import { PasswordlessCreateAuthRequest } from "../models/request/passwordlessCreateAuthRequest";
|
||||
import { PaymentRequest } from "../models/request/paymentRequest";
|
||||
import { PreloginRequest } from "../models/request/preloginRequest";
|
||||
import { ProviderAddOrganizationRequest } from "../models/request/provider/providerAddOrganizationRequest";
|
||||
@@ -92,6 +93,7 @@ import { VerifyEmailRequest } from "../models/request/verifyEmailRequest";
|
||||
import { ApiKeyResponse } from "../models/response/apiKeyResponse";
|
||||
import { AttachmentResponse } from "../models/response/attachmentResponse";
|
||||
import { AttachmentUploadDataResponse } from "../models/response/attachmentUploadDataResponse";
|
||||
import { AuthRequestResponse } from "../models/response/authRequestResponse";
|
||||
import { RegisterResponse } from "../models/response/authentication/registerResponse";
|
||||
import { BillingHistoryResponse } from "../models/response/billingHistoryResponse";
|
||||
import { BillingPaymentResponse } from "../models/response/billingPaymentResponse";
|
||||
@@ -265,6 +267,17 @@ export class ApiService implements ApiServiceAbstraction {
|
||||
}
|
||||
}
|
||||
|
||||
async postAuthRequest(request: PasswordlessCreateAuthRequest): Promise<AuthRequestResponse> {
|
||||
const r = await this.send("POST", "/auth-requests/", request, false, true);
|
||||
return new AuthRequestResponse(r);
|
||||
}
|
||||
|
||||
async getAuthResponse(id: string, accessCode: string): Promise<AuthRequestResponse> {
|
||||
const path = `/auth-requests/${id}/response?code=${accessCode}`;
|
||||
const r = await this.send("GET", path, null, false, true);
|
||||
return new AuthRequestResponse(r);
|
||||
}
|
||||
|
||||
// Account APIs
|
||||
|
||||
async getProfile(): Promise<ProfileResponse> {
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import { Observable, Subject } from "rxjs";
|
||||
|
||||
import { ApiService } from "../abstractions/api.service";
|
||||
import { AppIdService } from "../abstractions/appId.service";
|
||||
import { AuthService as AuthServiceAbstraction } from "../abstractions/auth.service";
|
||||
@@ -17,17 +19,20 @@ import { KdfType } from "../enums/kdfType";
|
||||
import { KeySuffixOptions } from "../enums/keySuffixOptions";
|
||||
import { ApiLogInStrategy } from "../misc/logInStrategies/apiLogin.strategy";
|
||||
import { PasswordLogInStrategy } from "../misc/logInStrategies/passwordLogin.strategy";
|
||||
import { PasswordlessLogInStrategy } from "../misc/logInStrategies/passwordlessLogin.strategy";
|
||||
import { SsoLogInStrategy } from "../misc/logInStrategies/ssoLogin.strategy";
|
||||
import { AuthResult } from "../models/domain/authResult";
|
||||
import {
|
||||
ApiLogInCredentials,
|
||||
PasswordLogInCredentials,
|
||||
SsoLogInCredentials,
|
||||
PasswordlessLogInCredentials,
|
||||
} from "../models/domain/logInCredentials";
|
||||
import { SymmetricCryptoKey } from "../models/domain/symmetricCryptoKey";
|
||||
import { TokenRequestTwoFactor } from "../models/request/identityToken/tokenRequestTwoFactor";
|
||||
import { PreloginRequest } from "../models/request/preloginRequest";
|
||||
import { ErrorResponse } from "../models/response/errorResponse";
|
||||
import { AuthRequestPushNotification } from "../models/response/notificationResponse";
|
||||
|
||||
const sessionTimeoutLength = 2 * 60 * 1000; // 2 minutes
|
||||
|
||||
@@ -42,9 +47,15 @@ export class AuthService implements AuthServiceAbstraction {
|
||||
: null;
|
||||
}
|
||||
|
||||
private logInStrategy: ApiLogInStrategy | PasswordLogInStrategy | SsoLogInStrategy;
|
||||
private logInStrategy:
|
||||
| ApiLogInStrategy
|
||||
| PasswordLogInStrategy
|
||||
| SsoLogInStrategy
|
||||
| PasswordlessLogInStrategy;
|
||||
private sessionTimeout: any;
|
||||
|
||||
private pushNotificationSubject = new Subject<string>();
|
||||
|
||||
constructor(
|
||||
protected cryptoService: CryptoService,
|
||||
protected apiService: ApiService,
|
||||
@@ -61,52 +72,78 @@ export class AuthService implements AuthServiceAbstraction {
|
||||
) {}
|
||||
|
||||
async logIn(
|
||||
credentials: ApiLogInCredentials | PasswordLogInCredentials | SsoLogInCredentials
|
||||
credentials:
|
||||
| ApiLogInCredentials
|
||||
| PasswordLogInCredentials
|
||||
| SsoLogInCredentials
|
||||
| PasswordlessLogInCredentials
|
||||
): Promise<AuthResult> {
|
||||
this.clearState();
|
||||
|
||||
let strategy: ApiLogInStrategy | PasswordLogInStrategy | SsoLogInStrategy;
|
||||
let strategy:
|
||||
| ApiLogInStrategy
|
||||
| PasswordLogInStrategy
|
||||
| SsoLogInStrategy
|
||||
| PasswordlessLogInStrategy;
|
||||
|
||||
if (credentials.type === AuthenticationType.Password) {
|
||||
strategy = new PasswordLogInStrategy(
|
||||
this.cryptoService,
|
||||
this.apiService,
|
||||
this.tokenService,
|
||||
this.appIdService,
|
||||
this.platformUtilsService,
|
||||
this.messagingService,
|
||||
this.logService,
|
||||
this.stateService,
|
||||
this.twoFactorService,
|
||||
this
|
||||
);
|
||||
} else if (credentials.type === 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
|
||||
);
|
||||
} else if (credentials.type === AuthenticationType.Api) {
|
||||
strategy = new ApiLogInStrategy(
|
||||
this.cryptoService,
|
||||
this.apiService,
|
||||
this.tokenService,
|
||||
this.appIdService,
|
||||
this.platformUtilsService,
|
||||
this.messagingService,
|
||||
this.logService,
|
||||
this.stateService,
|
||||
this.twoFactorService,
|
||||
this.environmentService,
|
||||
this.keyConnectorService
|
||||
);
|
||||
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.Api:
|
||||
strategy = new ApiLogInStrategy(
|
||||
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);
|
||||
@@ -202,7 +239,21 @@ export class AuthService implements AuthServiceAbstraction {
|
||||
return this.cryptoService.makeKey(masterPassword, email, kdf, kdfIterations);
|
||||
}
|
||||
|
||||
private saveState(strategy: ApiLogInStrategy | PasswordLogInStrategy | SsoLogInStrategy) {
|
||||
async authResponsePushNotifiction(notification: AuthRequestPushNotification): Promise<any> {
|
||||
this.pushNotificationSubject.next(notification.id);
|
||||
}
|
||||
|
||||
getPushNotifcationObs$(): Observable<any> {
|
||||
return this.pushNotificationSubject.asObservable();
|
||||
}
|
||||
|
||||
private saveState(
|
||||
strategy:
|
||||
| ApiLogInStrategy
|
||||
| PasswordLogInStrategy
|
||||
| SsoLogInStrategy
|
||||
| PasswordlessLogInStrategy
|
||||
) {
|
||||
this.logInStrategy = strategy;
|
||||
this.startSessionTimeout();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user