@kubun/service-credential-api 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/adapter.d.ts CHANGED
@@ -1,10 +1,25 @@
1
- import type { Client } from '@enkaku/client';
2
- import type { AliasBindingCredentialProvider, CredentialProvider } from '@kubun/credential-types';
1
+ import { type Client } from '@enkaku/client';
2
+ import type { AliasBindingCredentialProvider, CredentialProvider, EntryPairedCredentialProvider, ProviderAccount } from '@kubun/credential-types';
3
3
  import type { CredentialServiceProtocol } from './index.js';
4
+ /**
5
+ * What a storage-only account peek discloses: the provider account and the
6
+ * scopes the credential was granted (absent from a server that predates
7
+ * them), nothing else.
8
+ */
9
+ export type CredentialAccountPeek = {
10
+ providerAccount: ProviderAccount | null;
11
+ scopes?: Array<string>;
12
+ };
4
13
  export type CredentialProviderWithProvenance = CredentialProvider & {
14
+ /**
15
+ * Storage-only, non-refreshing read of the stored credential's provider
16
+ * account only — no token material. Optional on the type so a provider that
17
+ * cannot offer one simply omits it; the service adapter always implements it.
18
+ */
19
+ peekAccount?(providerName: string, ownerDID: string): Promise<CredentialAccountPeek | null>;
5
20
  getCredentialProvenance(providerName: string, ownerDID: string): Promise<{
6
21
  updatedAt: string | null;
7
22
  writerDID: string | null;
8
23
  } | null>;
9
24
  };
10
- export declare function toCredentialProvider(client: Client<CredentialServiceProtocol>): CredentialProviderWithProvenance & AliasBindingCredentialProvider;
25
+ export declare function toCredentialProvider(client: Client<CredentialServiceProtocol>): CredentialProviderWithProvenance & AliasBindingCredentialProvider & EntryPairedCredentialProvider & Required<Pick<CredentialProviderWithProvenance, 'peekAccount'>>;
package/lib/adapter.js CHANGED
@@ -3,7 +3,10 @@
3
3
  // single `credential-provider`. It reuses the exact encoding the local
4
4
  // `DBCredentialProvider` writes, so a credential minted through the service reads
5
5
  // back byte-identical to one minted standalone.
6
+ import { RequestError } from '@enkaku/client';
7
+ import { ErrorCodes } from '@enkaku/protocol';
6
8
  import { deserializeCredential, serializeCredential } from '@kubun/credential';
9
+ import { CredentialPeekUnsupportedError } from './errors.js';
7
10
  export function toCredentialProvider(client) {
8
11
  return {
9
12
  async get (providerName, ownerDID) {
@@ -15,6 +18,59 @@ export function toCredentialProvider(client) {
15
18
  });
16
19
  return raw == null ? null : deserializeCredential(raw);
17
20
  },
21
+ async getCredentialRef (providerName, ownerDID) {
22
+ const ref = await client.request('credential/get-ref', {
23
+ param: {
24
+ providerName,
25
+ ownerDID
26
+ }
27
+ });
28
+ return ref == null ? null : {
29
+ credential: deserializeCredential(ref.credential),
30
+ entryID: ref.entryID
31
+ };
32
+ },
33
+ setCredentialForEntry ({ providerName, ownerDID, entryID, credential }) {
34
+ return client.request('credential/refresh-entry', {
35
+ param: {
36
+ providerName,
37
+ ownerDID,
38
+ entryID,
39
+ credential: serializeCredential(credential)
40
+ }
41
+ });
42
+ },
43
+ async peekAccount (providerName, ownerDID) {
44
+ let peeked;
45
+ try {
46
+ peeked = await client.request('credential/peek', {
47
+ param: {
48
+ providerName,
49
+ ownerDID
50
+ }
51
+ });
52
+ } catch (err) {
53
+ // Every current server grants `credential/peek` to any verified issuer
54
+ // (the subject authorizer answers a denial with `null`), so an access
55
+ // denial here most likely means the server does not serve the
56
+ // procedure — though a caller token that is expired or revoked is
57
+ // denied the same way. Either way no storage-only read is available.
58
+ if (err instanceof RequestError && err.code === ErrorCodes.ACCESS_DENIED) {
59
+ // biome-ignore lint/style/useErrorCause: cause is forwarded to super via the options arg
60
+ throw new CredentialPeekUnsupportedError({
61
+ cause: err
62
+ });
63
+ }
64
+ throw err;
65
+ }
66
+ if (peeked == null) return null;
67
+ return {
68
+ providerAccount: peeked.providerAccount ?? null,
69
+ ...peeked.scopes != null ? {
70
+ scopes: peeked.scopes
71
+ } : {}
72
+ };
73
+ },
18
74
  async set ({ providerName, ownerDID, credential, ownerWrappableDID, controllerWrappableDID }) {
19
75
  // Create-vs-refresh dispatches on the owner's wrappable DID, mirroring
20
76
  // `DBCredentialProvider.set`: present ⇒ the mint (which fixes who can read),
@@ -0,0 +1,18 @@
1
+ /**
2
+ * The error code `peekAccount` rejects with when the credential service
3
+ * denies access to `credential/peek`. The usual cause is a server that
4
+ * predates the procedure — its access rules deny what it does not serve — but
5
+ * an access denial is not specific to that: an expired or revoked caller
6
+ * token is denied the same way. A consumer treats it as "no storage-only read
7
+ * available" (and may prompt reauthorization), never as a transient failure.
8
+ */
9
+ export declare const CREDENTIAL_PEEK_UNSUPPORTED = "CREDENTIAL_PEEK_UNSUPPORTED";
10
+ /**
11
+ * Raised by `peekAccount` when the credential service denies
12
+ * `credential/peek` — see {@link CREDENTIAL_PEEK_UNSUPPORTED}.
13
+ */
14
+ export declare class CredentialPeekUnsupportedError extends Error {
15
+ #private;
16
+ constructor(options?: ErrorOptions);
17
+ get code(): typeof CREDENTIAL_PEEK_UNSUPPORTED;
18
+ }
package/lib/errors.js ADDED
@@ -0,0 +1,22 @@
1
+ /**
2
+ * The error code `peekAccount` rejects with when the credential service
3
+ * denies access to `credential/peek`. The usual cause is a server that
4
+ * predates the procedure — its access rules deny what it does not serve — but
5
+ * an access denial is not specific to that: an expired or revoked caller
6
+ * token is denied the same way. A consumer treats it as "no storage-only read
7
+ * available" (and may prompt reauthorization), never as a transient failure.
8
+ */ export const CREDENTIAL_PEEK_UNSUPPORTED = 'CREDENTIAL_PEEK_UNSUPPORTED';
9
+ /**
10
+ * Raised by `peekAccount` when the credential service denies
11
+ * `credential/peek` — see {@link CREDENTIAL_PEEK_UNSUPPORTED}.
12
+ */ export class CredentialPeekUnsupportedError extends Error {
13
+ #code;
14
+ constructor(options){
15
+ super('CREDENTIAL_PEEK_UNSUPPORTED: the credential service does not serve credential/peek', options);
16
+ this.name = 'CredentialPeekUnsupportedError';
17
+ this.#code = CREDENTIAL_PEEK_UNSUPPORTED;
18
+ }
19
+ get code() {
20
+ return this.#code;
21
+ }
22
+ }
package/lib/index.d.ts CHANGED
@@ -1,4 +1,5 @@
1
- export { type CredentialProviderWithProvenance, toCredentialProvider, } from './adapter.js';
1
+ export { type CredentialAccountPeek, type CredentialProviderWithProvenance, toCredentialProvider, } from './adapter.js';
2
+ export { CREDENTIAL_PEEK_UNSUPPORTED, CredentialPeekUnsupportedError } from './errors.js';
2
3
  export declare const credentialServiceProtocol: {
3
4
  readonly 'credential/get': {
4
5
  readonly type: "request";
@@ -23,6 +24,89 @@ export declare const credentialServiceProtocol: {
23
24
  }];
24
25
  };
25
26
  };
27
+ readonly 'credential/get-ref': {
28
+ readonly type: "request";
29
+ readonly param: {
30
+ readonly type: "object";
31
+ readonly properties: {
32
+ readonly providerName: {
33
+ readonly type: "string";
34
+ };
35
+ readonly ownerDID: {
36
+ readonly type: "string";
37
+ };
38
+ };
39
+ readonly required: readonly ["providerName", "ownerDID"];
40
+ readonly additionalProperties: false;
41
+ };
42
+ readonly result: {
43
+ readonly anyOf: [{
44
+ readonly type: "object";
45
+ readonly properties: {
46
+ readonly credential: {
47
+ readonly type: "string";
48
+ };
49
+ readonly entryID: {
50
+ readonly type: "string";
51
+ };
52
+ };
53
+ readonly required: readonly ["credential", "entryID"];
54
+ readonly additionalProperties: false;
55
+ }, {
56
+ readonly type: "null";
57
+ }];
58
+ };
59
+ };
60
+ readonly 'credential/peek': {
61
+ readonly type: "request";
62
+ readonly param: {
63
+ readonly type: "object";
64
+ readonly properties: {
65
+ readonly providerName: {
66
+ readonly type: "string";
67
+ };
68
+ readonly ownerDID: {
69
+ readonly type: "string";
70
+ };
71
+ };
72
+ readonly required: readonly ["providerName", "ownerDID"];
73
+ readonly additionalProperties: false;
74
+ };
75
+ readonly result: {
76
+ readonly anyOf: [{
77
+ readonly type: "object";
78
+ readonly properties: {
79
+ readonly providerAccount: {
80
+ readonly anyOf: readonly [{
81
+ readonly type: "object";
82
+ readonly properties: {
83
+ readonly iss: {
84
+ readonly type: "string";
85
+ };
86
+ readonly sub: {
87
+ readonly type: "string";
88
+ };
89
+ };
90
+ readonly required: readonly ["iss", "sub"];
91
+ readonly additionalProperties: false;
92
+ }, {
93
+ readonly type: "null";
94
+ }];
95
+ };
96
+ readonly scopes: {
97
+ readonly type: "array";
98
+ readonly items: {
99
+ readonly type: "string";
100
+ };
101
+ };
102
+ };
103
+ readonly required: readonly ["providerAccount"];
104
+ readonly additionalProperties: false;
105
+ }, {
106
+ readonly type: "null";
107
+ }];
108
+ };
109
+ };
26
110
  readonly 'credential/provenance': {
27
111
  readonly type: "request";
28
112
  readonly param: {
@@ -114,6 +198,32 @@ export declare const credentialServiceProtocol: {
114
198
  readonly type: "null";
115
199
  };
116
200
  };
201
+ readonly 'credential/refresh-entry': {
202
+ readonly type: "request";
203
+ readonly param: {
204
+ readonly type: "object";
205
+ readonly properties: {
206
+ readonly providerName: {
207
+ readonly type: "string";
208
+ };
209
+ readonly ownerDID: {
210
+ readonly type: "string";
211
+ };
212
+ readonly entryID: {
213
+ readonly type: "string";
214
+ };
215
+ readonly credential: {
216
+ readonly type: "string";
217
+ };
218
+ };
219
+ readonly required: readonly ["providerName", "ownerDID", "entryID", "credential"];
220
+ readonly additionalProperties: false;
221
+ };
222
+ readonly result: {
223
+ readonly type: "string";
224
+ readonly enum: readonly ["updated", "absent"];
225
+ };
226
+ };
117
227
  readonly 'credential/delete': {
118
228
  readonly type: "request";
119
229
  readonly param: {
package/lib/index.js CHANGED
@@ -5,13 +5,15 @@
5
5
  // authenticated client into the `credential-provider` the connector registers, so
6
6
  // this package is no longer type-only.
7
7
  export { toCredentialProvider } from './adapter.js';
8
+ export { CREDENTIAL_PEEK_UNSUPPORTED, CredentialPeekUnsupportedError } from './errors.js';
8
9
  // Param↔seam mapping. `providerName`/`ownerDID` are the alias key the seam
9
10
  // resolves. `ownerDID` is ALSO the authorization subject: each handler passes it
10
11
  // to `context.authorize(ownerDID)` (the per-serve `callerMayActFor`), NOT the
11
12
  // Enkaku message `sub` — the host rejects a `sub` that differs from `iss`, so a
12
13
  // caller acting for another owner carries that owner in the param, not the
13
14
  // envelope. Serialized credential bytes cross as a UTF string; the vault stores
14
- // the ciphertext, never the plaintext. Writes ack with `null`.
15
+ // the ciphertext, never the plaintext. Entry-scoped writes report their outcome;
16
+ // the other writes ack with `null`.
15
17
  export const credentialServiceProtocol = {
16
18
  'credential/get': {
17
19
  type: 'request',
@@ -42,6 +44,124 @@ export const credentialServiceProtocol = {
42
44
  ]
43
45
  }
44
46
  },
47
+ 'credential/get-ref': {
48
+ type: 'request',
49
+ param: {
50
+ type: 'object',
51
+ properties: {
52
+ providerName: {
53
+ type: 'string'
54
+ },
55
+ ownerDID: {
56
+ type: 'string'
57
+ }
58
+ },
59
+ required: [
60
+ 'providerName',
61
+ 'ownerDID'
62
+ ],
63
+ additionalProperties: false
64
+ },
65
+ result: {
66
+ anyOf: [
67
+ {
68
+ type: 'object',
69
+ properties: {
70
+ credential: {
71
+ type: 'string'
72
+ },
73
+ entryID: {
74
+ type: 'string'
75
+ }
76
+ },
77
+ required: [
78
+ 'credential',
79
+ 'entryID'
80
+ ],
81
+ additionalProperties: false
82
+ },
83
+ {
84
+ type: 'null'
85
+ }
86
+ ]
87
+ }
88
+ },
89
+ // Storage-only, non-refreshing read of the stored credential's provider
90
+ // account and the scopes it was granted — and nothing else: no token
91
+ // material crosses the wire. The server
92
+ // reads its vault and never runs a token refresh, so a consumer can pin the
93
+ // provider-account fingerprint without triggering provider network I/O.
94
+ // `null` when no credential is stored (or the read is denied);
95
+ // `providerAccount: null` when the stored credential captured none.
96
+ // Additive: a server that predates it rejects the procedure, and a consumer
97
+ // that needs a non-refreshing read must treat that as unavailable rather than
98
+ // fall back to `credential/get`.
99
+ 'credential/peek': {
100
+ type: 'request',
101
+ param: {
102
+ type: 'object',
103
+ properties: {
104
+ providerName: {
105
+ type: 'string'
106
+ },
107
+ ownerDID: {
108
+ type: 'string'
109
+ }
110
+ },
111
+ required: [
112
+ 'providerName',
113
+ 'ownerDID'
114
+ ],
115
+ additionalProperties: false
116
+ },
117
+ result: {
118
+ anyOf: [
119
+ {
120
+ type: 'object',
121
+ properties: {
122
+ providerAccount: {
123
+ anyOf: [
124
+ {
125
+ type: 'object',
126
+ properties: {
127
+ iss: {
128
+ type: 'string'
129
+ },
130
+ sub: {
131
+ type: 'string'
132
+ }
133
+ },
134
+ required: [
135
+ 'iss',
136
+ 'sub'
137
+ ],
138
+ additionalProperties: false
139
+ },
140
+ {
141
+ type: 'null'
142
+ }
143
+ ]
144
+ },
145
+ // Optional: a server that predates it omits it, and a consumer
146
+ // then checks the grant against the credential it spends.
147
+ scopes: {
148
+ type: 'array',
149
+ items: {
150
+ type: 'string'
151
+ }
152
+ }
153
+ },
154
+ required: [
155
+ 'providerAccount'
156
+ ],
157
+ additionalProperties: false
158
+ },
159
+ {
160
+ type: 'null'
161
+ }
162
+ ]
163
+ }
164
+ },
45
165
  'credential/provenance': {
46
166
  type: 'request',
47
167
  param: {
@@ -157,6 +277,40 @@ export const credentialServiceProtocol = {
157
277
  type: 'null'
158
278
  }
159
279
  },
280
+ 'credential/refresh-entry': {
281
+ type: 'request',
282
+ param: {
283
+ type: 'object',
284
+ properties: {
285
+ providerName: {
286
+ type: 'string'
287
+ },
288
+ ownerDID: {
289
+ type: 'string'
290
+ },
291
+ entryID: {
292
+ type: 'string'
293
+ },
294
+ credential: {
295
+ type: 'string'
296
+ }
297
+ },
298
+ required: [
299
+ 'providerName',
300
+ 'ownerDID',
301
+ 'entryID',
302
+ 'credential'
303
+ ],
304
+ additionalProperties: false
305
+ },
306
+ result: {
307
+ type: 'string',
308
+ enum: [
309
+ 'updated',
310
+ 'absent'
311
+ ]
312
+ }
313
+ },
160
314
  'credential/delete': {
161
315
  type: 'request',
162
316
  param: {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kubun/service-credential-api",
3
- "version": "0.17.1",
3
+ "version": "0.17.3",
4
4
  "keywords": [],
5
5
  "license": "see LICENSE.md",
6
6
  "sideEffects": false,
@@ -17,8 +17,8 @@
17
17
  "dependencies": {
18
18
  "@enkaku/client": "^0.21.2",
19
19
  "@enkaku/protocol": "^0.21.1",
20
- "@kubun/credential": "^0.17.1",
21
- "@kubun/credential-types": "^0.17.1"
20
+ "@kubun/credential": "^0.17.4",
21
+ "@kubun/credential-types": "^0.17.2"
22
22
  },
23
23
  "publishConfig": {
24
24
  "access": "public"