@kubun/credential 0.15.0 → 0.17.0
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.
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { OAuthProviderDefinition } from '@kokuin/oauth';
|
|
2
|
+
import type { Credential, CredentialProvider } from '@kubun/credential-types';
|
|
2
3
|
import type { Logger } from '@kubun/logger';
|
|
3
4
|
import { EventEmitter } from '@sozai/event';
|
|
4
5
|
import type { Runtime } from '@sozai/runtime';
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { OAuthTokenError, refreshToken as requestRefreshedToken } from '@kokuin/oauth';
|
|
1
2
|
import { normalizeDID } from '@kokuin/token';
|
|
2
3
|
import { EventEmitter } from '@sozai/event';
|
|
3
4
|
const DEFAULT_BUFFER_SECONDS = 300;
|
|
@@ -103,42 +104,26 @@ export class RefreshingCredentialProvider {
|
|
|
103
104
|
// outcome and does not emit or fall back itself.
|
|
104
105
|
async #exchange(providerName, ownerDID, credential, refreshToken) {
|
|
105
106
|
const provider = this.#providers.find((p)=>p.name === providerName);
|
|
106
|
-
if (provider == null
|
|
107
|
+
if (provider == null) {
|
|
107
108
|
return {
|
|
108
109
|
ok: false,
|
|
109
110
|
category: 'configuration'
|
|
110
111
|
};
|
|
111
112
|
}
|
|
112
|
-
let response;
|
|
113
|
-
try {
|
|
114
|
-
response = await this.#runtime.fetch(provider.tokenEndpoint, {
|
|
115
|
-
method: 'POST',
|
|
116
|
-
headers: {
|
|
117
|
-
'Content-Type': 'application/x-www-form-urlencoded'
|
|
118
|
-
},
|
|
119
|
-
body: new URLSearchParams({
|
|
120
|
-
grant_type: 'refresh_token',
|
|
121
|
-
refresh_token: refreshToken,
|
|
122
|
-
client_id: provider.clientID,
|
|
123
|
-
client_secret: provider.clientSecret
|
|
124
|
-
})
|
|
125
|
-
});
|
|
126
|
-
} catch {
|
|
127
|
-
return {
|
|
128
|
-
ok: false,
|
|
129
|
-
category: 'transient'
|
|
130
|
-
};
|
|
131
|
-
}
|
|
132
|
-
if (!response.ok) {
|
|
133
|
-
return {
|
|
134
|
-
ok: false,
|
|
135
|
-
category: await this.#classifyErrorResponse(response)
|
|
136
|
-
};
|
|
137
|
-
}
|
|
138
113
|
let tokenData;
|
|
139
114
|
try {
|
|
140
|
-
tokenData = await
|
|
141
|
-
|
|
115
|
+
tokenData = await requestRefreshedToken({
|
|
116
|
+
definition: provider,
|
|
117
|
+
runtime: this.#runtime,
|
|
118
|
+
refreshToken
|
|
119
|
+
});
|
|
120
|
+
} catch (err) {
|
|
121
|
+
if (err instanceof OAuthTokenError) {
|
|
122
|
+
return {
|
|
123
|
+
ok: false,
|
|
124
|
+
category: err.code === 'invalid_grant' ? 'invalid_grant' : 'transient'
|
|
125
|
+
};
|
|
126
|
+
}
|
|
142
127
|
return {
|
|
143
128
|
ok: false,
|
|
144
129
|
category: 'transient'
|
|
@@ -159,9 +144,15 @@ export class RefreshingCredentialProvider {
|
|
|
159
144
|
credential: refreshed
|
|
160
145
|
});
|
|
161
146
|
} catch {
|
|
147
|
+
// A rotated refresh token (the endpoint issued a new one) means the prior
|
|
148
|
+
// token may already be invalidated server-side, so a lost write here
|
|
149
|
+
// cannot fall back to the stale credential — that would silently retry a
|
|
150
|
+
// refresh token the provider has already discarded. Surface it as the
|
|
151
|
+
// terminal re-auth category instead.
|
|
152
|
+
const rotated = tokenData.refresh_token != null && tokenData.refresh_token !== refreshToken;
|
|
162
153
|
return {
|
|
163
154
|
ok: false,
|
|
164
|
-
category: 'persistence'
|
|
155
|
+
category: rotated ? 'invalid_grant' : 'persistence'
|
|
165
156
|
};
|
|
166
157
|
}
|
|
167
158
|
return {
|
|
@@ -169,17 +160,4 @@ export class RefreshingCredentialProvider {
|
|
|
169
160
|
credential: refreshed
|
|
170
161
|
};
|
|
171
162
|
}
|
|
172
|
-
// Distinguishes a confirmed OAuth invalidation from a generic non-OK status.
|
|
173
|
-
// The standard OAuth2 error response is `{ "error": "invalid_grant", ... }` in
|
|
174
|
-
// the JSON body of a 400; anything else — including an empty or non-JSON body —
|
|
175
|
-
// is treated as transient so a stale-but-valid token is never abandoned.
|
|
176
|
-
async #classifyErrorResponse(response) {
|
|
177
|
-
try {
|
|
178
|
-
const body = await response.json();
|
|
179
|
-
if (body?.error === 'invalid_grant') return 'invalid_grant';
|
|
180
|
-
} catch {
|
|
181
|
-
// Empty or non-JSON error body: cannot confirm invalidation.
|
|
182
|
-
}
|
|
183
|
-
return 'transient';
|
|
184
|
-
}
|
|
185
163
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kubun/credential",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.17.0",
|
|
4
4
|
"keywords": [],
|
|
5
5
|
"license": "see LICENSE.md",
|
|
6
6
|
"sideEffects": false,
|
|
@@ -16,13 +16,14 @@
|
|
|
16
16
|
],
|
|
17
17
|
"dependencies": {
|
|
18
18
|
"@kokuin/jwe": "^0.1.0",
|
|
19
|
+
"@kokuin/oauth": "^0.1.0",
|
|
19
20
|
"@kokuin/token": "^0.5.0",
|
|
20
|
-
"@kubun/credential-types": "^0.
|
|
21
|
-
"@kubun/db": "^0.
|
|
22
|
-
"@kubun/hlc": "^0.
|
|
23
|
-
"@kubun/id": "^0.
|
|
24
|
-
"@kubun/logger": "^0.
|
|
25
|
-
"@kubun/store-credential": "^0.
|
|
21
|
+
"@kubun/credential-types": "^0.17.0",
|
|
22
|
+
"@kubun/db": "^0.17.0",
|
|
23
|
+
"@kubun/hlc": "^0.17.0",
|
|
24
|
+
"@kubun/id": "^0.17.0",
|
|
25
|
+
"@kubun/logger": "^0.17.0",
|
|
26
|
+
"@kubun/store-credential": "^0.17.0",
|
|
26
27
|
"@noble/ciphers": "^2.4.0",
|
|
27
28
|
"@noble/hashes": "^2.4.0",
|
|
28
29
|
"@sozai/codec": "^0.4.0",
|
|
@@ -30,7 +31,7 @@
|
|
|
30
31
|
"@sozai/runtime": "^0.1.0"
|
|
31
32
|
},
|
|
32
33
|
"devDependencies": {
|
|
33
|
-
"@kubun/db-better-sqlite": "^0.
|
|
34
|
+
"@kubun/db-better-sqlite": "^0.17.0"
|
|
34
35
|
},
|
|
35
36
|
"publishConfig": {
|
|
36
37
|
"access": "public"
|
|
@@ -38,8 +39,8 @@
|
|
|
38
39
|
"scripts": {
|
|
39
40
|
"build": "pnpm run build:clean && pnpm run build:js && pnpm run build:types",
|
|
40
41
|
"build:clean": "del lib",
|
|
41
|
-
"build:js": "swc src -d ./lib --config-file ../../node_modules/@kigu/dev/swc.json --strip-leading-paths",
|
|
42
|
-
"build:types": "tsc --emitDeclarationOnly --skipLibCheck",
|
|
42
|
+
"build:js": "del 'lib/**/*.js' && swc src -d ./lib --config-file ../../node_modules/@kigu/dev/swc.json --strip-leading-paths",
|
|
43
|
+
"build:types": "del 'lib/**/*.d.ts' 'lib/**/*.d.ts.map' && tsc --emitDeclarationOnly --skipLibCheck",
|
|
43
44
|
"build:types:ci": "tsc --emitDeclarationOnly --declarationMap false",
|
|
44
45
|
"test": "pnpm run test:types && pnpm run test:unit",
|
|
45
46
|
"test:types": "tsc --noEmit -p tsconfig.test.json",
|