@kubun/service-credential-api 0.17.0 → 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 +18 -3
- package/lib/adapter.js +43 -0
- package/lib/errors.d.ts +18 -0
- package/lib/errors.js +22 -0
- package/lib/index.d.ts +74 -1
- package/lib/index.js +103 -0
- package/package.json +3 -3
package/lib/adapter.d.ts
CHANGED
|
@@ -1,10 +1,25 @@
|
|
|
1
|
-
import type
|
|
2
|
-
import type { 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
|
|
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),
|
|
@@ -54,6 +88,15 @@ export function toCredentialProvider(client) {
|
|
|
54
88
|
ownerDID
|
|
55
89
|
}
|
|
56
90
|
});
|
|
91
|
+
},
|
|
92
|
+
async bindAlias (providerName, ownerDID, entryID) {
|
|
93
|
+
await client.request('credential/bind-alias', {
|
|
94
|
+
param: {
|
|
95
|
+
providerName,
|
|
96
|
+
ownerDID,
|
|
97
|
+
entryID
|
|
98
|
+
}
|
|
99
|
+
});
|
|
57
100
|
}
|
|
58
101
|
};
|
|
59
102
|
}
|
package/lib/errors.d.ts
ADDED
|
@@ -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: {
|
|
@@ -133,5 +184,27 @@ export declare const credentialServiceProtocol: {
|
|
|
133
184
|
readonly type: "null";
|
|
134
185
|
};
|
|
135
186
|
};
|
|
187
|
+
readonly 'credential/bind-alias': {
|
|
188
|
+
readonly type: "request";
|
|
189
|
+
readonly param: {
|
|
190
|
+
readonly type: "object";
|
|
191
|
+
readonly properties: {
|
|
192
|
+
readonly providerName: {
|
|
193
|
+
readonly type: "string";
|
|
194
|
+
};
|
|
195
|
+
readonly ownerDID: {
|
|
196
|
+
readonly type: "string";
|
|
197
|
+
};
|
|
198
|
+
readonly entryID: {
|
|
199
|
+
readonly type: "string";
|
|
200
|
+
};
|
|
201
|
+
};
|
|
202
|
+
readonly required: readonly ["providerName", "ownerDID", "entryID"];
|
|
203
|
+
readonly additionalProperties: false;
|
|
204
|
+
};
|
|
205
|
+
readonly result: {
|
|
206
|
+
readonly type: "null";
|
|
207
|
+
};
|
|
208
|
+
};
|
|
136
209
|
};
|
|
137
210
|
export type CredentialServiceProtocol = typeof credentialServiceProtocol;
|
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: {
|
|
@@ -178,5 +255,31 @@ export const credentialServiceProtocol = {
|
|
|
178
255
|
result: {
|
|
179
256
|
type: 'null'
|
|
180
257
|
}
|
|
258
|
+
},
|
|
259
|
+
'credential/bind-alias': {
|
|
260
|
+
type: 'request',
|
|
261
|
+
param: {
|
|
262
|
+
type: 'object',
|
|
263
|
+
properties: {
|
|
264
|
+
providerName: {
|
|
265
|
+
type: 'string'
|
|
266
|
+
},
|
|
267
|
+
ownerDID: {
|
|
268
|
+
type: 'string'
|
|
269
|
+
},
|
|
270
|
+
entryID: {
|
|
271
|
+
type: 'string'
|
|
272
|
+
}
|
|
273
|
+
},
|
|
274
|
+
required: [
|
|
275
|
+
'providerName',
|
|
276
|
+
'ownerDID',
|
|
277
|
+
'entryID'
|
|
278
|
+
],
|
|
279
|
+
additionalProperties: false
|
|
280
|
+
},
|
|
281
|
+
result: {
|
|
282
|
+
type: 'null'
|
|
283
|
+
}
|
|
181
284
|
}
|
|
182
285
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kubun/service-credential-api",
|
|
3
|
-
"version": "0.17.
|
|
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.
|
|
21
|
-
"@kubun/credential-types": "^0.17.
|
|
20
|
+
"@kubun/credential": "^0.17.3",
|
|
21
|
+
"@kubun/credential-types": "^0.17.2"
|
|
22
22
|
},
|
|
23
23
|
"publishConfig": {
|
|
24
24
|
"access": "public"
|