Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
618198f
added aws sdk
nafees87n Jan 15, 2026
36a2aa7
added refreshSecrets
nafees87n Jan 15, 2026
74de3a0
Merge branch 'initialization' into aws-secrets-manager
nafees87n Jan 15, 2026
19e82b5
fix: webpack config
nafees87n Jan 15, 2026
f19b6d4
fix
nafees87n Jan 16, 2026
2c7faa3
Merge branch 'initialization' into aws-secrets-manager
nafees87n Jan 16, 2026
7804ed1
fix
nafees87n Jan 16, 2026
74f3589
fix: class singleton initialization
nafees87n Jan 16, 2026
f01d364
fix: SecretReference type
nafees87n Jan 16, 2026
c935804
fix: getSecrets
nafees87n Jan 16, 2026
f88792e
Merge branch 'initialization' into aws-secrets-manager
nafees87n Jan 20, 2026
c848582
fix: getSecrets
nafees87n Jan 20, 2026
94f66c6
Merge branch 'initialization' into aws-secrets-manager
nafees87n Jan 20, 2026
2cb9f34
added fallback
nafees87n Jan 20, 2026
988dd94
Merge branch 'initialization' into aws-secrets-manager
nafees87n Jan 20, 2026
2aba2f9
Merge branch 'initialization' into aws-secrets-manager
nafees87n Jan 20, 2026
71cfafa
fix: cache cleanup
nafees87n Jan 20, 2026
4a648db
Merge branch 'initialization' into aws-secrets-manager
nafees87n Jan 20, 2026
6452f00
fix: initialization
nafees87n Jan 20, 2026
a502c64
fix: infinite loop
nafees87n Jan 20, 2026
4e410c4
fix: types
nafees87n Jan 20, 2026
9beaa0a
Merge branch 'initialization' into aws-secrets-manager
nafees87n Jan 21, 2026
6829224
Merge branch 'initialization' into aws-secrets-manager
nafees87n Jan 21, 2026
07b888a
fix: init
nafees87n Jan 21, 2026
491ebdb
Merge branch 'initialization' into aws-secrets-manager
nafees87n Jan 21, 2026
b48422c
fix: eslint rules
nafees87n Jan 21, 2026
9e0478e
revert eslint changes
nafees87n Jan 21, 2026
986438a
remove change
nafees87n Jan 21, 2026
3ae4aab
Merge branch 'initialization' into aws-secrets-manager
nafees87n Jan 21, 2026
cce2bd6
added storage listener in secrets manager
nafees87n Jan 27, 2026
a0d5cf6
Merge branch 'initialization' into aws-secrets-manager
nafees87n Jan 28, 2026
6b06a4e
Merge branch 'aws-secrets-manager' into secrets/storage-listener
nafees87n Jan 28, 2026
57c27c8
remove unsubscribe listener
nafees87n Jan 28, 2026
203acf0
Merge branch 'initialization' into secrets/storage-listener
nafees87n Jan 30, 2026
645fa39
Merge branch 'initialization' into secrets/storage-listener
nafees87n Jan 30, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
import { SecretProviderConfig } from "../types";

export type StorageChangeCallback = (
data: Record<string, SecretProviderConfig>
) => void;

export abstract class AbstractSecretsManagerStorage {
abstract set(_key: string, _data: SecretProviderConfig): Promise<void>;

Expand All @@ -8,4 +12,6 @@ export abstract class AbstractSecretsManagerStorage {
abstract getAll(): Promise<SecretProviderConfig[]>;

abstract delete(_key: string): Promise<void>;

abstract onStorageChange(callback: StorageChangeCallback): () => void;
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
import { AbstractSecretsManagerStorage } from "./AbstractSecretsManagerStorage";
import {
AbstractSecretsManagerStorage,
StorageChangeCallback,
} from "./AbstractSecretsManagerStorage";
import { EncryptedElectronStore } from "../../storage/EncryptedElectronStore";
import { SecretProviderConfig } from "../types";

Expand Down Expand Up @@ -26,4 +29,8 @@ export class SecretsManagerEncryptedStorage extends AbstractSecretsManagerStorag
async delete(key: string): Promise<void> {
return this.encryptedStore.delete(key);
}

onStorageChange(callback: StorageChangeCallback): () => void {
return this.encryptedStore.onChange<SecretProviderConfig>(callback);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,15 @@ import { SecretProviderConfig, SecretProviderType } from "../types";
import { AbstractSecretsManagerStorage } from "../encryptedStorage/AbstractSecretsManagerStorage";
import { AbstractSecretProvider } from "../providerService/AbstractSecretProvider";

/**
* Abstract registry for managing secret providers.
*
* The registry stores provider configurations and maintains instances of providers.
* Providers are stored with type erasure but maintain their full type safety
* when accessed through type-aware methods.
*/
export type ProviderChangeCallback = (
configs: Omit<SecretProviderConfig, "config">[]
) => void;

export abstract class AbstractProviderRegistry {
protected store: AbstractSecretsManagerStorage;

protected providers: Map<string, AbstractSecretProvider<SecretProviderType>> = new Map();
protected providers: Map<string, AbstractSecretProvider<SecretProviderType>> =
new Map();

constructor(store: AbstractSecretsManagerStorage) {
this.store = store;
Expand All @@ -28,7 +26,11 @@ export abstract class AbstractProviderRegistry {

abstract deleteProviderConfig(_id: string): Promise<void>;

abstract getProvider(_providerId: string): AbstractSecretProvider<SecretProviderType> | null;
abstract onProvidersChange(callback: ProviderChangeCallback): () => void;

abstract getProvider(
_providerId: string
): AbstractSecretProvider<SecretProviderType> | null;

/**
* Type-safe method to get a provider with a specific type.
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,17 @@
import { SecretProviderConfig, SecretProviderType } from "../types";
import { createProviderInstance } from "../providerService/providerFactory";
import { AbstractSecretProvider } from "../providerService/AbstractSecretProvider";
import { AbstractProviderRegistry } from "./AbstractProviderRegistry";
import {
AbstractProviderRegistry,
ProviderChangeCallback,
} from "./AbstractProviderRegistry";

export class FileBasedProviderRegistry extends AbstractProviderRegistry {
private changeCallbacks: Set<ProviderChangeCallback> = new Set();

async initialize(): Promise<void> {
await this.initProvidersFromStorage();
this.setupStorageListener();
}

private async initProvidersFromStorage(): Promise<void> {
Expand Down Expand Up @@ -52,4 +58,58 @@ export class FileBasedProviderRegistry extends AbstractProviderRegistry {
getProvider(providerId: string): AbstractSecretProvider<SecretProviderType> | null {
return this.providers.get(providerId) ?? null;
}

onProvidersChange(callback: ProviderChangeCallback): () => void {
this.changeCallbacks.add(callback);

return () => {
this.changeCallbacks.delete(callback);
};
}

private setupStorageListener(): void {
this.store.onStorageChange((data) => {
this.syncProvidersFromStorageData(data);
this.notifyChangeCallbacks(data);
});
}

private syncProvidersFromStorageData(
data: Record<string, SecretProviderConfig>
): void {
const newConfigIds = new Set(Object.keys(data));
const existingProviderIds = new Set(this.providers.keys());

// Remove providers that no longer exist
for (const existingId of existingProviderIds) {
if (!newConfigIds.has(existingId)) {
this.providers.delete(existingId);
}
}

for (const [id, config] of Object.entries(data)) {
try {
// recreate provider instance
this.providers.set(id, createProviderInstance(config));
} catch (error) {
console.log(
"!!!debug",
`Failed to sync provider for config id: ${id}`,
error
);
}
}
}

private notifyChangeCallbacks(
data: Record<string, SecretProviderConfig>
): void {
this.changeCallbacks.forEach((callback) => {
const configsMetadata = Object.values(data).map((config) => {
const { config: _, ...metadata } = config;
return metadata;
});
callback(configsMetadata);
});
}
}
33 changes: 19 additions & 14 deletions src/lib/secretsManager/providerService/AbstractSecretProvider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,9 @@ export abstract class AbstractSecretProvider<T extends SecretProviderType> {

abstract testConnection(): Promise<boolean>;

abstract getSecret(_ref: ReferenceForProvider<T>): Promise<ValueForProvider<T> | null>;
abstract getSecret(
_ref: ReferenceForProvider<T>
): Promise<ValueForProvider<T> | null>;

abstract getSecrets(
_refs: ReferenceForProvider<T>[]
Expand All @@ -42,13 +44,28 @@ export abstract class AbstractSecretProvider<T extends SecretProviderType> {
): Promise<void>;

abstract setSecrets(
_entries: Array<{ ref: ReferenceForProvider<T>; value: string | Record<string, any> }>
_entries: Array<{
ref: ReferenceForProvider<T>;
value: string | Record<string, any>;
}>
): Promise<void>;

abstract removeSecret(_ref: ReferenceForProvider<T>): Promise<void>;

abstract removeSecrets(_refs: ReferenceForProvider<T>[]): Promise<void>;

abstract refreshSecrets(): Promise<(ValueForProvider<T> | null)[]>;

static validateConfig(config: any): boolean {
// Base implementation rejects all configs as a fail-safe.
// Provider implementations must override with specific validation.
if (!config) {
return false;
}

return false;
}

protected invalidateCache(): void {
this.cache.clear();
}
Expand Down Expand Up @@ -91,16 +108,4 @@ export abstract class AbstractSecretProvider<T extends SecretProviderType> {

keysToDelete.forEach((key) => this.cache.delete(key));
}

abstract refreshSecrets(): Promise<(ValueForProvider<T> | null)[]>;

static validateConfig(config: any): boolean {
// Base implementation rejects all configs as a fail-safe.
// Provider implementations must override with specific validation.
if (!config) {
return false;
}

return false;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -146,24 +146,19 @@ export class AWSSecretsManagerProvider extends AbstractSecretProvider<SecretProv
return Promise.all(refs.map((ref) => this.getSecret(ref)));
}

async setSecret(
_ref: AwsSecretReference,
_value: string | Record<string, any>
): Promise<void> {
async setSecret(): Promise<void> {
throw new Error("Method not implemented.");
}

async setSecrets(
_entries: Array<{ ref: AwsSecretReference; value: string | Record<string, any> }>
): Promise<void> {
async setSecrets(): Promise<void> {
throw new Error("Method not implemented.");
}

async removeSecret(_ref: AwsSecretReference): Promise<void> {
async removeSecret(): Promise<void> {
throw new Error("Method not implemented.");
}

async removeSecrets(_refs: AwsSecretReference[]): Promise<void> {
async removeSecrets(): Promise<void> {
throw new Error("Method not implemented.");
}

Expand Down
14 changes: 14 additions & 0 deletions src/lib/secretsManager/secretsManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,20 @@ export class SecretsManager {

return provider.refreshSecrets();
}

async listProviders(): Promise<Omit<SecretProviderConfig, "config">[]> {
const configs = await this.registry.getAllProviderConfigs();

const configMetadata: Omit<SecretProviderConfig, "config">[] = configs.map(
({ config: _, ...rest }) => rest
);

return configMetadata;
}

onProvidersChange(callback: ProviderChangeCallback): () => void {
return this.registry.onProvidersChange(callback);
}
}

/**
Expand Down
2 changes: 1 addition & 1 deletion src/lib/storage/EncryptedElectronStore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ export class EncryptedElectronStore {
* @param callback - Function to call when data changes
* @returns Unsubscribe function
*/
onChange(callback: (_data: Record<string, string>) => void): () => void {
onChange<T>(callback: (_data: Record<string, T>) => void): () => void {
return this.store.onDidChange("data", (newValue) => {
if (newValue) {
callback(newValue);
Expand Down