@cdmbase/wiki-browser 12.0.18-alpha.45 → 12.0.18-alpha.48
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.
|
@@ -540,6 +540,18 @@
|
|
|
540
540
|
"updatedAt": "Recently",
|
|
541
541
|
"frontmatter": {}
|
|
542
542
|
},
|
|
543
|
+
{
|
|
544
|
+
"contentId": "adminide-modules-preferences-credentials-contribution",
|
|
545
|
+
"slug": "preferences-credentials-contribution",
|
|
546
|
+
"filePath": "/content/docs/adminide-modules/preferences/credentials-contribution.md",
|
|
547
|
+
"relativePath": "adminide-modules/preferences/credentials-contribution.md",
|
|
548
|
+
"categoryId": "adminide-modules",
|
|
549
|
+
"title": "Credentials Contribution",
|
|
550
|
+
"description": "",
|
|
551
|
+
"author": "Documentation",
|
|
552
|
+
"updatedAt": "Recently",
|
|
553
|
+
"frontmatter": {}
|
|
554
|
+
},
|
|
543
555
|
{
|
|
544
556
|
"contentId": "adminide-modules-preferences-generate-urii",
|
|
545
557
|
"slug": "preferences-generate-urii",
|
|
@@ -3865,6 +3877,15 @@
|
|
|
3865
3877
|
"updatedAt": "Recently",
|
|
3866
3878
|
"categoryId": "adminide-modules"
|
|
3867
3879
|
},
|
|
3880
|
+
{
|
|
3881
|
+
"id": "adminide-modules-preferences-credentials-contribution",
|
|
3882
|
+
"title": "Credentials Contribution",
|
|
3883
|
+
"description": "",
|
|
3884
|
+
"slug": "preferences-credentials-contribution",
|
|
3885
|
+
"author": "Documentation",
|
|
3886
|
+
"updatedAt": "Recently",
|
|
3887
|
+
"categoryId": "adminide-modules"
|
|
3888
|
+
},
|
|
3868
3889
|
{
|
|
3869
3890
|
"id": "adminide-modules-account-auth0-login",
|
|
3870
3891
|
"title": "Facebook Setup",
|
|
@@ -5508,6 +5529,7 @@
|
|
|
5508
5529
|
"adminide-modules-preferences-policy-configuration": "/content/docs/adminide-modules/preferences/Policy-Configuration.md",
|
|
5509
5530
|
"adminide-modules-preferences-ui-components-resourcesettingsloader": "/content/docs/adminide-modules/preferences/UI-components/ResourceSettingsLoader.md",
|
|
5510
5531
|
"adminide-modules-preferences-contribute_scope_target": "/content/docs/adminide-modules/preferences/contribute_scope_target.md",
|
|
5532
|
+
"adminide-modules-preferences-credentials-contribution": "/content/docs/adminide-modules/preferences/credentials-contribution.md",
|
|
5511
5533
|
"adminide-modules-preferences-generate-urii": "/content/docs/adminide-modules/preferences/generate-urii.md",
|
|
5512
5534
|
"adminide-modules-preferences-machine-configuration": "/content/docs/adminide-modules/preferences/machine-configuration.md",
|
|
5513
5535
|
"adminide-modules-preferences-pagesettings-generatecdecodeuri": "/content/docs/adminide-modules/preferences/pageSettings/generateCdecodeUri.md",
|
|
@@ -6131,6 +6153,21 @@
|
|
|
6131
6153
|
"updatedAt": "Recently",
|
|
6132
6154
|
"frontmatter": {}
|
|
6133
6155
|
},
|
|
6156
|
+
{
|
|
6157
|
+
"type": "file",
|
|
6158
|
+
"name": "credentials-contribution",
|
|
6159
|
+
"title": "Credentials Contribution",
|
|
6160
|
+
"path": "adminide-modules/preferences/credentials-contribution.md",
|
|
6161
|
+
"contentId": "adminide-modules-preferences-credentials-contribution",
|
|
6162
|
+
"slug": "preferences-credentials-contribution",
|
|
6163
|
+
"categoryId": "adminide-modules",
|
|
6164
|
+
"filePath": "/content/docs/adminide-modules/preferences/credentials-contribution.md",
|
|
6165
|
+
"relativePath": "adminide-modules/preferences/credentials-contribution.md",
|
|
6166
|
+
"description": "",
|
|
6167
|
+
"author": "Documentation",
|
|
6168
|
+
"updatedAt": "Recently",
|
|
6169
|
+
"frontmatter": {}
|
|
6170
|
+
},
|
|
6134
6171
|
{
|
|
6135
6172
|
"type": "file",
|
|
6136
6173
|
"name": "generate-urii",
|
|
@@ -10384,6 +10421,12 @@
|
|
|
10384
10421
|
"children": [],
|
|
10385
10422
|
"isFile": true
|
|
10386
10423
|
},
|
|
10424
|
+
{
|
|
10425
|
+
"title": "Credentials Contribution",
|
|
10426
|
+
"path": "/help/adminide-modules/preferences-credentials-contribution",
|
|
10427
|
+
"children": [],
|
|
10428
|
+
"isFile": true
|
|
10429
|
+
},
|
|
10387
10430
|
{
|
|
10388
10431
|
"title": "Generate URI",
|
|
10389
10432
|
"path": "/help/adminide-modules/preferences-generate-urii",
|
|
@@ -0,0 +1,692 @@
|
|
|
1
|
+
# Contributed Credential Fields — Scope-Based Vault Routing
|
|
2
|
+
|
|
3
|
+
## Summary
|
|
4
|
+
|
|
5
|
+
Extension-contributed configuration properties with `format: "secret"` and `defaultValueSource.type: "vault"` need to route secrets to the correct vault based on **scope**. User-scoped secrets go to the **personal (account) vault**. Organization/project-scoped secrets go to the **project vault**. Today all secrets are routed to project vaults regardless of scope — this document defines the scope-aware routing design.
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## Current State
|
|
10
|
+
|
|
11
|
+
### How Secrets Flow Today
|
|
12
|
+
|
|
13
|
+
```
|
|
14
|
+
Extension contributes configuration:
|
|
15
|
+
format: "secret" + defaultValueSource.type: "vault"
|
|
16
|
+
│
|
|
17
|
+
▼
|
|
18
|
+
PreferencesService.writeSettings()
|
|
19
|
+
│
|
|
20
|
+
├── Separates secrets from regular settings (format === 'secret')
|
|
21
|
+
├── Extracts projectId from cdecode URI path segments
|
|
22
|
+
│
|
|
23
|
+
▼
|
|
24
|
+
SecretResolverService.storeSecret({ projectId, secretKey, secretValue, secretType })
|
|
25
|
+
│
|
|
26
|
+
▼
|
|
27
|
+
KeyMgmtSecretService.createSecret() → Always project-scoped vault
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
**Problem:** The `scope` value from the contribution schema is ignored during secret storage. An org-scoped secret (scope 3) and a publisher-scoped secret (scope 6) both end up in the same project vault. Additionally, there is no mechanism for users to choose whether a BYOK API key should be stored personally (account vault) or per-project (project vault).
|
|
31
|
+
|
|
32
|
+
---
|
|
33
|
+
|
|
34
|
+
## Configuration Scope Values
|
|
35
|
+
|
|
36
|
+
From `ConfigurationScope` enum:
|
|
37
|
+
|
|
38
|
+
| Value | Name | Level | Settings Storage | Secret Storage Target |
|
|
39
|
+
|---|---|---|---|---|
|
|
40
|
+
| 1 | `APPLICATION` | User | User settings | **Personal vault** (accountId) |
|
|
41
|
+
| 2 | `MACHINE` | User | User settings | **Personal vault** (accountId) |
|
|
42
|
+
| 3 | `WINDOW` | Organization | Organization settings | **Organization vault** (orgId) or project vault |
|
|
43
|
+
| 4 | `RESOURCE` | Project | Project settings | **Project vault** (projectId) |
|
|
44
|
+
| 5 | `RESOURCE_OVERRIDABLE` | Project | Project settings (overridable) | **Project vault** (projectId) |
|
|
45
|
+
| 6 | `MACHINE_OVERRIDABLE` | Publisher (global) | All orgs/projects can access | **Publisher vault** (shared across orgs) |
|
|
46
|
+
|
|
47
|
+
### Routing Rule
|
|
48
|
+
|
|
49
|
+
```
|
|
50
|
+
Scope 1-2 (APPLICATION, MACHINE) → Personal vault (account-scoped, user-only)
|
|
51
|
+
Scope 3 (WINDOW) → Organization-level (org settings + org/project vault)
|
|
52
|
+
Scope 4-5 (RESOURCE, RESOURCE_OVERRIDABLE) → Project vault (project-scoped)
|
|
53
|
+
Scope 6 (MACHINE_OVERRIDABLE) → Publisher scope (shared across all orgs/projects)
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
### Why This Split
|
|
57
|
+
|
|
58
|
+
- **Scope 1-2** are user-centric — values that follow the user across orgs/projects. Secrets at these scopes are personal credentials (personal API keys, personal OAuth tokens).
|
|
59
|
+
- **Scope 3 (WINDOW)** is organization-level — values scoped to an organization. Non-secret settings are stored in organization settings.
|
|
60
|
+
- **Scope 4-5 (RESOURCE)** are project-level — values tied to a specific project. Non-secret settings are stored in project settings, secrets are stored in the project vault with the `projectId`.
|
|
61
|
+
- **Scope 6 (MACHINE_OVERRIDABLE)** is publisher scope — a temporary designation for credentials that need to be accessible across all organizations and projects (e.g., OAuth client ID/secret provided by the extension publisher).
|
|
62
|
+
|
|
63
|
+
---
|
|
64
|
+
|
|
65
|
+
## Contributed Configuration Schema
|
|
66
|
+
|
|
67
|
+
### Field Structure
|
|
68
|
+
|
|
69
|
+
```jsonc
|
|
70
|
+
{
|
|
71
|
+
"contributes": {
|
|
72
|
+
"configuration": [{
|
|
73
|
+
"title": "integrationSettings",
|
|
74
|
+
"properties": {
|
|
75
|
+
// Publisher-scoped: shared OAuth app credentials (same for all orgs/projects)
|
|
76
|
+
"integrationSettings.settings.oauth.gmail.oauthConnector.clientId": {
|
|
77
|
+
"type": "string",
|
|
78
|
+
"title": "Client ID",
|
|
79
|
+
"description": "OAuth application client identifier",
|
|
80
|
+
"default": "",
|
|
81
|
+
"format": "secret",
|
|
82
|
+
"defaultValueSource": {
|
|
83
|
+
"type": "vault",
|
|
84
|
+
"secretType": "OAUTH_CLIENT",
|
|
85
|
+
"secretKey": "GMAIL_CLIENT_ID",
|
|
86
|
+
"requiredSignature": true
|
|
87
|
+
},
|
|
88
|
+
"scope": 6 // MACHINE_OVERRIDABLE = publisher scope
|
|
89
|
+
},
|
|
90
|
+
// Project-scoped: user tokens stored per-project in project vault
|
|
91
|
+
"integrationSettings.settings.oauth.gmail.oauthConnector.accessToken": {
|
|
92
|
+
"type": "string",
|
|
93
|
+
"title": "Access Token",
|
|
94
|
+
"format": "secret",
|
|
95
|
+
"defaultValueSource": {
|
|
96
|
+
"type": "vault",
|
|
97
|
+
"secretType": "OAUTH_CLIENT",
|
|
98
|
+
"secretKey": "GMAIL_ACCESS_TOKEN"
|
|
99
|
+
},
|
|
100
|
+
"scope": 4 // RESOURCE = project scope
|
|
101
|
+
},
|
|
102
|
+
// Project-scoped: non-secret config stored in project settings
|
|
103
|
+
"integrationSettings.settings.oauth.gmail.oauthConnector.provider": {
|
|
104
|
+
"type": "string",
|
|
105
|
+
"title": "Provider",
|
|
106
|
+
"default": "gmail",
|
|
107
|
+
"scope": 4 // RESOURCE = project scope (stored in project settings)
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
}]
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
### Field Descriptions
|
|
116
|
+
|
|
117
|
+
| Field | Purpose |
|
|
118
|
+
|---|---|
|
|
119
|
+
| `format: "secret"` | UI hint: mask the value, exclude from regular config store (`bulkWrite`) |
|
|
120
|
+
| `defaultValueSource.type` | Storage backend: `"vault"` routes to vault system |
|
|
121
|
+
| `defaultValueSource.secretType` | Vault item categorization (`OAUTH_CLIENT`, `API_KEY`, `ENVIRONMENT_VAR`, etc.) |
|
|
122
|
+
| `defaultValueSource.secretKey` | The key name used to store/retrieve the secret in the vault |
|
|
123
|
+
| `defaultValueSource.requiredSignature` | Whether the secret requires cryptographic signing before storage |
|
|
124
|
+
| `scope` | **Determines vault routing.** Single number or array. `1-2` → personal, `3` → org, `4-5` → project, `6` → publisher. Array (e.g., `[2, 4]`) = user chooses at write time |
|
|
125
|
+
| `scopePreference` | _(new)_ When `scope` is an array, determines **read priority** — which vault to check first |
|
|
126
|
+
---
|
|
127
|
+
|
|
128
|
+
## Third-Party API Keys — Multi-Scope Vault Routing (BYOK)
|
|
129
|
+
|
|
130
|
+
### Problem
|
|
131
|
+
|
|
132
|
+
The contributed configuration for third-party integrations (Alibaba, Anthropic, OpenAI, AWS, etc.) contains ~100+ API key fields that all currently use `"scope": 3` (WINDOW / organization). These are **Bring Your Own Key (BYOK)** credentials — the user provides their personal API key.
|
|
133
|
+
|
|
134
|
+
A single fixed scope doesn't work here because the same API key field can legitimately be stored in different places depending on the user's intent:
|
|
135
|
+
|
|
136
|
+
| Intent | Vault | Reasoning |
|
|
137
|
+
|---|---|---|
|
|
138
|
+
| "This is **my personal** API key, use it everywhere I work" | **Personal vault** (accountId) | Key follows the user across projects/orgs |
|
|
139
|
+
| "This API key is for **this project**, share it with the team" | **Project vault** (projectId) | Key is tied to the project, visible to members with access |
|
|
140
|
+
|
|
141
|
+
Two users in the same project can each make a different choice — User A stores as shared (project vault), User B stores as personal (personal vault). Both are valid simultaneously.
|
|
142
|
+
|
|
143
|
+
### Schema Change: `scope` as Array + `scopePreference`
|
|
144
|
+
|
|
145
|
+
The `scope` field already supports arrays. When a field declares multiple scopes, it means the field can be stored at **any** of those levels and the user chooses which one at write time.
|
|
146
|
+
|
|
147
|
+
A new `scopePreference` field determines **read priority** — which vault to check first when both personal and project copies might exist:
|
|
148
|
+
|
|
149
|
+
```jsonc
|
|
150
|
+
"integrationSettings.settings.api.openai.openaiApiKey": {
|
|
151
|
+
"type": "string",
|
|
152
|
+
"title": "OpenAI API Key",
|
|
153
|
+
"default": "",
|
|
154
|
+
"format": "secret",
|
|
155
|
+
"defaultValueSource": {
|
|
156
|
+
"type": "vault",
|
|
157
|
+
"secretType": "ENVIRONMENT_VAR",
|
|
158
|
+
"secretKey": "OPEN_AI_API_KEY"
|
|
159
|
+
},
|
|
160
|
+
"scope": [2, 4], // MACHINE (personal) + RESOURCE (project)
|
|
161
|
+
"scopePreference": 2, // prefer personal vault on read
|
|
162
|
+
"writeOnly": true
|
|
163
|
+
}
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
| Field | Type | Purpose |
|
|
167
|
+
|---|---|---|
|
|
168
|
+
| `scope` (array) | `number[]` | Declares all valid storage scopes. `[2, 4]` = personal + project |
|
|
169
|
+
| `scopePreference` | `number` | Which scope to **prefer** on read. Determines vault check order |
|
|
170
|
+
|
|
171
|
+
### How `scope: [2, 4]` Works
|
|
172
|
+
|
|
173
|
+
- **Scope 2** (`MACHINE`) = personal / account-level → routes to **personal vault** (accountId)
|
|
174
|
+
- **Scope 4** (`RESOURCE`) = project-level → routes to **project vault** (projectId)
|
|
175
|
+
|
|
176
|
+
When a field has `scope: [2, 4]`, the UI presents both options and the user picks one:
|
|
177
|
+
|
|
178
|
+
```
|
|
179
|
+
┌─────────────────────────────────────────────────────┐
|
|
180
|
+
│ OpenAI API Key │
|
|
181
|
+
│ ┌───────────────────────────────────┐ │
|
|
182
|
+
│ │ ●●●●●●●●●●●●●●●●●●●●●●●● │ │
|
|
183
|
+
│ └───────────────────────────────────┘ │
|
|
184
|
+
│ │
|
|
185
|
+
│ Store as: ● Personal (my account) │
|
|
186
|
+
│ ○ Project (shared with team) │
|
|
187
|
+
│ │
|
|
188
|
+
│ ℹ️ Personal: follows you across all projects │
|
|
189
|
+
│ Project: visible to project members with access │
|
|
190
|
+
└─────────────────────────────────────────────────────┘
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
- **Default selection**: Driven by `scopePreference` value (2 = personal selected by default)
|
|
194
|
+
- The user's choice determines which vault the secret is written to
|
|
195
|
+
- Each user in the same project can independently choose personal or project
|
|
196
|
+
|
|
197
|
+
### Why `scopePreference` Is Needed
|
|
198
|
+
|
|
199
|
+
When reading a secret for a field with `scope: [2, 4]`, both a personal and project copy may exist. The system needs to know which to prefer:
|
|
200
|
+
|
|
201
|
+
| `scopePreference` | Read order | Use case |
|
|
202
|
+
|---|---|---|
|
|
203
|
+
| `2` (personal first) | Personal vault → Project vault | BYOK API keys — user's own key takes priority |
|
|
204
|
+
| `4` (project first) | Project vault → Personal vault | Shared credentials — team key takes priority, personal as fallback |
|
|
205
|
+
|
|
206
|
+
**Scenario: Two users in the same project**
|
|
207
|
+
|
|
208
|
+
```
|
|
209
|
+
Project "acme-app" — OpenAI API Key field (scope: [2, 4], scopePreference: 2)
|
|
210
|
+
|
|
211
|
+
User A: stores as "project" → goes to project vault for acme-app
|
|
212
|
+
User B: stores as "personal" → goes to B's personal vault
|
|
213
|
+
|
|
214
|
+
When User A reads: scopePreference=2 → check personal vault (empty) → fallback to project vault ✓
|
|
215
|
+
When User B reads: scopePreference=2 → check personal vault (found!) → returns B's key
|
|
216
|
+
When User C reads: scopePreference=2 → check personal vault (empty) → fallback to project vault (User A's shared key) ✓
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
This means:
|
|
220
|
+
- User B always uses their own personal key (even though a project key exists)
|
|
221
|
+
- Users A and C share the project key
|
|
222
|
+
- No conflicts, no overwrites
|
|
223
|
+
|
|
224
|
+
### Scope Change for All API Key Fields
|
|
225
|
+
|
|
226
|
+
All `integrationSettings.settings.api.*` fields currently at `"scope": 3` should be updated:
|
|
227
|
+
|
|
228
|
+
**Before:** `"scope": 3` → Organization vault (wrong — these are not org-wide credentials)
|
|
229
|
+
**After (secret fields):** `"scope": [2, 4]` + `"scopePreference": 2` → Personal + project, prefer personal
|
|
230
|
+
**After (non-secret fields):** `"scope": 4` → Project settings (endpoints, URLs, names)
|
|
231
|
+
|
|
232
|
+
### Write Flow with Multi-Scope
|
|
233
|
+
|
|
234
|
+
```typescript
|
|
235
|
+
// In writeSettings(), extract the user's chosen scope from the payload
|
|
236
|
+
const chosenScope = settingsPayload[`${secretKey}.__chosenScope`]; // 2 | 4
|
|
237
|
+
|
|
238
|
+
function resolveVaultOwnerForMultiScope(
|
|
239
|
+
scopes: number | number[],
|
|
240
|
+
chosenScope: number | undefined,
|
|
241
|
+
context: IUserContext,
|
|
242
|
+
projectId: string,
|
|
243
|
+
orgId: string
|
|
244
|
+
) {
|
|
245
|
+
const scopeArray = Array.isArray(scopes) ? scopes : [scopes];
|
|
246
|
+
|
|
247
|
+
// If multi-scope and user explicitly chose, use that scope
|
|
248
|
+
if (scopeArray.length > 1 && chosenScope && scopeArray.includes(chosenScope)) {
|
|
249
|
+
return resolveVaultOwner(chosenScope, context, projectId, orgId);
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
// Single scope or no explicit choice — use first scope in array
|
|
253
|
+
return resolveVaultOwner(scopeArray[0], context, projectId, orgId);
|
|
254
|
+
}
|
|
255
|
+
```
|
|
256
|
+
|
|
257
|
+
### Read Flow with `scopePreference`
|
|
258
|
+
|
|
259
|
+
When reading a secret for a multi-scope field, check vaults in `scopePreference` order:
|
|
260
|
+
|
|
261
|
+
```typescript
|
|
262
|
+
async resolveSecretForMultiScope(params: {
|
|
263
|
+
secretKey: string;
|
|
264
|
+
scopes: number | number[];
|
|
265
|
+
scopePreference: number;
|
|
266
|
+
accountId: string;
|
|
267
|
+
projectId: string;
|
|
268
|
+
orgId: string;
|
|
269
|
+
}) {
|
|
270
|
+
const { secretKey, scopes, scopePreference, accountId, projectId, orgId } = params;
|
|
271
|
+
const scopeArray = Array.isArray(scopes) ? scopes : [scopes];
|
|
272
|
+
|
|
273
|
+
// Single scope — standard resolution
|
|
274
|
+
if (scopeArray.length === 1) {
|
|
275
|
+
const { ownerType, ownerId } = resolveVaultOwner(scopeArray[0], { accountId }, projectId, orgId);
|
|
276
|
+
return this.resolveSecret({ ownerType, ownerId, secretKey });
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
// Multi-scope: order by scopePreference first, then remaining scopes
|
|
280
|
+
const orderedScopes = [
|
|
281
|
+
scopePreference,
|
|
282
|
+
...scopeArray.filter(s => s !== scopePreference)
|
|
283
|
+
];
|
|
284
|
+
|
|
285
|
+
for (const scope of orderedScopes) {
|
|
286
|
+
const { ownerType, ownerId } = resolveVaultOwner(scope, { accountId }, projectId, orgId);
|
|
287
|
+
const secret = await this.resolveSecret({ ownerType, ownerId, secretKey });
|
|
288
|
+
if (secret) {
|
|
289
|
+
return { ...secret, resolvedFromScope: scope };
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
return null; // Not stored in any vault
|
|
294
|
+
}
|
|
295
|
+
```
|
|
296
|
+
|
|
297
|
+
**Resolution order for `scope: [2, 4]` with `scopePreference: 2`:**
|
|
298
|
+
1. Check personal vault (scope 2) → if found, return it
|
|
299
|
+
2. Check project vault (scope 4) → if found, return it
|
|
300
|
+
3. Return null
|
|
301
|
+
|
|
302
|
+
### Affected Fields
|
|
303
|
+
|
|
304
|
+
All `integrationSettings.settings.api.*` fields with `format: "secret"` and `scope: 3` should be updated:
|
|
305
|
+
|
|
306
|
+
| Change | Count | Details |
|
|
307
|
+
|---|---|---|
|
|
308
|
+
| `"scope": 3` → `"scope": [2, 4]` | ~80 secret fields | Personal + project vault |
|
|
309
|
+
| Add `"scopePreference": 2` | ~80 secret fields | Prefer personal vault on read |
|
|
310
|
+
| Non-secret fields `"scope": 3` → `"scope": 4` | ~40 config fields | Endpoints, URLs, names — project settings only |
|
|
311
|
+
|
|
312
|
+
**Examples of affected providers:** Alibaba, Anthropic, Apify, Arize, AssemblyAI, Astra DB, AWS, Azure OpenAI, Baidu Qianfan, Brave Search, Cerebras, Chroma, Cohere, Composio, Confluence, Couchbase, DeepSeek, DynamoDB, E2B, ElasticSearch, Exa Search, Figma, FireCrawl, Fireworks, GitHub, Google (Custom Search, Generative AI, Vertex AI), Groq, HuggingFace, IBM Watsonx, Jina AI, Langfuse, Langsmith, LangWatch, LocalAI, Lunary, Meilisearch, Milvus, Mistral AI, Momento, MongoDB, MySQL, Neo4j, Notion, NVIDIA NIM, OpenAI, OpenRouter, OpenSearch, Phoenix, Pinecone, PostgreSQL, Qdrant, Redis, Replicate, SearchAPI, SerpApi, Serper, SingleStore, Slack, Spider, Stripe, Supabase, Tavily, Together AI, Unstructured, Upstash (Redis, Vector), Vectara, Voyage AI, Weaviate, Wolfram Alpha, xAI, Zep.
|
|
313
|
+
|
|
314
|
+
### Example: Updated Field
|
|
315
|
+
|
|
316
|
+
```jsonc
|
|
317
|
+
// BEFORE
|
|
318
|
+
"integrationSettings.settings.api.openai.openaiApiKey": {
|
|
319
|
+
"type": "string",
|
|
320
|
+
"title": "OpenAI API Key",
|
|
321
|
+
"default": "",
|
|
322
|
+
"format": "secret",
|
|
323
|
+
"defaultValueSource": {
|
|
324
|
+
"type": "vault",
|
|
325
|
+
"secretType": "ENVIRONMENT_VAR",
|
|
326
|
+
"secretKey": "OPEN_AI_API_KEY"
|
|
327
|
+
},
|
|
328
|
+
"scope": 3, // WINDOW (org) — wrong
|
|
329
|
+
"writeOnly": true
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
// AFTER
|
|
333
|
+
"integrationSettings.settings.api.openai.openaiApiKey": {
|
|
334
|
+
"type": "string",
|
|
335
|
+
"title": "OpenAI API Key",
|
|
336
|
+
"default": "",
|
|
337
|
+
"format": "secret",
|
|
338
|
+
"defaultValueSource": {
|
|
339
|
+
"type": "vault",
|
|
340
|
+
"secretType": "ENVIRONMENT_VAR",
|
|
341
|
+
"secretKey": "OPEN_AI_API_KEY"
|
|
342
|
+
},
|
|
343
|
+
"scope": [2, 4], // MACHINE (personal) + RESOURCE (project)
|
|
344
|
+
"scopePreference": 2, // prefer personal vault on read
|
|
345
|
+
"writeOnly": true
|
|
346
|
+
}
|
|
347
|
+
```
|
|
348
|
+
|
|
349
|
+
---
|
|
350
|
+
|
|
351
|
+
## Design: Scope-Aware Secret Routing
|
|
352
|
+
|
|
353
|
+
### Updated Flow
|
|
354
|
+
|
|
355
|
+
```
|
|
356
|
+
Extension contributes configuration:
|
|
357
|
+
format: "secret" + defaultValueSource.type: "vault" + scope
|
|
358
|
+
│
|
|
359
|
+
▼
|
|
360
|
+
PreferencesService.writeSettings()
|
|
361
|
+
│
|
|
362
|
+
├── Separates secrets from regular settings
|
|
363
|
+
├── Extracts scope from schemaInfo per key
|
|
364
|
+
│
|
|
365
|
+
▼
|
|
366
|
+
┌── scope 1-2? ──┬── scope 3? ──────┬── scope 4-5? ─────┬── scope 6? ──────────┐
|
|
367
|
+
│ │ │ │ │
|
|
368
|
+
▼ ▼ ▼ ▼ │
|
|
369
|
+
storeSecret({ storeSecret({ storeSecret({ storeSecret({ │
|
|
370
|
+
ownerType: ownerType: ownerType: ownerType: │
|
|
371
|
+
'account', 'organization', 'project', 'publisher', │
|
|
372
|
+
ownerId: ownerId: ownerId: ownerId: │
|
|
373
|
+
accountId orgId projectId publisherId │
|
|
374
|
+
}) }) }) }) │
|
|
375
|
+
│ │ │ │ │
|
|
376
|
+
▼ ▼ ▼ ▼ │
|
|
377
|
+
Personal vault Org vault Project vault Publisher vault │
|
|
378
|
+
(user-only) (org settings) (per-project) (shared globally) │
|
|
379
|
+
```
|
|
380
|
+
|
|
381
|
+
### Changes Required
|
|
382
|
+
|
|
383
|
+
#### 1. `_validateSettingsBeforeWrite` — Return Scope in `schemaInfo`
|
|
384
|
+
|
|
385
|
+
Currently returns `{ format, defaultValueSource }` per key. Add `scope`:
|
|
386
|
+
|
|
387
|
+
```typescript
|
|
388
|
+
// schemaInfo[key] shape — BEFORE
|
|
389
|
+
{ format?: string; defaultValueSource?: object }
|
|
390
|
+
|
|
391
|
+
// schemaInfo[key] shape — AFTER
|
|
392
|
+
{ format?: string; defaultValueSource?: object; scope?: number }
|
|
393
|
+
```
|
|
394
|
+
|
|
395
|
+
Extract `scope` from the schema node alongside `format` and `defaultValueSource`.
|
|
396
|
+
|
|
397
|
+
#### 2. `writeSettings` — Pass Scope to `storeSecret`
|
|
398
|
+
|
|
399
|
+
```typescript
|
|
400
|
+
// Current code
|
|
401
|
+
const result = await this.secretResolverService.storeSecret({
|
|
402
|
+
projectId,
|
|
403
|
+
secretKey: vaultKeyName,
|
|
404
|
+
secretValue,
|
|
405
|
+
secretType: secretTypeValue,
|
|
406
|
+
environmentTag,
|
|
407
|
+
});
|
|
408
|
+
|
|
409
|
+
// Updated code — scope-aware routing
|
|
410
|
+
const secretScope = schemaInfo[secretKey]?.scope;
|
|
411
|
+
|
|
412
|
+
function resolveVaultOwner(scope: number, context: IUserContext, projectId: string, orgId: string) {
|
|
413
|
+
switch (true) {
|
|
414
|
+
case scope <= ConfigurationScope.MACHINE: // 1-2: user-only
|
|
415
|
+
return { ownerType: 'account' as const, ownerId: context.accountId };
|
|
416
|
+
case scope === ConfigurationScope.WINDOW: // 3: organization
|
|
417
|
+
return { ownerType: 'organization' as const, ownerId: orgId };
|
|
418
|
+
case scope <= ConfigurationScope.RESOURCE_OVERRIDABLE: // 4-5: project
|
|
419
|
+
return { ownerType: 'project' as const, ownerId: projectId };
|
|
420
|
+
case scope === ConfigurationScope.MACHINE_OVERRIDABLE: // 6: publisher (global)
|
|
421
|
+
return { ownerType: 'publisher' as const, ownerId: 'global' };
|
|
422
|
+
default:
|
|
423
|
+
return { ownerType: 'project' as const, ownerId: projectId }; // safe default
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
const { ownerType, ownerId } = resolveVaultOwner(secretScope, context, projectId, orgId);
|
|
428
|
+
|
|
429
|
+
const result = await this.secretResolverService.storeSecret({
|
|
430
|
+
ownerType,
|
|
431
|
+
ownerId,
|
|
432
|
+
secretKey: vaultKeyName,
|
|
433
|
+
secretValue,
|
|
434
|
+
secretType: secretTypeValue,
|
|
435
|
+
environmentTag,
|
|
436
|
+
});
|
|
437
|
+
```
|
|
438
|
+
|
|
439
|
+
#### 3. `ISecretResolverService.storeSecret` — Accept Owner Context
|
|
440
|
+
|
|
441
|
+
```typescript
|
|
442
|
+
// Current signature
|
|
443
|
+
storeSecret(params: {
|
|
444
|
+
projectId: string;
|
|
445
|
+
secretKey: string;
|
|
446
|
+
secretValue: string;
|
|
447
|
+
secretType: string;
|
|
448
|
+
environmentTag?: string;
|
|
449
|
+
}): Promise<{ success: boolean; error?: string }>;
|
|
450
|
+
|
|
451
|
+
// Updated signature
|
|
452
|
+
storeSecret(params: {
|
|
453
|
+
ownerType: 'account' | 'organization' | 'project' | 'publisher'; // NEW
|
|
454
|
+
ownerId: string; // NEW (replaces projectId)
|
|
455
|
+
projectId?: string; // DEPRECATED — kept for backward compat
|
|
456
|
+
secretKey: string;
|
|
457
|
+
secretValue: string;
|
|
458
|
+
secretType: string;
|
|
459
|
+
environmentTag?: string;
|
|
460
|
+
}): Promise<{ success: boolean; error?: string }>;
|
|
461
|
+
```
|
|
462
|
+
|
|
463
|
+
#### 4. `SecretResolverService.storeSecret` — Route to Correct Vault
|
|
464
|
+
|
|
465
|
+
```typescript
|
|
466
|
+
async storeSecret(params) {
|
|
467
|
+
const { ownerType, ownerId, projectId, secretKey, secretValue, secretType, environmentTag } = params;
|
|
468
|
+
|
|
469
|
+
// Backward compat: if ownerType not provided, default to project
|
|
470
|
+
const resolvedOwnerType = ownerType || 'project';
|
|
471
|
+
const resolvedOwnerId = ownerId || projectId;
|
|
472
|
+
|
|
473
|
+
let vault;
|
|
474
|
+
switch (resolvedOwnerType) {
|
|
475
|
+
case 'account':
|
|
476
|
+
// Personal vault — lazy creation
|
|
477
|
+
vault = await this.vaultService.getOrCreatePersonalVault(resolvedOwnerId);
|
|
478
|
+
break;
|
|
479
|
+
case 'organization':
|
|
480
|
+
// Org vault — lazy creation
|
|
481
|
+
vault = await this.vaultService.getOrCreateOrgVault(resolvedOwnerId);
|
|
482
|
+
break;
|
|
483
|
+
case 'publisher':
|
|
484
|
+
// Publisher vault — global, shared across all orgs/projects
|
|
485
|
+
vault = await this.vaultService.getOrCreatePublisherVault(resolvedOwnerId);
|
|
486
|
+
break;
|
|
487
|
+
case 'project':
|
|
488
|
+
default:
|
|
489
|
+
// Project vault — existing behavior
|
|
490
|
+
vault = await this.vaultService.getVault(resolvedOwnerId);
|
|
491
|
+
break;
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
return this.keyMgmtSecretService.createSecret({
|
|
495
|
+
vaultId: vault.id,
|
|
496
|
+
secretKey,
|
|
497
|
+
secretValue,
|
|
498
|
+
secretType,
|
|
499
|
+
environmentTag,
|
|
500
|
+
});
|
|
501
|
+
}
|
|
502
|
+
```
|
|
503
|
+
|
|
504
|
+
#### 5. `readSecret` — Same Scope-Based Resolution
|
|
505
|
+
|
|
506
|
+
The read path must mirror the write path. When resolving a `defaultValueSource` secret for display:
|
|
507
|
+
|
|
508
|
+
```typescript
|
|
509
|
+
async resolveSecret(params) {
|
|
510
|
+
const { ownerType, ownerId, secretKey } = params;
|
|
511
|
+
|
|
512
|
+
let vault;
|
|
513
|
+
switch (ownerType) {
|
|
514
|
+
case 'account':
|
|
515
|
+
vault = await this.vaultService.getPersonalVault(ownerId);
|
|
516
|
+
break;
|
|
517
|
+
case 'organization':
|
|
518
|
+
vault = await this.vaultService.getOrgVault(ownerId);
|
|
519
|
+
break;
|
|
520
|
+
case 'publisher':
|
|
521
|
+
vault = await this.vaultService.getPublisherVault(ownerId);
|
|
522
|
+
break;
|
|
523
|
+
case 'project':
|
|
524
|
+
default:
|
|
525
|
+
vault = await this.vaultService.getVault(ownerId);
|
|
526
|
+
break;
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
if (!vault) return null; // Vault not created yet — secret not stored
|
|
530
|
+
return this.keyMgmtSecretService.getSecret(vault.id, secretKey);
|
|
531
|
+
}
|
|
532
|
+
```
|
|
533
|
+
|
|
534
|
+
---
|
|
535
|
+
|
|
536
|
+
## Example: Gmail OAuth Connector Contributed Fields
|
|
537
|
+
|
|
538
|
+
Given the contribution from the user's example, fields should be scoped as follows:
|
|
539
|
+
|
|
540
|
+
### Publisher-Scoped (scope 6 — MACHINE_OVERRIDABLE)
|
|
541
|
+
|
|
542
|
+
Shared across all organizations and projects. These are the extension publisher's OAuth app credentials:
|
|
543
|
+
|
|
544
|
+
| Property | Scope | Storage | Reasoning |
|
|
545
|
+
|---|---|---|---|
|
|
546
|
+
| `clientId` | 6 | **Publisher vault** (shared) | OAuth app credential provided by publisher — same for all orgs/projects |
|
|
547
|
+
| `clientSecret` | 6 | **Publisher vault** (shared) | OAuth app secret provided by publisher — same for all orgs/projects |
|
|
548
|
+
|
|
549
|
+
### Project-Scoped (scope 4 — RESOURCE)
|
|
550
|
+
|
|
551
|
+
Per-project settings and secrets. Non-secret settings go to project settings, secrets go to the project vault with `projectId`:
|
|
552
|
+
|
|
553
|
+
| Property | Scope | Storage | Reasoning |
|
|
554
|
+
|---|---|---|---|
|
|
555
|
+
| `apiKey` | 4 | **Project vault** (projectId) | API key used within a specific project context |
|
|
556
|
+
| `accessToken` | 4 | **Project vault** (projectId) | User's OAuth access token for this project |
|
|
557
|
+
| `refreshToken` | 4 | **Project vault** (projectId) | User's OAuth refresh token for this project |
|
|
558
|
+
| `provider` | 4 | **Project settings** (not secret) | Configuration value — stored in project settings |
|
|
559
|
+
| `providerTitle` | 4 | **Project settings** (not secret) | Display name — stored in project settings |
|
|
560
|
+
| `redirectUri` | 4 | **Project settings** (not secret) | Callback URL — stored in project settings |
|
|
561
|
+
| `scopes` | 4 | **Project settings** (not secret) | OAuth scopes list — stored in project settings |
|
|
562
|
+
| `description` | 4 | **Project settings** (not secret) | Display text — stored in project settings |
|
|
563
|
+
| `isConnected` | 4 | **Project settings** (not secret) | Connection state — stored in project settings |
|
|
564
|
+
|
|
565
|
+
### Scope Change Required
|
|
566
|
+
|
|
567
|
+
The original contribution uses `scope: 3` (WINDOW/Organization) for project-level fields. These should be changed to `scope: 4` (RESOURCE/Project) because:
|
|
568
|
+
- Non-secret settings (provider, redirectUri, scopes, etc.) are stored in **project settings**
|
|
569
|
+
- Secret values (accessToken, refreshToken, apiKey) are stored in the **project vault** with `projectId`
|
|
570
|
+
- WINDOW (3) is organization-level — these values are not org-wide, they are project-specific
|
|
571
|
+
|
|
572
|
+
This correctly separates:
|
|
573
|
+
- **Publisher credentials** (clientId/clientSecret at scope 6) — provided by the extension publisher, shared globally across all orgs and projects
|
|
574
|
+
- **Project secrets** (accessToken/refreshToken/apiKey at scope 4) — stored per-project in the project vault
|
|
575
|
+
- **Project config** (provider/redirectUri/scopes at scope 4) — stored per-project in project settings
|
|
576
|
+
|
|
577
|
+
> **Note:** OAuth connector fields use **fixed single scopes** (publisher vs project). They do NOT use multi-scope arrays because the routing is deterministic — publisher credentials are always scope 6 (shared), user tokens are always scope 4 (project-scoped). Multi-scope `[2, 4]` is only for BYOK API key fields where the user decides personal vs shared.
|
|
578
|
+
|
|
579
|
+
---
|
|
580
|
+
|
|
581
|
+
## Access Control During Read
|
|
582
|
+
|
|
583
|
+
When a user reads settings, secrets are resolved from the appropriate vault based on scope:
|
|
584
|
+
|
|
585
|
+
```
|
|
586
|
+
User opens settings page
|
|
587
|
+
│
|
|
588
|
+
▼
|
|
589
|
+
PreferencesService.readSettings()
|
|
590
|
+
│
|
|
591
|
+
├── Regular settings: read from config store (filtered by scope + target)
|
|
592
|
+
│
|
|
593
|
+
├── Secret settings (format: "secret"):
|
|
594
|
+
│ │
|
|
595
|
+
│ ├── scope 1-2? → resolveSecret(ownerType: 'account', ownerId: accountId)
|
|
596
|
+
│ │ Returns value ONLY if accountId matches requesting user
|
|
597
|
+
│ │
|
|
598
|
+
│ ├── scope 3? → resolveSecret(ownerType: 'organization', ownerId: orgId)
|
|
599
|
+
│ │ Returns value based on org role permissions
|
|
600
|
+
│ │
|
|
601
|
+
│ ├── scope 4-5? → resolveSecret(ownerType: 'project', ownerId: projectId)
|
|
602
|
+
│ │ Returns value based on project role permissions
|
|
603
|
+
│ │
|
|
604
|
+
│ └── scope 6? → resolveSecret(ownerType: 'publisher', ownerId: publisherId)
|
|
605
|
+
│ Returns value (read-only, provided by publisher)
|
|
606
|
+
│
|
|
607
|
+
▼
|
|
608
|
+
Merged settings response (secrets masked in UI)
|
|
609
|
+
```
|
|
610
|
+
|
|
611
|
+
### Security Rules
|
|
612
|
+
|
|
613
|
+
| Scenario | Allowed? |
|
|
614
|
+
|---|---|
|
|
615
|
+
| User reads their own personal vault secret (scope 1-2) | Yes |
|
|
616
|
+
| User reads another user's personal vault secret | **No** — account vault is owner-only |
|
|
617
|
+
| Org admin reads org vault secret (scope 3) | Yes |
|
|
618
|
+
| Org member reads org vault secret (scope 3) | Based on org role |
|
|
619
|
+
| Project admin reads project vault secret (scope 4-5) | Yes |
|
|
620
|
+
| Project member (read role) reads project vault secret (scope 4-5) | Yes (value displayed masked) |
|
|
621
|
+
| Any user reads publisher vault secret (scope 6) | Yes (read-only, set by publisher) |
|
|
622
|
+
| Org admin reads a user's personal vault secret | **No** — personal vault is never org-visible |
|
|
623
|
+
|
|
624
|
+
---
|
|
625
|
+
|
|
626
|
+
## `requiredSignature` Field
|
|
627
|
+
|
|
628
|
+
Some secrets require cryptographic signing (e.g., `clientId` and `clientSecret` with `requiredSignature: true`):
|
|
629
|
+
|
|
630
|
+
```json
|
|
631
|
+
"defaultValueSource": {
|
|
632
|
+
"type": "vault",
|
|
633
|
+
"secretType": "OAUTH_CLIENT",
|
|
634
|
+
"secretKey": "GMAIL_CLIENT_ID",
|
|
635
|
+
"requiredSignature": true
|
|
636
|
+
}
|
|
637
|
+
```
|
|
638
|
+
|
|
639
|
+
When `requiredSignature: true`:
|
|
640
|
+
1. Before storing, the secret value is signed using the vault's encryption key
|
|
641
|
+
2. The signature is stored alongside the encrypted value as metadata
|
|
642
|
+
3. On retrieval, the signature is verified to detect tampering
|
|
643
|
+
4. If verification fails, the secret is treated as invalid and the user is prompted to re-enter
|
|
644
|
+
|
|
645
|
+
This is independent of scope routing — it applies to both personal and project vaults.
|
|
646
|
+
|
|
647
|
+
---
|
|
648
|
+
|
|
649
|
+
## Migration Path
|
|
650
|
+
|
|
651
|
+
### Existing Secrets
|
|
652
|
+
|
|
653
|
+
Existing secrets are all stored in project vaults. After implementing scope-aware routing:
|
|
654
|
+
|
|
655
|
+
1. **New secrets** written through `writeSettings` will be routed correctly based on scope
|
|
656
|
+
2. **Existing user-scoped secrets** (scope 1-3) that were stored in project vaults need migration:
|
|
657
|
+
- Run a migration that reads each secret's schema to determine its scope
|
|
658
|
+
- For scope 1-3 secrets, move them from project vault to the user's personal vault
|
|
659
|
+
- This requires knowing which user created the secret (audit trail)
|
|
660
|
+
3. **Existing project-scoped secrets** (scope 4-6) stay where they are — no change needed
|
|
661
|
+
|
|
662
|
+
### Backward Compatibility
|
|
663
|
+
|
|
664
|
+
- `storeSecret({ projectId })` without `ownerType` continues to work (defaults to `'project'`)
|
|
665
|
+
- Extensions that don't set `scope` default to project vault (safe default)
|
|
666
|
+
- Read path checks both vaults as fallback during migration period
|
|
667
|
+
|
|
668
|
+
---
|
|
669
|
+
|
|
670
|
+
## Files to Change
|
|
671
|
+
|
|
672
|
+
| File | Change |
|
|
673
|
+
|---|---|
|
|
674
|
+
| `packages/adminide-platform/server/src/services/preferences/preferences-service.ts` | `_validateSettingsBeforeWrite`: return `scope` (may be array) + `scopePreference` in schemaInfo. `writeSettings`: resolve `chosenScope` from payload, pass `ownerType`/`ownerId` |
|
|
675
|
+
| `packages/common/src/services/SecretResolverService.ts` | Update `storeSecret` interface to accept `ownerType` + `ownerId`. Add `resolveSecretForMultiScope` for BYOK fields |
|
|
676
|
+
| `packages-modules/user-auth0/server/src/modules/key-management/services/SecretResolverService.ts` | Implement vault routing based on `ownerType`. Implement multi-vault resolution with `scopePreference` ordering |
|
|
677
|
+
| `packages-modules/user-auth0/server/src/modules/key-management/services/KeyMgmtVaultService.ts` | Use `getOrCreatePersonalVault` for account-scoped secrets (depends on account vault feature from issue #3838) |
|
|
678
|
+
| `packages/credentials/server/src/migrations/...` | Update all `integrationSettings.settings.api.*` secret fields: `scope: 3` → `scope: [2, 4]`, add `scopePreference: 2`. Non-secret fields: `scope: 3` → `scope: 4` |
|
|
679
|
+
|
|
680
|
+
---
|
|
681
|
+
|
|
682
|
+
## Dependency
|
|
683
|
+
|
|
684
|
+
This design depends on **[#3838 — Account-level personal vault support](https://github.com/CDEBase/adminIde-stack/issues/3838)** being implemented first. The personal vault (`getOrCreatePersonalVault`) must exist before user-scoped secrets can be routed there.
|
|
685
|
+
|
|
686
|
+
Implementation order:
|
|
687
|
+
1. Account-level vault support (#3838)
|
|
688
|
+
2. Scope-aware routing in `writeSettings` / `storeSecret` — support `scope` as `number | number[]`
|
|
689
|
+
3. Multi-scope support — UI toggle for `scope: [2, 4]` fields, `scopePreference` read ordering
|
|
690
|
+
4. Migration: update all `integrationSettings.settings.api.*` secret fields from `scope: 3` → `scope: [2, 4]` + add `scopePreference: 2`. Non-secret fields from `scope: 3` → `scope: 4`
|
|
691
|
+
5. Read-path resolution with `scopePreference` ordering for multi-scope fields
|
|
692
|
+
6. Migration of existing user-scoped secrets from project vaults to personal vaults
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cdmbase/wiki-browser",
|
|
3
|
-
"version": "12.0.18-alpha.
|
|
3
|
+
"version": "12.0.18-alpha.48",
|
|
4
4
|
"description": "Sample core for higher packages to depend on",
|
|
5
5
|
"license": "ISC",
|
|
6
6
|
"author": "CDMBase LLC",
|
|
@@ -65,7 +65,7 @@
|
|
|
65
65
|
}
|
|
66
66
|
]
|
|
67
67
|
},
|
|
68
|
-
"gitHead": "
|
|
68
|
+
"gitHead": "696410c4abf236e6bb163f0a557b858a4b73279d",
|
|
69
69
|
"typescript": {
|
|
70
70
|
"definition": "lib/index.d.ts"
|
|
71
71
|
}
|