import * as plugins from '../ts/plugins.js'; export const flexProviderCredentialStoreId = 'flex-provider-credentials.v1'; export const flexProviderCredentialConnectionLimit = 512; export interface IFlexProviderCredentialStore { getEntry(accountArg: string): Promise; setEntry(accountArg: string, valueArg: Uint8Array): Promise; deleteEntry(accountArg: string): Promise; } export interface IFlexProviderCredentialKernelStore extends IFlexProviderCredentialStore { deleteEntryIfValue( accountArg: string, expectedValueArg: Uint8Array, ): Promise<'deleted' | 'alreadyAbsent'>; } export interface IFlexProviderCredentialStorePair< TKernelStore extends IFlexProviderCredentialKernelStore = IFlexProviderCredentialKernelStore, TSealedStore extends IFlexProviderCredentialStore = IFlexProviderCredentialStore, > { kernelStore: TKernelStore; sealedStore: TSealedStore; } export interface IFlexProviderCredentialMigrationOptions< TKernelStore extends IFlexProviderCredentialKernelStore, TSealedStore extends IFlexProviderCredentialStore, > { stores: IFlexProviderCredentialStorePair; recreateStores: ( storesArg: IFlexProviderCredentialStorePair, ) => Promise>; connectionIds: Iterable; } export type TFlexProviderCredentialMigrationErrorCode = | 'CONNECTION_LIMIT_EXCEEDED' | 'CREDENTIAL_CONFLICT' | 'CREDENTIAL_INVALID' | 'MUTATION_RECONCILIATION_FAILED'; export class FlexProviderCredentialMigrationError extends Error { constructor(public readonly code: TFlexProviderCredentialMigrationErrorCode) { super(`Flex provider credential migration failed (${code}).`); this.name = 'FlexProviderCredentialMigrationError'; } } const maximumMutationAttempts = 2; const credentialAccount = (connectionIdArg: string): string => `provider:${connectionIdArg}`; const isMutationOutcomeUnknown = (errorArg: unknown): boolean => ( errorArg instanceof plugins.smartsecret.SmartSecretKernelStoreError || errorArg instanceof plugins.smartsecret.SmartSecretSealedFileStoreError ) && errorArg.code === 'MUTATION_OUTCOME_UNKNOWN'; const credentialBytesEqual = (leftArg: Uint8Array, rightArg: Uint8Array): boolean => leftArg.byteLength === rightArg.byteLength && plugins.crypto.timingSafeEqual(leftArg, rightArg); const validateCredentialBytes = (bytesArg: Uint8Array): void => { try { const credential = plugins.openAiAccount.parseProviderCredential(bytesArg); if (credential.kind !== 'chatgptOAuth' || credential.providerId !== 'openai') { throw new FlexProviderCredentialMigrationError('CREDENTIAL_INVALID'); } } catch (errorArg) { if (errorArg instanceof FlexProviderCredentialMigrationError) throw errorArg; throw new FlexProviderCredentialMigrationError('CREDENTIAL_INVALID'); } }; const readValidatedCredential = async ( storeArg: IFlexProviderCredentialStore, accountArg: string, ): Promise => { const bytes = await storeArg.getEntry(accountArg); if (!bytes) return null; try { validateCredentialBytes(bytes); return bytes; } catch (errorArg) { bytes.fill(0); throw errorArg; } }; const deleteLegacyCredentialEntry = async < TKernelStore extends IFlexProviderCredentialKernelStore, TSealedStore extends IFlexProviderCredentialStore, >( optionsArg: Pick< IFlexProviderCredentialMigrationOptions, 'stores' | 'recreateStores' >, accountArg: string, expectedBytesArg: Uint8Array, ): Promise> => { let stores = optionsArg.stores; let requiresConfirmedRetry = false; for (let attempt = 0; attempt < maximumMutationAttempts; attempt += 1) { let outcomeWasUnknown = false; try { const status = await stores.kernelStore.deleteEntryIfValue(accountArg, expectedBytesArg); if (status !== 'deleted' && status !== 'alreadyAbsent') { throw new FlexProviderCredentialMigrationError('MUTATION_RECONCILIATION_FAILED'); } } catch (errorArg) { if ( errorArg instanceof plugins.smartsecret.SmartSecretKernelStoreError && errorArg.code === 'SOURCE_CHANGED' ) throw new FlexProviderCredentialMigrationError('CREDENTIAL_CONFLICT'); if (!isMutationOutcomeUnknown(errorArg)) throw errorArg; stores = await optionsArg.recreateStores(stores); outcomeWasUnknown = true; requiresConfirmedRetry = true; } const readback = await readValidatedCredential(stores.kernelStore, accountArg); try { if (!readback) { if (!requiresConfirmedRetry || !outcomeWasUnknown) return stores; continue; } if (!credentialBytesEqual(readback, expectedBytesArg)) { throw new FlexProviderCredentialMigrationError('CREDENTIAL_CONFLICT'); } if (!outcomeWasUnknown || attempt === maximumMutationAttempts - 1) { throw new FlexProviderCredentialMigrationError('MUTATION_RECONCILIATION_FAILED'); } } finally { readback?.fill(0); } } throw new FlexProviderCredentialMigrationError('MUTATION_RECONCILIATION_FAILED'); }; export const migrateFlexProviderCredentials = async < TKernelStore extends IFlexProviderCredentialKernelStore, TSealedStore extends IFlexProviderCredentialStore, >( optionsArg: IFlexProviderCredentialMigrationOptions, signalArg?: AbortSignal, ): Promise> => { const connectionIds = [...new Set(optionsArg.connectionIds)].sort(); if (connectionIds.length > flexProviderCredentialConnectionLimit) { throw new FlexProviderCredentialMigrationError('CONNECTION_LIMIT_EXCEEDED'); } let stores = optionsArg.stores; for (const connectionId of connectionIds) { signalArg?.throwIfAborted(); const account = credentialAccount(connectionId); let sourceBytes: Uint8Array | null = null; let sealedBytes: Uint8Array | null = null; try { sourceBytes = await readValidatedCredential(stores.kernelStore, account); sealedBytes = await readValidatedCredential(stores.sealedStore, account); if (!sourceBytes) continue; if (sealedBytes && !credentialBytesEqual(sourceBytes, sealedBytes)) { throw new FlexProviderCredentialMigrationError('CREDENTIAL_CONFLICT'); } if (!sealedBytes) { let copied = false; let requiresConfirmedRetry = false; for (let attempt = 0; attempt < maximumMutationAttempts; attempt += 1) { let outcomeWasUnknown = false; try { await stores.sealedStore.setEntry(account, sourceBytes); } catch (errorArg) { if (!isMutationOutcomeUnknown(errorArg)) throw errorArg; stores = await optionsArg.recreateStores(stores); outcomeWasUnknown = true; requiresConfirmedRetry = true; } const readback = await readValidatedCredential(stores.sealedStore, account); try { if (readback && credentialBytesEqual(readback, sourceBytes)) { if (!requiresConfirmedRetry || !outcomeWasUnknown) { copied = true; break; } continue; } if (readback) { throw new FlexProviderCredentialMigrationError('CREDENTIAL_CONFLICT'); } if (!outcomeWasUnknown || attempt === maximumMutationAttempts - 1) { throw new FlexProviderCredentialMigrationError('MUTATION_RECONCILIATION_FAILED'); } } finally { readback?.fill(0); } } if (!copied) { throw new FlexProviderCredentialMigrationError('MUTATION_RECONCILIATION_FAILED'); } } stores = await deleteLegacyCredentialEntry({ stores, recreateStores: optionsArg.recreateStores, }, account, sourceBytes); } finally { sourceBytes?.fill(0); sealedBytes?.fill(0); } } return stores; };