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