1
0
mirror of https://github.com/bitwarden/browser synced 2025-12-19 17:53:39 +00:00

fix(token-service) [PM-15333]: Portable App Is Not Portable (#17781)

* feat(token-service) [PM-15333]: Update Portable secure storage resolution to use disk.

* feat(token-service) [PM-15333]: Move isWindowsPortable evaluation to preload with other platform evaluations.
This commit is contained in:
Dave
2025-12-11 15:03:10 -05:00
committed by GitHub
parent 22e9c6a72f
commit 4576a52fd1
3 changed files with 22 additions and 4 deletions

View File

@@ -203,8 +203,16 @@ const safeProviders: SafeProvider[] = [
// We manually override the value of SUPPORTS_SECURE_STORAGE here to avoid
// the TokenService having to inject the PlatformUtilsService which introduces a
// circular dependency on Desktop only.
//
// For Windows portable builds, we disable secure storage to ensure tokens are
// stored on disk (in bitwarden-appdata) rather than in Windows Credential
// Manager, making them portable across machines. This allows users to move the USB drive
// between computers while maintaining authentication.
//
// Note: Portable mode does not use secure storage for read/write/clear operations,
// preventing any collision with tokens from a regular desktop installation.
provide: SUPPORTS_SECURE_STORAGE,
useValue: ELECTRON_SUPPORTS_SECURE_STORAGE,
useValue: ELECTRON_SUPPORTS_SECURE_STORAGE && !ipc.platform.isWindowsPortable,
}),
safeProvider({
provide: DEFAULT_VAULT_TIMEOUT,

View File

@@ -17,6 +17,7 @@ import {
isFlatpak,
isMacAppStore,
isSnapStore,
isWindowsPortable,
isWindowsStore,
} from "../utils";
@@ -133,6 +134,7 @@ export default {
isDev: isDev(),
isMacAppStore: isMacAppStore(),
isWindowsStore: isWindowsStore(),
isWindowsPortable: isWindowsPortable(),
isFlatpak: isFlatpak(),
isSnapStore: isSnapStore(),
isAppImage: isAppImage(),

View File

@@ -445,13 +445,15 @@ export class TokenService implements TokenServiceAbstraction {
// we can't determine storage location w/out vaultTimeoutAction and vaultTimeout
// but we can simply clear all locations to avoid the need to require those parameters.
// When secure storage is supported, clear the encryption key from secure storage.
// When not supported (e.g., portable builds), tokens are stored on disk and this step is skipped.
if (this.platformSupportsSecureStorage) {
// Always clear the access token key when clearing the access token
// The next set of the access token will create a new access token key
// Always clear the access token key when clearing the access token.
// The next set of the access token will create a new access token key.
await this.clearAccessTokenKey(userId);
}
// Platform doesn't support secure storage, so use state provider implementation
// Clear tokens from disk storage (all platforms)
await this.singleUserStateProvider.get(userId, ACCESS_TOKEN_DISK).update((_) => null, {
shouldUpdate: (previousValue) => previousValue !== null,
});
@@ -478,6 +480,9 @@ export class TokenService implements TokenServiceAbstraction {
return null;
}
// When platformSupportsSecureStorage=true, tokens on disk are encrypted and require
// decryption keys from secure storage. When false (e.g., portable builds), tokens are
// stored on disk.
if (this.platformSupportsSecureStorage) {
let accessTokenKey: AccessTokenKey;
try {
@@ -1118,6 +1123,9 @@ export class TokenService implements TokenServiceAbstraction {
) {
return TokenStorageLocation.Memory;
} else {
// Secure storage (e.g., OS credential manager) is preferred when available.
// Desktop portable builds set platformSupportsSecureStorage=false to store tokens
// on disk for portability across machines.
if (useSecureStorage && this.platformSupportsSecureStorage) {
return TokenStorageLocation.SecureStorage;
}