1
0
mirror of https://github.com/bitwarden/jslib synced 2025-12-23 11:43:51 +00:00

Merge branch 'master' of https://github.com/bitwarden/jslib into feature/additional-item-types-scaffold

This commit is contained in:
Hinton
2022-04-29 10:38:28 +02:00
120 changed files with 22066 additions and 7968 deletions

View File

@@ -6,7 +6,6 @@ module.exports = {
name: "angular",
displayName: "angular tests",
preset: "jest-preset-angular",
roots: ["<rootDir>/spec/"],
testMatch: ["**/+(*.)+(spec).+(ts)"],
setupFilesAfterEnv: ["<rootDir>/spec/test.ts"],
collectCoverage: true,

View File

@@ -3,6 +3,7 @@ import { ActivatedRoute } from "@angular/router";
import { first } from "rxjs/operators";
import { I18nService } from "jslib-common/abstractions/i18n.service";
import { LogService } from "jslib-common/abstractions/log.service";
import { PasswordGenerationService } from "jslib-common/abstractions/passwordGeneration.service";
import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service";
import { StateService } from "jslib-common/abstractions/state.service";
@@ -15,6 +16,7 @@ export class GeneratorComponent implements OnInit {
@Input() type: string;
@Output() onSelected = new EventEmitter<string>();
usernameGeneratingPromise: Promise<string>;
typeOptions: any[];
passTypeOptions: any[];
usernameTypeOptions: any[];
@@ -36,6 +38,7 @@ export class GeneratorComponent implements OnInit {
protected platformUtilsService: PlatformUtilsService,
protected stateService: StateService,
protected i18nService: I18nService,
protected logService: LogService,
protected route: ActivatedRoute,
private win: Window
) {
@@ -58,13 +61,20 @@ export class GeneratorComponent implements OnInit {
value: "catchall",
desc: i18nService.t("catchallEmailDesc"),
},
{
name: i18nService.t("forwardedEmail"),
value: "forwarded",
desc: i18nService.t("forwardedEmailDesc"),
},
{ name: i18nService.t("randomWord"), value: "word" },
];
this.subaddressOptions = [{ name: i18nService.t("random"), value: "random" }];
this.catchallOptions = [{ name: i18nService.t("random"), value: "random" }];
this.forwardOptions = [
{ name: "SimpleLogin", value: "simplelogin" },
{ name: "FastMail", value: "fastmail" },
{ name: "AnonAddy", value: "anonaddy" },
{ name: "Firefox Relay", value: "firefoxrelay" },
// { name: "FastMail", value: "fastmail" },
];
}
@@ -104,13 +114,17 @@ export class GeneratorComponent implements OnInit {
this.type = generatorOptions?.type ?? "password";
}
}
await this.regenerate();
if (this.regenerateWithoutButtonPress()) {
await this.regenerate();
}
});
}
async typeChanged() {
await this.stateService.setGeneratorOptions({ type: this.type });
await this.regenerate();
if (this.regenerateWithoutButtonPress()) {
await this.regenerate();
}
}
async regenerate() {
@@ -135,14 +149,17 @@ export class GeneratorComponent implements OnInit {
this.normalizePasswordOptions();
await this.passwordGenerationService.saveOptions(this.passwordOptions);
if (regenerate) {
if (regenerate && this.regenerateWithoutButtonPress()) {
await this.regeneratePassword();
}
}
async saveUsernameOptions(regenerate = true) {
await this.usernameGenerationService.saveOptions(this.usernameOptions);
if (regenerate) {
if (this.usernameOptions.type === "forwarded") {
this.username = "-";
}
if (regenerate && this.regenerateWithoutButtonPress()) {
await this.regenerateUsername();
}
}
@@ -157,9 +174,16 @@ export class GeneratorComponent implements OnInit {
}
async generateUsername() {
this.username = await this.usernameGenerationService.generateUsername(this.usernameOptions);
if (this.username === "" || this.username === null) {
this.username = "-";
try {
this.usernameGeneratingPromise = this.usernameGenerationService.generateUsername(
this.usernameOptions
);
this.username = await this.usernameGeneratingPromise;
if (this.username === "" || this.username === null) {
this.username = "-";
}
} catch (e) {
this.logService.error(e);
}
}
@@ -185,6 +209,10 @@ export class GeneratorComponent implements OnInit {
this.showOptions = !this.showOptions;
}
regenerateWithoutButtonPress() {
return this.type !== "username" || this.usernameOptions.type !== "forwarded";
}
private normalizePasswordOptions() {
// Application level normalize options depedent on class variables
this.passwordOptions.ambiguous = !this.avoidAmbiguous;

View File

@@ -6,6 +6,10 @@ import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.se
import { ModalRef } from "./modal/modal.ref";
/**
* Used to verify the user's Master Password for the "Master Password Re-prompt" feature only.
* See UserVerificationComponent for any other situation where you need to verify the user's identity.
*/
@Directive()
export class PasswordRepromptComponent {
showPassword = false;

View File

@@ -4,6 +4,7 @@ import * as DuoWebSDK from "duo_web_sdk";
import { first } from "rxjs/operators";
import { ApiService } from "jslib-common/abstractions/api.service";
import { AppIdService } from "jslib-common/abstractions/appId.service";
import { AuthService } from "jslib-common/abstractions/auth.service";
import { EnvironmentService } from "jslib-common/abstractions/environment.service";
import { I18nService } from "jslib-common/abstractions/i18n.service";
@@ -57,7 +58,8 @@ export class TwoFactorComponent extends CaptchaProtectedComponent implements OnI
protected stateService: StateService,
protected route: ActivatedRoute,
protected logService: LogService,
protected twoFactorService: TwoFactorService
protected twoFactorService: TwoFactorService,
protected appIdService: AppIdService
) {
super(environmentService, i18nService, platformUtilsService);
this.webAuthnSupported = this.platformUtilsService.supportsWebAuthn(win);
@@ -234,6 +236,7 @@ export class TwoFactorComponent extends CaptchaProtectedComponent implements OnI
const request = new TwoFactorEmailRequest();
request.email = this.authService.email;
request.masterPasswordHash = this.authService.masterPasswordHash;
request.deviceIdentifier = await this.appIdService.getAppId();
this.emailPromise = this.apiService.postTwoFactorEmail(request);
await this.emailPromise;
if (doToast) {

View File

@@ -61,7 +61,6 @@ export class UpdatePasswordComponent extends BaseChangePasswordComponent {
async cancel() {
await this.stateService.setOrganizationInvitation(null);
await this.stateService.setLoginRedirect(null);
this.router.navigate(["/vault"]);
}

View File

@@ -7,14 +7,20 @@ import { UserVerificationService } from "jslib-common/abstractions/userVerificat
import { VerificationType } from "jslib-common/enums/verificationType";
import { Verification } from "jslib-common/types/verification";
/**
* Used for general-purpose user verification throughout the app.
* Collects the user's master password, or if they are using Key Connector, prompts for an OTP via email.
* This is exposed to the parent component via the ControlValueAccessor interface (e.g. bind it to a FormControl).
* Use UserVerificationService to verify the user's input.
*/
@Component({
selector: "app-verify-master-password",
templateUrl: "verify-master-password.component.html",
selector: "app-user-verification",
templateUrl: "user-verification.component.html",
providers: [
{
provide: NG_VALUE_ACCESSOR,
multi: true,
useExisting: VerifyMasterPasswordComponent,
useExisting: UserVerificationComponent,
},
],
animations: [
@@ -23,7 +29,7 @@ import { Verification } from "jslib-common/types/verification";
]),
],
})
export class VerifyMasterPasswordComponent implements ControlValueAccessor, OnInit {
export class UserVerificationComponent implements ControlValueAccessor, OnInit {
usesKeyConnector = false;
disableRequestOTP = false;
sentCode = false;
@@ -41,7 +47,7 @@ export class VerifyMasterPasswordComponent implements ControlValueAccessor, OnIn
this.usesKeyConnector = await this.keyConnectorService.getUsesKeyConnector();
this.processChanges(this.secret.value);
this.secret.valueChanges.subscribe((secret) => this.processChanges(secret));
this.secret.valueChanges.subscribe((secret: string) => this.processChanges(secret));
}
async requestOTP() {

View File

@@ -5,6 +5,12 @@ import { ErrorResponse } from "jslib-common/models/response/errorResponse";
import { ValidationService } from "../services/validation.service";
/**
* Provides error handling, in particular for any error returned by the server in an api call.
* Attach it to a <form> element and provide the name of the class property that will hold the api call promise.
* e.g. <form [appApiAction]="this.formPromise">
* Any errors/rejections that occur will be intercepted and displayed as error toasts.
*/
@Directive({
selector: "[appApiAction]",
})

View File

@@ -19,7 +19,7 @@ export class AuthGuardService implements CanActivate {
async canActivate(route: ActivatedRouteSnapshot, routerState: RouterStateSnapshot) {
const isAuthed = await this.stateService.getIsAuthenticated();
if (!isAuthed) {
this.messagingService.send("authBlocked");
this.messagingService.send("authBlocked", { url: routerState.url });
return false;
}
@@ -28,16 +28,14 @@ export class AuthGuardService implements CanActivate {
if (routerState != null) {
this.messagingService.send("lockedUrl", { url: routerState.url });
}
this.router.navigate(["lock"], { queryParams: { promptBiometric: true } });
return false;
return this.router.createUrlTree(["lock"], { queryParams: { promptBiometric: true } });
}
if (
!routerState.url.includes("remove-password") &&
(await this.keyConnectorService.getConvertAccountRequired())
) {
this.router.navigate(["/remove-password"]);
return false;
return this.router.createUrlTree(["/remove-password"]);
}
return true;

View File

@@ -1,4 +1,4 @@
import { Injector, LOCALE_ID, NgModule } from "@angular/core";
import { InjectionToken, Injector, LOCALE_ID, NgModule } from "@angular/core";
import { ApiService as ApiServiceAbstraction } from "jslib-common/abstractions/api.service";
import { AppIdService as AppIdServiceAbstraction } from "jslib-common/abstractions/appId.service";
@@ -82,20 +82,60 @@ import { PasswordRepromptService } from "./passwordReprompt.service";
import { UnauthGuardService } from "./unauth-guard.service";
import { ValidationService } from "./validation.service";
export const WINDOW = new InjectionToken<Window>("WINDOW");
export const SECURE_STORAGE = new InjectionToken<StorageServiceAbstraction>("SECURE_STORAGE");
export const STATE_FACTORY = new InjectionToken<StateFactory>("STATE_FACTORY");
export const STATE_SERVICE_USE_CACHE = new InjectionToken<boolean>("STATE_SERVICE_USE_CACHE");
export const LOGOUT_CALLBACK = new InjectionToken<(expired: boolean, userId?: string) => void>(
"LOGOUT_CALLBACK"
);
export const LOCKED_CALLBACK = new InjectionToken<() => void>("LOCKED_CALLBACK");
export const CLIENT_TYPE = new InjectionToken<boolean>("CLIENT_TYPE");
export const LOCALES_DIRECTORY = new InjectionToken<string>("LOCALES_DIRECTORY");
export const SYSTEM_LANGUAGE = new InjectionToken<string>("SYSTEM_LANGUAGE");
@NgModule({
declarations: [],
providers: [
{ provide: "WINDOW", useValue: window },
{
provide: LOCALE_ID,
useFactory: (i18nService: I18nServiceAbstraction) => i18nService.translationLocale,
deps: [I18nServiceAbstraction],
},
ValidationService,
AuthGuardService,
UnauthGuardService,
LockGuardService,
ModalService,
{ provide: WINDOW, useValue: window },
{
provide: LOCALE_ID,
useFactory: (i18nService: I18nServiceAbstraction) => i18nService.translationLocale,
deps: [I18nServiceAbstraction],
},
{
provide: LOCALES_DIRECTORY,
useValue: "./locales",
},
{
provide: SYSTEM_LANGUAGE,
useFactory: (window: Window) => window.navigator.language,
deps: [WINDOW],
},
{
provide: STATE_FACTORY,
useValue: new StateFactory(GlobalState, Account),
},
{
provide: STATE_SERVICE_USE_CACHE,
useValue: true,
},
{
provide: LOGOUT_CALLBACK,
useFactory:
(messagingService: MessagingServiceAbstraction) => (expired: boolean, userId?: string) =>
messagingService.send("logout", { expired: expired, userId: userId }),
deps: [MessagingServiceAbstraction],
},
{
provide: LOCKED_CALLBACK,
useValue: null,
},
{
provide: AppIdServiceAbstraction,
useClass: AppIdService,
@@ -203,30 +243,17 @@ import { ValidationService } from "./validation.service";
{
provide: UsernameGenerationServiceAbstraction,
useClass: UsernameGenerationService,
deps: [CryptoServiceAbstraction, StateServiceAbstraction],
deps: [CryptoServiceAbstraction, StateServiceAbstraction, ApiServiceAbstraction],
},
{
provide: ApiServiceAbstraction,
useFactory: (
tokenService: TokenServiceAbstraction,
platformUtilsService: PlatformUtilsServiceAbstraction,
environmentService: EnvironmentServiceAbstraction,
messagingService: MessagingServiceAbstraction,
appIdService: AppIdServiceAbstraction
) =>
new ApiService(
tokenService,
platformUtilsService,
environmentService,
appIdService,
async (expired: boolean) => messagingService.send("logout", { expired: expired })
),
useClass: ApiService,
deps: [
TokenServiceAbstraction,
PlatformUtilsServiceAbstraction,
EnvironmentServiceAbstraction,
MessagingServiceAbstraction,
AppIdServiceAbstraction,
LOGOUT_CALLBACK,
],
},
{
@@ -236,39 +263,7 @@ import { ValidationService } from "./validation.service";
},
{
provide: SyncServiceAbstraction,
useFactory: (
apiService: ApiServiceAbstraction,
settingsService: SettingsServiceAbstraction,
folderService: FolderServiceAbstraction,
cipherService: CipherServiceAbstraction,
cryptoService: CryptoServiceAbstraction,
collectionService: CollectionServiceAbstraction,
messagingService: MessagingServiceAbstraction,
policyService: PolicyServiceAbstraction,
sendService: SendServiceAbstraction,
logService: LogService,
keyConnectorService: KeyConnectorServiceAbstraction,
stateService: StateServiceAbstraction,
organizationService: OrganizationServiceAbstraction,
providerService: ProviderServiceAbstraction
) =>
new SyncService(
apiService,
settingsService,
folderService,
cipherService,
cryptoService,
collectionService,
messagingService,
policyService,
sendService,
logService,
keyConnectorService,
stateService,
organizationService,
providerService,
async (expired: boolean) => messagingService.send("logout", { expired: expired })
),
useClass: SyncService,
deps: [
ApiServiceAbstraction,
SettingsServiceAbstraction,
@@ -284,6 +279,7 @@ import { ValidationService } from "./validation.service";
StateServiceAbstraction,
OrganizationServiceAbstraction,
ProviderServiceAbstraction,
LOGOUT_CALLBACK,
],
},
{ provide: BroadcasterServiceAbstraction, useClass: BroadcasterService },
@@ -294,35 +290,7 @@ import { ValidationService } from "./validation.service";
},
{
provide: VaultTimeoutServiceAbstraction,
useFactory: (
cipherService: CipherServiceAbstraction,
folderService: FolderServiceAbstraction,
collectionService: CollectionServiceAbstraction,
cryptoService: CryptoServiceAbstraction,
platformUtilsService: PlatformUtilsServiceAbstraction,
messagingService: MessagingServiceAbstraction,
searchService: SearchServiceAbstraction,
tokenService: TokenServiceAbstraction,
policyService: PolicyServiceAbstraction,
keyConnectorService: KeyConnectorServiceAbstraction,
stateService: StateServiceAbstraction
) =>
new VaultTimeoutService(
cipherService,
folderService,
collectionService,
cryptoService,
platformUtilsService,
messagingService,
searchService,
tokenService,
policyService,
keyConnectorService,
stateService,
null,
async (userId?: string) =>
messagingService.send("logout", { expired: false, userId: userId })
),
useClass: VaultTimeoutService,
deps: [
CipherServiceAbstraction,
FolderServiceAbstraction,
@@ -335,42 +303,26 @@ import { ValidationService } from "./validation.service";
PolicyServiceAbstraction,
KeyConnectorServiceAbstraction,
StateServiceAbstraction,
LOCKED_CALLBACK,
LOGOUT_CALLBACK,
],
},
{
provide: StateServiceAbstraction,
useFactory: (
storageService: StorageServiceAbstraction,
secureStorageService: StorageServiceAbstraction,
logService: LogService,
stateMigrationService: StateMigrationServiceAbstraction
) =>
new StateService(
storageService,
secureStorageService,
logService,
stateMigrationService,
new StateFactory(GlobalState, Account)
),
useClass: StateService,
deps: [
StorageServiceAbstraction,
"SECURE_STORAGE",
SECURE_STORAGE,
LogService,
StateMigrationServiceAbstraction,
STATE_FACTORY,
STATE_SERVICE_USE_CACHE,
],
},
{
provide: StateMigrationServiceAbstraction,
useFactory: (
storageService: StorageServiceAbstraction,
secureStorageService: StorageServiceAbstraction
) =>
new StateMigrationService(
storageService,
secureStorageService,
new StateFactory(GlobalState, Account)
),
deps: [StorageServiceAbstraction, "SECURE_STORAGE"],
useClass: StateMigrationService,
deps: [StorageServiceAbstraction, SECURE_STORAGE, STATE_FACTORY],
},
{
provide: ExportServiceAbstraction,
@@ -389,33 +341,14 @@ import { ValidationService } from "./validation.service";
},
{
provide: NotificationsServiceAbstraction,
useFactory: (
syncService: SyncServiceAbstraction,
appIdService: AppIdServiceAbstraction,
apiService: ApiServiceAbstraction,
vaultTimeoutService: VaultTimeoutServiceAbstraction,
environmentService: EnvironmentServiceAbstraction,
messagingService: MessagingServiceAbstraction,
logService: LogService,
stateService: StateServiceAbstraction
) =>
new NotificationsService(
syncService,
appIdService,
apiService,
vaultTimeoutService,
environmentService,
async () => messagingService.send("logout", { expired: true }),
logService,
stateService
),
useClass: NotificationsService,
deps: [
SyncServiceAbstraction,
AppIdServiceAbstraction,
ApiServiceAbstraction,
VaultTimeoutServiceAbstraction,
EnvironmentServiceAbstraction,
MessagingServiceAbstraction,
LOGOUT_CALLBACK,
LogService,
StateServiceAbstraction,
],
@@ -423,7 +356,7 @@ import { ValidationService } from "./validation.service";
{
provide: CryptoFunctionServiceAbstraction,
useClass: WebCryptoFunctionService,
deps: ["WINDOW"],
deps: [WINDOW],
},
{
provide: EventServiceAbstraction,

View File

@@ -7,6 +7,10 @@ import { PasswordRepromptComponent } from "../components/password-reprompt.compo
import { ModalService } from "./modal.service";
/**
* Used to verify the user's Master Password for the "Master Password Re-prompt" feature only.
* See UserVerificationService for any other situation where you need to verify the user's identity.
*/
@Injectable()
export class PasswordRepromptService implements PasswordRepromptServiceAbstraction {
protected component = PasswordRepromptComponent;

View File

@@ -18,11 +18,9 @@ export class UnauthGuardService implements CanActivate {
if (isAuthed) {
const locked = await this.vaultTimeoutService.isLocked();
if (locked) {
this.router.navigate(["lock"]);
} else {
this.router.navigate([this.homepage]);
return this.router.createUrlTree(["lock"]);
}
return false;
return this.router.createUrlTree([this.homepage]);
}
return true;
}