diff --git a/.eslintrc.json b/.eslintrc.json index 72b65d361d0..cd1a22c5cca 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -324,6 +324,26 @@ "rules": { "no-restricted-imports": ["error", { "patterns": ["@bitwarden/bit-common/*", "src/**/*"] }] } + }, + { + "files": ["apps/**/*.ts"], + "rules": { + // Catches static imports + "no-restricted-imports": [ + "error", + { + "patterns": ["biwarden_license/**", "@bitwarden/bit-common/*", "@bitwarden/bit-web/*"] + } + ], + // Catches dynamic imports, e.g. in routing modules where modules are lazy-loaded + "no-restricted-syntax": [ + "error", + { + "message": "Don't import Bitwarden licensed code into OSS code.", + "selector": "ImportExpression > Literal.source[value=/.*(bitwarden_license|bit-common|bit-web).*/]" + } + ] + } } ] } diff --git a/apps/browser/src/platform/popup/layout/popup-header.component.html b/apps/browser/src/platform/popup/layout/popup-header.component.html index 55caf1b91e7..e866ba4e81f 100644 --- a/apps/browser/src/platform/popup/layout/popup-header.component.html +++ b/apps/browser/src/platform/popup/layout/popup-header.component.html @@ -9,7 +9,7 @@ *ngIf="showBackButton" [title]="'back' | i18n" [ariaLabel]="'back' | i18n" - (click)="back()" + [bitAction]="backAction" >

{{ pageTitle }}

diff --git a/apps/browser/src/platform/popup/layout/popup-header.component.ts b/apps/browser/src/platform/popup/layout/popup-header.component.ts index f2f8eb95af0..1b491ea881c 100644 --- a/apps/browser/src/platform/popup/layout/popup-header.component.ts +++ b/apps/browser/src/platform/popup/layout/popup-header.component.ts @@ -3,13 +3,18 @@ import { CommonModule, Location } from "@angular/common"; import { Component, Input } from "@angular/core"; import { JslibModule } from "@bitwarden/angular/jslib.module"; -import { IconButtonModule, TypographyModule } from "@bitwarden/components"; +import { + AsyncActionsModule, + FunctionReturningAwaitable, + IconButtonModule, + TypographyModule, +} from "@bitwarden/components"; @Component({ selector: "popup-header", templateUrl: "popup-header.component.html", standalone: true, - imports: [TypographyModule, CommonModule, IconButtonModule, JslibModule], + imports: [TypographyModule, CommonModule, IconButtonModule, JslibModule, AsyncActionsModule], }) export class PopupHeaderComponent { /** Display the back button, which uses Location.back() to go back one page in history */ @@ -26,9 +31,15 @@ export class PopupHeaderComponent { /** Title string that will be inserted as an h1 */ @Input({ required: true }) pageTitle: string; - constructor(private location: Location) {} - - back() { + /** + * Async action that occurs when clicking the back button + * + * If unset, will call `location.back()` + **/ + @Input() + backAction: FunctionReturningAwaitable = async () => { this.location.back(); - } + }; + + constructor(private location: Location) {} } diff --git a/apps/web/src/app/auth/organization-invite/accept-organization.service.ts b/apps/web/src/app/auth/organization-invite/accept-organization.service.ts index e43023c37d7..d1ffa61f6a9 100644 --- a/apps/web/src/app/auth/organization-invite/accept-organization.service.ts +++ b/apps/web/src/app/auth/organization-invite/accept-organization.service.ts @@ -32,17 +32,17 @@ import { OrganizationInvite } from "./organization-invite"; // We're storing the organization invite for 2 reasons: // 1. If the org requires a MP policy check, we need to keep track that the user has already been redirected when they return. // 2. The MP policy check happens on login/register flows, we need to store the token to retrieve the policies then. -export const ORGANIZATION_INVITE = new KeyDefinition( +export const ORGANIZATION_INVITE = new KeyDefinition( ORGANIZATION_INVITE_DISK, "organizationInvite", { - deserializer: (invite) => OrganizationInvite.fromJSON(invite), + deserializer: (invite) => (invite ? OrganizationInvite.fromJSON(invite) : null), }, ); @Injectable() export class AcceptOrganizationInviteService { - private organizationInvitationState: GlobalState; + private organizationInvitationState: GlobalState; private orgNameSubject: BehaviorSubject = new BehaviorSubject(null); private policyCache: Policy[]; @@ -66,7 +66,7 @@ export class AcceptOrganizationInviteService { } /** Returns the currently stored organization invite */ - async getOrganizationInvite(): Promise { + async getOrganizationInvite(): Promise { return await firstValueFrom(this.organizationInvitationState.state$); } diff --git a/apps/web/src/app/auth/organization-invite/organization-invite.ts b/apps/web/src/app/auth/organization-invite/organization-invite.ts index 9a0bbf83348..ec90fe96d5e 100644 --- a/apps/web/src/app/auth/organization-invite/organization-invite.ts +++ b/apps/web/src/app/auth/organization-invite/organization-invite.ts @@ -11,11 +11,19 @@ export class OrganizationInvite { organizationUserId: string; token: string; - static fromJSON(json: Jsonify) { + static fromJSON(json: Jsonify): OrganizationInvite | null { + if (json == null) { + return null; + } + return Object.assign(new OrganizationInvite(), json); } - static fromParams(params: Params): OrganizationInvite { + static fromParams(params: Params): OrganizationInvite | null { + if (params == null) { + return null; + } + return Object.assign(new OrganizationInvite(), { email: params.email, initOrganization: params.initOrganization?.toLocaleLowerCase() === "true", diff --git a/bitwarden_license/bit-cli/src/admin-console/device-approval/approve.command.ts b/bitwarden_license/bit-cli/src/admin-console/device-approval/approve.command.ts index 918bd077b05..dc6a6d8e906 100644 --- a/bitwarden_license/bit-cli/src/admin-console/device-approval/approve.command.ts +++ b/bitwarden_license/bit-cli/src/admin-console/device-approval/approve.command.ts @@ -43,7 +43,7 @@ export class ApproveCommand { const request = pendingRequests.find((r) => r.id == id); if (request == null) { - return Response.error("Invalid request id"); + return Response.error("The request id is invalid."); } await this.organizationAuthRequestService.approvePendingRequest(organizationId, request); diff --git a/bitwarden_license/bit-cli/src/admin-console/device-approval/deny.command.ts b/bitwarden_license/bit-cli/src/admin-console/device-approval/deny.command.ts index 3470baaa25e..cf2356b1950 100644 --- a/bitwarden_license/bit-cli/src/admin-console/device-approval/deny.command.ts +++ b/bitwarden_license/bit-cli/src/admin-console/device-approval/deny.command.ts @@ -38,10 +38,16 @@ export class DenyCommand { } try { - await this.organizationAuthRequestService.denyPendingRequests(organizationId, id); + await this.organizationAuthRequestService.denyPendingRequest(organizationId, id); return Response.success(); - } catch (e) { - return Response.error(e); + } catch (error) { + if (error?.statusCode === 404) { + return Response.error( + "The request id is invalid or you do not have permission to update it.", + ); + } + + return Response.error(error); } } diff --git a/bitwarden_license/bit-common/src/admin-console/auth-requests/organization-auth-request-api.service.ts b/bitwarden_license/bit-common/src/admin-console/auth-requests/organization-auth-request-api.service.ts index 3387b7d54ed..4995df79922 100644 --- a/bitwarden_license/bit-common/src/admin-console/auth-requests/organization-auth-request-api.service.ts +++ b/bitwarden_license/bit-common/src/admin-console/auth-requests/organization-auth-request-api.service.ts @@ -61,4 +61,14 @@ export class OrganizationAuthRequestApiService { false, ); } + + async denyPendingRequest(organizationId: string, requestId: string): Promise { + await this.apiService.send( + "POST", + `/organizations/${organizationId}/auth-requests/${requestId}`, + new AdminAuthRequestUpdateRequest(false), + true, + false, + ); + } } diff --git a/bitwarden_license/bit-common/src/admin-console/auth-requests/organization-auth-request.service.ts b/bitwarden_license/bit-common/src/admin-console/auth-requests/organization-auth-request.service.ts index c35b91396d0..245baf7e722 100644 --- a/bitwarden_license/bit-common/src/admin-console/auth-requests/organization-auth-request.service.ts +++ b/bitwarden_license/bit-common/src/admin-console/auth-requests/organization-auth-request.service.ts @@ -85,6 +85,10 @@ export class OrganizationAuthRequestService { ); } + async denyPendingRequest(organizationId: string, requestId: string) { + await this.organizationAuthRequestApiService.denyPendingRequest(organizationId, requestId); + } + /** * Creates a copy of the user key that has been encrypted with the provided device's public key. * @param organizationId diff --git a/libs/auth/src/common/services/login-strategies/login-strategy.state.ts b/libs/auth/src/common/services/login-strategies/login-strategy.state.ts index 90fcd89913f..5dc03755050 100644 --- a/libs/auth/src/common/services/login-strategies/login-strategy.state.ts +++ b/libs/auth/src/common/services/login-strategies/login-strategy.state.ts @@ -36,7 +36,7 @@ export const CACHE_EXPIRATION_KEY = new KeyDefinition( * foreground instance to send out the notification. * TODO: Move to Auth Request service. */ -export const AUTH_REQUEST_PUSH_NOTIFICATION_KEY = new KeyDefinition( +export const AUTH_REQUEST_PUSH_NOTIFICATION_KEY = new KeyDefinition( LOGIN_STRATEGY_MEMORY, "authRequestPushNotification", { diff --git a/libs/common/src/auth/services/device-trust.service.implementation.ts b/libs/common/src/auth/services/device-trust.service.implementation.ts index 51461e653b1..b02ff114489 100644 --- a/libs/common/src/auth/services/device-trust.service.implementation.ts +++ b/libs/common/src/auth/services/device-trust.service.implementation.ts @@ -28,13 +28,18 @@ import { } from "../models/request/update-devices-trust.request"; /** Uses disk storage so that the device key can persist after log out and tab removal. */ -export const DEVICE_KEY = new UserKeyDefinition(DEVICE_TRUST_DISK_LOCAL, "deviceKey", { - deserializer: (deviceKey) => SymmetricCryptoKey.fromJSON(deviceKey) as DeviceKey, - clearOn: [], // Device key is needed to log back into device, so we can't clear it automatically during lock or logout -}); +export const DEVICE_KEY = new UserKeyDefinition( + DEVICE_TRUST_DISK_LOCAL, + "deviceKey", + { + deserializer: (deviceKey) => + deviceKey ? (SymmetricCryptoKey.fromJSON(deviceKey) as DeviceKey) : null, + clearOn: [], // Device key is needed to log back into device, so we can't clear it automatically during lock or logout + }, +); /** Uses disk storage so that the shouldTrustDevice bool can persist across login. */ -export const SHOULD_TRUST_DEVICE = new UserKeyDefinition( +export const SHOULD_TRUST_DEVICE = new UserKeyDefinition( DEVICE_TRUST_DISK_LOCAL, "shouldTrustDevice", { diff --git a/libs/common/src/auth/services/key-connector.service.ts b/libs/common/src/auth/services/key-connector.service.ts index 6b81844afb4..a70bbb6ffb0 100644 --- a/libs/common/src/auth/services/key-connector.service.ts +++ b/libs/common/src/auth/services/key-connector.service.ts @@ -29,7 +29,7 @@ import { KeyConnectorUserKeyRequest } from "../models/request/key-connector-user import { SetKeyConnectorKeyRequest } from "../models/request/set-key-connector-key.request"; import { IdentityTokenResponse } from "../models/response/identity-token.response"; -export const USES_KEY_CONNECTOR = new UserKeyDefinition( +export const USES_KEY_CONNECTOR = new UserKeyDefinition( KEY_CONNECTOR_DISK, "usesKeyConnector", { @@ -38,7 +38,7 @@ export const USES_KEY_CONNECTOR = new UserKeyDefinition( }, ); -export const CONVERT_ACCOUNT_TO_KEY_CONNECTOR = new UserKeyDefinition( +export const CONVERT_ACCOUNT_TO_KEY_CONNECTOR = new UserKeyDefinition( KEY_CONNECTOR_DISK, "convertAccountToKeyConnector", { diff --git a/libs/components/src/index.ts b/libs/components/src/index.ts index 9e9329b2158..6881d801e0f 100644 --- a/libs/components/src/index.ts +++ b/libs/components/src/index.ts @@ -35,4 +35,4 @@ export * from "./tabs"; export * from "./toast"; export * from "./toggle-group"; export * from "./typography"; -export * from "./utils/i18n-mock.service"; +export * from "./utils"; diff --git a/libs/components/src/utils/index.ts b/libs/components/src/utils/index.ts new file mode 100644 index 00000000000..afadd6b3b41 --- /dev/null +++ b/libs/components/src/utils/index.ts @@ -0,0 +1,2 @@ +export * from "./function-to-observable"; +export * from "./i18n-mock.service";