@feelflow/ffid-sdk 1.10.0 → 1.11.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.
- package/dist/{chunk-PSSNMEJB.js → chunk-GW23IWNE.js} +502 -387
- package/dist/{chunk-OAPA5VKR.cjs → chunk-WZZP7SQB.cjs} +502 -387
- package/dist/components/index.cjs +7 -7
- package/dist/components/index.d.cts +1 -1
- package/dist/components/index.d.ts +1 -1
- package/dist/components/index.js +1 -1
- package/dist/{index-DsXjcF0i.d.cts → index-DMgtXEt5.d.cts} +20 -5
- package/dist/{index-DsXjcF0i.d.ts → index-DMgtXEt5.d.ts} +20 -5
- package/dist/index.cjs +22 -22
- package/dist/index.d.cts +6 -6
- package/dist/index.d.ts +6 -6
- package/dist/index.js +2 -2
- package/dist/server/index.cjs +487 -374
- package/dist/server/index.d.cts +24 -9
- package/dist/server/index.d.ts +24 -9
- package/dist/server/index.js +487 -374
- package/package.json +1 -1
package/dist/server/index.js
CHANGED
|
@@ -4,6 +4,7 @@ import { createRemoteJWKSet, jwtVerify } from 'jose';
|
|
|
4
4
|
|
|
5
5
|
// src/auth/token-store.ts
|
|
6
6
|
var STORAGE_KEY = "ffid_tokens";
|
|
7
|
+
var TOKEN_STORE_LOG_PREFIX = "[FFID SDK] TokenStore:";
|
|
7
8
|
var EXPIRY_BUFFER_SECONDS = 30;
|
|
8
9
|
var EXPIRY_BUFFER_MS = EXPIRY_BUFFER_SECONDS * 1e3;
|
|
9
10
|
function isLocalStorageAvailable() {
|
|
@@ -27,22 +28,33 @@ function createLocalStorageStore() {
|
|
|
27
28
|
const raw = storage.getItem(STORAGE_KEY);
|
|
28
29
|
if (!raw) return null;
|
|
29
30
|
const parsed = JSON.parse(raw);
|
|
30
|
-
if (!isTokenData(parsed))
|
|
31
|
+
if (!isTokenData(parsed)) {
|
|
32
|
+
console.warn(TOKEN_STORE_LOG_PREFIX, "\u30C8\u30FC\u30AF\u30F3\u30C7\u30FC\u30BF\u304C\u4E0D\u6B63\u306A\u5F62\u5F0F\u3067\u3059\u3002\u30B9\u30C8\u30EC\u30FC\u30B8\u3092\u30AF\u30EA\u30A2\u3057\u307E\u3059");
|
|
33
|
+
storage.removeItem(STORAGE_KEY);
|
|
34
|
+
return null;
|
|
35
|
+
}
|
|
31
36
|
return parsed;
|
|
32
|
-
} catch {
|
|
37
|
+
} catch (error) {
|
|
38
|
+
console.warn(TOKEN_STORE_LOG_PREFIX, "\u30C8\u30FC\u30AF\u30F3\u8AAD\u307F\u53D6\u308A\u306B\u5931\u6557\u3057\u307E\u3057\u305F\u3002\u7834\u640D\u30C7\u30FC\u30BF\u3092\u30AF\u30EA\u30A2\u3057\u307E\u3059", error);
|
|
39
|
+
try {
|
|
40
|
+
storage.removeItem(STORAGE_KEY);
|
|
41
|
+
} catch {
|
|
42
|
+
}
|
|
33
43
|
return null;
|
|
34
44
|
}
|
|
35
45
|
},
|
|
36
46
|
setTokens(tokens) {
|
|
37
47
|
try {
|
|
38
48
|
storage.setItem(STORAGE_KEY, JSON.stringify(tokens));
|
|
39
|
-
} catch {
|
|
49
|
+
} catch (error) {
|
|
50
|
+
console.warn(TOKEN_STORE_LOG_PREFIX, "\u30C8\u30FC\u30AF\u30F3\u4FDD\u5B58\u306B\u5931\u6557\u3057\u307E\u3057\u305F\uFF08\u30B9\u30C8\u30EC\u30FC\u30B8\u5BB9\u91CF\u8D85\u904E\u306E\u53EF\u80FD\u6027\uFF09", error);
|
|
40
51
|
}
|
|
41
52
|
},
|
|
42
53
|
clearTokens() {
|
|
43
54
|
try {
|
|
44
55
|
storage.removeItem(STORAGE_KEY);
|
|
45
|
-
} catch {
|
|
56
|
+
} catch (error) {
|
|
57
|
+
console.warn(TOKEN_STORE_LOG_PREFIX, "\u30C8\u30FC\u30AF\u30F3\u524A\u9664\u306B\u5931\u6557\u3057\u307E\u3057\u305F", error);
|
|
46
58
|
}
|
|
47
59
|
},
|
|
48
60
|
isAccessTokenExpired() {
|
|
@@ -85,42 +97,6 @@ function createTokenStore(storageType) {
|
|
|
85
97
|
return createMemoryStore();
|
|
86
98
|
}
|
|
87
99
|
|
|
88
|
-
// src/auth/pkce.ts
|
|
89
|
-
var VERIFIER_STORAGE_KEY = "ffid_code_verifier";
|
|
90
|
-
var CODE_VERIFIER_MIN_LENGTH = 43;
|
|
91
|
-
var UNRESERVED_CHARS = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-._~";
|
|
92
|
-
function generateCodeVerifier() {
|
|
93
|
-
const length = CODE_VERIFIER_MIN_LENGTH;
|
|
94
|
-
const randomValues = new Uint8Array(length);
|
|
95
|
-
crypto.getRandomValues(randomValues);
|
|
96
|
-
let verifier = "";
|
|
97
|
-
for (let i = 0; i < length; i++) {
|
|
98
|
-
verifier += UNRESERVED_CHARS[randomValues[i] % UNRESERVED_CHARS.length];
|
|
99
|
-
}
|
|
100
|
-
return verifier;
|
|
101
|
-
}
|
|
102
|
-
async function generateCodeChallenge(verifier) {
|
|
103
|
-
const encoder = new TextEncoder();
|
|
104
|
-
const data = encoder.encode(verifier);
|
|
105
|
-
const digest = await crypto.subtle.digest("SHA-256", data);
|
|
106
|
-
return base64UrlEncode(digest);
|
|
107
|
-
}
|
|
108
|
-
function storeCodeVerifier(verifier) {
|
|
109
|
-
try {
|
|
110
|
-
if (typeof window === "undefined") return;
|
|
111
|
-
window.sessionStorage.setItem(VERIFIER_STORAGE_KEY, verifier);
|
|
112
|
-
} catch {
|
|
113
|
-
}
|
|
114
|
-
}
|
|
115
|
-
function base64UrlEncode(buffer) {
|
|
116
|
-
const bytes = new Uint8Array(buffer);
|
|
117
|
-
let binary = "";
|
|
118
|
-
for (let i = 0; i < bytes.length; i++) {
|
|
119
|
-
binary += String.fromCharCode(bytes[i]);
|
|
120
|
-
}
|
|
121
|
-
return btoa(binary).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
|
|
122
|
-
}
|
|
123
|
-
|
|
124
100
|
// src/client/oauth-userinfo.ts
|
|
125
101
|
var VALID_SUBSCRIPTION_STATUSES = ["trialing", "active", "past_due", "canceled", "paused"];
|
|
126
102
|
function isValidSubscriptionStatus(value) {
|
|
@@ -224,6 +200,7 @@ var OAUTH_INTROSPECT_ENDPOINT = "/api/v1/oauth/introspect";
|
|
|
224
200
|
var CACHE_KEY_PREFIX = "ffid:introspect:";
|
|
225
201
|
var HEX_BASE = 16;
|
|
226
202
|
var HEX_BYTE_WIDTH = 2;
|
|
203
|
+
var TOKEN_LOG_PREFIX_LENGTH = 8;
|
|
227
204
|
function createVerifyAccessToken(deps) {
|
|
228
205
|
const { authMode, baseUrl, serviceCode, serviceApiKey, verifyStrategy, logger, createError, errorCodes, cache, timeout } = deps;
|
|
229
206
|
let jwtVerify2 = null;
|
|
@@ -239,7 +216,7 @@ function createVerifyAccessToken(deps) {
|
|
|
239
216
|
}
|
|
240
217
|
return jwtVerify2;
|
|
241
218
|
}
|
|
242
|
-
async function verifyAccessToken(accessToken) {
|
|
219
|
+
async function verifyAccessToken(accessToken, options) {
|
|
243
220
|
if (authMode !== "service-key") {
|
|
244
221
|
return {
|
|
245
222
|
error: createError(
|
|
@@ -256,12 +233,24 @@ function createVerifyAccessToken(deps) {
|
|
|
256
233
|
)
|
|
257
234
|
};
|
|
258
235
|
}
|
|
236
|
+
if (options?.includeProfile && verifyStrategy === "jwt" && !serviceApiKey) {
|
|
237
|
+
return {
|
|
238
|
+
error: createError(
|
|
239
|
+
errorCodes.TOKEN_VERIFICATION_ERROR,
|
|
240
|
+
"includeProfile: true \u3092\u4F7F\u7528\u3059\u308B\u306B\u306F serviceApiKey \u306E\u8A2D\u5B9A\u304C\u5FC5\u8981\u3067\u3059"
|
|
241
|
+
)
|
|
242
|
+
};
|
|
243
|
+
}
|
|
259
244
|
if (verifyStrategy === "jwt") {
|
|
260
|
-
|
|
245
|
+
const jwtResult = await getJwtVerifier()(accessToken);
|
|
246
|
+
if (!options?.includeProfile || jwtResult.error) {
|
|
247
|
+
return jwtResult;
|
|
248
|
+
}
|
|
249
|
+
return verifyViaIntrospect(accessToken, "profile");
|
|
261
250
|
}
|
|
262
251
|
return verifyViaIntrospect(accessToken);
|
|
263
252
|
}
|
|
264
|
-
async function verifyViaIntrospect(accessToken) {
|
|
253
|
+
async function verifyViaIntrospect(accessToken, cacheKeySuffix) {
|
|
265
254
|
if (!serviceApiKey) {
|
|
266
255
|
return {
|
|
267
256
|
error: createError(
|
|
@@ -272,7 +261,7 @@ function createVerifyAccessToken(deps) {
|
|
|
272
261
|
}
|
|
273
262
|
const url = `${baseUrl}${OAUTH_INTROSPECT_ENDPOINT}`;
|
|
274
263
|
logger.debug("Verifying access token:", url);
|
|
275
|
-
const cacheKey = await buildCacheKey(accessToken);
|
|
264
|
+
const cacheKey = cache ? await buildCacheKey(accessToken, cacheKeySuffix) : "";
|
|
276
265
|
if (cache) {
|
|
277
266
|
try {
|
|
278
267
|
const cached = await cache.adapter.get(cacheKey);
|
|
@@ -374,16 +363,23 @@ function createVerifyAccessToken(deps) {
|
|
|
374
363
|
}
|
|
375
364
|
return { data: userinfo };
|
|
376
365
|
}
|
|
377
|
-
async function buildCacheKey(token) {
|
|
366
|
+
async function buildCacheKey(token, suffix) {
|
|
367
|
+
let key;
|
|
378
368
|
if (typeof globalThis.crypto?.subtle?.digest === "function") {
|
|
379
369
|
const encoder = new TextEncoder();
|
|
380
370
|
const data = encoder.encode(token);
|
|
381
371
|
const hashBuffer = await crypto.subtle.digest("SHA-256", data);
|
|
382
372
|
const hashArray = Array.from(new Uint8Array(hashBuffer));
|
|
383
373
|
const hashHex = hashArray.map((b) => b.toString(HEX_BASE).padStart(HEX_BYTE_WIDTH, "0")).join("");
|
|
384
|
-
|
|
374
|
+
key = CACHE_KEY_PREFIX + hashHex;
|
|
375
|
+
} else {
|
|
376
|
+
const tokenPrefix = token.length > TOKEN_LOG_PREFIX_LENGTH ? token.substring(0, TOKEN_LOG_PREFIX_LENGTH) + "..." : "***";
|
|
377
|
+
logger.warn(
|
|
378
|
+
`crypto.subtle \u304C\u5229\u7528\u3067\u304D\u306A\u3044\u305F\u3081\u3001\u30AD\u30E3\u30C3\u30B7\u30E5\u30AD\u30FC\u306B\u30CF\u30C3\u30B7\u30E5\u5316\u3055\u308C\u3066\u3044\u306A\u3044\u30C8\u30FC\u30AF\u30F3\u3092\u4F7F\u7528\u3057\u307E\u3059 (token prefix: ${tokenPrefix})`
|
|
379
|
+
);
|
|
380
|
+
key = CACHE_KEY_PREFIX + token;
|
|
385
381
|
}
|
|
386
|
-
return
|
|
382
|
+
return suffix ? `${key}:${suffix}` : key;
|
|
387
383
|
}
|
|
388
384
|
return verifyAccessToken;
|
|
389
385
|
}
|
|
@@ -436,9 +432,15 @@ function createBillingMethods(deps) {
|
|
|
436
432
|
}
|
|
437
433
|
|
|
438
434
|
// src/client/version-check.ts
|
|
439
|
-
var SDK_VERSION = "1.
|
|
435
|
+
var SDK_VERSION = "1.11.0";
|
|
440
436
|
var SDK_USER_AGENT = `FFID-SDK/${SDK_VERSION} (TypeScript)`;
|
|
441
437
|
var SDK_VERSION_HEADER = "X-FFID-SDK-Version";
|
|
438
|
+
function sdkHeaders() {
|
|
439
|
+
return {
|
|
440
|
+
"User-Agent": SDK_USER_AGENT,
|
|
441
|
+
[SDK_VERSION_HEADER]: SDK_VERSION
|
|
442
|
+
};
|
|
443
|
+
}
|
|
442
444
|
var LATEST_VERSION_HEADER = "X-FFID-SDK-Latest-Version";
|
|
443
445
|
var SEMVER_PATTERN = /^\d+(\.\d+)*$/;
|
|
444
446
|
var _versionWarningShown = false;
|
|
@@ -472,178 +474,213 @@ npm install @feelflow/ffid-sdk@latest \u3067\u30A2\u30C3\u30D7\u30C7\u30FC\u30C8
|
|
|
472
474
|
}
|
|
473
475
|
}
|
|
474
476
|
|
|
475
|
-
// src/client/
|
|
476
|
-
var NO_CONTENT_STATUS = 204;
|
|
477
|
-
var SESSION_ENDPOINT = "/api/v1/auth/session";
|
|
478
|
-
var LOGOUT_ENDPOINT = "/api/v1/auth/signout";
|
|
477
|
+
// src/client/oauth-token.ts
|
|
479
478
|
var OAUTH_TOKEN_ENDPOINT = "/api/v1/oauth/token";
|
|
480
|
-
var OAUTH_USERINFO_ENDPOINT = "/api/v1/oauth/userinfo";
|
|
481
|
-
var OAUTH_AUTHORIZE_ENDPOINT = "/api/v1/oauth/authorize";
|
|
482
479
|
var OAUTH_REVOKE_ENDPOINT = "/api/v1/oauth/revoke";
|
|
483
|
-
var EXT_CHECK_ENDPOINT = "/api/v1/subscriptions/ext/check";
|
|
484
|
-
var SDK_LOG_PREFIX = "[FFID SDK]";
|
|
485
480
|
var MS_PER_SECOND = 1e3;
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
const baseUrl = config.apiBaseUrl ?? DEFAULT_API_BASE_URL;
|
|
518
|
-
const authMode = config.authMode ?? "cookie";
|
|
519
|
-
const clientId = config.clientId ?? config.serviceCode;
|
|
520
|
-
const resolvedRedirectUri = config.redirectUri ?? null;
|
|
521
|
-
const serviceApiKey = config.serviceApiKey?.trim();
|
|
522
|
-
const verifyStrategy = config.verifyStrategy ?? "jwt";
|
|
523
|
-
const cache = config.cache;
|
|
524
|
-
const timeout = config.timeout;
|
|
525
|
-
if (authMode === "service-key" && !serviceApiKey) {
|
|
526
|
-
throw new Error("FFID Client: service-key \u30E2\u30FC\u30C9\u3067\u306F serviceApiKey \u304C\u5FC5\u9808\u3067\u3059");
|
|
527
|
-
}
|
|
528
|
-
if (cache && cache.ttl <= 0) {
|
|
529
|
-
throw new Error("FFID Client: cache.ttl \u306F\u6B63\u306E\u6570\u5024\u3092\u6307\u5B9A\u3057\u3066\u304F\u3060\u3055\u3044");
|
|
530
|
-
}
|
|
531
|
-
if (timeout !== void 0 && timeout <= 0) {
|
|
532
|
-
throw new Error("FFID Client: timeout \u306F\u6B63\u306E\u6570\u5024\u3092\u6307\u5B9A\u3057\u3066\u304F\u3060\u3055\u3044");
|
|
533
|
-
}
|
|
534
|
-
const logger = config.logger ?? (config.debug ? consoleLogger : noopLogger);
|
|
535
|
-
const tokenStore = authMode === "token" ? createTokenStore() : createTokenStore("memory");
|
|
536
|
-
async function fetchWithAuth(endpoint, options = {}) {
|
|
537
|
-
const url = `${baseUrl}${endpoint}`;
|
|
538
|
-
logger.debug("Fetching:", url);
|
|
539
|
-
const fetchOptions = buildFetchOptions(options);
|
|
481
|
+
function createOAuthTokenMethods(deps) {
|
|
482
|
+
const {
|
|
483
|
+
baseUrl,
|
|
484
|
+
clientId,
|
|
485
|
+
resolvedRedirectUri,
|
|
486
|
+
tokenStore,
|
|
487
|
+
logger,
|
|
488
|
+
errorCodes
|
|
489
|
+
} = deps;
|
|
490
|
+
async function exchangeCodeForTokens(code, codeVerifier) {
|
|
491
|
+
const url = `${baseUrl}${OAUTH_TOKEN_ENDPOINT}`;
|
|
492
|
+
logger.debug("Exchanging code for tokens:", url);
|
|
493
|
+
const effectiveRedirectUri = resolvedRedirectUri ?? (typeof window !== "undefined" ? window.location.origin + window.location.pathname : null);
|
|
494
|
+
if (!effectiveRedirectUri) {
|
|
495
|
+
logger.error("redirectUri is required for token exchange in SSR environments. Set config.redirectUri explicitly.");
|
|
496
|
+
return {
|
|
497
|
+
error: {
|
|
498
|
+
code: errorCodes.TOKEN_EXCHANGE_ERROR,
|
|
499
|
+
message: "redirectUri \u304C\u672A\u8A2D\u5B9A\u3067\u3059\u3002SSR\u74B0\u5883\u3067\u306F config.redirectUri \u3092\u660E\u793A\u7684\u306B\u6307\u5B9A\u3057\u3066\u304F\u3060\u3055\u3044"
|
|
500
|
+
}
|
|
501
|
+
};
|
|
502
|
+
}
|
|
503
|
+
const body = {
|
|
504
|
+
grant_type: "authorization_code",
|
|
505
|
+
code,
|
|
506
|
+
client_id: clientId,
|
|
507
|
+
redirect_uri: effectiveRedirectUri
|
|
508
|
+
};
|
|
509
|
+
if (codeVerifier) {
|
|
510
|
+
body.code_verifier = codeVerifier;
|
|
511
|
+
}
|
|
540
512
|
let response;
|
|
541
513
|
try {
|
|
542
|
-
response = await fetch(url,
|
|
514
|
+
response = await fetch(url, {
|
|
515
|
+
method: "POST",
|
|
516
|
+
credentials: "omit",
|
|
517
|
+
headers: { "Content-Type": "application/x-www-form-urlencoded", ...sdkHeaders() },
|
|
518
|
+
body: new URLSearchParams(body).toString()
|
|
519
|
+
});
|
|
543
520
|
} catch (error) {
|
|
544
|
-
logger.error("Network error:", error);
|
|
521
|
+
logger.error("Network error during token exchange:", error);
|
|
545
522
|
return {
|
|
546
523
|
error: {
|
|
547
|
-
code:
|
|
524
|
+
code: errorCodes.NETWORK_ERROR,
|
|
548
525
|
message: error instanceof Error ? error.message : "\u30CD\u30C3\u30C8\u30EF\u30FC\u30AF\u30A8\u30E9\u30FC\u304C\u767A\u751F\u3057\u307E\u3057\u305F"
|
|
549
526
|
}
|
|
550
527
|
};
|
|
551
528
|
}
|
|
552
|
-
|
|
553
|
-
const refreshResult = await refreshAccessToken();
|
|
554
|
-
if (!refreshResult.error) {
|
|
555
|
-
logger.debug("Token refreshed, retrying request");
|
|
556
|
-
const retryOptions = buildFetchOptions(options);
|
|
557
|
-
try {
|
|
558
|
-
response = await fetch(url, retryOptions);
|
|
559
|
-
} catch (retryError) {
|
|
560
|
-
logger.error("Network error on retry:", retryError);
|
|
561
|
-
return {
|
|
562
|
-
error: {
|
|
563
|
-
code: FFID_ERROR_CODES.NETWORK_ERROR,
|
|
564
|
-
message: retryError instanceof Error ? retryError.message : "\u30CD\u30C3\u30C8\u30EF\u30FC\u30AF\u30A8\u30E9\u30FC\u304C\u767A\u751F\u3057\u307E\u3057\u305F"
|
|
565
|
-
}
|
|
566
|
-
};
|
|
567
|
-
}
|
|
568
|
-
}
|
|
569
|
-
}
|
|
570
|
-
let raw;
|
|
529
|
+
let tokenResponse;
|
|
571
530
|
try {
|
|
572
|
-
|
|
531
|
+
tokenResponse = await response.json();
|
|
573
532
|
} catch (parseError) {
|
|
574
|
-
logger.error("Parse error
|
|
533
|
+
logger.error("Parse error during token exchange:", parseError);
|
|
575
534
|
return {
|
|
576
535
|
error: {
|
|
577
|
-
code:
|
|
578
|
-
message: `\
|
|
536
|
+
code: errorCodes.PARSE_ERROR,
|
|
537
|
+
message: `\u30C8\u30FC\u30AF\u30F3\u30EC\u30B9\u30DD\u30F3\u30B9\u306E\u89E3\u6790\u306B\u5931\u6557\u3057\u307E\u3057\u305F (status: ${response.status})`
|
|
579
538
|
}
|
|
580
539
|
};
|
|
581
540
|
}
|
|
582
|
-
logger.debug("Response:", response.status, raw);
|
|
583
|
-
checkVersionHeader(response, logger);
|
|
584
541
|
if (!response.ok) {
|
|
542
|
+
const errorBody = tokenResponse;
|
|
585
543
|
return {
|
|
586
|
-
error:
|
|
587
|
-
code:
|
|
588
|
-
message: "\
|
|
544
|
+
error: {
|
|
545
|
+
code: errorBody.error ?? errorCodes.TOKEN_EXCHANGE_ERROR,
|
|
546
|
+
message: errorBody.error_description ?? "\u30C8\u30FC\u30AF\u30F3\u4EA4\u63DB\u306B\u5931\u6557\u3057\u307E\u3057\u305F"
|
|
589
547
|
}
|
|
590
548
|
};
|
|
591
549
|
}
|
|
592
|
-
|
|
550
|
+
tokenStore.setTokens({
|
|
551
|
+
accessToken: tokenResponse.access_token,
|
|
552
|
+
refreshToken: tokenResponse.refresh_token,
|
|
553
|
+
expiresAt: Date.now() + tokenResponse.expires_in * MS_PER_SECOND
|
|
554
|
+
});
|
|
555
|
+
logger.debug("Token exchange successful");
|
|
556
|
+
return { data: void 0 };
|
|
557
|
+
}
|
|
558
|
+
async function refreshAccessToken() {
|
|
559
|
+
const tokens = tokenStore.getTokens();
|
|
560
|
+
if (!tokens) {
|
|
593
561
|
return {
|
|
594
562
|
error: {
|
|
595
|
-
code:
|
|
596
|
-
message: "\
|
|
563
|
+
code: errorCodes.NO_TOKENS,
|
|
564
|
+
message: "\u30EA\u30D5\u30EC\u30C3\u30B7\u30E5\u30C8\u30FC\u30AF\u30F3\u304C\u3042\u308A\u307E\u305B\u3093"
|
|
597
565
|
}
|
|
598
566
|
};
|
|
599
567
|
}
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
};
|
|
607
|
-
}
|
|
608
|
-
function buildFetchOptions(options) {
|
|
609
|
-
if (authMode === "service-key") {
|
|
610
|
-
return {
|
|
611
|
-
...options,
|
|
568
|
+
const url = `${baseUrl}${OAUTH_TOKEN_ENDPOINT}`;
|
|
569
|
+
logger.debug("Refreshing access token:", url);
|
|
570
|
+
let response;
|
|
571
|
+
try {
|
|
572
|
+
response = await fetch(url, {
|
|
573
|
+
method: "POST",
|
|
612
574
|
credentials: "omit",
|
|
613
|
-
headers: {
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
575
|
+
headers: { "Content-Type": "application/x-www-form-urlencoded", ...sdkHeaders() },
|
|
576
|
+
body: new URLSearchParams({
|
|
577
|
+
grant_type: "refresh_token",
|
|
578
|
+
refresh_token: tokens.refreshToken,
|
|
579
|
+
client_id: clientId
|
|
580
|
+
}).toString()
|
|
581
|
+
});
|
|
582
|
+
} catch (error) {
|
|
583
|
+
logger.error("Network error during token refresh:", error);
|
|
584
|
+
return {
|
|
585
|
+
error: {
|
|
586
|
+
code: errorCodes.NETWORK_ERROR,
|
|
587
|
+
message: error instanceof Error ? error.message : "\u30CD\u30C3\u30C8\u30EF\u30FC\u30AF\u30A8\u30E9\u30FC\u304C\u767A\u751F\u3057\u307E\u3057\u305F"
|
|
618
588
|
}
|
|
619
589
|
};
|
|
620
590
|
}
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
...options.headers
|
|
627
|
-
};
|
|
628
|
-
if (tokens) {
|
|
629
|
-
headers["Authorization"] = `Bearer ${tokens.accessToken}`;
|
|
630
|
-
}
|
|
591
|
+
let tokenResponse;
|
|
592
|
+
try {
|
|
593
|
+
tokenResponse = await response.json();
|
|
594
|
+
} catch (parseError) {
|
|
595
|
+
logger.error("Parse error during token refresh:", parseError);
|
|
631
596
|
return {
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
597
|
+
error: {
|
|
598
|
+
code: errorCodes.PARSE_ERROR,
|
|
599
|
+
message: `\u30C8\u30FC\u30AF\u30F3\u30EC\u30B9\u30DD\u30F3\u30B9\u306E\u89E3\u6790\u306B\u5931\u6557\u3057\u307E\u3057\u305F (status: ${response.status})`
|
|
600
|
+
}
|
|
635
601
|
};
|
|
636
602
|
}
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
603
|
+
if (!response.ok) {
|
|
604
|
+
const errorBody = tokenResponse;
|
|
605
|
+
logger.error("Token refresh failed:", errorBody);
|
|
606
|
+
const irrecoverableErrors = ["token_revoked", "invalid_grant"];
|
|
607
|
+
if (errorBody.error && irrecoverableErrors.includes(errorBody.error)) {
|
|
608
|
+
tokenStore.clearTokens();
|
|
609
|
+
logger.debug("Cleared tokens due to irrecoverable refresh error:", errorBody.error);
|
|
644
610
|
}
|
|
645
|
-
|
|
611
|
+
return {
|
|
612
|
+
error: {
|
|
613
|
+
code: errorBody.error ?? errorCodes.TOKEN_REFRESH_ERROR,
|
|
614
|
+
message: errorBody.error_description ?? "\u30C8\u30FC\u30AF\u30F3\u30EA\u30D5\u30EC\u30C3\u30B7\u30E5\u306B\u5931\u6557\u3057\u307E\u3057\u305F"
|
|
615
|
+
}
|
|
616
|
+
};
|
|
617
|
+
}
|
|
618
|
+
tokenStore.setTokens({
|
|
619
|
+
accessToken: tokenResponse.access_token,
|
|
620
|
+
refreshToken: tokenResponse.refresh_token,
|
|
621
|
+
expiresAt: Date.now() + tokenResponse.expires_in * MS_PER_SECOND
|
|
622
|
+
});
|
|
623
|
+
logger.debug("Token refresh successful");
|
|
624
|
+
return { data: void 0 };
|
|
625
|
+
}
|
|
626
|
+
async function signOutToken() {
|
|
627
|
+
const tokens = tokenStore.getTokens();
|
|
628
|
+
tokenStore.clearTokens();
|
|
629
|
+
if (!tokens) {
|
|
630
|
+
logger.debug("No tokens to revoke");
|
|
631
|
+
return { data: void 0 };
|
|
632
|
+
}
|
|
633
|
+
const url = `${baseUrl}${OAUTH_REVOKE_ENDPOINT}`;
|
|
634
|
+
logger.debug("Revoking token:", url);
|
|
635
|
+
try {
|
|
636
|
+
const response = await fetch(url, {
|
|
637
|
+
method: "POST",
|
|
638
|
+
credentials: "omit",
|
|
639
|
+
headers: { "Content-Type": "application/x-www-form-urlencoded", ...sdkHeaders() },
|
|
640
|
+
body: new URLSearchParams({
|
|
641
|
+
token: tokens.accessToken,
|
|
642
|
+
client_id: clientId
|
|
643
|
+
}).toString()
|
|
644
|
+
});
|
|
645
|
+
if (!response.ok) {
|
|
646
|
+
logger.warn(
|
|
647
|
+
"\u30C8\u30FC\u30AF\u30F3\u7121\u52B9\u5316\u30EA\u30AF\u30A8\u30B9\u30C8\u304C\u30B5\u30FC\u30D0\u30FC\u3067\u5931\u6557\u3057\u307E\u3057\u305F:",
|
|
648
|
+
`status=${response.status}`
|
|
649
|
+
);
|
|
650
|
+
}
|
|
651
|
+
} catch (error) {
|
|
652
|
+
logger.warn("\u30C8\u30FC\u30AF\u30F3\u7121\u52B9\u5316\u306E\u30CD\u30C3\u30C8\u30EF\u30FC\u30AF\u30EA\u30AF\u30A8\u30B9\u30C8\u306B\u5931\u6557\u3057\u307E\u3057\u305F:", error);
|
|
653
|
+
}
|
|
654
|
+
logger.debug("Token sign-out completed");
|
|
655
|
+
return { data: void 0 };
|
|
646
656
|
}
|
|
657
|
+
return { exchangeCodeForTokens, refreshAccessToken, signOutToken };
|
|
658
|
+
}
|
|
659
|
+
|
|
660
|
+
// src/client/session.ts
|
|
661
|
+
var SESSION_ENDPOINT = "/api/v1/auth/session";
|
|
662
|
+
var LOGOUT_ENDPOINT = "/api/v1/auth/signout";
|
|
663
|
+
var OAUTH_USERINFO_ENDPOINT = "/api/v1/oauth/userinfo";
|
|
664
|
+
var NO_CONTENT_STATUS = 204;
|
|
665
|
+
var UNAUTHORIZED_STATUS = 401;
|
|
666
|
+
function normalizeFFIDUser(user) {
|
|
667
|
+
return {
|
|
668
|
+
...user,
|
|
669
|
+
locale: user.locale ?? null,
|
|
670
|
+
timezone: user.timezone ?? null
|
|
671
|
+
};
|
|
672
|
+
}
|
|
673
|
+
function createSessionMethods(deps) {
|
|
674
|
+
const {
|
|
675
|
+
authMode,
|
|
676
|
+
baseUrl,
|
|
677
|
+
serviceCode,
|
|
678
|
+
tokenStore,
|
|
679
|
+
logger,
|
|
680
|
+
fetchWithAuth,
|
|
681
|
+
refreshAccessToken,
|
|
682
|
+
errorCodes
|
|
683
|
+
} = deps;
|
|
647
684
|
async function getSession() {
|
|
648
685
|
if (authMode === "token") {
|
|
649
686
|
return getSessionFromUserinfo();
|
|
@@ -660,19 +697,12 @@ function createFFIDClient(config) {
|
|
|
660
697
|
}
|
|
661
698
|
return result;
|
|
662
699
|
}
|
|
663
|
-
function normalizeFFIDUser(user) {
|
|
664
|
-
return {
|
|
665
|
-
...user,
|
|
666
|
-
locale: user.locale ?? null,
|
|
667
|
-
timezone: user.timezone ?? null
|
|
668
|
-
};
|
|
669
|
-
}
|
|
670
700
|
async function getSessionFromUserinfo() {
|
|
671
701
|
const tokens = tokenStore.getTokens();
|
|
672
702
|
if (!tokens) {
|
|
673
703
|
return {
|
|
674
704
|
error: {
|
|
675
|
-
code:
|
|
705
|
+
code: errorCodes.NO_TOKENS,
|
|
676
706
|
message: "\u30C8\u30FC\u30AF\u30F3\u304C\u4FDD\u5B58\u3055\u308C\u3066\u3044\u307E\u305B\u3093"
|
|
677
707
|
}
|
|
678
708
|
};
|
|
@@ -693,7 +723,7 @@ function createFFIDClient(config) {
|
|
|
693
723
|
logger.error("Network error:", error);
|
|
694
724
|
return {
|
|
695
725
|
error: {
|
|
696
|
-
code:
|
|
726
|
+
code: errorCodes.NETWORK_ERROR,
|
|
697
727
|
message: error instanceof Error ? error.message : "\u30CD\u30C3\u30C8\u30EF\u30FC\u30AF\u30A8\u30E9\u30FC\u304C\u767A\u751F\u3057\u307E\u3057\u305F"
|
|
698
728
|
}
|
|
699
729
|
};
|
|
@@ -716,7 +746,7 @@ function createFFIDClient(config) {
|
|
|
716
746
|
logger.error("Network error on retry:", retryError);
|
|
717
747
|
return {
|
|
718
748
|
error: {
|
|
719
|
-
code:
|
|
749
|
+
code: errorCodes.NETWORK_ERROR,
|
|
720
750
|
message: retryError instanceof Error ? retryError.message : "\u30CD\u30C3\u30C8\u30EF\u30FC\u30AF\u30A8\u30E9\u30FC\u304C\u767A\u751F\u3057\u307E\u3057\u305F"
|
|
721
751
|
}
|
|
722
752
|
};
|
|
@@ -733,7 +763,7 @@ function createFFIDClient(config) {
|
|
|
733
763
|
logger.error("Parse error:", parseError, "Status:", response.status);
|
|
734
764
|
return {
|
|
735
765
|
error: {
|
|
736
|
-
code:
|
|
766
|
+
code: errorCodes.PARSE_ERROR,
|
|
737
767
|
message: `\u30B5\u30FC\u30D0\u30FC\u304B\u3089\u4E0D\u6B63\u306A\u30EC\u30B9\u30DD\u30F3\u30B9\u3092\u53D7\u4FE1\u3057\u307E\u3057\u305F (status: ${response.status})`
|
|
738
768
|
}
|
|
739
769
|
};
|
|
@@ -742,7 +772,7 @@ function createFFIDClient(config) {
|
|
|
742
772
|
const errorBody = rawUserinfo;
|
|
743
773
|
return {
|
|
744
774
|
error: {
|
|
745
|
-
code: errorBody.code ??
|
|
775
|
+
code: errorBody.code ?? errorCodes.UNKNOWN_ERROR,
|
|
746
776
|
message: errorBody.message ?? "\u4E0D\u660E\u306A\u30A8\u30E9\u30FC\u304C\u767A\u751F\u3057\u307E\u3057\u305F"
|
|
747
777
|
}
|
|
748
778
|
};
|
|
@@ -761,16 +791,10 @@ function createFFIDClient(config) {
|
|
|
761
791
|
data: {
|
|
762
792
|
user,
|
|
763
793
|
organizations: [],
|
|
764
|
-
subscriptions: mapUserinfoSubscriptionToSession(userinfo,
|
|
794
|
+
subscriptions: mapUserinfoSubscriptionToSession(userinfo, serviceCode)
|
|
765
795
|
}
|
|
766
796
|
};
|
|
767
797
|
}
|
|
768
|
-
async function signOut() {
|
|
769
|
-
if (authMode === "token") {
|
|
770
|
-
return signOutToken();
|
|
771
|
-
}
|
|
772
|
-
return signOutCookie();
|
|
773
|
-
}
|
|
774
798
|
async function signOutCookie() {
|
|
775
799
|
const url = `${baseUrl}${LOGOUT_ENDPOINT}`;
|
|
776
800
|
logger.debug("Fetching:", url);
|
|
@@ -785,7 +809,7 @@ function createFFIDClient(config) {
|
|
|
785
809
|
logger.error("Network error:", error);
|
|
786
810
|
return {
|
|
787
811
|
error: {
|
|
788
|
-
code:
|
|
812
|
+
code: errorCodes.NETWORK_ERROR,
|
|
789
813
|
message: error instanceof Error ? error.message : "\u30CD\u30C3\u30C8\u30EF\u30FC\u30AF\u30A8\u30E9\u30FC\u304C\u767A\u751F\u3057\u307E\u3057\u305F"
|
|
790
814
|
}
|
|
791
815
|
};
|
|
@@ -799,14 +823,20 @@ function createFFIDClient(config) {
|
|
|
799
823
|
const raw = await response.json();
|
|
800
824
|
return {
|
|
801
825
|
error: raw.error ?? {
|
|
802
|
-
code:
|
|
826
|
+
code: errorCodes.UNKNOWN_ERROR,
|
|
803
827
|
message: "\u4E0D\u660E\u306A\u30A8\u30E9\u30FC\u304C\u767A\u751F\u3057\u307E\u3057\u305F"
|
|
804
828
|
}
|
|
805
829
|
};
|
|
806
|
-
} catch {
|
|
830
|
+
} catch (parseError) {
|
|
831
|
+
logger.error(
|
|
832
|
+
"\u30B5\u30A4\u30F3\u30A2\u30A6\u30C8\u30EC\u30B9\u30DD\u30F3\u30B9\u306E\u89E3\u6790\u306B\u5931\u6557\u3057\u307E\u3057\u305F:",
|
|
833
|
+
parseError,
|
|
834
|
+
"Status:",
|
|
835
|
+
response.status
|
|
836
|
+
);
|
|
807
837
|
return {
|
|
808
838
|
error: {
|
|
809
|
-
code:
|
|
839
|
+
code: errorCodes.PARSE_ERROR,
|
|
810
840
|
message: `\u30B5\u30FC\u30D0\u30FC\u304B\u3089\u4E0D\u6B63\u306A\u30EC\u30B9\u30DD\u30F3\u30B9\u3092\u53D7\u4FE1\u3057\u307E\u3057\u305F (status: ${response.status})`
|
|
811
841
|
}
|
|
812
842
|
};
|
|
@@ -815,125 +845,221 @@ function createFFIDClient(config) {
|
|
|
815
845
|
logger.debug("Response:", response.status);
|
|
816
846
|
return { data: void 0 };
|
|
817
847
|
}
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
848
|
+
return { getSession, signOutCookie };
|
|
849
|
+
}
|
|
850
|
+
|
|
851
|
+
// src/auth/pkce.ts
|
|
852
|
+
var VERIFIER_STORAGE_KEY = "ffid_code_verifier";
|
|
853
|
+
var CODE_VERIFIER_MIN_LENGTH = 43;
|
|
854
|
+
var UNRESERVED_CHARS = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-._~";
|
|
855
|
+
function generateCodeVerifier() {
|
|
856
|
+
const length = CODE_VERIFIER_MIN_LENGTH;
|
|
857
|
+
const randomValues = new Uint8Array(length);
|
|
858
|
+
crypto.getRandomValues(randomValues);
|
|
859
|
+
let verifier = "";
|
|
860
|
+
for (let i = 0; i < length; i++) {
|
|
861
|
+
verifier += UNRESERVED_CHARS[randomValues[i] % UNRESERVED_CHARS.length];
|
|
862
|
+
}
|
|
863
|
+
return verifier;
|
|
864
|
+
}
|
|
865
|
+
async function generateCodeChallenge(verifier) {
|
|
866
|
+
const encoder = new TextEncoder();
|
|
867
|
+
const data = encoder.encode(verifier);
|
|
868
|
+
const digest = await crypto.subtle.digest("SHA-256", data);
|
|
869
|
+
return base64UrlEncode(digest);
|
|
870
|
+
}
|
|
871
|
+
function storeCodeVerifier(verifier) {
|
|
872
|
+
try {
|
|
873
|
+
if (typeof window === "undefined") return;
|
|
874
|
+
window.sessionStorage.setItem(VERIFIER_STORAGE_KEY, verifier);
|
|
875
|
+
} catch {
|
|
876
|
+
}
|
|
877
|
+
}
|
|
878
|
+
function base64UrlEncode(buffer) {
|
|
879
|
+
const bytes = new Uint8Array(buffer);
|
|
880
|
+
let binary = "";
|
|
881
|
+
for (let i = 0; i < bytes.length; i++) {
|
|
882
|
+
binary += String.fromCharCode(bytes[i]);
|
|
883
|
+
}
|
|
884
|
+
return btoa(binary).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
|
|
885
|
+
}
|
|
886
|
+
|
|
887
|
+
// src/client/redirect.ts
|
|
888
|
+
var OAUTH_AUTHORIZE_ENDPOINT = "/api/v1/oauth/authorize";
|
|
889
|
+
var STATE_RANDOM_BYTES = 16;
|
|
890
|
+
var HEX_BASE2 = 16;
|
|
891
|
+
function generateRandomState() {
|
|
892
|
+
const array = new Uint8Array(STATE_RANDOM_BYTES);
|
|
893
|
+
crypto.getRandomValues(array);
|
|
894
|
+
return Array.from(array, (byte) => byte.toString(HEX_BASE2).padStart(2, "0")).join("");
|
|
895
|
+
}
|
|
896
|
+
function createRedirectMethods(deps) {
|
|
897
|
+
const {
|
|
898
|
+
authMode,
|
|
899
|
+
baseUrl,
|
|
900
|
+
clientId,
|
|
901
|
+
serviceCode,
|
|
902
|
+
resolvedRedirectUri,
|
|
903
|
+
logger
|
|
904
|
+
} = deps;
|
|
905
|
+
async function redirectToAuthorize() {
|
|
906
|
+
const verifier = generateCodeVerifier();
|
|
907
|
+
storeCodeVerifier(verifier);
|
|
827
908
|
try {
|
|
828
|
-
await
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
909
|
+
const challenge = await generateCodeChallenge(verifier);
|
|
910
|
+
const state = generateRandomState();
|
|
911
|
+
const redirectUri = resolvedRedirectUri ?? window.location.origin + window.location.pathname;
|
|
912
|
+
const params = new URLSearchParams({
|
|
913
|
+
response_type: "code",
|
|
914
|
+
client_id: clientId,
|
|
915
|
+
redirect_uri: redirectUri,
|
|
916
|
+
state,
|
|
917
|
+
code_challenge: challenge,
|
|
918
|
+
code_challenge_method: "S256"
|
|
836
919
|
});
|
|
920
|
+
const authorizeUrl = `${baseUrl}${OAUTH_AUTHORIZE_ENDPOINT}?${params.toString()}`;
|
|
921
|
+
logger.debug("Redirecting to authorize:", authorizeUrl);
|
|
922
|
+
window.location.href = authorizeUrl;
|
|
923
|
+
return true;
|
|
837
924
|
} catch (error) {
|
|
838
|
-
logger.
|
|
925
|
+
logger.error("PKCE \u30B3\u30FC\u30C9\u30C1\u30E3\u30EC\u30F3\u30B8\u306E\u751F\u6210\u306B\u5931\u6557\u3057\u307E\u3057\u305F:", error);
|
|
926
|
+
return false;
|
|
839
927
|
}
|
|
840
|
-
logger.debug("Token sign-out completed");
|
|
841
|
-
return { data: void 0 };
|
|
842
928
|
}
|
|
843
|
-
async function
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
if (!effectiveRedirectUri) {
|
|
848
|
-
logger.error("redirectUri is required for token exchange in SSR environments. Set config.redirectUri explicitly.");
|
|
849
|
-
return {
|
|
850
|
-
error: {
|
|
851
|
-
code: FFID_ERROR_CODES.TOKEN_EXCHANGE_ERROR,
|
|
852
|
-
message: "redirectUri \u304C\u672A\u8A2D\u5B9A\u3067\u3059\u3002SSR\u74B0\u5883\u3067\u306F config.redirectUri \u3092\u660E\u793A\u7684\u306B\u6307\u5B9A\u3057\u3066\u304F\u3060\u3055\u3044"
|
|
853
|
-
}
|
|
854
|
-
};
|
|
929
|
+
async function redirectToLogin() {
|
|
930
|
+
if (typeof window === "undefined") {
|
|
931
|
+
logger.debug("Cannot redirect in SSR context");
|
|
932
|
+
return false;
|
|
855
933
|
}
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
code,
|
|
859
|
-
client_id: clientId,
|
|
860
|
-
redirect_uri: effectiveRedirectUri
|
|
861
|
-
};
|
|
862
|
-
if (codeVerifier) {
|
|
863
|
-
body.code_verifier = codeVerifier;
|
|
934
|
+
if (authMode === "token") {
|
|
935
|
+
return redirectToAuthorize();
|
|
864
936
|
}
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
}
|
|
874
|
-
|
|
937
|
+
const currentUrl = window.location.href;
|
|
938
|
+
const loginUrl = `${baseUrl}/login?redirect=${encodeURIComponent(currentUrl)}&service=${encodeURIComponent(serviceCode)}`;
|
|
939
|
+
logger.debug("Redirecting to login:", loginUrl);
|
|
940
|
+
window.location.href = loginUrl;
|
|
941
|
+
return true;
|
|
942
|
+
}
|
|
943
|
+
function getLoginUrl(redirectUrl) {
|
|
944
|
+
const redirect = redirectUrl ?? (typeof window !== "undefined" ? window.location.href : "");
|
|
945
|
+
return `${baseUrl}/login?redirect=${encodeURIComponent(redirect)}&service=${encodeURIComponent(serviceCode)}`;
|
|
946
|
+
}
|
|
947
|
+
function getSignupUrl(redirectUrl) {
|
|
948
|
+
const redirect = redirectUrl ?? (typeof window !== "undefined" ? window.location.href : "");
|
|
949
|
+
return `${baseUrl}/signup?redirect=${encodeURIComponent(redirect)}&service=${encodeURIComponent(serviceCode)}`;
|
|
950
|
+
}
|
|
951
|
+
return { redirectToLogin, redirectToAuthorize, getLoginUrl, getSignupUrl };
|
|
952
|
+
}
|
|
953
|
+
|
|
954
|
+
// src/client/ffid-client.ts
|
|
955
|
+
var UNAUTHORIZED_STATUS2 = 401;
|
|
956
|
+
var SDK_LOG_PREFIX = "[FFID SDK]";
|
|
957
|
+
var noopLogger = {
|
|
958
|
+
debug: () => {
|
|
959
|
+
},
|
|
960
|
+
info: () => {
|
|
961
|
+
},
|
|
962
|
+
warn: (...args) => console.warn(SDK_LOG_PREFIX, ...args),
|
|
963
|
+
error: (...args) => console.error(SDK_LOG_PREFIX, ...args)
|
|
964
|
+
};
|
|
965
|
+
var consoleLogger = {
|
|
966
|
+
debug: (...args) => console.debug(SDK_LOG_PREFIX, ...args),
|
|
967
|
+
info: (...args) => console.info(SDK_LOG_PREFIX, ...args),
|
|
968
|
+
warn: (...args) => console.warn(SDK_LOG_PREFIX, ...args),
|
|
969
|
+
error: (...args) => console.error(SDK_LOG_PREFIX, ...args)
|
|
970
|
+
};
|
|
971
|
+
var FFID_ERROR_CODES = {
|
|
972
|
+
NETWORK_ERROR: "NETWORK_ERROR",
|
|
973
|
+
PARSE_ERROR: "PARSE_ERROR",
|
|
974
|
+
UNKNOWN_ERROR: "UNKNOWN_ERROR",
|
|
975
|
+
TOKEN_EXCHANGE_ERROR: "TOKEN_EXCHANGE_ERROR",
|
|
976
|
+
TOKEN_REFRESH_ERROR: "TOKEN_REFRESH_ERROR",
|
|
977
|
+
NO_TOKENS: "NO_TOKENS",
|
|
978
|
+
TOKEN_VERIFICATION_ERROR: "TOKEN_VERIFICATION_ERROR"
|
|
979
|
+
};
|
|
980
|
+
var EXT_CHECK_ENDPOINT = "/api/v1/subscriptions/ext/check";
|
|
981
|
+
function createFFIDClient(config) {
|
|
982
|
+
if (!config.serviceCode || !config.serviceCode.trim()) {
|
|
983
|
+
throw new Error("FFID Client: serviceCode \u304C\u672A\u8A2D\u5B9A\u3067\u3059");
|
|
984
|
+
}
|
|
985
|
+
const baseUrl = config.apiBaseUrl ?? DEFAULT_API_BASE_URL;
|
|
986
|
+
const authMode = config.authMode ?? "cookie";
|
|
987
|
+
const clientId = config.clientId ?? config.serviceCode;
|
|
988
|
+
const resolvedRedirectUri = config.redirectUri ?? null;
|
|
989
|
+
const serviceApiKey = config.serviceApiKey?.trim();
|
|
990
|
+
const verifyStrategy = config.verifyStrategy ?? "jwt";
|
|
991
|
+
const cache = config.cache;
|
|
992
|
+
const timeout = config.timeout;
|
|
993
|
+
if (authMode === "service-key" && !serviceApiKey) {
|
|
994
|
+
throw new Error("FFID Client: service-key \u30E2\u30FC\u30C9\u3067\u306F serviceApiKey \u304C\u5FC5\u9808\u3067\u3059");
|
|
995
|
+
}
|
|
996
|
+
if (cache && cache.ttl <= 0) {
|
|
997
|
+
throw new Error("FFID Client: cache.ttl \u306F\u6B63\u306E\u6570\u5024\u3092\u6307\u5B9A\u3057\u3066\u304F\u3060\u3055\u3044");
|
|
998
|
+
}
|
|
999
|
+
if (timeout !== void 0 && timeout <= 0) {
|
|
1000
|
+
throw new Error("FFID Client: timeout \u306F\u6B63\u306E\u6570\u5024\u3092\u6307\u5B9A\u3057\u3066\u304F\u3060\u3055\u3044");
|
|
1001
|
+
}
|
|
1002
|
+
const logger = config.logger ?? (config.debug ? consoleLogger : noopLogger);
|
|
1003
|
+
const tokenStore = authMode === "token" ? createTokenStore() : createTokenStore("memory");
|
|
1004
|
+
function createError(code, message) {
|
|
1005
|
+
return { code, message };
|
|
1006
|
+
}
|
|
1007
|
+
function buildFetchOptions(options) {
|
|
1008
|
+
if (authMode === "service-key") {
|
|
875
1009
|
return {
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
1010
|
+
...options,
|
|
1011
|
+
credentials: "omit",
|
|
1012
|
+
headers: {
|
|
1013
|
+
"Content-Type": "application/json",
|
|
1014
|
+
...sdkHeaders(),
|
|
1015
|
+
"X-Service-Api-Key": serviceApiKey,
|
|
1016
|
+
...options.headers
|
|
879
1017
|
}
|
|
880
1018
|
};
|
|
881
1019
|
}
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
error: {
|
|
889
|
-
code: FFID_ERROR_CODES.PARSE_ERROR,
|
|
890
|
-
message: `\u30C8\u30FC\u30AF\u30F3\u30EC\u30B9\u30DD\u30F3\u30B9\u306E\u89E3\u6790\u306B\u5931\u6557\u3057\u307E\u3057\u305F (status: ${response.status})`
|
|
891
|
-
}
|
|
1020
|
+
if (authMode === "token") {
|
|
1021
|
+
const tokens = tokenStore.getTokens();
|
|
1022
|
+
const headers = {
|
|
1023
|
+
"Content-Type": "application/json",
|
|
1024
|
+
...sdkHeaders(),
|
|
1025
|
+
...options.headers
|
|
892
1026
|
};
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
1027
|
+
if (tokens) {
|
|
1028
|
+
headers["Authorization"] = `Bearer ${tokens.accessToken}`;
|
|
1029
|
+
}
|
|
896
1030
|
return {
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
}
|
|
1031
|
+
...options,
|
|
1032
|
+
credentials: "omit",
|
|
1033
|
+
headers
|
|
901
1034
|
};
|
|
902
1035
|
}
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
1036
|
+
return {
|
|
1037
|
+
...options,
|
|
1038
|
+
credentials: "include",
|
|
1039
|
+
headers: {
|
|
1040
|
+
"Content-Type": "application/json",
|
|
1041
|
+
...sdkHeaders(),
|
|
1042
|
+
...options.headers
|
|
1043
|
+
}
|
|
1044
|
+
};
|
|
910
1045
|
}
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
}
|
|
921
|
-
|
|
922
|
-
|
|
1046
|
+
const { exchangeCodeForTokens, refreshAccessToken, signOutToken } = createOAuthTokenMethods({
|
|
1047
|
+
baseUrl,
|
|
1048
|
+
clientId,
|
|
1049
|
+
resolvedRedirectUri,
|
|
1050
|
+
tokenStore,
|
|
1051
|
+
logger,
|
|
1052
|
+
errorCodes: FFID_ERROR_CODES
|
|
1053
|
+
});
|
|
1054
|
+
async function fetchWithAuth(endpoint, options = {}) {
|
|
1055
|
+
const url = `${baseUrl}${endpoint}`;
|
|
1056
|
+
logger.debug("Fetching:", url);
|
|
1057
|
+
const fetchOptions = buildFetchOptions(options);
|
|
923
1058
|
let response;
|
|
924
1059
|
try {
|
|
925
|
-
response = await fetch(url,
|
|
926
|
-
method: "POST",
|
|
927
|
-
credentials: "omit",
|
|
928
|
-
headers: { "Content-Type": "application/x-www-form-urlencoded", ...sdkHeaders() },
|
|
929
|
-
body: new URLSearchParams({
|
|
930
|
-
grant_type: "refresh_token",
|
|
931
|
-
refresh_token: tokens.refreshToken,
|
|
932
|
-
client_id: clientId
|
|
933
|
-
}).toString()
|
|
934
|
-
});
|
|
1060
|
+
response = await fetch(url, fetchOptions);
|
|
935
1061
|
} catch (error) {
|
|
936
|
-
logger.error("Network error
|
|
1062
|
+
logger.error("Network error:", error);
|
|
937
1063
|
return {
|
|
938
1064
|
error: {
|
|
939
1065
|
code: FFID_ERROR_CODES.NETWORK_ERROR,
|
|
@@ -941,88 +1067,80 @@ function createFFIDClient(config) {
|
|
|
941
1067
|
}
|
|
942
1068
|
};
|
|
943
1069
|
}
|
|
944
|
-
|
|
1070
|
+
if (authMode === "token" && response.status === UNAUTHORIZED_STATUS2) {
|
|
1071
|
+
const refreshResult = await refreshAccessToken();
|
|
1072
|
+
if (!refreshResult.error) {
|
|
1073
|
+
logger.debug("Token refreshed, retrying request");
|
|
1074
|
+
const retryOptions = buildFetchOptions(options);
|
|
1075
|
+
try {
|
|
1076
|
+
response = await fetch(url, retryOptions);
|
|
1077
|
+
} catch (retryError) {
|
|
1078
|
+
logger.error("Network error on retry:", retryError);
|
|
1079
|
+
return {
|
|
1080
|
+
error: {
|
|
1081
|
+
code: FFID_ERROR_CODES.NETWORK_ERROR,
|
|
1082
|
+
message: retryError instanceof Error ? retryError.message : "\u30CD\u30C3\u30C8\u30EF\u30FC\u30AF\u30A8\u30E9\u30FC\u304C\u767A\u751F\u3057\u307E\u3057\u305F"
|
|
1083
|
+
}
|
|
1084
|
+
};
|
|
1085
|
+
}
|
|
1086
|
+
}
|
|
1087
|
+
}
|
|
1088
|
+
let raw;
|
|
945
1089
|
try {
|
|
946
|
-
|
|
1090
|
+
raw = await response.json();
|
|
947
1091
|
} catch (parseError) {
|
|
948
|
-
logger.error("Parse error
|
|
1092
|
+
logger.error("Parse error:", parseError, "Status:", response.status);
|
|
949
1093
|
return {
|
|
950
1094
|
error: {
|
|
951
1095
|
code: FFID_ERROR_CODES.PARSE_ERROR,
|
|
952
|
-
message: `\
|
|
1096
|
+
message: `\u30B5\u30FC\u30D0\u30FC\u304B\u3089\u4E0D\u6B63\u306A\u30EC\u30B9\u30DD\u30F3\u30B9\u3092\u53D7\u4FE1\u3057\u307E\u3057\u305F (status: ${response.status})`
|
|
953
1097
|
}
|
|
954
1098
|
};
|
|
955
1099
|
}
|
|
1100
|
+
logger.debug("Response:", response.status, raw);
|
|
1101
|
+
checkVersionHeader(response, logger);
|
|
956
1102
|
if (!response.ok) {
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
1103
|
+
return {
|
|
1104
|
+
error: raw.error ?? {
|
|
1105
|
+
code: FFID_ERROR_CODES.UNKNOWN_ERROR,
|
|
1106
|
+
message: "\u4E0D\u660E\u306A\u30A8\u30E9\u30FC\u304C\u767A\u751F\u3057\u307E\u3057\u305F"
|
|
1107
|
+
}
|
|
1108
|
+
};
|
|
1109
|
+
}
|
|
1110
|
+
if (raw.data === void 0) {
|
|
964
1111
|
return {
|
|
965
1112
|
error: {
|
|
966
|
-
code:
|
|
967
|
-
message:
|
|
1113
|
+
code: FFID_ERROR_CODES.UNKNOWN_ERROR,
|
|
1114
|
+
message: "\u30B5\u30FC\u30D0\u30FC\u304B\u3089\u30C7\u30FC\u30BF\u304C\u8FD4\u3055\u308C\u307E\u305B\u3093\u3067\u3057\u305F"
|
|
968
1115
|
}
|
|
969
1116
|
};
|
|
970
1117
|
}
|
|
971
|
-
|
|
972
|
-
accessToken: tokenResponse.access_token,
|
|
973
|
-
refreshToken: tokenResponse.refresh_token,
|
|
974
|
-
expiresAt: Date.now() + tokenResponse.expires_in * MS_PER_SECOND
|
|
975
|
-
});
|
|
976
|
-
logger.debug("Token refresh successful");
|
|
977
|
-
return { data: void 0 };
|
|
1118
|
+
return { data: raw.data };
|
|
978
1119
|
}
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
1120
|
+
const { getSession, signOutCookie } = createSessionMethods({
|
|
1121
|
+
authMode,
|
|
1122
|
+
baseUrl,
|
|
1123
|
+
serviceCode: config.serviceCode,
|
|
1124
|
+
tokenStore,
|
|
1125
|
+
logger,
|
|
1126
|
+
fetchWithAuth,
|
|
1127
|
+
refreshAccessToken,
|
|
1128
|
+
errorCodes: FFID_ERROR_CODES
|
|
1129
|
+
});
|
|
1130
|
+
async function signOut() {
|
|
984
1131
|
if (authMode === "token") {
|
|
985
|
-
return
|
|
1132
|
+
return signOutToken();
|
|
986
1133
|
}
|
|
987
|
-
|
|
988
|
-
const loginUrl = `${baseUrl}/login?redirect=${encodeURIComponent(currentUrl)}&service=${encodeURIComponent(config.serviceCode)}`;
|
|
989
|
-
logger.debug("Redirecting to login:", loginUrl);
|
|
990
|
-
window.location.href = loginUrl;
|
|
991
|
-
return true;
|
|
992
|
-
}
|
|
993
|
-
function redirectToAuthorize() {
|
|
994
|
-
const verifier = generateCodeVerifier();
|
|
995
|
-
storeCodeVerifier(verifier);
|
|
996
|
-
generateCodeChallenge(verifier).then((challenge) => {
|
|
997
|
-
const state = generateRandomState();
|
|
998
|
-
const redirectUri = resolvedRedirectUri ?? window.location.origin + window.location.pathname;
|
|
999
|
-
const params = new URLSearchParams({
|
|
1000
|
-
response_type: "code",
|
|
1001
|
-
client_id: clientId,
|
|
1002
|
-
redirect_uri: redirectUri,
|
|
1003
|
-
state,
|
|
1004
|
-
code_challenge: challenge,
|
|
1005
|
-
code_challenge_method: "S256"
|
|
1006
|
-
});
|
|
1007
|
-
const authorizeUrl = `${baseUrl}${OAUTH_AUTHORIZE_ENDPOINT}?${params.toString()}`;
|
|
1008
|
-
logger.debug("Redirecting to authorize:", authorizeUrl);
|
|
1009
|
-
window.location.href = authorizeUrl;
|
|
1010
|
-
}).catch((error) => {
|
|
1011
|
-
logger.error("Failed to generate code challenge:", error);
|
|
1012
|
-
});
|
|
1013
|
-
return true;
|
|
1014
|
-
}
|
|
1015
|
-
function getLoginUrl(redirectUrl) {
|
|
1016
|
-
const redirect = redirectUrl ?? (typeof window !== "undefined" ? window.location.href : "");
|
|
1017
|
-
return `${baseUrl}/login?redirect=${encodeURIComponent(redirect)}&service=${encodeURIComponent(config.serviceCode)}`;
|
|
1018
|
-
}
|
|
1019
|
-
function getSignupUrl(redirectUrl) {
|
|
1020
|
-
const redirect = redirectUrl ?? (typeof window !== "undefined" ? window.location.href : "");
|
|
1021
|
-
return `${baseUrl}/signup?redirect=${encodeURIComponent(redirect)}&service=${encodeURIComponent(config.serviceCode)}`;
|
|
1022
|
-
}
|
|
1023
|
-
function createError(code, message) {
|
|
1024
|
-
return { code, message };
|
|
1134
|
+
return signOutCookie();
|
|
1025
1135
|
}
|
|
1136
|
+
const { redirectToLogin, getLoginUrl, getSignupUrl } = createRedirectMethods({
|
|
1137
|
+
authMode,
|
|
1138
|
+
baseUrl,
|
|
1139
|
+
clientId,
|
|
1140
|
+
serviceCode: config.serviceCode,
|
|
1141
|
+
resolvedRedirectUri,
|
|
1142
|
+
logger
|
|
1143
|
+
});
|
|
1026
1144
|
async function checkSubscription(params) {
|
|
1027
1145
|
if (!params.userId || !params.organizationId) {
|
|
1028
1146
|
return {
|
|
@@ -1079,11 +1197,6 @@ function createFFIDClient(config) {
|
|
|
1079
1197
|
redirectUri: resolvedRedirectUri
|
|
1080
1198
|
};
|
|
1081
1199
|
}
|
|
1082
|
-
function generateRandomState() {
|
|
1083
|
-
const array = new Uint8Array(STATE_RANDOM_BYTES);
|
|
1084
|
-
crypto.getRandomValues(array);
|
|
1085
|
-
return Array.from(array, (byte) => byte.toString(HEX_BASE2).padStart(2, "0")).join("");
|
|
1086
|
-
}
|
|
1087
1200
|
|
|
1088
1201
|
// src/client/cache/memory-cache-adapter.ts
|
|
1089
1202
|
var MS_PER_SECOND2 = 1e3;
|