/** * @license * Copyright 2025 Steven Roussey * SPDX-License-Identifier: Apache-2.0 */ import type { CredentialPutOptions, ICredentialStore } from "@workglow/util"; import type { IKvStorage } from "../kv/IKvStorage"; /** * Serialized form of a credential stored in the KV backend */ interface StoredCredential { readonly encrypted: string; readonly iv: string; readonly label: string | undefined; readonly provider: string | undefined; readonly createdAt: string; readonly updatedAt: string; readonly expiresAt: string | undefined; } /** * Reserved KV key holding a marker entry encrypted with the store's * passphrase. Used by {@link EncryptedKvCredentialStore.verifyPassphrase} * to detect mistyped passphrases before they silently encrypt new entries * under the wrong key and irreversibly diverge from existing entries. * * The sentinel is hidden from {@link EncryptedKvCredentialStore.keys}, * {@link EncryptedKvCredentialStore.has}, and {@link EncryptedKvCredentialStore.delete}; * {@link EncryptedKvCredentialStore.deleteAll} rewrites it after clearing. */ export declare const CREDSTORE_SENTINEL_KEY = "__credstore_sentinel__"; /** * Plaintext that is encrypted and stored under {@link CREDSTORE_SENTINEL_KEY}. * The exact value is irrelevant — AES-GCM authentication makes any deviation * fail decryption — but a stable string is useful for diagnostics. */ export declare const SENTINEL_PLAINTEXT = "workglow:credstore:v1"; /** * Outcome of a sentinel-based passphrase check. * * - `"match"` — sentinel present and decrypts successfully; passphrase is correct. * - `"mismatch"` — sentinel present but decryption fails (AES-GCM auth-tag failure). * The supplied passphrase does not match the one that wrote the sentinel. * - `"absent"` — no sentinel row. Caller decides whether this is a first-time * init (write a sentinel) or a legacy migration (try decrypting * one existing row before committing to this passphrase). */ export type PassphraseVerification = "match" | "mismatch" | "absent"; /** * Credential store that encrypts values with AES-256-GCM before persisting * them to an {@link IKvStorage} backend. * * Works with any KV backend (SQLite, PostgreSQL, IndexedDB, in-memory, etc.). * Uses the Web Crypto API (available in Node 20+, Bun, and browsers). * * @example * ```ts * import { SqliteKvStorage } from "@workglow/storage"; * * const kv = new SqliteKvStorage(":memory:"); * const store = new EncryptedKvCredentialStore(kv, "my-encryption-key"); * * await store.put("openai-api-key", "sk-...", { provider: "openai" }); * const key = await store.get("openai-api-key"); // "sk-..." * ``` */ export declare class EncryptedKvCredentialStore implements ICredentialStore { private readonly kv; private readonly passphrase; /** Per-instance cache of derived CryptoKey instances keyed by base64(salt). */ private readonly keyCache; constructor(kv: IKvStorage, passphrase: string); get(key: string): Promise; /** * Encrypt {@link SENTINEL_PLAINTEXT} under the store's passphrase and * persist it at {@link CREDSTORE_SENTINEL_KEY}. Idempotent: a second call * overwrites the existing sentinel. */ writeSentinel(): Promise; /** * Check whether the store's passphrase matches the one that wrote the * sentinel. * * @see {@link PassphraseVerification} */ verifyPassphrase(): Promise; put(key: string, value: string, options?: CredentialPutOptions): Promise; delete(key: string): Promise; has(key: string): Promise; keys(): Promise; /** * Delete every credential in the store and rewrite the sentinel so the * passphrase verification path keeps working after the clear. (Without * the rewrite, the store would slip back into {@link PassphraseVerification} * `"absent"` and a subsequent mistyped passphrase could re-init the * store under the wrong key.) */ deleteAll(): Promise; } export {}; //# sourceMappingURL=EncryptedKvCredentialStore.d.ts.map