@kubun/plugin-credential 0.17.1 → 0.17.3
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/service.d.ts +1 -1
- package/lib/service.js +101 -2
- package/package.json +10 -9
package/lib/service.d.ts
CHANGED
|
@@ -4,7 +4,7 @@ import type { KubunPlugin, PluginFactoryParams } from '@kubun/engine';
|
|
|
4
4
|
* (graph-schema-only, for an engine that consumes credentials), this registers a
|
|
5
5
|
* `service` provider that hosts the vault: it builds the credential seam over
|
|
6
6
|
* this engine's own db/HLC/identity and exposes
|
|
7
|
-
* `credential/get|create|refresh|delete|provenance` over a transport. The mint
|
|
7
|
+
* `credential/get|peek|create|refresh|delete|provenance` over a transport. The mint
|
|
8
8
|
* runs here, resolving a `did:kokuin:` owner-wrapping locally because this engine
|
|
9
9
|
* holds the owner's controller log.
|
|
10
10
|
*
|
package/lib/service.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { isSigningIdentity, normalizeDID } from '@kokuin/token';
|
|
2
|
-
import { createCredentialAPI, createCredentialManager } from '@kubun/credential';
|
|
2
|
+
import { CredentialAliasAlreadyBound, CredentialBindNotAuthorized, CredentialEntryAlreadyAliased, CredentialInvalidEntry, CredentialKeyVersionMismatch, CredentialNotFound, CredentialSupersededBranch, createCredentialAPI, createCredentialManager } from '@kubun/credential';
|
|
3
3
|
import { authorizeRead, authorizeWrite, ServiceAuthorizationError } from '@kubun/plugin-service-server';
|
|
4
4
|
import { credentialStoreDefinition, getCredentialStore } from '@kubun/store-credential';
|
|
5
5
|
// The spelling of a DID that can be encrypted to. A did:peer:4 identity carries a
|
|
@@ -9,12 +9,76 @@ function wrappableDID(identity) {
|
|
|
9
9
|
const longForm = identity.longForm;
|
|
10
10
|
return typeof longForm === 'string' ? longForm : identity.id;
|
|
11
11
|
}
|
|
12
|
+
// The service boundary has no CREDENTIAL_* → KB* mapping; toHandlerError forwards
|
|
13
|
+
// a thrown error's `code` only when it is already a registered KubunErrorCode,
|
|
14
|
+
// preserving `data`. This maps the known credential outcomes to an envelope code
|
|
15
|
+
// plus a stable `data.reason`, so a remote caller can branch on it without seeing
|
|
16
|
+
// the internal CREDENTIAL_* code or message. `ServiceAuthorizationError` (Gate A)
|
|
17
|
+
// already carries KB07 on its own `code` getter and never reaches this function —
|
|
18
|
+
// it throws before the try/catch below. An unmapped error stays opaque.
|
|
19
|
+
function translateCredentialError(err) {
|
|
20
|
+
if (err instanceof CredentialBindNotAuthorized) {
|
|
21
|
+
return withCode(err, 'KB07', 'CREDENTIAL_NOT_AUTHORIZED');
|
|
22
|
+
}
|
|
23
|
+
if (err instanceof CredentialAliasAlreadyBound) {
|
|
24
|
+
return withCode(err, 'KB04', 'CREDENTIAL_ALIAS_ALREADY_BOUND');
|
|
25
|
+
}
|
|
26
|
+
if (err instanceof CredentialEntryAlreadyAliased) {
|
|
27
|
+
return withCode(err, 'KB04', 'CREDENTIAL_ENTRY_ALREADY_ALIASED');
|
|
28
|
+
}
|
|
29
|
+
if (err instanceof CredentialNotFound) {
|
|
30
|
+
return withCode(err, 'KB02', 'CREDENTIAL_NOT_FOUND');
|
|
31
|
+
}
|
|
32
|
+
if (err instanceof CredentialInvalidEntry) {
|
|
33
|
+
return withCode(err, 'KB01', 'CREDENTIAL_INVALID_ENTRY');
|
|
34
|
+
}
|
|
35
|
+
if (err instanceof CredentialSupersededBranch || err instanceof CredentialKeyVersionMismatch) {
|
|
36
|
+
return withCode(err, 'KB01', 'CREDENTIAL_INVALID_ENTRY');
|
|
37
|
+
}
|
|
38
|
+
return err;
|
|
39
|
+
}
|
|
40
|
+
function withCode(cause, code, reason) {
|
|
41
|
+
return {
|
|
42
|
+
code,
|
|
43
|
+
message: cause.message,
|
|
44
|
+
data: {
|
|
45
|
+
reason
|
|
46
|
+
},
|
|
47
|
+
cause
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
// The provider account captured in a stored credential's serialized body, or
|
|
51
|
+
// null when it captured none (or the body is not a serialized credential).
|
|
52
|
+
// The token-free part of a stored credential a peek discloses: its provider
|
|
53
|
+
// account and the scopes it was granted.
|
|
54
|
+
function readAccountPeek(raw) {
|
|
55
|
+
let parsed;
|
|
56
|
+
try {
|
|
57
|
+
parsed = JSON.parse(raw);
|
|
58
|
+
} catch {
|
|
59
|
+
return {
|
|
60
|
+
providerAccount: null
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
const account = parsed.providerAccount;
|
|
64
|
+
const providerAccount = typeof account?.iss === 'string' && typeof account.sub === 'string' ? {
|
|
65
|
+
iss: account.iss,
|
|
66
|
+
sub: account.sub
|
|
67
|
+
} : null;
|
|
68
|
+
const scopes = parsed.scopes;
|
|
69
|
+
return Array.isArray(scopes) && scopes.every((scope)=>typeof scope === 'string') ? {
|
|
70
|
+
providerAccount,
|
|
71
|
+
scopes: scopes
|
|
72
|
+
} : {
|
|
73
|
+
providerAccount
|
|
74
|
+
};
|
|
75
|
+
}
|
|
12
76
|
/**
|
|
13
77
|
* The credential service provider. Unlike {@link createCredentialPlugin}
|
|
14
78
|
* (graph-schema-only, for an engine that consumes credentials), this registers a
|
|
15
79
|
* `service` provider that hosts the vault: it builds the credential seam over
|
|
16
80
|
* this engine's own db/HLC/identity and exposes
|
|
17
|
-
* `credential/get|create|refresh|delete|provenance` over a transport. The mint
|
|
81
|
+
* `credential/get|peek|create|refresh|delete|provenance` over a transport. The mint
|
|
18
82
|
* runs here, resolving a `did:kokuin:` owner-wrapping locally because this engine
|
|
19
83
|
* holds the owner's controller log.
|
|
20
84
|
*
|
|
@@ -94,6 +158,22 @@ function wrappableDID(identity) {
|
|
|
94
158
|
proceed: ()=>readSeam.getCredential(param.providerName, param.ownerDID)
|
|
95
159
|
});
|
|
96
160
|
},
|
|
161
|
+
// The same vault read as `credential/get` — the DB credential API, which
|
|
162
|
+
// never refreshes a token — narrowed to the provider account and the
|
|
163
|
+
// granted scopes: no token material leaves the vault on a peek.
|
|
164
|
+
'credential/peek': (context)=>{
|
|
165
|
+
const param = context.param;
|
|
166
|
+
return authorizeRead({
|
|
167
|
+
context,
|
|
168
|
+
subject: param.ownerDID,
|
|
169
|
+
emptyResult: null,
|
|
170
|
+
proceed: async ()=>{
|
|
171
|
+
const raw = await readSeam.getCredential(param.providerName, param.ownerDID);
|
|
172
|
+
if (raw == null) return null;
|
|
173
|
+
return readAccountPeek(raw);
|
|
174
|
+
}
|
|
175
|
+
});
|
|
176
|
+
},
|
|
97
177
|
'credential/provenance': (context)=>{
|
|
98
178
|
const param = context.param;
|
|
99
179
|
return authorizeRead({
|
|
@@ -146,6 +226,19 @@ function wrappableDID(identity) {
|
|
|
146
226
|
});
|
|
147
227
|
await writeInTx((seam)=>seam.deleteCredential(param.providerName, param.ownerDID));
|
|
148
228
|
return null;
|
|
229
|
+
},
|
|
230
|
+
'credential/bind-alias': async (context)=>{
|
|
231
|
+
const param = context.param;
|
|
232
|
+
await authorizeWrite({
|
|
233
|
+
context,
|
|
234
|
+
subject: param.ownerDID
|
|
235
|
+
}); // Gate A
|
|
236
|
+
try {
|
|
237
|
+
await writeInTx((seam)=>seam.bindAlias(param.providerName, param.ownerDID, param.entryID));
|
|
238
|
+
} catch (err) {
|
|
239
|
+
throw translateCredentialError(err);
|
|
240
|
+
}
|
|
241
|
+
return null;
|
|
149
242
|
}
|
|
150
243
|
};
|
|
151
244
|
params.registerProvider('service', {
|
|
@@ -155,6 +248,9 @@ function wrappableDID(identity) {
|
|
|
155
248
|
'credential/get': {
|
|
156
249
|
allow: true
|
|
157
250
|
},
|
|
251
|
+
'credential/peek': {
|
|
252
|
+
allow: true
|
|
253
|
+
},
|
|
158
254
|
'credential/provenance': {
|
|
159
255
|
allow: true
|
|
160
256
|
},
|
|
@@ -166,6 +262,9 @@ function wrappableDID(identity) {
|
|
|
166
262
|
},
|
|
167
263
|
'credential/delete': {
|
|
168
264
|
allow: true
|
|
265
|
+
},
|
|
266
|
+
'credential/bind-alias': {
|
|
267
|
+
allow: true
|
|
169
268
|
}
|
|
170
269
|
}
|
|
171
270
|
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kubun/plugin-credential",
|
|
3
|
-
"version": "0.17.
|
|
3
|
+
"version": "0.17.3",
|
|
4
4
|
"license": "see LICENSE.md",
|
|
5
5
|
"sideEffects": false,
|
|
6
6
|
"type": "module",
|
|
@@ -15,15 +15,16 @@
|
|
|
15
15
|
],
|
|
16
16
|
"dependencies": {
|
|
17
17
|
"@kokuin/token": "^0.5.0",
|
|
18
|
-
"@kubun/credential": "^0.17.
|
|
18
|
+
"@kubun/credential": "^0.17.3",
|
|
19
19
|
"@kubun/db": "^0.17.0",
|
|
20
|
-
"@kubun/engine": "^0.17.
|
|
21
|
-
"@kubun/graphql": "^0.17.
|
|
20
|
+
"@kubun/engine": "^0.17.2",
|
|
21
|
+
"@kubun/graphql": "^0.17.2",
|
|
22
22
|
"@kubun/hlc": "^0.17.0",
|
|
23
23
|
"@kubun/id": "^0.17.0",
|
|
24
|
-
"@kubun/plugin-service-api": "^0.17.
|
|
25
|
-
"@kubun/plugin-service-server": "^0.17.
|
|
26
|
-
"@kubun/
|
|
24
|
+
"@kubun/plugin-service-api": "^0.17.1",
|
|
25
|
+
"@kubun/plugin-service-server": "^0.17.1",
|
|
26
|
+
"@kubun/protocol": "^0.17.1",
|
|
27
|
+
"@kubun/store-credential": "^0.17.4",
|
|
27
28
|
"@sozai/codec": "^0.4.0",
|
|
28
29
|
"@sozai/runtime": "^0.1.0",
|
|
29
30
|
"graphql": "^17.0.2"
|
|
@@ -34,9 +35,9 @@
|
|
|
34
35
|
"@kokuin/capability": "^0.3.0",
|
|
35
36
|
"@kokuin/controller": "^0.1.0",
|
|
36
37
|
"@kubun/db-better-sqlite": "^0.17.0",
|
|
37
|
-
"@kubun/service-credential-api": "^0.17.
|
|
38
|
+
"@kubun/service-credential-api": "^0.17.2",
|
|
38
39
|
"@kubun/store-controller": "^0.17.0",
|
|
39
|
-
"@kubun/store-graph": "^0.17.
|
|
40
|
+
"@kubun/store-graph": "^0.17.1",
|
|
40
41
|
"@kubun/test-utils": "^0.0.0"
|
|
41
42
|
},
|
|
42
43
|
"publishConfig": {
|