@vessl-ai/mcpctl-control-plane
Version:
Vessl MCP Control Plane service for managing distributed jobs.
107 lines (91 loc) • 3.14 kB
text/typescript
import { Injectable } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
import { SecretRefSource } from '@vessl-ai/mcpctl-shared/types/domain/secret';
import {
deletePassword,
findCredentials,
getPassword,
setPassword,
} from 'keytar';
import { SecretConfig } from '../config/secret.config';
export interface SecretStore {
set(key: string, value: string): Promise<string>;
get(key: string): Promise<string | null>;
list(): Promise<string[]>;
delete(key: string): Promise<void>;
}
export class KeychainSecretStore implements SecretStore {
private readonly KEYCHAIN_SERVICE_PREFIX = 'mcpctl';
private sanitizeKey(key: string): string {
return key.replace(/[^a-zA-Z0-9\.-_]/g, '-');
}
// Set a secret value for a given key
async set(key: string, value: string): Promise<string> {
const sanitizedKey = this.sanitizeKey(key);
await setPassword(this.KEYCHAIN_SERVICE_PREFIX, sanitizedKey, value);
return sanitizedKey;
}
// Get the secret value for a given key
async get(key: string): Promise<string | null> {
return getPassword(this.KEYCHAIN_SERVICE_PREFIX, this.sanitizeKey(key));
}
// List all secret keys
async list(): Promise<string[]> {
const allCredentials = await findCredentials(this.KEYCHAIN_SERVICE_PREFIX);
return allCredentials.map((credential) => credential.account);
}
// Delete the secret for a given key
async delete(key: string): Promise<void> {
await deletePassword(this.KEYCHAIN_SERVICE_PREFIX, this.sanitizeKey(key));
}
}
export class VaultSecretStore implements SecretStore {
// Set a secret value for a given key
async set(key: string, value: string): Promise<string> {
// Implementation needed
throw new Error('Not implemented');
}
// Get the secret value for a given key
async get(key: string): Promise<string | null> {
// Implementation needed
throw new Error('Not implemented');
}
// List all secret keys
async list(): Promise<string[]> {
// Implementation needed
throw new Error('Not implemented');
}
// Delete the secret for a given key
async delete(key: string): Promise<void> {
// Implementation needed
throw new Error('Not implemented');
}
}
()
export class SecretStoreFactory {
private readonly secretStores: Record<SecretRefSource, SecretStore>;
constructor(private readonly configService: ConfigService) {
const secretConfig = configService.get<SecretConfig>('secret');
if (!secretConfig) {
throw new Error('Secret config not found');
}
this.secretStores = secretConfig.storeTypes.reduce(
(acc, type) => {
acc[type] = this.createSecretStore(type);
return acc;
},
{} as Record<SecretRefSource, SecretStore>,
);
}
private createSecretStore(type: SecretRefSource): SecretStore {
switch (type) {
case SecretRefSource.Keychain:
return new KeychainSecretStore();
case SecretRefSource.Vault:
return new VaultSecretStore();
}
}
getSecretStore(type: SecretRefSource): SecretStore | undefined {
return this.secretStores[type];
}
}