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