@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 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.1",
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.0",
18
+ "@kubun/credential": "^0.17.3",
19
19
  "@kubun/db": "^0.17.0",
20
- "@kubun/engine": "^0.17.1",
21
- "@kubun/graphql": "^0.17.1",
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.0",
25
- "@kubun/plugin-service-server": "^0.17.0",
26
- "@kubun/store-credential": "^0.17.1",
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.0",
38
+ "@kubun/service-credential-api": "^0.17.2",
38
39
  "@kubun/store-controller": "^0.17.0",
39
- "@kubun/store-graph": "^0.17.0",
40
+ "@kubun/store-graph": "^0.17.1",
40
41
  "@kubun/test-utils": "^0.0.0"
41
42
  },
42
43
  "publishConfig": {