@kubun/service-credential-api 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/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, 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 & 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,37 @@ export function toCredentialProvider(client) {
15
18
  });
16
19
  return raw == null ? null : deserializeCredential(raw);
17
20
  },
21
+ async peekAccount (providerName, ownerDID) {
22
+ let peeked;
23
+ try {
24
+ peeked = await client.request('credential/peek', {
25
+ param: {
26
+ providerName,
27
+ ownerDID
28
+ }
29
+ });
30
+ } catch (err) {
31
+ // Every current server grants `credential/peek` to any verified issuer
32
+ // (the subject authorizer answers a denial with `null`), so an access
33
+ // denial here most likely means the server does not serve the
34
+ // procedure — though a caller token that is expired or revoked is
35
+ // denied the same way. Either way no storage-only read is available.
36
+ if (err instanceof RequestError && err.code === ErrorCodes.ACCESS_DENIED) {
37
+ // biome-ignore lint/style/useErrorCause: cause is forwarded to super via the options arg
38
+ throw new CredentialPeekUnsupportedError({
39
+ cause: err
40
+ });
41
+ }
42
+ throw err;
43
+ }
44
+ if (peeked == null) return null;
45
+ return {
46
+ providerAccount: peeked.providerAccount ?? null,
47
+ ...peeked.scopes != null ? {
48
+ scopes: peeked.scopes
49
+ } : {}
50
+ };
51
+ },
18
52
  async set ({ providerName, ownerDID, credential, ownerWrappableDID, controllerWrappableDID }) {
19
53
  // Create-vs-refresh dispatches on the owner's wrappable DID, mirroring
20
54
  // `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,56 @@ export declare const credentialServiceProtocol: {
23
24
  }];
24
25
  };
25
26
  };
27
+ readonly 'credential/peek': {
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 providerAccount: {
47
+ readonly anyOf: readonly [{
48
+ readonly type: "object";
49
+ readonly properties: {
50
+ readonly iss: {
51
+ readonly type: "string";
52
+ };
53
+ readonly sub: {
54
+ readonly type: "string";
55
+ };
56
+ };
57
+ readonly required: readonly ["iss", "sub"];
58
+ readonly additionalProperties: false;
59
+ }, {
60
+ readonly type: "null";
61
+ }];
62
+ };
63
+ readonly scopes: {
64
+ readonly type: "array";
65
+ readonly items: {
66
+ readonly type: "string";
67
+ };
68
+ };
69
+ };
70
+ readonly required: readonly ["providerAccount"];
71
+ readonly additionalProperties: false;
72
+ }, {
73
+ readonly type: "null";
74
+ }];
75
+ };
76
+ };
26
77
  readonly 'credential/provenance': {
27
78
  readonly type: "request";
28
79
  readonly param: {
package/lib/index.js CHANGED
@@ -5,6 +5,7 @@
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
@@ -42,6 +43,82 @@ export const credentialServiceProtocol = {
42
43
  ]
43
44
  }
44
45
  },
46
+ // Storage-only, non-refreshing read of the stored credential's provider
47
+ // account and the scopes it was granted — and nothing else: no token
48
+ // material crosses the wire. The server
49
+ // reads its vault and never runs a token refresh, so a consumer can pin the
50
+ // provider-account fingerprint without triggering provider network I/O.
51
+ // `null` when no credential is stored (or the read is denied);
52
+ // `providerAccount: null` when the stored credential captured none.
53
+ // Additive: a server that predates it rejects the procedure, and a consumer
54
+ // that needs a non-refreshing read must treat that as unavailable rather than
55
+ // fall back to `credential/get`.
56
+ 'credential/peek': {
57
+ type: 'request',
58
+ param: {
59
+ type: 'object',
60
+ properties: {
61
+ providerName: {
62
+ type: 'string'
63
+ },
64
+ ownerDID: {
65
+ type: 'string'
66
+ }
67
+ },
68
+ required: [
69
+ 'providerName',
70
+ 'ownerDID'
71
+ ],
72
+ additionalProperties: false
73
+ },
74
+ result: {
75
+ anyOf: [
76
+ {
77
+ type: 'object',
78
+ properties: {
79
+ providerAccount: {
80
+ anyOf: [
81
+ {
82
+ type: 'object',
83
+ properties: {
84
+ iss: {
85
+ type: 'string'
86
+ },
87
+ sub: {
88
+ type: 'string'
89
+ }
90
+ },
91
+ required: [
92
+ 'iss',
93
+ 'sub'
94
+ ],
95
+ additionalProperties: false
96
+ },
97
+ {
98
+ type: 'null'
99
+ }
100
+ ]
101
+ },
102
+ // Optional: a server that predates it omits it, and a consumer
103
+ // then checks the grant against the credential it spends.
104
+ scopes: {
105
+ type: 'array',
106
+ items: {
107
+ type: 'string'
108
+ }
109
+ }
110
+ },
111
+ required: [
112
+ 'providerAccount'
113
+ ],
114
+ additionalProperties: false
115
+ },
116
+ {
117
+ type: 'null'
118
+ }
119
+ ]
120
+ }
121
+ },
45
122
  'credential/provenance': {
46
123
  type: 'request',
47
124
  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.2",
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.3",
21
+ "@kubun/credential-types": "^0.17.2"
22
22
  },
23
23
  "publishConfig": {
24
24
  "access": "public"