@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 { Credential, CredentialProvider, OAuthProviderDefinition } from '@kubun/credential-types';
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 || provider.clientID == null || provider.clientSecret == 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 response.json();
141
- } catch {
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.15.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.15.0",
21
- "@kubun/db": "^0.15.0",
22
- "@kubun/hlc": "^0.15.0",
23
- "@kubun/id": "^0.15.0",
24
- "@kubun/logger": "^0.15.0",
25
- "@kubun/store-credential": "^0.15.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.15.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",