@pikku/core 0.12.24 → 0.12.25
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +16 -0
- package/dist/data-classification.d.ts +16 -0
- package/dist/data-classification.js +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/services/gopass-secrets.d.ts +2 -35
- package/dist/services/gopass-secrets.js +10 -40
- package/dist/services/local-secrets.d.ts +2 -36
- package/dist/services/local-secrets.js +3 -52
- package/dist/services/local-variables.d.ts +3 -5
- package/dist/services/local-variables.js +10 -10
- package/dist/services/scoped-secret-service.d.ts +2 -3
- package/dist/services/scoped-secret-service.js +2 -6
- package/dist/services/secret-service.d.ts +5 -12
- package/dist/services/typed-secret-service.d.ts +3 -4
- package/dist/services/typed-secret-service.js +2 -5
- package/dist/services/typed-variables-service.d.ts +3 -6
- package/dist/services/typed-variables-service.js +0 -6
- package/dist/services/variables-service.d.ts +2 -4
- package/dist/testing/service-tests.js +13 -13
- package/dist/wirings/ai-agent/ai-agent-prepare.js +4 -1
- package/dist/wirings/ai-agent/ai-agent.types.d.ts +4 -0
- package/dist/wirings/oauth2/oauth2-client.js +3 -3
- package/package.json +1 -1
- package/src/data-classification.ts +15 -0
- package/src/index.ts +9 -0
- package/src/services/gopass-secrets.ts +10 -42
- package/src/services/local-secrets.test.ts +10 -10
- package/src/services/local-secrets.ts +11 -59
- package/src/services/local-variables.test.ts +4 -4
- package/src/services/local-variables.ts +12 -17
- package/src/services/scoped-secret-service.test.ts +10 -11
- package/src/services/scoped-secret-service.ts +4 -9
- package/src/services/secret-service.ts +5 -12
- package/src/services/typed-secret-service.test.ts +16 -17
- package/src/services/typed-secret-service.ts +5 -9
- package/src/services/typed-variables-service.test.ts +5 -5
- package/src/services/typed-variables-service.ts +5 -17
- package/src/services/variables-service.ts +2 -4
- package/src/testing/service-tests.ts +13 -13
- package/src/wirings/ai-agent/ai-agent-prepare.ts +7 -2
- package/src/wirings/ai-agent/ai-agent.types.ts +4 -0
- package/src/wirings/oauth2/oauth2-client.test.ts +2 -3
- package/src/wirings/oauth2/oauth2-client.ts +3 -3
- package/tsconfig.tsbuildinfo +1 -1
package/package.json
CHANGED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export type Private<T> = T & { readonly __pii__: 'private' }
|
|
2
|
+
export type Secret<T> = T & { readonly __pii__: 'secret' }
|
|
3
|
+
|
|
4
|
+
export type Classification = 'public' | 'private' | 'secret'
|
|
5
|
+
export type AnonymizeStrategy = 'fake:email' | 'fake:name' | 'hash' | 'keep' | null
|
|
6
|
+
|
|
7
|
+
export interface ColumnClassification {
|
|
8
|
+
classification: Classification
|
|
9
|
+
anonymize_strategy: AnonymizeStrategy
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export type ClassificationManifest = {
|
|
13
|
+
version: 1
|
|
14
|
+
tables: Record<string, Record<string, ColumnClassification>>
|
|
15
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -177,3 +177,12 @@ export {
|
|
|
177
177
|
type ScheduledTaskSummary,
|
|
178
178
|
} from './services/scheduler-service.js'
|
|
179
179
|
export { SchedulerService } from './services/scheduler-service.js'
|
|
180
|
+
|
|
181
|
+
export type {
|
|
182
|
+
Private,
|
|
183
|
+
Secret,
|
|
184
|
+
Classification,
|
|
185
|
+
AnonymizeStrategy,
|
|
186
|
+
ColumnClassification,
|
|
187
|
+
ClassificationManifest,
|
|
188
|
+
} from './data-classification.js'
|
|
@@ -6,10 +6,6 @@ import type { SecretService } from './secret-service.js'
|
|
|
6
6
|
* Requires gopass to be installed and configured on the system.
|
|
7
7
|
*/
|
|
8
8
|
export class GopassSecretService implements SecretService {
|
|
9
|
-
/**
|
|
10
|
-
* Creates an instance of GopassSecretService.
|
|
11
|
-
* @param prefix - Optional prefix for all secret keys (e.g., 'pikku/' will look up 'pikku/mykey' for key 'mykey')
|
|
12
|
-
*/
|
|
13
9
|
constructor(private prefix: string = '') {}
|
|
14
10
|
|
|
15
11
|
private getFullKey(key: string): string {
|
|
@@ -19,39 +15,22 @@ export class GopassSecretService implements SecretService {
|
|
|
19
15
|
return this.prefix ? `${this.prefix}${key}` : key
|
|
20
16
|
}
|
|
21
17
|
|
|
22
|
-
|
|
23
|
-
* Retrieves a secret by key and parses it as JSON.
|
|
24
|
-
* @param key - The key of the secret to retrieve.
|
|
25
|
-
* @returns A promise that resolves to the parsed secret value.
|
|
26
|
-
* @throws {Error} If the secret is not found or gopass fails.
|
|
27
|
-
*/
|
|
28
|
-
public async getSecretJSON<R>(key: string): Promise<R> {
|
|
29
|
-
const value = await this.getSecret(key)
|
|
30
|
-
return JSON.parse(value)
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
/**
|
|
34
|
-
* Retrieves a secret by key as a string.
|
|
35
|
-
* @param key - The key of the secret to retrieve.
|
|
36
|
-
* @returns A promise that resolves to the secret value.
|
|
37
|
-
* @throws {Error} If the secret is not found or gopass fails.
|
|
38
|
-
*/
|
|
39
|
-
public async getSecret(key: string): Promise<string> {
|
|
18
|
+
public async getSecret<T = string>(key: string): Promise<T> {
|
|
40
19
|
const fullKey = this.getFullKey(key)
|
|
41
20
|
try {
|
|
42
|
-
|
|
21
|
+
const raw = execFileSync('gopass', ['show', '-o', fullKey], {
|
|
43
22
|
encoding: 'utf8',
|
|
44
23
|
}).trim()
|
|
24
|
+
try {
|
|
25
|
+
return JSON.parse(raw) as T
|
|
26
|
+
} catch {
|
|
27
|
+
return raw as unknown as T
|
|
28
|
+
}
|
|
45
29
|
} catch (error: any) {
|
|
46
30
|
throw new Error(`Secret Not Found: ${key}`, { cause: error })
|
|
47
31
|
}
|
|
48
32
|
}
|
|
49
33
|
|
|
50
|
-
/**
|
|
51
|
-
* Checks if a secret exists without throwing.
|
|
52
|
-
* @param key - The key of the secret to check.
|
|
53
|
-
* @returns A promise that resolves to true if the secret exists.
|
|
54
|
-
*/
|
|
55
34
|
public async hasSecret(key: string): Promise<boolean> {
|
|
56
35
|
const fullKey = this.getFullKey(key)
|
|
57
36
|
try {
|
|
@@ -62,19 +41,13 @@ export class GopassSecretService implements SecretService {
|
|
|
62
41
|
}
|
|
63
42
|
}
|
|
64
43
|
|
|
65
|
-
|
|
66
|
-
* Stores a JSON value as a secret in gopass.
|
|
67
|
-
* @param key - The key to store the secret under.
|
|
68
|
-
* @param value - The JSON value to store.
|
|
69
|
-
* @returns A promise that resolves when the secret is stored.
|
|
70
|
-
*/
|
|
71
|
-
public async setSecretJSON(key: string, value: unknown): Promise<void> {
|
|
44
|
+
public async setSecret(key: string, value: unknown): Promise<void> {
|
|
72
45
|
const fullKey = this.getFullKey(key)
|
|
73
|
-
const
|
|
46
|
+
const encoded = typeof value === 'string' ? value : JSON.stringify(value)
|
|
74
47
|
try {
|
|
75
48
|
execFileSync('gopass', ['insert', '-f', fullKey], {
|
|
76
49
|
encoding: 'utf8',
|
|
77
|
-
input:
|
|
50
|
+
input: encoded,
|
|
78
51
|
stdio: ['pipe', 'pipe', 'pipe'],
|
|
79
52
|
})
|
|
80
53
|
} catch (error: any) {
|
|
@@ -82,11 +55,6 @@ export class GopassSecretService implements SecretService {
|
|
|
82
55
|
}
|
|
83
56
|
}
|
|
84
57
|
|
|
85
|
-
/**
|
|
86
|
-
* Deletes a secret from gopass.
|
|
87
|
-
* @param key - The key of the secret to delete.
|
|
88
|
-
* @returns A promise that resolves when the secret is deleted.
|
|
89
|
-
*/
|
|
90
58
|
public async deleteSecret(key: string): Promise<void> {
|
|
91
59
|
const fullKey = this.getFullKey(key)
|
|
92
60
|
try {
|
|
@@ -6,9 +6,9 @@ import { LocalVariablesService } from './local-variables.js'
|
|
|
6
6
|
describe('LocalSecretService', () => {
|
|
7
7
|
test('should get secret from local storage', async () => {
|
|
8
8
|
const service = new LocalSecretService()
|
|
9
|
-
await service.
|
|
9
|
+
await service.setSecret('MY_KEY', { token: 'abc' })
|
|
10
10
|
const result = await service.getSecret('MY_KEY')
|
|
11
|
-
assert.
|
|
11
|
+
assert.deepStrictEqual(result, { token: 'abc' })
|
|
12
12
|
})
|
|
13
13
|
|
|
14
14
|
test('should get secret from environment variables', async () => {
|
|
@@ -28,22 +28,22 @@ describe('LocalSecretService', () => {
|
|
|
28
28
|
|
|
29
29
|
test('should get JSON secret from local storage', async () => {
|
|
30
30
|
const service = new LocalSecretService()
|
|
31
|
-
await service.
|
|
32
|
-
const result = await service.
|
|
31
|
+
await service.setSecret('JSON_KEY', { data: 42 })
|
|
32
|
+
const result = await service.getSecret('JSON_KEY')
|
|
33
33
|
assert.deepStrictEqual(result, { data: 42 })
|
|
34
34
|
})
|
|
35
35
|
|
|
36
36
|
test('should get JSON secret from environment variables', async () => {
|
|
37
37
|
const vars = new LocalVariablesService({ CONFIG: '{"port":3000}' })
|
|
38
38
|
const service = new LocalSecretService(vars)
|
|
39
|
-
const result = await service.
|
|
39
|
+
const result = await service.getSecret('CONFIG')
|
|
40
40
|
assert.deepStrictEqual(result, { port: 3000 })
|
|
41
41
|
})
|
|
42
42
|
|
|
43
43
|
test('should throw when JSON secret not found', async () => {
|
|
44
44
|
const vars = new LocalVariablesService({})
|
|
45
45
|
const service = new LocalSecretService(vars)
|
|
46
|
-
await assert.rejects(() => service.
|
|
46
|
+
await assert.rejects(() => service.getSecret('MISSING'), {
|
|
47
47
|
message: 'Requested secret not found',
|
|
48
48
|
})
|
|
49
49
|
})
|
|
@@ -51,15 +51,15 @@ describe('LocalSecretService', () => {
|
|
|
51
51
|
test('should prefer local storage over env variables', async () => {
|
|
52
52
|
const vars = new LocalVariablesService({ KEY: 'env-value' })
|
|
53
53
|
const service = new LocalSecretService(vars)
|
|
54
|
-
await service.
|
|
54
|
+
await service.setSecret('KEY', 'local-value')
|
|
55
55
|
const result = await service.getSecret('KEY')
|
|
56
|
-
assert.strictEqual(result, '
|
|
56
|
+
assert.strictEqual(result, 'local-value')
|
|
57
57
|
})
|
|
58
58
|
|
|
59
59
|
test('should check hasSecret in local storage', async () => {
|
|
60
60
|
const vars = new LocalVariablesService({})
|
|
61
61
|
const service = new LocalSecretService(vars)
|
|
62
|
-
await service.
|
|
62
|
+
await service.setSecret('EXISTS', 'yes')
|
|
63
63
|
assert.strictEqual(await service.hasSecret('EXISTS'), true)
|
|
64
64
|
assert.strictEqual(await service.hasSecret('NOPE'), false)
|
|
65
65
|
})
|
|
@@ -72,7 +72,7 @@ describe('LocalSecretService', () => {
|
|
|
72
72
|
|
|
73
73
|
test('should delete secret from local storage', async () => {
|
|
74
74
|
const service = new LocalSecretService()
|
|
75
|
-
await service.
|
|
75
|
+
await service.setSecret('DEL_KEY', 'value')
|
|
76
76
|
assert.strictEqual(await service.hasSecret('DEL_KEY'), true)
|
|
77
77
|
await service.deleteSecret('DEL_KEY')
|
|
78
78
|
assert.strictEqual(await service.hasSecret('DEL_KEY'), false)
|
|
@@ -9,81 +9,38 @@ import type { VariablesService } from './variables-service.js'
|
|
|
9
9
|
export class LocalSecretService implements SecretService {
|
|
10
10
|
private localSecrets: Map<string, string> = new Map()
|
|
11
11
|
|
|
12
|
-
|
|
13
|
-
private parseSecret<R>(raw: string): R {
|
|
12
|
+
private parseSecret<T>(raw: string): T {
|
|
14
13
|
try {
|
|
15
|
-
return JSON.parse(raw) as
|
|
14
|
+
return JSON.parse(raw) as T
|
|
16
15
|
} catch {
|
|
17
|
-
return raw as unknown as
|
|
16
|
+
return raw as unknown as T
|
|
18
17
|
}
|
|
19
18
|
}
|
|
20
19
|
|
|
21
|
-
/**
|
|
22
|
-
* Creates an instance of LocalSecretService.
|
|
23
|
-
*/
|
|
24
20
|
constructor(
|
|
25
21
|
private variables: VariablesService = new LocalVariablesService()
|
|
26
22
|
) {}
|
|
27
23
|
|
|
28
|
-
|
|
29
|
-
* Retrieves a secret by key.
|
|
30
|
-
* Checks local storage first, then falls back to environment variables.
|
|
31
|
-
* @param key - The key of the secret to retrieve.
|
|
32
|
-
* @returns A promise that resolves to the secret value.
|
|
33
|
-
* @throws {Error} If the secret is not found.
|
|
34
|
-
*/
|
|
35
|
-
public async getSecretJSON<R>(key: string): Promise<R> {
|
|
36
|
-
// Check local storage first
|
|
24
|
+
public async getSecret<T = string>(key: string): Promise<T> {
|
|
37
25
|
const localValue = this.localSecrets.get(key)
|
|
38
26
|
if (localValue) {
|
|
39
|
-
return this.parseSecret(localValue)
|
|
27
|
+
return this.parseSecret<T>(localValue)
|
|
40
28
|
}
|
|
41
29
|
|
|
42
|
-
// Fall back to environment variables
|
|
43
30
|
const value = await this.variables.get(key)
|
|
44
31
|
if (value) {
|
|
45
|
-
return this.parseSecret(value)
|
|
32
|
+
return this.parseSecret<T>(value)
|
|
46
33
|
}
|
|
47
34
|
throw new Error('Requested secret not found')
|
|
48
35
|
}
|
|
49
36
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
* @throws {Error} If the secret is not found.
|
|
56
|
-
*/
|
|
57
|
-
public async getSecret(key: string): Promise<string> {
|
|
58
|
-
// Check local storage first
|
|
59
|
-
const localValue = this.localSecrets.get(key)
|
|
60
|
-
if (localValue) {
|
|
61
|
-
return localValue
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
// Fall back to environment variables
|
|
65
|
-
const value = await this.variables.get(key)
|
|
66
|
-
if (value) {
|
|
67
|
-
return value
|
|
68
|
-
}
|
|
69
|
-
throw new Error('Requested secret not found')
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
/**
|
|
73
|
-
* Stores a JSON value as a secret in local storage.
|
|
74
|
-
* @param key - The key to store the secret under.
|
|
75
|
-
* @param value - The JSON value to store.
|
|
76
|
-
* @returns A promise that resolves when the secret is stored.
|
|
77
|
-
*/
|
|
78
|
-
public async setSecretJSON(key: string, value: unknown): Promise<void> {
|
|
79
|
-
this.localSecrets.set(key, JSON.stringify(value))
|
|
37
|
+
public async setSecret(key: string, value: unknown): Promise<void> {
|
|
38
|
+
this.localSecrets.set(
|
|
39
|
+
key,
|
|
40
|
+
typeof value === 'string' ? value : JSON.stringify(value)
|
|
41
|
+
)
|
|
80
42
|
}
|
|
81
43
|
|
|
82
|
-
/**
|
|
83
|
-
* Checks if a secret exists without throwing.
|
|
84
|
-
* @param key - The key of the secret to check.
|
|
85
|
-
* @returns A promise that resolves to true if the secret exists.
|
|
86
|
-
*/
|
|
87
44
|
public async hasSecret(key: string): Promise<boolean> {
|
|
88
45
|
if (this.localSecrets.has(key)) {
|
|
89
46
|
return true
|
|
@@ -92,11 +49,6 @@ export class LocalSecretService implements SecretService {
|
|
|
92
49
|
return value !== undefined && value !== null && value !== ''
|
|
93
50
|
}
|
|
94
51
|
|
|
95
|
-
/**
|
|
96
|
-
* Deletes a secret from local storage.
|
|
97
|
-
* @param key - The key of the secret to delete.
|
|
98
|
-
* @returns A promise that resolves when the secret is deleted.
|
|
99
|
-
*/
|
|
100
52
|
public async deleteSecret(key: string): Promise<void> {
|
|
101
53
|
this.localSecrets.delete(key)
|
|
102
54
|
}
|
|
@@ -39,19 +39,19 @@ describe('LocalVariablesService', () => {
|
|
|
39
39
|
|
|
40
40
|
test('should get JSON variable', () => {
|
|
41
41
|
const service = new LocalVariablesService({ DATA: '{"key":"val"}' })
|
|
42
|
-
const result = service.
|
|
42
|
+
const result = service.get('DATA')
|
|
43
43
|
assert.deepStrictEqual(result, { key: 'val' })
|
|
44
44
|
})
|
|
45
45
|
|
|
46
46
|
test('should return undefined for missing JSON variable', () => {
|
|
47
47
|
const service = new LocalVariablesService({})
|
|
48
|
-
assert.strictEqual(service.
|
|
48
|
+
assert.strictEqual(service.get('MISSING'), undefined)
|
|
49
49
|
})
|
|
50
50
|
|
|
51
51
|
test('should set JSON variable', () => {
|
|
52
52
|
const service = new LocalVariablesService({})
|
|
53
|
-
service.
|
|
54
|
-
assert.
|
|
53
|
+
service.set('DATA', { key: 'val' })
|
|
54
|
+
assert.deepStrictEqual(service.get('DATA'), { key: 'val' })
|
|
55
55
|
})
|
|
56
56
|
|
|
57
57
|
test('should handle has with undefined value', () => {
|
|
@@ -5,28 +5,23 @@ export class LocalVariablesService implements VariablesService {
|
|
|
5
5
|
private variables: Record<string, string | undefined> = process.env
|
|
6
6
|
) {}
|
|
7
7
|
|
|
8
|
-
public getAll():
|
|
9
|
-
| Promise<Record<string, string | undefined>>
|
|
10
|
-
| Record<string, string | undefined> {
|
|
8
|
+
public getAll(): Record<string, string | undefined> {
|
|
11
9
|
return this.variables || {}
|
|
12
10
|
}
|
|
13
11
|
|
|
14
|
-
public get(name: string):
|
|
15
|
-
|
|
12
|
+
public get<T = string>(name: string): T | undefined {
|
|
13
|
+
const raw = this.variables[name]
|
|
14
|
+
if (raw === undefined) return undefined
|
|
15
|
+
try {
|
|
16
|
+
return JSON.parse(raw) as T
|
|
17
|
+
} catch {
|
|
18
|
+
return raw as unknown as T
|
|
19
|
+
}
|
|
16
20
|
}
|
|
17
21
|
|
|
18
|
-
public
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
return JSON.parse(value)
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
public set(name: string, value: string): void {
|
|
25
|
-
this.variables[name] = value
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
public setJSON(name: string, value: unknown): void {
|
|
29
|
-
this.variables[name] = JSON.stringify(value)
|
|
22
|
+
public set(name: string, value: unknown): void {
|
|
23
|
+
this.variables[name] =
|
|
24
|
+
typeof value === 'string' ? value : JSON.stringify(value)
|
|
30
25
|
}
|
|
31
26
|
|
|
32
27
|
public has(name: string): boolean {
|
|
@@ -3,10 +3,9 @@ import assert from 'node:assert'
|
|
|
3
3
|
import { ScopedSecretService } from './scoped-secret-service.js'
|
|
4
4
|
|
|
5
5
|
const createMockSecrets = () => ({
|
|
6
|
-
getSecret: async (key: string) =>
|
|
7
|
-
getSecretJSON: async (key: string) => ({ key }),
|
|
6
|
+
getSecret: async <T>(key: string) => ({ key }) as T,
|
|
8
7
|
hasSecret: async () => true,
|
|
9
|
-
|
|
8
|
+
setSecret: async () => {},
|
|
10
9
|
deleteSecret: async () => {},
|
|
11
10
|
})
|
|
12
11
|
|
|
@@ -15,7 +14,7 @@ describe('ScopedSecretService', () => {
|
|
|
15
14
|
const mock = createMockSecrets()
|
|
16
15
|
const scoped = new ScopedSecretService(mock, new Set(['KEY1', 'KEY2']))
|
|
17
16
|
const result = await scoped.getSecret('KEY1')
|
|
18
|
-
assert.
|
|
17
|
+
assert.deepStrictEqual(result, { key: 'KEY1' })
|
|
19
18
|
})
|
|
20
19
|
|
|
21
20
|
test('should deny access to non-allowed keys', async () => {
|
|
@@ -26,17 +25,17 @@ describe('ScopedSecretService', () => {
|
|
|
26
25
|
})
|
|
27
26
|
})
|
|
28
27
|
|
|
29
|
-
test('should allow
|
|
28
|
+
test('should allow getSecret for allowed keys', async () => {
|
|
30
29
|
const mock = createMockSecrets()
|
|
31
30
|
const scoped = new ScopedSecretService(mock, new Set(['KEY1']))
|
|
32
|
-
const result = await scoped.
|
|
31
|
+
const result = await scoped.getSecret('KEY1')
|
|
33
32
|
assert.deepStrictEqual(result, { key: 'KEY1' })
|
|
34
33
|
})
|
|
35
34
|
|
|
36
|
-
test('should deny
|
|
35
|
+
test('should deny getSecret for non-allowed keys', async () => {
|
|
37
36
|
const mock = createMockSecrets()
|
|
38
37
|
const scoped = new ScopedSecretService(mock, new Set(['KEY1']))
|
|
39
|
-
await assert.rejects(() => scoped.
|
|
38
|
+
await assert.rejects(() => scoped.getSecret('FORBIDDEN'), {
|
|
40
39
|
message: 'Access denied to secret key: FORBIDDEN',
|
|
41
40
|
})
|
|
42
41
|
})
|
|
@@ -56,11 +55,11 @@ describe('ScopedSecretService', () => {
|
|
|
56
55
|
})
|
|
57
56
|
})
|
|
58
57
|
|
|
59
|
-
test('should always throw on
|
|
58
|
+
test('should always throw on setSecret', async () => {
|
|
60
59
|
const mock = createMockSecrets()
|
|
61
60
|
const scoped = new ScopedSecretService(mock, new Set(['KEY1']))
|
|
62
|
-
await assert.rejects(() => scoped.
|
|
63
|
-
message: '
|
|
61
|
+
await assert.rejects(() => scoped.setSecret('KEY1', 'val'), {
|
|
62
|
+
message: 'setSecret is not allowed in scoped secret service',
|
|
64
63
|
})
|
|
65
64
|
})
|
|
66
65
|
|
|
@@ -16,14 +16,9 @@ export class ScopedSecretService implements SecretService {
|
|
|
16
16
|
}
|
|
17
17
|
}
|
|
18
18
|
|
|
19
|
-
async getSecret(key: string): Promise<
|
|
19
|
+
async getSecret<T = string>(key: string): Promise<T> {
|
|
20
20
|
this.assertAllowed(key)
|
|
21
|
-
return this.secrets.getSecret(key)
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
async getSecretJSON<T = {}>(key: string): Promise<T> {
|
|
25
|
-
this.assertAllowed(key)
|
|
26
|
-
return this.secrets.getSecretJSON<T>(key)
|
|
21
|
+
return this.secrets.getSecret<T>(key)
|
|
27
22
|
}
|
|
28
23
|
|
|
29
24
|
async hasSecret(key: string): Promise<boolean> {
|
|
@@ -31,8 +26,8 @@ export class ScopedSecretService implements SecretService {
|
|
|
31
26
|
return this.secrets.hasSecret(key)
|
|
32
27
|
}
|
|
33
28
|
|
|
34
|
-
async
|
|
35
|
-
throw new Error('
|
|
29
|
+
async setSecret(_key: string, _value: unknown): Promise<void> {
|
|
30
|
+
throw new Error('setSecret is not allowed in scoped secret service')
|
|
36
31
|
}
|
|
37
32
|
|
|
38
33
|
async deleteSecret(_key: string): Promise<void> {
|
|
@@ -3,19 +3,12 @@
|
|
|
3
3
|
*/
|
|
4
4
|
export interface SecretService {
|
|
5
5
|
/**
|
|
6
|
-
* Retrieves a secret by key
|
|
7
|
-
* @param key - The key of the secret to retrieve.
|
|
8
|
-
* @returns A promise that resolves to the parsed secret value.
|
|
9
|
-
* @throws If the secret is not found.
|
|
10
|
-
*/
|
|
11
|
-
getSecretJSON<Return = {}>(key: string): Promise<Return>
|
|
12
|
-
/**
|
|
13
|
-
* Retrieves a secret by key as a string.
|
|
6
|
+
* Retrieves a secret by key, typed as T (defaults to string).
|
|
14
7
|
* @param key - The key of the secret to retrieve.
|
|
15
8
|
* @returns A promise that resolves to the secret value.
|
|
16
9
|
* @throws If the secret is not found.
|
|
17
10
|
*/
|
|
18
|
-
getSecret(key: string): Promise<
|
|
11
|
+
getSecret<T = string>(key: string): Promise<T>
|
|
19
12
|
/**
|
|
20
13
|
* Checks if a secret exists without throwing.
|
|
21
14
|
* @param key - The key of the secret to check.
|
|
@@ -23,12 +16,12 @@ export interface SecretService {
|
|
|
23
16
|
*/
|
|
24
17
|
hasSecret(key: string): Promise<boolean>
|
|
25
18
|
/**
|
|
26
|
-
* Stores a
|
|
19
|
+
* Stores a secret value.
|
|
27
20
|
* @param key - The key to store the secret under.
|
|
28
|
-
* @param value - The
|
|
21
|
+
* @param value - The value to store.
|
|
29
22
|
* @returns A promise that resolves when the secret is stored.
|
|
30
23
|
*/
|
|
31
|
-
|
|
24
|
+
setSecret(key: string, value: unknown): Promise<void>
|
|
32
25
|
/**
|
|
33
26
|
* Deletes a secret by key.
|
|
34
27
|
* @param key - The key of the secret to delete.
|
|
@@ -3,18 +3,17 @@ import assert from 'node:assert'
|
|
|
3
3
|
import { TypedSecretService } from './typed-secret-service.js'
|
|
4
4
|
|
|
5
5
|
const createMockSecrets = (store: Map<string, string> = new Map()) => ({
|
|
6
|
-
getSecret: async (key: string) => {
|
|
6
|
+
getSecret: async <T = string>(key: string): Promise<T> => {
|
|
7
7
|
const val = store.get(key)
|
|
8
8
|
if (!val) throw new Error(`Not found: ${key}`)
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
return JSON.parse(val)
|
|
9
|
+
try {
|
|
10
|
+
return JSON.parse(val) as T
|
|
11
|
+
} catch {
|
|
12
|
+
return val as unknown as T
|
|
13
|
+
}
|
|
15
14
|
},
|
|
16
15
|
hasSecret: async (key: string) => store.has(key),
|
|
17
|
-
|
|
16
|
+
setSecret: async (key: string, value: unknown) => {
|
|
18
17
|
store.set(key, JSON.stringify(value))
|
|
19
18
|
},
|
|
20
19
|
deleteSecret: async (key: string) => {
|
|
@@ -24,42 +23,42 @@ const createMockSecrets = (store: Map<string, string> = new Map()) => ({
|
|
|
24
23
|
|
|
25
24
|
describe('TypedSecretService', () => {
|
|
26
25
|
test('should delegate getSecret to underlying service', async () => {
|
|
27
|
-
const store = new Map([['API_KEY', 'sk-123']])
|
|
26
|
+
const store = new Map([['API_KEY', '"sk-123"']])
|
|
28
27
|
const service = new TypedSecretService(createMockSecrets(store), {})
|
|
29
28
|
const result = await service.getSecret('API_KEY')
|
|
30
29
|
assert.strictEqual(result, 'sk-123')
|
|
31
30
|
})
|
|
32
31
|
|
|
33
|
-
test('should delegate
|
|
32
|
+
test('should delegate getSecret with type to underlying service', async () => {
|
|
34
33
|
const store = new Map([['CONFIG', '{"port":3000}']])
|
|
35
34
|
const service = new TypedSecretService(createMockSecrets(store), {})
|
|
36
|
-
const result = await service.
|
|
35
|
+
const result = await service.getSecret<{ port: number }>('CONFIG')
|
|
37
36
|
assert.deepStrictEqual(result, { port: 3000 })
|
|
38
37
|
})
|
|
39
38
|
|
|
40
39
|
test('should delegate hasSecret to underlying service', async () => {
|
|
41
|
-
const store = new Map([['EXISTS', 'val']])
|
|
40
|
+
const store = new Map([['EXISTS', '"val"']])
|
|
42
41
|
const service = new TypedSecretService(createMockSecrets(store), {})
|
|
43
42
|
assert.strictEqual(await service.hasSecret('EXISTS'), true)
|
|
44
43
|
assert.strictEqual(await service.hasSecret('MISSING'), false)
|
|
45
44
|
})
|
|
46
45
|
|
|
47
|
-
test('should delegate
|
|
46
|
+
test('should delegate setSecret to underlying service', async () => {
|
|
48
47
|
const store = new Map<string, string>()
|
|
49
48
|
const service = new TypedSecretService(createMockSecrets(store), {})
|
|
50
|
-
await service.
|
|
49
|
+
await service.setSecret('NEW_KEY', { data: 'val' })
|
|
51
50
|
assert.strictEqual(store.get('NEW_KEY'), '{"data":"val"}')
|
|
52
51
|
})
|
|
53
52
|
|
|
54
53
|
test('should delegate deleteSecret to underlying service', async () => {
|
|
55
|
-
const store = new Map([['KEY', 'val']])
|
|
54
|
+
const store = new Map([['KEY', '"val"']])
|
|
56
55
|
const service = new TypedSecretService(createMockSecrets(store), {})
|
|
57
56
|
await service.deleteSecret('KEY')
|
|
58
57
|
assert.strictEqual(store.has('KEY'), false)
|
|
59
58
|
})
|
|
60
59
|
|
|
61
60
|
test('should get all status for credentials', async () => {
|
|
62
|
-
const store = new Map([['STRIPE_KEY', 'sk-123']])
|
|
61
|
+
const store = new Map([['STRIPE_KEY', '"sk-123"']])
|
|
63
62
|
const meta = {
|
|
64
63
|
STRIPE_KEY: { name: 'stripe', displayName: 'Stripe' },
|
|
65
64
|
GITHUB_TOKEN: {
|
|
@@ -80,7 +79,7 @@ describe('TypedSecretService', () => {
|
|
|
80
79
|
})
|
|
81
80
|
|
|
82
81
|
test('should get missing credentials', async () => {
|
|
83
|
-
const store = new Map([['STRIPE_KEY', 'sk-123']])
|
|
82
|
+
const store = new Map([['STRIPE_KEY', '"sk-123"']])
|
|
84
83
|
const meta = {
|
|
85
84
|
STRIPE_KEY: { name: 'stripe', displayName: 'Stripe' },
|
|
86
85
|
GITHUB_TOKEN: { name: 'github', displayName: 'GitHub' },
|
|
@@ -22,13 +22,9 @@ export class TypedSecretService<
|
|
|
22
22
|
private credentialsMeta: Record<string, CredentialMeta>
|
|
23
23
|
) {}
|
|
24
24
|
|
|
25
|
-
async
|
|
26
|
-
async
|
|
27
|
-
async
|
|
28
|
-
return this.secrets.getSecretJSON(key)
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
async getSecret(key: string): Promise<string> {
|
|
25
|
+
async getSecret<K extends keyof TMap & string>(key: K): Promise<TMap[K]>
|
|
26
|
+
async getSecret<T = string>(key: string): Promise<T>
|
|
27
|
+
async getSecret(key: string): Promise<unknown> {
|
|
32
28
|
return this.secrets.getSecret(key)
|
|
33
29
|
}
|
|
34
30
|
|
|
@@ -36,11 +32,11 @@ export class TypedSecretService<
|
|
|
36
32
|
return this.secrets.hasSecret(key)
|
|
37
33
|
}
|
|
38
34
|
|
|
39
|
-
async
|
|
35
|
+
async setSecret<K extends string>(
|
|
40
36
|
key: K,
|
|
41
37
|
value: K extends keyof TMap ? TMap[K] : unknown
|
|
42
38
|
): Promise<void> {
|
|
43
|
-
return this.secrets.
|
|
39
|
+
return this.secrets.setSecret(key, value)
|
|
44
40
|
}
|
|
45
41
|
|
|
46
42
|
async deleteSecret(key: string): Promise<void> {
|
|
@@ -36,15 +36,15 @@ describe('TypedVariablesService', () => {
|
|
|
36
36
|
assert.strictEqual(service.has('DB_URL'), false)
|
|
37
37
|
})
|
|
38
38
|
|
|
39
|
-
test('should delegate
|
|
39
|
+
test('should delegate get to underlying service', () => {
|
|
40
40
|
const service = createService({ DATA: '{"key":"val"}' })
|
|
41
|
-
assert.deepStrictEqual(service.
|
|
41
|
+
assert.deepStrictEqual(service.get('DATA'), { key: 'val' })
|
|
42
42
|
})
|
|
43
43
|
|
|
44
|
-
test('should delegate
|
|
44
|
+
test('should delegate set to underlying service', () => {
|
|
45
45
|
const service = createService()
|
|
46
|
-
service.
|
|
47
|
-
assert.
|
|
46
|
+
service.set('DATA', { key: 'val' })
|
|
47
|
+
assert.deepStrictEqual(service.get('DATA'), { key: 'val' })
|
|
48
48
|
})
|
|
49
49
|
|
|
50
50
|
test('should get all status for configured variables', async () => {
|