@stream44.studio/t44 0.4.0-rc.24
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/.dco-signatures +9 -0
- package/.github/workflows/dco.yaml +12 -0
- package/.github/workflows/gordian-open-integrity.yaml +13 -0
- package/.github/workflows/test.yaml +31 -0
- package/.o/GordianOpenIntegrity-CurrentLifehash.svg +1026 -0
- package/.o/GordianOpenIntegrity-InceptionLifehash.svg +1026 -0
- package/.o/GordianOpenIntegrity.yaml +21 -0
- package/.o/assets/Hero-Terminal44-v0.jpeg +0 -0
- package/.o/stream44.studio/assets/Icon-v1.svg +1170 -0
- package/.repo-identifier +1 -0
- package/DCO.md +34 -0
- package/LICENSE.txt +186 -0
- package/README.md +189 -0
- package/bin/activate +36 -0
- package/bin/activate.ts +30 -0
- package/bin/postinstall.sh +19 -0
- package/bin/shell +27 -0
- package/bin/t44 +27 -0
- package/caps/ConfigSchemaStruct.ts +55 -0
- package/caps/Home.ts +57 -0
- package/caps/HomeRegistry.ts +319 -0
- package/caps/HomeRegistryFile.ts +144 -0
- package/caps/JsonSchemas.ts +220 -0
- package/caps/OpenApiSchema.ts +67 -0
- package/caps/PackageDescriptor.ts +88 -0
- package/caps/ProjectCatalogs.ts +153 -0
- package/caps/ProjectDeployment.ts +426 -0
- package/caps/ProjectDevelopment.ts +257 -0
- package/caps/ProjectPublishing.ts +654 -0
- package/caps/ProjectPulling.ts +234 -0
- package/caps/ProjectRack.ts +155 -0
- package/caps/ProjectRepository.ts +332 -0
- package/caps/ProjectTest.ts +251 -0
- package/caps/ProjectTestLib.ts +257 -0
- package/caps/RootKey.ts +219 -0
- package/caps/SigningKey.ts +243 -0
- package/caps/TaskWorkflow.ts +192 -0
- package/caps/WorkspaceCli.ts +448 -0
- package/caps/WorkspaceConfig.ts +268 -0
- package/caps/WorkspaceConfig.yaml +87 -0
- package/caps/WorkspaceConfigFile.ts +902 -0
- package/caps/WorkspaceConnection.ts +329 -0
- package/caps/WorkspaceEntityConfig.ts +78 -0
- package/caps/WorkspaceEntityConfig.v0.ts +77 -0
- package/caps/WorkspaceEntityFact.ts +218 -0
- package/caps/WorkspaceInfo.ts +619 -0
- package/caps/WorkspaceInit.ts +30 -0
- package/caps/WorkspaceKey.ts +338 -0
- package/caps/WorkspaceModel.ts +373 -0
- package/caps/WorkspaceProjects.ts +636 -0
- package/caps/WorkspacePrompt.ts +430 -0
- package/caps/WorkspaceShell.sh +39 -0
- package/caps/WorkspaceShell.ts +104 -0
- package/caps/WorkspaceShell.yaml +64 -0
- package/caps/WorkspaceShellCli.ts +109 -0
- package/caps/patterns/README.md +2 -0
- package/caps/patterns/git-scm.com/ProjectPublishing.ts +507 -0
- package/caps/patterns/semver.org/ProjectPublishing.ts +458 -0
- package/docs/Overview.drawio +248 -0
- package/docs/Overview.svg +4 -0
- package/examples/01-Lifecycle/main.test.ts +223 -0
- package/lib/crypto.ts +53 -0
- package/lib/key.ts +381 -0
- package/lib/schema-console-renderer.ts +181 -0
- package/lib/schema-resolver.ts +349 -0
- package/lib/ucan.ts +137 -0
- package/package.json +91 -0
- package/standalone-rt.test.ts +150 -0
- package/standalone-rt.ts +140 -0
- package/structs/HomeRegistry.ts +55 -0
- package/structs/HomeRegistryConfig.ts +60 -0
- package/structs/ProjectCatalogsConfig.ts +53 -0
- package/structs/ProjectDeploymentConfig.ts +56 -0
- package/structs/ProjectDeploymentFact.ts +106 -0
- package/structs/ProjectPublishingConfig.ts +78 -0
- package/structs/ProjectPublishingFact.ts +68 -0
- package/structs/ProjectPullingConfig.ts +52 -0
- package/structs/ProjectRack.ts +51 -0
- package/structs/ProjectRackConfig.ts +56 -0
- package/structs/RepositoryOriginDescriptor.ts +51 -0
- package/structs/RootKeyConfig.ts +64 -0
- package/structs/SigningKeyConfig.ts +64 -0
- package/structs/Workspace.ts +56 -0
- package/structs/WorkspaceCatalogs.ts +56 -0
- package/structs/WorkspaceCliConfig.ts +53 -0
- package/structs/WorkspaceConfig.ts +64 -0
- package/structs/WorkspaceConfigFile.ts +50 -0
- package/structs/WorkspaceConfigFileMeta.ts +70 -0
- package/structs/WorkspaceKey.ts +55 -0
- package/structs/WorkspaceKeyConfig.ts +56 -0
- package/structs/WorkspaceMappingsConfig.ts +56 -0
- package/structs/WorkspaceProject.ts +104 -0
- package/structs/WorkspaceProjectsConfig.ts +67 -0
- package/structs/WorkspaceShellConfig.ts +83 -0
- package/structs/patterns/README.md +2 -0
- package/structs/patterns/git-scm.com/ProjectPublishingFact.ts +46 -0
- package/tsconfig.json +33 -0
- package/workspace-rt.ts +152 -0
- package/workspace.yaml +3 -0
|
@@ -0,0 +1,338 @@
|
|
|
1
|
+
export async function capsule({
|
|
2
|
+
encapsulate,
|
|
3
|
+
CapsulePropertyTypes,
|
|
4
|
+
makeImportStack
|
|
5
|
+
}: {
|
|
6
|
+
encapsulate: any
|
|
7
|
+
CapsulePropertyTypes: any
|
|
8
|
+
makeImportStack: any
|
|
9
|
+
}) {
|
|
10
|
+
return encapsulate({
|
|
11
|
+
'#@stream44.studio/encapsulate/spine-contracts/CapsuleSpineContract.v0': {
|
|
12
|
+
'#@stream44.studio/encapsulate/structs/Capsule': {},
|
|
13
|
+
'#@stream44.studio/t44/structs/WorkspaceConfig': {
|
|
14
|
+
as: '$WorkspaceConfig'
|
|
15
|
+
},
|
|
16
|
+
'#@stream44.studio/t44/structs/WorkspaceKeyConfig': {
|
|
17
|
+
as: '$WorkspaceKeyConfig'
|
|
18
|
+
},
|
|
19
|
+
'#': {
|
|
20
|
+
WorkspacePrompt: {
|
|
21
|
+
type: CapsulePropertyTypes.Mapping,
|
|
22
|
+
value: '@stream44.studio/t44/caps/WorkspacePrompt'
|
|
23
|
+
},
|
|
24
|
+
HomeRegistry: {
|
|
25
|
+
type: CapsulePropertyTypes.Mapping,
|
|
26
|
+
value: '@stream44.studio/t44/caps/HomeRegistry'
|
|
27
|
+
},
|
|
28
|
+
RootKey: {
|
|
29
|
+
type: CapsulePropertyTypes.Mapping,
|
|
30
|
+
value: '@stream44.studio/t44/caps/RootKey'
|
|
31
|
+
},
|
|
32
|
+
ensureKey: {
|
|
33
|
+
type: CapsulePropertyTypes.Function,
|
|
34
|
+
value: async function (this: any): Promise<{ keyName: string; keyPath: string }> {
|
|
35
|
+
const workspaceConfig = await this.$WorkspaceConfig.config
|
|
36
|
+
const keyConfig = await this.$WorkspaceKeyConfig.config
|
|
37
|
+
|
|
38
|
+
// Check if key is already set in config (object format: { name, identifier })
|
|
39
|
+
if (keyConfig?.name && keyConfig?.identifier) {
|
|
40
|
+
const keyExists = await this.HomeRegistry.keyExists(keyConfig.name)
|
|
41
|
+
|
|
42
|
+
if (keyExists) {
|
|
43
|
+
// Migrate legacy: encrypt raw privateKey if not yet encrypted
|
|
44
|
+
const keyData = await this.HomeRegistry.getKey(keyConfig.name)
|
|
45
|
+
if (keyData?.privateKey && !keyData?.encryptedPrivateKey) {
|
|
46
|
+
const passphrase = await this._derivePassphrase(keyConfig.name)
|
|
47
|
+
const { encryptString: encryptFn } = await import('../lib/crypto.js')
|
|
48
|
+
const chalk = (await import('chalk')).default
|
|
49
|
+
keyData.encryptedPrivateKey = encryptFn(keyData.privateKey, passphrase)
|
|
50
|
+
delete keyData.privateKey
|
|
51
|
+
await this.HomeRegistry.setKey(keyConfig.name, keyData)
|
|
52
|
+
console.log(chalk.green(` ✓ Private key encrypted with root key\n`))
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// Verify decryption works with current root key derivation
|
|
56
|
+
if (keyData?.encryptedPrivateKey) {
|
|
57
|
+
const passphrase = await this._derivePassphrase(keyConfig.name)
|
|
58
|
+
const { decryptString: decryptFn } = await import('../lib/crypto.js')
|
|
59
|
+
try {
|
|
60
|
+
decryptFn(keyData.encryptedPrivateKey, passphrase)
|
|
61
|
+
} catch {
|
|
62
|
+
const chalk = (await import('chalk')).default
|
|
63
|
+
const { stat: statFile } = await import('fs/promises')
|
|
64
|
+
const keyPath = await this.HomeRegistry.getKeyPath(keyConfig.name)
|
|
65
|
+
|
|
66
|
+
// Run diagnostics
|
|
67
|
+
const rootKeyPath = await this.RootKey.getKeyPath()
|
|
68
|
+
let rootKeyFound = false
|
|
69
|
+
let rootKeyFingerprint = ''
|
|
70
|
+
if (rootKeyPath) {
|
|
71
|
+
try {
|
|
72
|
+
await statFile(rootKeyPath)
|
|
73
|
+
rootKeyFound = true
|
|
74
|
+
const { execSync } = await import('child_process')
|
|
75
|
+
const fpOutput = execSync(`ssh-keygen -lf ${JSON.stringify(rootKeyPath)}`, { encoding: 'utf-8', stdio: ['pipe', 'pipe', 'pipe'] }).trim()
|
|
76
|
+
const match = fpOutput.match(/(SHA256:\S+)/)
|
|
77
|
+
rootKeyFingerprint = match ? match[1] : fpOutput
|
|
78
|
+
} catch { }
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
console.error(chalk.red(`\n┌─────────────────────────────────────────────────────────────────┐`))
|
|
82
|
+
console.error(chalk.red(`│ ✗ Workspace Key Decryption Failed │`))
|
|
83
|
+
console.error(chalk.red(`├─────────────────────────────────────────────────────────────────┤`))
|
|
84
|
+
console.error(chalk.red(`│ │`))
|
|
85
|
+
console.error(chalk.red(`│ The workspace key's encrypted private key cannot be decrypted │`))
|
|
86
|
+
console.error(chalk.red(`│ with the current root key. This typically happens when the │`))
|
|
87
|
+
console.error(chalk.red(`│ root SSH key has been changed or regenerated since the │`))
|
|
88
|
+
console.error(chalk.red(`│ workspace key was first created. │`))
|
|
89
|
+
console.error(chalk.red(`│ │`))
|
|
90
|
+
console.error(chalk.red(`├─────────────────────────────────────────────────────────────────┤`))
|
|
91
|
+
console.error(chalk.red(`│ Diagnostics: │`))
|
|
92
|
+
console.error(chalk.red(`│ │`))
|
|
93
|
+
console.error(chalk.red(`│ ${rootKeyFound ? '✓' : '✗'} Root SSH key: ${rootKeyPath || '(not configured)'}`))
|
|
94
|
+
if (rootKeyFound && rootKeyFingerprint) {
|
|
95
|
+
console.error(chalk.red(`│ Fingerprint: ${rootKeyFingerprint}`))
|
|
96
|
+
}
|
|
97
|
+
console.error(chalk.red(`│ ✓ Workspace key file: ${keyPath}`))
|
|
98
|
+
console.error(chalk.red(`│ DID: ${keyData.did}`))
|
|
99
|
+
console.error(chalk.red(`│ Created: ${keyData.createdAt}`))
|
|
100
|
+
console.error(chalk.red(`│ ✗ Passphrase derivation: mismatch`))
|
|
101
|
+
console.error(chalk.red(`│ │`))
|
|
102
|
+
console.error(chalk.red(`├─────────────────────────────────────────────────────────────────┤`))
|
|
103
|
+
console.error(chalk.red(`│ To fix, delete the workspace key file and re-run: │`))
|
|
104
|
+
console.error(chalk.red(`│ │`))
|
|
105
|
+
console.error(chalk.red(`│ rm ${keyPath}`))
|
|
106
|
+
console.error(chalk.red(`│ │`))
|
|
107
|
+
console.error(chalk.red(`│ A new workspace key will be generated automatically. │`))
|
|
108
|
+
console.error(chalk.red(`│ You will need to re-enter any saved connection credentials │`))
|
|
109
|
+
console.error(chalk.red(`│ (e.g. GitHub tokens) as they were encrypted with the old key. │`))
|
|
110
|
+
console.error(chalk.red(`└─────────────────────────────────────────────────────────────────┘\n`))
|
|
111
|
+
process.exit(1)
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
const keyPath = await this.HomeRegistry.getKeyPath(keyConfig.name)
|
|
116
|
+
return { keyName: keyConfig.name, keyPath }
|
|
117
|
+
} else {
|
|
118
|
+
const chalk = (await import('chalk')).default
|
|
119
|
+
const keyPath = await this.HomeRegistry.getKeyPath(keyConfig.name)
|
|
120
|
+
console.log(chalk.yellow(`\n⚠️ Workspace key '${keyConfig.name}' is configured but key file not found at:`))
|
|
121
|
+
console.log(chalk.yellow(` ${keyPath}\n`))
|
|
122
|
+
// Fall through to generate the key
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
let keyName: string
|
|
127
|
+
|
|
128
|
+
const keyConfigStructKey = '#@stream44.studio/t44/structs/WorkspaceKeyConfig'
|
|
129
|
+
if (!keyConfig?.name) {
|
|
130
|
+
const chalk = (await import('chalk')).default
|
|
131
|
+
|
|
132
|
+
console.log(chalk.cyan(`\n🔐 Workspace Key Setup\n`))
|
|
133
|
+
console.log(chalk.gray(` Workspace: ${workspaceConfig?.name || 'unknown'}`))
|
|
134
|
+
console.log(chalk.gray(` Root: ${workspaceConfig?.rootDir || 'unknown'}`))
|
|
135
|
+
console.log(chalk.gray(''))
|
|
136
|
+
console.log(chalk.gray(' All credentials in this workspace are encrypted with a workspace key.'))
|
|
137
|
+
console.log(chalk.gray(' You can select an existing key or create a new one.'))
|
|
138
|
+
console.log(chalk.gray(''))
|
|
139
|
+
|
|
140
|
+
// List existing workspace keys from registry
|
|
141
|
+
const existingKeys = await this.HomeRegistry.listKeys()
|
|
142
|
+
|
|
143
|
+
// Build choices
|
|
144
|
+
const choices: Array<{ name: string; value: any }> = []
|
|
145
|
+
|
|
146
|
+
for (const key of existingKeys) {
|
|
147
|
+
choices.push({
|
|
148
|
+
name: `${key.name} ${chalk.gray(key.did ? key.did.substring(0, 50) + '...' : '')}`,
|
|
149
|
+
value: { type: 'existing', name: key.name }
|
|
150
|
+
})
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
choices.push({
|
|
154
|
+
name: chalk.yellow('+ Create a new workspace key'),
|
|
155
|
+
value: { type: 'create' }
|
|
156
|
+
})
|
|
157
|
+
|
|
158
|
+
const selected = await this.WorkspacePrompt.select({
|
|
159
|
+
message: 'Select a workspace key:',
|
|
160
|
+
choices,
|
|
161
|
+
defaultValue: { type: 'create' },
|
|
162
|
+
pageSize: 15
|
|
163
|
+
})
|
|
164
|
+
|
|
165
|
+
if (selected.type === 'existing') {
|
|
166
|
+
keyName = selected.name
|
|
167
|
+
} else {
|
|
168
|
+
// Prompt for key name
|
|
169
|
+
keyName = await this.WorkspacePrompt.input({
|
|
170
|
+
message: 'Enter a name for the new workspace key:',
|
|
171
|
+
defaultValue: workspaceConfig?.name || 'genesis',
|
|
172
|
+
validate: (input: string) => {
|
|
173
|
+
if (!input || input.trim().length === 0) {
|
|
174
|
+
return 'Key name cannot be empty'
|
|
175
|
+
}
|
|
176
|
+
if (!/^[a-zA-Z0-9_-]+$/.test(input)) {
|
|
177
|
+
return 'Key name can only contain letters, numbers, underscores, and hyphens'
|
|
178
|
+
}
|
|
179
|
+
return true
|
|
180
|
+
}
|
|
181
|
+
})
|
|
182
|
+
}
|
|
183
|
+
} else {
|
|
184
|
+
keyName = keyConfig.name
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
// Check if key already exists in registry
|
|
188
|
+
let keyData = await this.HomeRegistry.getKey(keyName)
|
|
189
|
+
|
|
190
|
+
// Derive passphrase for encrypting the private key
|
|
191
|
+
const passphrase = await this._derivePassphrase(keyName)
|
|
192
|
+
const { encryptString: encryptFn } = await import('../lib/crypto.js')
|
|
193
|
+
|
|
194
|
+
if (!keyData) {
|
|
195
|
+
const chalk = (await import('chalk')).default
|
|
196
|
+
// Generate Ed25519 key pair using UCAN library
|
|
197
|
+
console.log(chalk.cyan(`\n Generating Ed25519 key '${keyName}'...\n`))
|
|
198
|
+
|
|
199
|
+
const { generateKeypair } = await import('../lib/ucan.js')
|
|
200
|
+
const { did, privateKey } = await generateKeypair()
|
|
201
|
+
|
|
202
|
+
// Encrypt the private key before storing
|
|
203
|
+
const encryptedPrivateKey = encryptFn(privateKey, passphrase)
|
|
204
|
+
|
|
205
|
+
keyData = {
|
|
206
|
+
did,
|
|
207
|
+
encryptedPrivateKey,
|
|
208
|
+
createdAt: new Date().toISOString()
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
const keyPath = await this.HomeRegistry.setKey(keyName, keyData)
|
|
212
|
+
|
|
213
|
+
console.log(chalk.green(` ✓ Key generated and saved to:`))
|
|
214
|
+
console.log(chalk.green(` ${keyPath}`))
|
|
215
|
+
console.log(chalk.green(` ✓ DID: ${keyData.did}\n`))
|
|
216
|
+
} else {
|
|
217
|
+
const chalk = (await import('chalk')).default
|
|
218
|
+
const keyPath = await this.HomeRegistry.getKeyPath(keyName)
|
|
219
|
+
|
|
220
|
+
// Migrate legacy: encrypt raw privateKey and remove it
|
|
221
|
+
if (keyData.privateKey && !keyData.encryptedPrivateKey) {
|
|
222
|
+
const encryptedPrivateKey = encryptFn(keyData.privateKey, passphrase)
|
|
223
|
+
keyData.encryptedPrivateKey = encryptedPrivateKey
|
|
224
|
+
delete keyData.privateKey
|
|
225
|
+
await this.HomeRegistry.setKey(keyName, keyData)
|
|
226
|
+
console.log(chalk.green(`\n ✓ Using existing key at:`))
|
|
227
|
+
console.log(chalk.green(` ${keyPath}`))
|
|
228
|
+
console.log(chalk.green(` ✓ DID: ${keyData.did}`))
|
|
229
|
+
console.log(chalk.green(` ✓ Private key encrypted with root key\n`))
|
|
230
|
+
} else {
|
|
231
|
+
console.log(chalk.green(`\n ✓ Using existing key at:`))
|
|
232
|
+
console.log(chalk.green(` ${keyPath}`))
|
|
233
|
+
console.log(chalk.green(` ✓ DID: ${keyData.did}\n`))
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
// Store key as object { name, identifier } in key config struct
|
|
238
|
+
await this.$WorkspaceKeyConfig.setConfigValue(['name'], keyName)
|
|
239
|
+
await this.$WorkspaceKeyConfig.setConfigValue(['identifier'], keyData.did)
|
|
240
|
+
|
|
241
|
+
const keyPath = await this.HomeRegistry.getKeyPath(keyName)
|
|
242
|
+
return { keyName, keyPath }
|
|
243
|
+
}
|
|
244
|
+
},
|
|
245
|
+
getKeyPath: {
|
|
246
|
+
type: CapsulePropertyTypes.Function,
|
|
247
|
+
value: async function (this: any): Promise<string | null> {
|
|
248
|
+
const keyConfig = await this.$WorkspaceKeyConfig.config
|
|
249
|
+
|
|
250
|
+
if (!keyConfig?.name) {
|
|
251
|
+
return null
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
return this.HomeRegistry.getKeyPath(keyConfig.name)
|
|
255
|
+
}
|
|
256
|
+
},
|
|
257
|
+
getKey: {
|
|
258
|
+
type: CapsulePropertyTypes.Function,
|
|
259
|
+
value: async function (this: any): Promise<{ did: string; privateKey: string }> {
|
|
260
|
+
const keyConfig = await this.$WorkspaceKeyConfig.config
|
|
261
|
+
|
|
262
|
+
if (!keyConfig?.name) {
|
|
263
|
+
throw new Error('No workspace key configured. Run ensureKey() first.')
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
const keyData = await this.HomeRegistry.getKey(keyConfig.name)
|
|
267
|
+
|
|
268
|
+
if (!keyData) {
|
|
269
|
+
throw new Error(`Workspace key '${keyConfig.name}' not found in registry. Run ensureKey() first.`)
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
// Decrypt encryptedPrivateKey at runtime
|
|
273
|
+
if (keyData.encryptedPrivateKey) {
|
|
274
|
+
const passphrase = await this._derivePassphrase(keyConfig.name)
|
|
275
|
+
const { decryptString: decryptFn } = await import('../lib/crypto.js')
|
|
276
|
+
const privateKey = decryptFn(keyData.encryptedPrivateKey, passphrase)
|
|
277
|
+
return { did: keyData.did, privateKey }
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
// Legacy fallback: raw privateKey
|
|
281
|
+
if (keyData.privateKey) {
|
|
282
|
+
return { did: keyData.did, privateKey: keyData.privateKey }
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
throw new Error(`Workspace key '${keyConfig.name}' has no private key data.`)
|
|
286
|
+
}
|
|
287
|
+
},
|
|
288
|
+
getDid: {
|
|
289
|
+
type: CapsulePropertyTypes.Function,
|
|
290
|
+
value: async function (this: any): Promise<string> {
|
|
291
|
+
const { did } = await this.getKey()
|
|
292
|
+
return did
|
|
293
|
+
}
|
|
294
|
+
},
|
|
295
|
+
_derivePassphrase: {
|
|
296
|
+
type: CapsulePropertyTypes.Function,
|
|
297
|
+
value: async function (this: any, keyName: string): Promise<string> {
|
|
298
|
+
const crypto = await import('crypto')
|
|
299
|
+
const { readFile } = await import('fs/promises')
|
|
300
|
+
|
|
301
|
+
const rootKeyPath = await this.RootKey.getKeyPath()
|
|
302
|
+
if (!rootKeyPath) {
|
|
303
|
+
throw new Error('Root key not configured. Run RootKey.ensureKey() first.')
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
const privateKeyData = await readFile(rootKeyPath, 'utf-8')
|
|
307
|
+
const hash = crypto.createHmac('sha256', privateKeyData)
|
|
308
|
+
.update(keyName)
|
|
309
|
+
.digest('base64url')
|
|
310
|
+
|
|
311
|
+
return hash
|
|
312
|
+
}
|
|
313
|
+
},
|
|
314
|
+
encryptString: {
|
|
315
|
+
type: CapsulePropertyTypes.Function,
|
|
316
|
+
value: async function (this: any, plaintext: string): Promise<string> {
|
|
317
|
+
const { privateKey } = await this.getKey()
|
|
318
|
+
const { encryptString } = await import('../lib/crypto.js')
|
|
319
|
+
return encryptString(plaintext, privateKey)
|
|
320
|
+
}
|
|
321
|
+
},
|
|
322
|
+
decryptString: {
|
|
323
|
+
type: CapsulePropertyTypes.Function,
|
|
324
|
+
value: async function (this: any, ciphertext: string): Promise<string> {
|
|
325
|
+
const { privateKey } = await this.getKey()
|
|
326
|
+
const { decryptString } = await import('../lib/crypto.js')
|
|
327
|
+
return decryptString(ciphertext, privateKey)
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
}, {
|
|
333
|
+
importMeta: import.meta,
|
|
334
|
+
importStack: makeImportStack(),
|
|
335
|
+
capsuleName: capsule['#'],
|
|
336
|
+
})
|
|
337
|
+
}
|
|
338
|
+
capsule['#'] = '@stream44.studio/t44/caps/WorkspaceKey'
|