@kubun/credential 0.17.4 → 0.18.0
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 +61 -30
- package/lib/api.js +455 -111
- package/lib/authority.d.ts +4 -0
- package/lib/authority.js +3 -0
- package/lib/branch.d.ts +4 -0
- package/lib/branch.js +24 -0
- package/lib/builder.d.ts +5 -0
- package/lib/builder.js +8 -6
- package/lib/canonical-token.d.ts +2 -0
- package/lib/canonical-token.js +10 -0
- package/lib/errors.d.ts +39 -1
- package/lib/errors.js +77 -0
- package/lib/fork-repair.d.ts +19 -0
- package/lib/fork-repair.js +3 -0
- package/lib/grantor-authority.d.ts +26 -0
- package/lib/grantor-authority.js +43 -0
- package/lib/index.d.ts +15 -6
- package/lib/index.js +7 -2
- package/lib/key-cache.d.ts +4 -3
- package/lib/key-cache.js +22 -15
- package/lib/manager-context.d.ts +50 -0
- package/lib/manager-context.js +82 -0
- package/lib/manager-entries.d.ts +3 -0
- package/lib/manager-entries.js +585 -0
- package/lib/manager-keys.d.ts +3 -0
- package/lib/manager-keys.js +413 -0
- package/lib/manager-open.d.ts +45 -0
- package/lib/manager-open.js +421 -0
- package/lib/manager-records.d.ts +94 -0
- package/lib/manager-records.js +291 -0
- package/lib/manager-removal-recovery.d.ts +7 -0
- package/lib/manager-removal-recovery.js +211 -0
- package/lib/manager-rotate.d.ts +15 -0
- package/lib/manager-rotate.js +536 -0
- package/lib/manager-types.d.ts +337 -0
- package/lib/manager-types.js +1 -0
- package/lib/manager.d.ts +2 -293
- package/lib/manager.js +38 -1073
- package/lib/manifest-token.d.ts +31 -0
- package/lib/manifest-token.js +51 -0
- package/lib/provider.d.ts +15 -2
- package/lib/provider.js +39 -0
- package/lib/record-types.d.ts +65 -0
- package/lib/record-types.js +865 -0
- package/lib/refreshing-credential-provider.d.ts +6 -2
- package/lib/refreshing-credential-provider.js +76 -13
- package/lib/suite.d.ts +5 -4
- package/lib/suite.js +2 -2
- package/package.json +16 -15
- package/lib/op.d.ts +0 -128
- package/lib/op.js +0 -304
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { type MethodRegistry, type SigningIdentity } from '@kokuin/token';
|
|
2
|
+
/**
|
|
3
|
+
* What a grantor signs to record the set of keys it granted a recipient: a
|
|
4
|
+
* claim the recipient checks its store against to prove completeness. Domain
|
|
5
|
+
* separation from other signed payloads comes from the `kind` marker plus the
|
|
6
|
+
* structural check in `verifyCredentialManifest` -- `signToken` has no
|
|
7
|
+
* domain override of its own.
|
|
8
|
+
*/
|
|
9
|
+
export type CredentialManifestClaim = {
|
|
10
|
+
kind: 'credential/key-manifest';
|
|
11
|
+
ownerDID: string;
|
|
12
|
+
recipientDID: string;
|
|
13
|
+
grantorDID: string;
|
|
14
|
+
epoch: number;
|
|
15
|
+
sequence: number;
|
|
16
|
+
keys: Array<{
|
|
17
|
+
keyID: string;
|
|
18
|
+
version: number;
|
|
19
|
+
}>;
|
|
20
|
+
issuedAt: string;
|
|
21
|
+
};
|
|
22
|
+
/** The envelope carried on the wire and persisted is the signed token string itself. */
|
|
23
|
+
export type CredentialManifestEnvelope = string;
|
|
24
|
+
export declare function signCredentialManifest(identity: SigningIdentity, claim: Omit<CredentialManifestClaim, 'kind' | 'grantorDID'>): Promise<CredentialManifestEnvelope>;
|
|
25
|
+
/**
|
|
26
|
+
* Verify a credential-manifest envelope and extract its claim. Returns `null`
|
|
27
|
+
* (never throws) when the token is unparseable, unsigned (`alg: 'none'`), or
|
|
28
|
+
* structurally not a credential-manifest claim -- `grantorDID` is trusted
|
|
29
|
+
* only from the verified `iss`, never from a same-named field in the payload.
|
|
30
|
+
*/
|
|
31
|
+
export declare function verifyCredentialManifest(envelope: CredentialManifestEnvelope, methods?: MethodRegistry): Promise<CredentialManifestClaim | null>;
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { isVerifiedToken, normalizeDID, stringifyToken, verifyToken } from '@kokuin/token';
|
|
2
|
+
/**
|
|
3
|
+
* Check that a value is a finite, safe, positive integer (>= 1).
|
|
4
|
+
* Rejects NaN, Infinity, floats, negatives, and zero.
|
|
5
|
+
*/ const isPosInt = (n)=>typeof n === 'number' && Number.isSafeInteger(n) && n >= 1;
|
|
6
|
+
export async function signCredentialManifest(identity, claim) {
|
|
7
|
+
const fullClaim = {
|
|
8
|
+
kind: 'credential/key-manifest',
|
|
9
|
+
grantorDID: identity.id,
|
|
10
|
+
...claim
|
|
11
|
+
};
|
|
12
|
+
const signed = await identity.signToken(fullClaim, {
|
|
13
|
+
embedLongForm: true
|
|
14
|
+
});
|
|
15
|
+
return stringifyToken(signed);
|
|
16
|
+
}
|
|
17
|
+
function isValidKeysArray(value) {
|
|
18
|
+
return Array.isArray(value) && value.every((entry)=>entry != null && typeof entry === 'object' && typeof entry.keyID === 'string' && isPosInt(entry.version));
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Verify a credential-manifest envelope and extract its claim. Returns `null`
|
|
22
|
+
* (never throws) when the token is unparseable, unsigned (`alg: 'none'`), or
|
|
23
|
+
* structurally not a credential-manifest claim -- `grantorDID` is trusted
|
|
24
|
+
* only from the verified `iss`, never from a same-named field in the payload.
|
|
25
|
+
*/ export async function verifyCredentialManifest(envelope, methods) {
|
|
26
|
+
let verified;
|
|
27
|
+
try {
|
|
28
|
+
verified = await verifyToken(envelope, {
|
|
29
|
+
methods
|
|
30
|
+
});
|
|
31
|
+
} catch {
|
|
32
|
+
return null;
|
|
33
|
+
}
|
|
34
|
+
if (!isVerifiedToken(verified)) {
|
|
35
|
+
return null;
|
|
36
|
+
}
|
|
37
|
+
const { iss, kind, ownerDID, recipientDID, epoch, sequence, keys, issuedAt } = verified.payload;
|
|
38
|
+
if (kind !== 'credential/key-manifest' || typeof ownerDID !== 'string' || typeof recipientDID !== 'string' || !isPosInt(epoch) || !isPosInt(sequence) || typeof issuedAt !== 'string' || !isValidKeysArray(keys)) {
|
|
39
|
+
return null;
|
|
40
|
+
}
|
|
41
|
+
return {
|
|
42
|
+
kind: 'credential/key-manifest',
|
|
43
|
+
ownerDID,
|
|
44
|
+
recipientDID,
|
|
45
|
+
grantorDID: normalizeDID(iss),
|
|
46
|
+
epoch,
|
|
47
|
+
sequence,
|
|
48
|
+
keys,
|
|
49
|
+
issuedAt
|
|
50
|
+
};
|
|
51
|
+
}
|
package/lib/provider.d.ts
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
import type { AliasBindingCredentialProvider, Credential, CredentialProvider, EntryPairedCredentialProvider } from '@kubun/credential-types';
|
|
1
|
+
import type { AliasBindingCredentialProvider, Credential, CredentialProvider, EntryPairedCredentialProvider, GrantAddress, VersionedEntryCredentialProvider } from '@kubun/credential-types';
|
|
2
2
|
import type { CredentialAPI } from './api.js';
|
|
3
3
|
export declare function serializeCredential(credential: Credential): string;
|
|
4
4
|
export declare function deserializeCredential(raw: string | Record<string, unknown>): Credential;
|
|
5
5
|
export type DBCredentialProviderParams = {
|
|
6
6
|
api: CredentialAPI;
|
|
7
7
|
};
|
|
8
|
-
export declare class DBCredentialProvider implements CredentialProvider, AliasBindingCredentialProvider, EntryPairedCredentialProvider {
|
|
8
|
+
export declare class DBCredentialProvider implements CredentialProvider, AliasBindingCredentialProvider, EntryPairedCredentialProvider, VersionedEntryCredentialProvider {
|
|
9
9
|
#private;
|
|
10
10
|
constructor(params: DBCredentialProviderParams);
|
|
11
11
|
get(providerName: string, ownerDID: string): Promise<Credential | null>;
|
|
@@ -17,6 +17,7 @@ export declare class DBCredentialProvider implements CredentialProvider, AliasBi
|
|
|
17
17
|
controllerWrappableDID?: string;
|
|
18
18
|
}): Promise<void>;
|
|
19
19
|
delete(providerName: string, ownerDID: string): Promise<void>;
|
|
20
|
+
update(address: GrantAddress, change: (current: Credential | null) => Credential): Promise<void>;
|
|
20
21
|
bindAlias(providerName: string, ownerDID: string, entryID: string): Promise<void>;
|
|
21
22
|
getCredentialRef(providerName: string, ownerDID: string): Promise<{
|
|
22
23
|
credential: Credential;
|
|
@@ -28,4 +29,16 @@ export declare class DBCredentialProvider implements CredentialProvider, AliasBi
|
|
|
28
29
|
entryID: string;
|
|
29
30
|
credential: Credential;
|
|
30
31
|
}): Promise<'updated' | 'absent'>;
|
|
32
|
+
getVersionedCredentialRef(providerName: string, ownerDID: string): Promise<{
|
|
33
|
+
credential: Credential;
|
|
34
|
+
entryID: string;
|
|
35
|
+
version: string;
|
|
36
|
+
} | null>;
|
|
37
|
+
setCredentialForEntryIfUnchanged(params: {
|
|
38
|
+
providerName: string;
|
|
39
|
+
ownerDID: string;
|
|
40
|
+
entryID: string;
|
|
41
|
+
credential: Credential;
|
|
42
|
+
expectedVersion: string;
|
|
43
|
+
}): Promise<'updated' | 'absent' | 'superseded'>;
|
|
31
44
|
}
|
package/lib/provider.js
CHANGED
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
import { normalizeDID } from '@kokuin/token';
|
|
2
|
+
import { CredentialUpdateUnavailableError } from '@kubun/credential-types';
|
|
3
|
+
import { CredentialBindNotAuthorized } from './errors.js';
|
|
1
4
|
export function serializeCredential(credential) {
|
|
2
5
|
const serialized = {
|
|
3
6
|
accessToken: credential.accessToken,
|
|
@@ -78,6 +81,24 @@ export class DBCredentialProvider {
|
|
|
78
81
|
async delete(providerName, ownerDID) {
|
|
79
82
|
await this.#api.deleteCredential(providerName, ownerDID);
|
|
80
83
|
}
|
|
84
|
+
async update(address, change) {
|
|
85
|
+
if (normalizeDID(address.deviceDID) !== this.#api.getLocalDeviceDID()) {
|
|
86
|
+
throw new CredentialBindNotAuthorized(address.providerName, address.ownerDID);
|
|
87
|
+
}
|
|
88
|
+
for(;;){
|
|
89
|
+
const ref = await this.getVersionedCredentialRef(address.providerName, address.ownerDID);
|
|
90
|
+
if (ref == null || ref.version == null) throw new CredentialUpdateUnavailableError();
|
|
91
|
+
const credential = change(ref.credential);
|
|
92
|
+
const result = await this.setCredentialForEntryIfUnchanged({
|
|
93
|
+
...address,
|
|
94
|
+
entryID: ref.entryID,
|
|
95
|
+
credential,
|
|
96
|
+
expectedVersion: ref.version
|
|
97
|
+
});
|
|
98
|
+
if (result === 'updated') return;
|
|
99
|
+
if (result === 'absent') throw new CredentialUpdateUnavailableError();
|
|
100
|
+
}
|
|
101
|
+
}
|
|
81
102
|
bindAlias(providerName, ownerDID, entryID) {
|
|
82
103
|
return this.#api.bindAlias(providerName, ownerDID, entryID);
|
|
83
104
|
}
|
|
@@ -97,4 +118,22 @@ export class DBCredentialProvider {
|
|
|
97
118
|
credential: serializeCredential(params.credential)
|
|
98
119
|
});
|
|
99
120
|
}
|
|
121
|
+
async getVersionedCredentialRef(providerName, ownerDID) {
|
|
122
|
+
const ref = await this.#api.getVersionedCredentialRef(providerName, ownerDID);
|
|
123
|
+
if (ref == null) return null;
|
|
124
|
+
return {
|
|
125
|
+
credential: deserializeCredential(ref.credential),
|
|
126
|
+
entryID: ref.entryID,
|
|
127
|
+
version: ref.version
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
async setCredentialForEntryIfUnchanged(params) {
|
|
131
|
+
return await this.#api.setCredentialForEntryIfUnchanged({
|
|
132
|
+
providerName: params.providerName,
|
|
133
|
+
ownerDID: params.ownerDID,
|
|
134
|
+
entryID: params.entryID,
|
|
135
|
+
credential: serializeCredential(params.credential),
|
|
136
|
+
expectedVersion: params.expectedVersion
|
|
137
|
+
});
|
|
138
|
+
}
|
|
100
139
|
}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { type MethodRegistry } from '@kokuin/token';
|
|
2
|
+
import { type RecordStoreAdapter, type RecordTypeDefinition, type VisibilityContext } from '@kubun/records';
|
|
3
|
+
import { type CredentialStoreAPI } from '@kubun/store-credential';
|
|
4
|
+
export type { CredentialRecordType } from '@kubun/store-credential';
|
|
5
|
+
/**
|
|
6
|
+
* What the credential record types check against on the receiving device.
|
|
7
|
+
*
|
|
8
|
+
* - `store` is the device's credential store. The hooks never read it: every read inside a
|
|
9
|
+
* decision goes through the store the decision runs against, since this one is bound
|
|
10
|
+
* outside the apply transaction.
|
|
11
|
+
* - `resolveController` answers the `did:kokuin` controller a device key acts for, or `null`.
|
|
12
|
+
* For controller-owned records, only devices at the verifier's current head act for it.
|
|
13
|
+
* - `verifyAdministerChain` checks, at present time, that `tokens` give `issuer` the
|
|
14
|
+
* `credential/administer` right over `owner`.
|
|
15
|
+
* - `verifyGrantorEnvelope` checks, at present time, that a grantor envelope is valid for the
|
|
16
|
+
* wrapping op `wrappingOpHash`, its administer chain included.
|
|
17
|
+
* - `selfDID` is this device. A wrapping, wrapping authority or manifest is only stored here
|
|
18
|
+
* when it is addressed to this device, or when this device acts for the record's owner.
|
|
19
|
+
*/
|
|
20
|
+
export type CredentialRecordTypeDeps = {
|
|
21
|
+
store: CredentialStoreAPI;
|
|
22
|
+
resolveController: (did: string) => Promise<string | null>;
|
|
23
|
+
verifyAdministerChain: (tokens: ReadonlyArray<string>, owner: string, issuer: string) => Promise<boolean>;
|
|
24
|
+
verifyGrantorEnvelope: (token: string, wrappingOpHash: string) => Promise<boolean>;
|
|
25
|
+
selfDID: string;
|
|
26
|
+
methods?: MethodRegistry;
|
|
27
|
+
};
|
|
28
|
+
/** Thrown when a credential hook runs against a store it cannot reach the indexed tables of. */
|
|
29
|
+
export declare class CredentialRecordHookError extends Error {
|
|
30
|
+
constructor(message: string);
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* The id of the entry holding the OAuth grant `name` that `grantDeviceDID` spends for `owner`.
|
|
34
|
+
* Derived, so the device that may write an entry is a property of the entry's id, whatever
|
|
35
|
+
* ops a peer has or has not seen for it.
|
|
36
|
+
*/
|
|
37
|
+
export declare function credentialGrantEntryID(owner: string, grantDeviceDID: string, name: string): string;
|
|
38
|
+
/** Revocation removes every indexed wrapping for that recipient, at every key version. */
|
|
39
|
+
export declare function credentialStoreForHooks(adapter: RecordStoreAdapter): CredentialStoreAPI;
|
|
40
|
+
/**
|
|
41
|
+
* The credential record types, in dependency order: how each merges, who may write it, who
|
|
42
|
+
* may receive it, what it needs first, and what applying it does to the indexed tables.
|
|
43
|
+
*
|
|
44
|
+
* Every authorizer decodes a live payload with the store's codec after checking it against the
|
|
45
|
+
* signed digest, binds the record to the owner of the key it belongs to, and accepts only an
|
|
46
|
+
* issuer that acts for that owner. None decrypts or unwraps anything.
|
|
47
|
+
*/
|
|
48
|
+
export declare function credentialRecordTypes(deps: CredentialRecordTypeDeps): ReadonlyMap<string, RecordTypeDefinition>;
|
|
49
|
+
/**
|
|
50
|
+
* The record types that prove a requester holds a key: wrappings only. A revocation also names
|
|
51
|
+
* a key and a recipient, and must never pass for a holding.
|
|
52
|
+
*/
|
|
53
|
+
export declare function credentialHolderTypes(types: ReadonlyMap<string, RecordTypeDefinition>): ReadonlyMap<string, RecordTypeDefinition>;
|
|
54
|
+
/**
|
|
55
|
+
* The responder's current version of a key, for `VisibilityContext.currentKeyVersion`: the
|
|
56
|
+
* stored key record of that id, when it is live and belongs to `owner`.
|
|
57
|
+
*/
|
|
58
|
+
export declare function credentialKeyVersionReader(store: RecordStoreAdapter, methods?: MethodRegistry): VisibilityContext['currentKeyVersion'];
|
|
59
|
+
/**
|
|
60
|
+
* The authorizer a responder runs on a holder proof, for `VisibilityContext.authorizeProof`:
|
|
61
|
+
* the wrapping authorizer apply runs, reading `store`, without the receiver binding — a proof
|
|
62
|
+
* is the requester's wrapping, never addressed to the responder. Anything but a wrapping, or
|
|
63
|
+
* a record whose op does not verify, is refused.
|
|
64
|
+
*/
|
|
65
|
+
export declare function credentialProofAuthorizer(deps: CredentialRecordTypeDeps, store: RecordStoreAdapter): VisibilityContext['authorizeProof'];
|