mirror of
https://github.com/bitwarden/browser
synced 2025-12-11 22:03:36 +00:00
[PS-74] Fix user authentication state checks (#721)
* Create authService.authStatus, refactor isLocked checks * Rename authStatus -> getAuthStatus
This commit is contained in:
@@ -1,30 +1,29 @@
|
|||||||
import { Injectable } from "@angular/core";
|
import { Injectable } from "@angular/core";
|
||||||
import { ActivatedRouteSnapshot, CanActivate, Router, RouterStateSnapshot } from "@angular/router";
|
import { ActivatedRouteSnapshot, CanActivate, Router, RouterStateSnapshot } from "@angular/router";
|
||||||
|
|
||||||
|
import { AuthService } from "jslib-common/abstractions/auth.service";
|
||||||
import { KeyConnectorService } from "jslib-common/abstractions/keyConnector.service";
|
import { KeyConnectorService } from "jslib-common/abstractions/keyConnector.service";
|
||||||
import { MessagingService } from "jslib-common/abstractions/messaging.service";
|
import { MessagingService } from "jslib-common/abstractions/messaging.service";
|
||||||
import { StateService } from "jslib-common/abstractions/state.service";
|
import { AuthenticationStatus } from "jslib-common/enums/authenticationStatus";
|
||||||
import { VaultTimeoutService } from "jslib-common/abstractions/vaultTimeout.service";
|
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class AuthGuardService implements CanActivate {
|
export class AuthGuardService implements CanActivate {
|
||||||
constructor(
|
constructor(
|
||||||
private vaultTimeoutService: VaultTimeoutService,
|
private authService: AuthService,
|
||||||
private router: Router,
|
private router: Router,
|
||||||
private messagingService: MessagingService,
|
private messagingService: MessagingService,
|
||||||
private keyConnectorService: KeyConnectorService,
|
private keyConnectorService: KeyConnectorService
|
||||||
private stateService: StateService
|
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
async canActivate(route: ActivatedRouteSnapshot, routerState: RouterStateSnapshot) {
|
async canActivate(route: ActivatedRouteSnapshot, routerState: RouterStateSnapshot) {
|
||||||
const isAuthed = await this.stateService.getIsAuthenticated();
|
const authStatus = await this.authService.getAuthStatus();
|
||||||
if (!isAuthed) {
|
|
||||||
|
if (authStatus === AuthenticationStatus.LoggedOut) {
|
||||||
this.messagingService.send("authBlocked", { url: routerState.url });
|
this.messagingService.send("authBlocked", { url: routerState.url });
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
const locked = await this.vaultTimeoutService.isLocked();
|
if (authStatus === AuthenticationStatus.Locked) {
|
||||||
if (locked) {
|
|
||||||
if (routerState != null) {
|
if (routerState != null) {
|
||||||
this.messagingService.send("lockedUrl", { url: routerState.url });
|
this.messagingService.send("lockedUrl", { url: routerState.url });
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -303,6 +303,7 @@ export const SYSTEM_LANGUAGE = new InjectionToken<string>("SYSTEM_LANGUAGE");
|
|||||||
PolicyServiceAbstraction,
|
PolicyServiceAbstraction,
|
||||||
KeyConnectorServiceAbstraction,
|
KeyConnectorServiceAbstraction,
|
||||||
StateServiceAbstraction,
|
StateServiceAbstraction,
|
||||||
|
AuthServiceAbstraction,
|
||||||
LOCKED_CALLBACK,
|
LOCKED_CALLBACK,
|
||||||
LOGOUT_CALLBACK,
|
LOGOUT_CALLBACK,
|
||||||
],
|
],
|
||||||
@@ -346,11 +347,11 @@ export const SYSTEM_LANGUAGE = new InjectionToken<string>("SYSTEM_LANGUAGE");
|
|||||||
SyncServiceAbstraction,
|
SyncServiceAbstraction,
|
||||||
AppIdServiceAbstraction,
|
AppIdServiceAbstraction,
|
||||||
ApiServiceAbstraction,
|
ApiServiceAbstraction,
|
||||||
VaultTimeoutServiceAbstraction,
|
|
||||||
EnvironmentServiceAbstraction,
|
EnvironmentServiceAbstraction,
|
||||||
LOGOUT_CALLBACK,
|
LOGOUT_CALLBACK,
|
||||||
LogService,
|
LogService,
|
||||||
StateServiceAbstraction,
|
StateServiceAbstraction,
|
||||||
|
AuthServiceAbstraction,
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,27 +1,24 @@
|
|||||||
import { Injectable } from "@angular/core";
|
import { Injectable } from "@angular/core";
|
||||||
import { CanActivate, Router } from "@angular/router";
|
import { CanActivate, Router } from "@angular/router";
|
||||||
|
|
||||||
import { StateService } from "jslib-common/abstractions/state.service";
|
import { AuthService } from "jslib-common/abstractions/auth.service";
|
||||||
import { VaultTimeoutService } from "jslib-common/abstractions/vaultTimeout.service";
|
import { AuthenticationStatus } from "jslib-common/enums/authenticationStatus";
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class LockGuardService implements CanActivate {
|
export class LockGuardService implements CanActivate {
|
||||||
protected homepage = "vault";
|
protected homepage = "vault";
|
||||||
protected loginpage = "login";
|
protected loginpage = "login";
|
||||||
constructor(
|
constructor(private authService: AuthService, private router: Router) {}
|
||||||
private vaultTimeoutService: VaultTimeoutService,
|
|
||||||
private router: Router,
|
|
||||||
private stateService: StateService
|
|
||||||
) {}
|
|
||||||
|
|
||||||
async canActivate() {
|
async canActivate() {
|
||||||
if (await this.vaultTimeoutService.isLocked()) {
|
const authStatus = await this.authService.getAuthStatus();
|
||||||
|
|
||||||
|
if (authStatus === AuthenticationStatus.Locked) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
const redirectUrl = (await this.stateService.getIsAuthenticated())
|
const redirectUrl =
|
||||||
? [this.homepage]
|
authStatus === AuthenticationStatus.LoggedOut ? [this.loginpage] : [this.homepage];
|
||||||
: [this.loginpage];
|
|
||||||
|
|
||||||
this.router.navigate(redirectUrl);
|
this.router.navigate(redirectUrl);
|
||||||
return false;
|
return false;
|
||||||
|
|||||||
@@ -1,27 +1,25 @@
|
|||||||
import { Injectable } from "@angular/core";
|
import { Injectable } from "@angular/core";
|
||||||
import { CanActivate, Router } from "@angular/router";
|
import { CanActivate, Router } from "@angular/router";
|
||||||
|
|
||||||
import { StateService } from "jslib-common/abstractions/state.service";
|
import { AuthService } from "jslib-common/abstractions/auth.service";
|
||||||
import { VaultTimeoutService } from "jslib-common/abstractions/vaultTimeout.service";
|
import { AuthenticationStatus } from "jslib-common/enums/authenticationStatus";
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class UnauthGuardService implements CanActivate {
|
export class UnauthGuardService implements CanActivate {
|
||||||
protected homepage = "vault";
|
protected homepage = "vault";
|
||||||
constructor(
|
constructor(private authService: AuthService, private router: Router) {}
|
||||||
private vaultTimeoutService: VaultTimeoutService,
|
|
||||||
private router: Router,
|
|
||||||
private stateService: StateService
|
|
||||||
) {}
|
|
||||||
|
|
||||||
async canActivate() {
|
async canActivate() {
|
||||||
const isAuthed = await this.stateService.getIsAuthenticated();
|
const authStatus = await this.authService.getAuthStatus();
|
||||||
if (isAuthed) {
|
|
||||||
const locked = await this.vaultTimeoutService.isLocked();
|
if (authStatus === AuthenticationStatus.LoggedOut) {
|
||||||
if (locked) {
|
|
||||||
return this.router.createUrlTree(["lock"]);
|
|
||||||
}
|
|
||||||
return this.router.createUrlTree([this.homepage]);
|
|
||||||
}
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (authStatus === AuthenticationStatus.Locked) {
|
||||||
|
return this.router.createUrlTree(["lock"]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.router.createUrlTree([this.homepage]);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import { AuthenticationStatus } from "../enums/authenticationStatus";
|
||||||
import { AuthResult } from "../models/domain/authResult";
|
import { AuthResult } from "../models/domain/authResult";
|
||||||
import {
|
import {
|
||||||
ApiLogInCredentials,
|
ApiLogInCredentials,
|
||||||
@@ -22,4 +23,5 @@ export abstract class AuthService {
|
|||||||
authingWithApiKey: () => boolean;
|
authingWithApiKey: () => boolean;
|
||||||
authingWithSso: () => boolean;
|
authingWithSso: () => boolean;
|
||||||
authingWithPassword: () => boolean;
|
authingWithPassword: () => boolean;
|
||||||
|
getAuthStatus: (userId?: string) => Promise<AuthenticationStatus>;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
export abstract class VaultTimeoutService {
|
export abstract class VaultTimeoutService {
|
||||||
isLocked: (userId?: string) => Promise<boolean>;
|
|
||||||
checkVaultTimeout: () => Promise<void>;
|
checkVaultTimeout: () => Promise<void>;
|
||||||
lock: (allowSoftLock?: boolean, userId?: string) => Promise<void>;
|
lock: (allowSoftLock?: boolean, userId?: string) => Promise<void>;
|
||||||
logOut: (userId?: string) => Promise<void>;
|
logOut: (userId?: string) => Promise<void>;
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
export enum AuthenticationStatus {
|
export enum AuthenticationStatus {
|
||||||
Locked = "locked",
|
LoggedOut = 0,
|
||||||
Unlocked = "unlocked",
|
Locked = 1,
|
||||||
LoggedOut = "loggedOut",
|
Unlocked = 2,
|
||||||
Active = "active",
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,8 +11,10 @@ import { PlatformUtilsService } from "../abstractions/platformUtils.service";
|
|||||||
import { StateService } from "../abstractions/state.service";
|
import { StateService } from "../abstractions/state.service";
|
||||||
import { TokenService } from "../abstractions/token.service";
|
import { TokenService } from "../abstractions/token.service";
|
||||||
import { TwoFactorService } from "../abstractions/twoFactor.service";
|
import { TwoFactorService } from "../abstractions/twoFactor.service";
|
||||||
|
import { AuthenticationStatus } from "../enums/authenticationStatus";
|
||||||
import { AuthenticationType } from "../enums/authenticationType";
|
import { AuthenticationType } from "../enums/authenticationType";
|
||||||
import { KdfType } from "../enums/kdfType";
|
import { KdfType } from "../enums/kdfType";
|
||||||
|
import { KeySuffixOptions } from "../enums/keySuffixOptions";
|
||||||
import { ApiLogInStrategy } from "../misc/logInStrategies/apiLogin.strategy";
|
import { ApiLogInStrategy } from "../misc/logInStrategies/apiLogin.strategy";
|
||||||
import { PasswordLogInStrategy } from "../misc/logInStrategies/passwordLogin.strategy";
|
import { PasswordLogInStrategy } from "../misc/logInStrategies/passwordLogin.strategy";
|
||||||
import { SsoLogInStrategy } from "../misc/logInStrategies/ssoLogin.strategy";
|
import { SsoLogInStrategy } from "../misc/logInStrategies/ssoLogin.strategy";
|
||||||
@@ -157,6 +159,31 @@ export class AuthService implements AuthServiceAbstraction {
|
|||||||
return this.logInStrategy instanceof PasswordLogInStrategy;
|
return this.logInStrategy instanceof PasswordLogInStrategy;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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> {
|
async makePreloginKey(masterPassword: string, email: string): Promise<SymmetricCryptoKey> {
|
||||||
email = email.trim().toLowerCase();
|
email = email.trim().toLowerCase();
|
||||||
let kdf: KdfType = null;
|
let kdf: KdfType = null;
|
||||||
|
|||||||
@@ -3,12 +3,13 @@ import * as signalRMsgPack from "@microsoft/signalr-protocol-msgpack";
|
|||||||
|
|
||||||
import { ApiService } from "../abstractions/api.service";
|
import { ApiService } from "../abstractions/api.service";
|
||||||
import { AppIdService } from "../abstractions/appId.service";
|
import { AppIdService } from "../abstractions/appId.service";
|
||||||
|
import { AuthService } from "../abstractions/auth.service";
|
||||||
import { EnvironmentService } from "../abstractions/environment.service";
|
import { EnvironmentService } from "../abstractions/environment.service";
|
||||||
import { LogService } from "../abstractions/log.service";
|
import { LogService } from "../abstractions/log.service";
|
||||||
import { NotificationsService as NotificationsServiceAbstraction } from "../abstractions/notifications.service";
|
import { NotificationsService as NotificationsServiceAbstraction } from "../abstractions/notifications.service";
|
||||||
import { StateService } from "../abstractions/state.service";
|
import { StateService } from "../abstractions/state.service";
|
||||||
import { SyncService } from "../abstractions/sync.service";
|
import { SyncService } from "../abstractions/sync.service";
|
||||||
import { VaultTimeoutService } from "../abstractions/vaultTimeout.service";
|
import { AuthenticationStatus } from "../enums/authenticationStatus";
|
||||||
import { NotificationType } from "../enums/notificationType";
|
import { NotificationType } from "../enums/notificationType";
|
||||||
import {
|
import {
|
||||||
NotificationResponse,
|
NotificationResponse,
|
||||||
@@ -29,11 +30,11 @@ export class NotificationsService implements NotificationsServiceAbstraction {
|
|||||||
private syncService: SyncService,
|
private syncService: SyncService,
|
||||||
private appIdService: AppIdService,
|
private appIdService: AppIdService,
|
||||||
private apiService: ApiService,
|
private apiService: ApiService,
|
||||||
private vaultTimeoutService: VaultTimeoutService,
|
|
||||||
private environmentService: EnvironmentService,
|
private environmentService: EnvironmentService,
|
||||||
private logoutCallback: (expired: boolean) => Promise<void>,
|
private logoutCallback: (expired: boolean) => Promise<void>,
|
||||||
private logService: LogService,
|
private logService: LogService,
|
||||||
private stateService: StateService
|
private stateService: StateService,
|
||||||
|
private authService: AuthService
|
||||||
) {
|
) {
|
||||||
this.environmentService.urls.subscribe(() => {
|
this.environmentService.urls.subscribe(() => {
|
||||||
if (!this.inited) {
|
if (!this.inited) {
|
||||||
@@ -216,11 +217,8 @@ export class NotificationsService implements NotificationsServiceAbstraction {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private async isAuthedAndUnlocked() {
|
private async isAuthedAndUnlocked() {
|
||||||
if (await this.stateService.getIsAuthenticated()) {
|
const authStatus = await this.authService.getAuthStatus();
|
||||||
const locked = await this.vaultTimeoutService.isLocked();
|
return authStatus >= AuthenticationStatus.Unlocked;
|
||||||
return !locked;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private random(min: number, max: number) {
|
private random(min: number, max: number) {
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import { AuthService } from "../abstractions/auth.service";
|
||||||
import { CipherService } from "../abstractions/cipher.service";
|
import { CipherService } from "../abstractions/cipher.service";
|
||||||
import { CollectionService } from "../abstractions/collection.service";
|
import { CollectionService } from "../abstractions/collection.service";
|
||||||
import { CryptoService } from "../abstractions/crypto.service";
|
import { CryptoService } from "../abstractions/crypto.service";
|
||||||
@@ -10,7 +11,7 @@ import { SearchService } from "../abstractions/search.service";
|
|||||||
import { StateService } from "../abstractions/state.service";
|
import { StateService } from "../abstractions/state.service";
|
||||||
import { TokenService } from "../abstractions/token.service";
|
import { TokenService } from "../abstractions/token.service";
|
||||||
import { VaultTimeoutService as VaultTimeoutServiceAbstraction } from "../abstractions/vaultTimeout.service";
|
import { VaultTimeoutService as VaultTimeoutServiceAbstraction } from "../abstractions/vaultTimeout.service";
|
||||||
import { KeySuffixOptions } from "../enums/keySuffixOptions";
|
import { AuthenticationStatus } from "../enums/authenticationStatus";
|
||||||
import { PolicyType } from "../enums/policyType";
|
import { PolicyType } from "../enums/policyType";
|
||||||
|
|
||||||
export class VaultTimeoutService implements VaultTimeoutServiceAbstraction {
|
export class VaultTimeoutService implements VaultTimeoutServiceAbstraction {
|
||||||
@@ -28,6 +29,7 @@ export class VaultTimeoutService implements VaultTimeoutServiceAbstraction {
|
|||||||
private policyService: PolicyService,
|
private policyService: PolicyService,
|
||||||
private keyConnectorService: KeyConnectorService,
|
private keyConnectorService: KeyConnectorService,
|
||||||
private stateService: StateService,
|
private stateService: StateService,
|
||||||
|
private authService: AuthService,
|
||||||
private lockedCallback: (userId?: string) => Promise<void> = null,
|
private lockedCallback: (userId?: string) => Promise<void> = null,
|
||||||
private loggedOutCallback: (expired: boolean, userId?: string) => Promise<void> = null
|
private loggedOutCallback: (expired: boolean, userId?: string) => Promise<void> = null
|
||||||
) {}
|
) {}
|
||||||
@@ -48,20 +50,6 @@ export class VaultTimeoutService implements VaultTimeoutServiceAbstraction {
|
|||||||
setInterval(() => this.checkVaultTimeout(), 10 * 1000); // check every 10 seconds
|
setInterval(() => this.checkVaultTimeout(), 10 * 1000); // check every 10 seconds
|
||||||
}
|
}
|
||||||
|
|
||||||
// Keys aren't stored for a device that is locked or logged out.
|
|
||||||
async isLocked(userId?: string): Promise<boolean> {
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
|
|
||||||
return !(await this.cryptoService.hasKeyInMemory(userId));
|
|
||||||
}
|
|
||||||
|
|
||||||
async checkVaultTimeout(): Promise<void> {
|
async checkVaultTimeout(): Promise<void> {
|
||||||
if (await this.platformUtilsService.isViewOpen()) {
|
if (await this.platformUtilsService.isViewOpen()) {
|
||||||
return;
|
return;
|
||||||
@@ -187,16 +175,12 @@ export class VaultTimeoutService implements VaultTimeoutServiceAbstraction {
|
|||||||
await this.stateService.setProtectedPin(null, { userId: userId });
|
await this.stateService.setProtectedPin(null, { userId: userId });
|
||||||
}
|
}
|
||||||
|
|
||||||
private async isLoggedOut(userId?: string): Promise<boolean> {
|
|
||||||
return !(await this.stateService.getIsAuthenticated({ userId: userId }));
|
|
||||||
}
|
|
||||||
|
|
||||||
private async shouldLock(userId: string): Promise<boolean> {
|
private async shouldLock(userId: string): Promise<boolean> {
|
||||||
if (await this.isLoggedOut(userId)) {
|
const authStatus = await this.authService.getAuthStatus(userId);
|
||||||
return false;
|
if (
|
||||||
}
|
authStatus === AuthenticationStatus.Locked ||
|
||||||
|
authStatus === AuthenticationStatus.LoggedOut
|
||||||
if (await this.isLocked(userId)) {
|
) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user