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"
>
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";