Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 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
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
3,333 changes: 3,141 additions & 192 deletions package-lock.json

Large diffs are not rendered by default.

5 changes: 3 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -254,6 +254,7 @@
"rimraf": "^3.0.2",
"sass": "^1.42.1",
"sass-loader": "^12.2.0",
"source-map-support": "^0.5.21",
"style-loader": "^3.3.0",
"terser-webpack-plugin": "^5.2.4",
"ts-loader": "^9.2.6",
Expand All @@ -263,10 +264,10 @@
"webpack-bundle-analyzer": "^4.5.0",
"webpack-cli": "^4.9.0",
"webpack-dev-server": "^4.3.1",
"webpack-merge": "^5.8.0",
"source-map-support": "^0.5.21"
"webpack-merge": "^5.8.0"
},
"dependencies": {
"@aws-sdk/client-secrets-manager": "^3.969.0",
"@devicefarmer/adbkit": "^3.2.6",
"@electron/remote": "^2.1.2",
"@requestly/requestly-core": "1.1.1",
Expand Down
26 changes: 0 additions & 26 deletions release/app/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

85 changes: 78 additions & 7 deletions src/lib/secretsManager/providerService/AbstractSecretProvider.ts
Original file line number Diff line number Diff line change
@@ -1,27 +1,98 @@
import { CachedSecret, ProviderSpecificConfig, SecretProviderType, SecretReference } from "../types";
import {
ProviderSpecificConfig,
SecretProviderType,
SecretReference,
SecretValue,
} from "../types";

const DEFAULT_CACHE_TTL_MS = 60 * 60 * 1000; // 1 hour
const DEFAULT_MAX_CACHE_SIZE = 100;

export abstract class AbstractSecretProvider {
protected cache: Map<string, CachedSecret> = new Map();
protected cache: Map<string, SecretValue> = new Map();

/** Cache TTL in milliseconds. Subclasses can override. */
protected cacheTtlMs: number = DEFAULT_CACHE_TTL_MS;

/** Maximum cache size (Size of the map). Subclasses can override. */
protected maxCacheSize: number = DEFAULT_MAX_CACHE_SIZE;

abstract readonly type: SecretProviderType;

abstract readonly id: string;

protected config: ProviderSpecificConfig;
protected abstract config: ProviderSpecificConfig;

protected abstract getSecretIdentfier(ref: SecretReference): string;
protected abstract getCacheKey(_ref: SecretReference): string;

abstract testConnection(): Promise<boolean>;

abstract getSecret(ref: SecretReference): Promise<string>;
abstract getSecret(_ref: SecretReference): Promise<SecretValue | null>;

abstract getSecrets(): Promise<string[]>;
abstract getSecrets(
_refs: SecretReference[]
): Promise<(SecretValue | null)[]>;

abstract setSecret(): Promise<void>;

abstract setSecrets(): Promise<void>;

abstract removeSecret(): Promise<void>;

abstract removeSecrets(): Promise<void>;

protected invalidateCache(): void {
this.cache.clear();
}

protected getCachedSecret(key: string): SecretValue | null {
const cached = this.cache.get(key);
if (cached && cached.fetchedAt + this.cacheTtlMs > Date.now()) {
return cached;
}
return null;
}

protected setCacheEntry(key: string, value: SecretValue): void {
if (this.maxCacheSize <= 0) {
return;
}

this.evictExpiredEntries();

while (this.cache.size >= this.maxCacheSize) {
const oldestKey = this.cache.keys().next().value;
if (!oldestKey) {
break;
}
this.cache.delete(oldestKey);
}

this.cache.set(key, value);
}

protected evictExpiredEntries(): void {
const now = Date.now();
const keysToDelete: string[] = [];

this.cache.forEach((value, key) => {
if (value.fetchedAt + this.cacheTtlMs <= now) {
keysToDelete.push(key);
}
});

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

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

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

return false;
}
}
156 changes: 154 additions & 2 deletions src/lib/secretsManager/providerService/awsSecretManagerProvider.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,156 @@
/* eslint-disable class-methods-use-this */
import {
AwsSecretReference,
AWSSecretsManagerConfig,
AwsSecretValue,
SecretProviderConfig,
SecretProviderType,
} from "../types";
import { AbstractSecretProvider } from "./AbstractSecretProvider";
import {
GetSecretValueCommand,
ListSecretsCommand,
SecretsManagerClient,
} from "@aws-sdk/client-secrets-manager";

export class AWSSecretsManagerProvider extends AbstractSecretProvider {}
export class AWSSecretsManagerProvider extends AbstractSecretProvider {
readonly type = SecretProviderType.AWS_SECRETS_MANAGER;

readonly id: string;

protected config: AWSSecretsManagerConfig;

private client: SecretsManagerClient;

constructor(providerConfig: SecretProviderConfig) {
super();
this.id = providerConfig.id;
this.config = providerConfig.config as AWSSecretsManagerConfig;
this.client = new SecretsManagerClient({
region: this.config.region,
credentials: {
accessKeyId: this.config.accessKeyId,
secretAccessKey: this.config.secretAccessKey,
sessionToken: this.config.sessionToken,
},
});
}

protected getCacheKey(ref: AwsSecretReference): string {
return `name:${ref.identifier};version:${ref.version ?? "latest"}`;
}

async testConnection(): Promise<boolean> {
if (!AWSSecretsManagerProvider.validateConfig(this.config)) {
return false;
}

try {
const listSecretsCommand = new ListSecretsCommand({ MaxResults: 1 });
const res = await this.client.send(listSecretsCommand);
console.log("!!!debug", "aws result", res);

if (res.$metadata.httpStatusCode !== 200) {
return false;
}

return true;
} catch (err) {
console.error(
"!!!debug",
"aws secrets manager test connection error",
err
);
return false;
}
}

async getSecret(ref: AwsSecretReference): Promise<AwsSecretValue | null> {
if (!this.client) {
throw new Error("AWS Secrets Manager client is not initialized.");
}

const cacheKey = this.getCacheKey(ref);
const cachedSecret = this.getCachedSecret(cacheKey) as AwsSecretValue | null;

if (cachedSecret) {
console.log("!!!debug", "returning from cache", cachedSecret);
return cachedSecret;
}

const getSecretCommand = new GetSecretValueCommand({
SecretId: ref.identifier,
VersionId: ref.version,
});

const secretResponse = await this.client.send(getSecretCommand);

if (secretResponse.$metadata.httpStatusCode !== 200) {
console.error("!!!debug", "Failed to fetch secret", secretResponse);
return null;
}

if (!secretResponse.SecretString) {
console.error("!!!debug", "SecretString is empty", secretResponse);
return null;
}

const awsSecret: AwsSecretValue = {
providerId: this.id,
secretReference: ref,
fetchedAt: Date.now(),
name: secretResponse.Name,
value: secretResponse.SecretString,
ARN: secretResponse.ARN,
versionId: secretResponse.VersionId,
};

console.log("!!!debug", "returning after fetching", awsSecret);

this.setCacheEntry(cacheKey, awsSecret);

return awsSecret;
}

async getSecrets(
refs: AwsSecretReference[]
): Promise<(AwsSecretValue | null)[]> {
if (!this.client) {
throw new Error("AWS Secrets Manager client is not initialized.");
}

// Not using BatchGetSecretValueCommand as it would require additional permissions
return Promise.all(refs.map((ref) => this.getSecret(ref)));
}

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

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

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

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

async refreshSecrets(): Promise<(AwsSecretValue | null)[]> {
const allSecretRefs = Array.from(this.cache.values()).map(
(secret) => secret.secretReference
);

this.invalidateCache();

return this.getSecrets(allSecretRefs);
}

static validateConfig(config: AWSSecretsManagerConfig): boolean {
return Boolean(
config.accessKeyId && config.secretAccessKey && config.region
);
}
}
Loading