@kubun/store-credential 0.17.1 → 0.17.2
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/lib/api.d.ts +43 -0
- package/lib/api.js +41 -0
- package/lib/index.d.ts +2 -2
- package/lib/index.js +1 -1
- package/lib/migrations.js +6 -0
- package/lib/tables.d.ts +16 -0
- package/package.json +3 -3
package/lib/api.d.ts
CHANGED
|
@@ -361,5 +361,48 @@ export type CredentialStoreAPI = {
|
|
|
361
361
|
* `listProvisioningExpectations` can't give on its own (it's per-owner).
|
|
362
362
|
*/
|
|
363
363
|
listProvisioningExpectationOwners(): Promise<Array<string>>;
|
|
364
|
+
/**
|
|
365
|
+
* Refresh a controller-owned key's `credential/administer` delegation chain,
|
|
366
|
+
* gated strictly-greater on `refreshSeq` (stale/replayed is a silent no-op,
|
|
367
|
+
* same shape as {@link CredentialStoreAPI.putWrappingAuthority}). Handles
|
|
368
|
+
* both the absent-row insert and the monotone race. Returns whether a row
|
|
369
|
+
* was inserted or updated.
|
|
370
|
+
*/
|
|
371
|
+
refreshKeyOwnerDelegation(params: {
|
|
372
|
+
keyID: string;
|
|
373
|
+
delegationTokens: Array<string>;
|
|
374
|
+
refreshSeq: number;
|
|
375
|
+
}): Promise<boolean>;
|
|
376
|
+
/**
|
|
377
|
+
* Compare-and-swap a key-owner delegation chain, guarded on the `revision`
|
|
378
|
+
* the caller read. Replaces the row only when its CURRENT `revision` still
|
|
379
|
+
* equals `expectedRevision`, so a writer that read an invalid-present chain
|
|
380
|
+
* and revalidated it can install a replacement without racing another such
|
|
381
|
+
* writer or a concurrent refresh. The guard is `revision`, NOT `refreshSeq`:
|
|
382
|
+
* two distinct valid chains can share a `refreshSeq` (min-exp ms), so guarding
|
|
383
|
+
* on the seq has an ABA hole. Unlike {@link refreshKeyOwnerDelegation} this
|
|
384
|
+
* never inserts and is not monotone — it is the escape hatch for the
|
|
385
|
+
* invalid-present case, where the incoming chain's `refreshSeq` may be lower.
|
|
386
|
+
* Absent row is a no-op false, same as a revision mismatch. A successful
|
|
387
|
+
* write bumps `revision`.
|
|
388
|
+
*/
|
|
389
|
+
replaceKeyOwnerDelegationIfUnchanged(params: {
|
|
390
|
+
keyID: string;
|
|
391
|
+
expectedRevision: number;
|
|
392
|
+
delegationTokens: Array<string>;
|
|
393
|
+
refreshSeq: number;
|
|
394
|
+
}): Promise<boolean>;
|
|
395
|
+
/** The stored delegation chain, its `refreshSeq`, and its `revision`, or `undefined` when unset. */
|
|
396
|
+
getKeyOwnerDelegation(keyID: string): Promise<{
|
|
397
|
+
delegationTokens: Array<string>;
|
|
398
|
+
refreshSeq: number;
|
|
399
|
+
revision: number;
|
|
400
|
+
} | undefined>;
|
|
364
401
|
};
|
|
402
|
+
/**
|
|
403
|
+
* `refreshSeq` sentinel for a delegation chain that has no future refresh —
|
|
404
|
+
* larger than any real ms timestamp, so a bounded refresh can never overtake
|
|
405
|
+
* it. Consumed by `@kubun/plugin-p2p`.
|
|
406
|
+
*/
|
|
407
|
+
export declare const UNBOUNDED_REFRESH_SEQ = 8640000000000000;
|
|
365
408
|
export declare function createCredentialStore(db: Kysely<CredentialTables>, adapter: Adapter): CredentialStoreAPI;
|
package/lib/api.js
CHANGED
|
@@ -1,4 +1,9 @@
|
|
|
1
1
|
import { normalizeDID } from '@kokuin/token';
|
|
2
|
+
/**
|
|
3
|
+
* `refreshSeq` sentinel for a delegation chain that has no future refresh —
|
|
4
|
+
* larger than any real ms timestamp, so a bounded refresh can never overtake
|
|
5
|
+
* it. Consumed by `@kubun/plugin-p2p`.
|
|
6
|
+
*/ export const UNBOUNDED_REFRESH_SEQ = 8_640_000_000_000_000;
|
|
2
7
|
/**
|
|
3
8
|
* Run `write` as one atomic unit.
|
|
4
9
|
*
|
|
@@ -696,6 +701,42 @@ export function createCredentialStore(db, adapter) {
|
|
|
696
701
|
return [
|
|
697
702
|
...owners
|
|
698
703
|
];
|
|
704
|
+
},
|
|
705
|
+
async refreshKeyOwnerDelegation (params) {
|
|
706
|
+
const values = {
|
|
707
|
+
key_id: params.keyID,
|
|
708
|
+
delegation_tokens: adapter.encodeJSON(params.delegationTokens),
|
|
709
|
+
refresh_seq: params.refreshSeq,
|
|
710
|
+
revision: 1
|
|
711
|
+
};
|
|
712
|
+
const result = await db.insertInto('kubun_credential_key_owner_delegation').values(values).onConflict((oc)=>oc.column('key_id').doUpdateSet({
|
|
713
|
+
delegation_tokens: values.delegation_tokens,
|
|
714
|
+
refresh_seq: values.refresh_seq,
|
|
715
|
+
// Bump so a concurrent replace CAS on the pre-refresh revision fails.
|
|
716
|
+
revision: (eb)=>eb('kubun_credential_key_owner_delegation.revision', '+', 1)
|
|
717
|
+
}).where('kubun_credential_key_owner_delegation.refresh_seq', '<', values.refresh_seq)).executeTakeFirst();
|
|
718
|
+
return (result.numInsertedOrUpdatedRows ?? 0n) > 0n;
|
|
719
|
+
},
|
|
720
|
+
async replaceKeyOwnerDelegationIfUnchanged (params) {
|
|
721
|
+
const result = await db.updateTable('kubun_credential_key_owner_delegation').set({
|
|
722
|
+
delegation_tokens: adapter.encodeJSON(params.delegationTokens),
|
|
723
|
+
refresh_seq: params.refreshSeq,
|
|
724
|
+
revision: (eb)=>eb('kubun_credential_key_owner_delegation.revision', '+', 1)
|
|
725
|
+
}).where('key_id', '=', params.keyID).where('revision', '=', params.expectedRevision).executeTakeFirst();
|
|
726
|
+
return (result.numUpdatedRows ?? 0n) > 0n;
|
|
727
|
+
},
|
|
728
|
+
async getKeyOwnerDelegation (keyID) {
|
|
729
|
+
const row = await db.selectFrom('kubun_credential_key_owner_delegation').select([
|
|
730
|
+
'delegation_tokens',
|
|
731
|
+
'refresh_seq',
|
|
732
|
+
'revision'
|
|
733
|
+
]).where('key_id', '=', keyID).executeTakeFirst();
|
|
734
|
+
if (row == null) return undefined;
|
|
735
|
+
return {
|
|
736
|
+
delegationTokens: row.delegation_tokens,
|
|
737
|
+
refreshSeq: row.refresh_seq,
|
|
738
|
+
revision: row.revision
|
|
739
|
+
};
|
|
699
740
|
}
|
|
700
741
|
};
|
|
701
742
|
return api;
|
package/lib/index.d.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import type { StoreProvider } from '@kubun/db';
|
|
2
2
|
import type { CredentialStoreAPI } from './api.js';
|
|
3
3
|
export type { CredentialManifestRecord, CredentialOpInput, CredentialStoreAPI, CredentialTombstoneInput, HeldCredentialSubject, ListManifestsParams, NextGrantorSequenceParams, PutCredentialEntryParams, PutCredentialKeyParams, PutCredentialWrappingParams, PutWrappingAuthorityParams, StoredCredentialManifest, WrappingAuthority, } from './api.js';
|
|
4
|
-
export { createCredentialStore } from './api.js';
|
|
4
|
+
export { createCredentialStore, UNBOUNDED_REFRESH_SEQ } from './api.js';
|
|
5
5
|
export { credentialStoreDefinition } from './definition.js';
|
|
6
6
|
export declare const CREDENTIAL_STORE: "credential";
|
|
7
7
|
export declare function getCredentialStore(provider: StoreProvider): Promise<CredentialStoreAPI>;
|
|
8
|
-
export type { CredentialAlias, CredentialEntry, CredentialEntryTable, CredentialEntryWithProvenance, CredentialFactorDescriptor, CredentialGrantorCounter, CredentialGrantorCounterTable, CredentialKey, CredentialKeyBranch, CredentialKeyBranchTable, CredentialKeyState, CredentialKeyTable, CredentialManifest, CredentialManifestKey, CredentialManifestTable, CredentialOp, CredentialOpSubjectKind, CredentialOpTable, CredentialTables, CredentialWrapping, CredentialWrappingAuthority, CredentialWrappingAuthorityTable, CredentialWrappingTable, CredentialWrappingWithBranch, InsertCredentialAlias, InsertCredentialEntry, InsertCredentialGrantorCounter, InsertCredentialKey, InsertCredentialManifest, InsertCredentialOp, InsertCredentialWrapping, InsertCredentialWrappingAuthority, KubunCredentialAliasTable, } from './tables.js';
|
|
8
|
+
export type { CredentialAlias, CredentialEntry, CredentialEntryTable, CredentialEntryWithProvenance, CredentialFactorDescriptor, CredentialGrantorCounter, CredentialGrantorCounterTable, CredentialKey, CredentialKeyBranch, CredentialKeyBranchTable, CredentialKeyOwnerDelegation, CredentialKeyOwnerDelegationTable, CredentialKeyState, CredentialKeyTable, CredentialManifest, CredentialManifestKey, CredentialManifestTable, CredentialOp, CredentialOpSubjectKind, CredentialOpTable, CredentialTables, CredentialWrapping, CredentialWrappingAuthority, CredentialWrappingAuthorityTable, CredentialWrappingTable, CredentialWrappingWithBranch, InsertCredentialAlias, InsertCredentialEntry, InsertCredentialGrantorCounter, InsertCredentialKey, InsertCredentialKeyOwnerDelegation, InsertCredentialManifest, InsertCredentialOp, InsertCredentialWrapping, InsertCredentialWrappingAuthority, KubunCredentialAliasTable, } from './tables.js';
|
package/lib/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export { createCredentialStore } from './api.js';
|
|
1
|
+
export { createCredentialStore, UNBOUNDED_REFRESH_SEQ } from './api.js';
|
|
2
2
|
export { credentialStoreDefinition } from './definition.js';
|
|
3
3
|
export const CREDENTIAL_STORE = 'credential';
|
|
4
4
|
export function getCredentialStore(provider) {
|
package/lib/migrations.js
CHANGED
|
@@ -80,8 +80,14 @@ export function getCredentialMigrations(ctx) {
|
|
|
80
80
|
'recipient_did',
|
|
81
81
|
'grantor_did'
|
|
82
82
|
]).execute();
|
|
83
|
+
// A controller-owned key's administer delegation chain, keyed on the key
|
|
84
|
+
// itself; `refresh_seq` gates every upsert strictly-greater.
|
|
85
|
+
await db.schema.createTable('kubun_credential_key_owner_delegation').ifNotExists().addColumn('key_id', t.text, (col)=>col.notNull().primaryKey()).addColumn('delegation_tokens', t.json, (col)=>col.notNull()).addColumn('refresh_seq', t.bigint, (col)=>col.notNull())// Per-write revision for the replace CAS; `refresh_seq` is not a unique
|
|
86
|
+
// version (see CredentialKeyOwnerDelegationTable).
|
|
87
|
+
.addColumn('revision', t.bigint, (col)=>col.notNull()).execute();
|
|
83
88
|
},
|
|
84
89
|
async down (db) {
|
|
90
|
+
await db.schema.dropTable('kubun_credential_key_owner_delegation').ifExists().execute();
|
|
85
91
|
await db.schema.dropTable('kubun_credential_manifests').ifExists().execute();
|
|
86
92
|
await db.schema.dropTable('kubun_credential_provisioning_attempts').ifExists().execute();
|
|
87
93
|
await db.schema.dropTable('kubun_credential_provisioning_expectations').ifExists().execute();
|
package/lib/tables.d.ts
CHANGED
|
@@ -259,6 +259,21 @@ export type CredentialManifestTable = {
|
|
|
259
259
|
};
|
|
260
260
|
export type CredentialManifest = Selectable<CredentialManifestTable>;
|
|
261
261
|
export type InsertCredentialManifest = Insertable<CredentialManifestTable>;
|
|
262
|
+
/**
|
|
263
|
+
* A controller-owned key's `credential/administer` delegation chain, keyed on
|
|
264
|
+
* the key itself rather than a triple — the chain authorizes whoever holds it
|
|
265
|
+
* to administer THIS key, independent of any one (owner, recipient, grantor)
|
|
266
|
+
* relationship. `refresh_seq` gates every `refresh` strictly-greater, same
|
|
267
|
+
* shape as {@link CredentialWrappingAuthorityTable}.
|
|
268
|
+
*/
|
|
269
|
+
export type CredentialKeyOwnerDelegationTable = {
|
|
270
|
+
key_id: string;
|
|
271
|
+
delegation_tokens: JSONArrayColumn<string>;
|
|
272
|
+
refresh_seq: number;
|
|
273
|
+
revision: number;
|
|
274
|
+
};
|
|
275
|
+
export type CredentialKeyOwnerDelegation = Selectable<CredentialKeyOwnerDelegationTable>;
|
|
276
|
+
export type InsertCredentialKeyOwnerDelegation = Insertable<CredentialKeyOwnerDelegationTable>;
|
|
262
277
|
export type CredentialTables = {
|
|
263
278
|
kubun_credential_keys: CredentialKeyTable;
|
|
264
279
|
kubun_credential_key_branches: CredentialKeyBranchTable;
|
|
@@ -272,5 +287,6 @@ export type CredentialTables = {
|
|
|
272
287
|
kubun_credential_provisioning_expectations: CredentialProvisioningExpectationTable;
|
|
273
288
|
kubun_credential_provisioning_attempts: CredentialProvisioningAttemptTable;
|
|
274
289
|
kubun_credential_manifests: CredentialManifestTable;
|
|
290
|
+
kubun_credential_key_owner_delegation: CredentialKeyOwnerDelegationTable;
|
|
275
291
|
};
|
|
276
292
|
export {};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kubun/store-credential",
|
|
3
|
-
"version": "0.17.
|
|
3
|
+
"version": "0.17.2",
|
|
4
4
|
"keywords": [],
|
|
5
5
|
"license": "see LICENSE.md",
|
|
6
6
|
"sideEffects": false,
|
|
@@ -18,11 +18,11 @@
|
|
|
18
18
|
"@kokuin/token": "^0.5.0",
|
|
19
19
|
"@kubun/db": "^0.17.0",
|
|
20
20
|
"@kubun/db-adapter": "^0.17.0",
|
|
21
|
-
"kysely": "^0.29.
|
|
21
|
+
"kysely": "^0.29.6"
|
|
22
22
|
},
|
|
23
23
|
"devDependencies": {
|
|
24
24
|
"@kubun/db-better-sqlite": "^0.17.0",
|
|
25
|
-
"@kubun/db-postgres": "^0.17.
|
|
25
|
+
"@kubun/db-postgres": "^0.17.1",
|
|
26
26
|
"@testcontainers/postgresql": "^12.1.0"
|
|
27
27
|
},
|
|
28
28
|
"publishConfig": {
|