@feelflow/ffid-sdk 1.10.0 → 1.13.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-OAPA5VKR.cjs → chunk-7YV3SUEY.cjs} +810 -369
- package/dist/{chunk-PSSNMEJB.js → chunk-KFOIVZEY.js} +810 -369
- 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-GrAWBpmf.d.cts} +32 -5
- package/dist/{index-DsXjcF0i.d.ts → index-GrAWBpmf.d.ts} +32 -5
- package/dist/index.cjs +22 -22
- package/dist/index.d.cts +65 -9
- package/dist/index.d.ts +65 -9
- package/dist/index.js +2 -2
- package/dist/server/index.cjs +814 -376
- package/dist/server/index.d.cts +84 -9
- package/dist/server/index.d.ts +84 -9
- package/dist/server/index.js +814 -376
- 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.13.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,249 @@ 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
|
-
debug: () => {
|
|
508
|
-
},
|
|
509
|
-
info: () => {
|
|
510
|
-
},
|
|
511
|
-
warn: () => {
|
|
512
|
-
},
|
|
513
|
-
error: (...args) => console.error(SDK_LOG_PREFIX, ...args)
|
|
514
|
-
};
|
|
515
|
-
var consoleLogger = {
|
|
516
|
-
debug: (...args) => console.debug(SDK_LOG_PREFIX, ...args),
|
|
517
|
-
info: (...args) => console.info(SDK_LOG_PREFIX, ...args),
|
|
518
|
-
warn: (...args) => console.warn(SDK_LOG_PREFIX, ...args),
|
|
519
|
-
error: (...args) => console.error(SDK_LOG_PREFIX, ...args)
|
|
520
|
-
};
|
|
521
|
-
var FFID_ERROR_CODES = {
|
|
522
|
-
NETWORK_ERROR: "NETWORK_ERROR",
|
|
523
|
-
PARSE_ERROR: "PARSE_ERROR",
|
|
524
|
-
UNKNOWN_ERROR: "UNKNOWN_ERROR",
|
|
525
|
-
TOKEN_EXCHANGE_ERROR: "TOKEN_EXCHANGE_ERROR",
|
|
526
|
-
TOKEN_REFRESH_ERROR: "TOKEN_REFRESH_ERROR",
|
|
527
|
-
NO_TOKENS: "NO_TOKENS",
|
|
528
|
-
TOKEN_VERIFICATION_ERROR: "TOKEN_VERIFICATION_ERROR"
|
|
529
|
-
};
|
|
530
|
-
function createFFIDClient(config) {
|
|
531
|
-
if (!config.serviceCode || !config.serviceCode.trim()) {
|
|
532
|
-
throw new Error("FFID Client: serviceCode \u304C\u672A\u8A2D\u5B9A\u3067\u3059");
|
|
486
|
+
function validateTokenResponse(tokenResponse) {
|
|
487
|
+
const invalid = [];
|
|
488
|
+
if (!tokenResponse.access_token) {
|
|
489
|
+
invalid.push("access_token");
|
|
533
490
|
}
|
|
534
|
-
|
|
535
|
-
|
|
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");
|
|
491
|
+
if (!tokenResponse.refresh_token) {
|
|
492
|
+
invalid.push("refresh_token");
|
|
544
493
|
}
|
|
545
|
-
if (
|
|
546
|
-
|
|
494
|
+
if (typeof tokenResponse.expires_in !== "number" || tokenResponse.expires_in <= 0) {
|
|
495
|
+
invalid.push("expires_in");
|
|
547
496
|
}
|
|
548
|
-
if (
|
|
549
|
-
|
|
497
|
+
if (invalid.length > 0) {
|
|
498
|
+
return `\u30C8\u30FC\u30AF\u30F3\u30EC\u30B9\u30DD\u30F3\u30B9\u306B\u4E0D\u6B63\u306A\u30D5\u30A3\u30FC\u30EB\u30C9\u304C\u3042\u308A\u307E\u3059: ${invalid.join(", ")}`;
|
|
550
499
|
}
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
500
|
+
return null;
|
|
501
|
+
}
|
|
502
|
+
function createOAuthTokenMethods(deps) {
|
|
503
|
+
const {
|
|
504
|
+
baseUrl,
|
|
505
|
+
clientId,
|
|
506
|
+
resolvedRedirectUri,
|
|
507
|
+
tokenStore,
|
|
508
|
+
logger,
|
|
509
|
+
errorCodes
|
|
510
|
+
} = deps;
|
|
511
|
+
async function exchangeCodeForTokens(code, codeVerifier) {
|
|
512
|
+
const url = `${baseUrl}${OAUTH_TOKEN_ENDPOINT}`;
|
|
513
|
+
logger.debug("Exchanging code for tokens:", url);
|
|
514
|
+
const effectiveRedirectUri = resolvedRedirectUri ?? (typeof window !== "undefined" ? window.location.origin + window.location.pathname : null);
|
|
515
|
+
if (!effectiveRedirectUri) {
|
|
516
|
+
logger.error("redirectUri is required for token exchange in SSR environments. Set config.redirectUri explicitly.");
|
|
517
|
+
return {
|
|
518
|
+
error: {
|
|
519
|
+
code: errorCodes.TOKEN_EXCHANGE_ERROR,
|
|
520
|
+
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"
|
|
521
|
+
}
|
|
522
|
+
};
|
|
523
|
+
}
|
|
524
|
+
const body = {
|
|
525
|
+
grant_type: "authorization_code",
|
|
526
|
+
code,
|
|
527
|
+
client_id: clientId,
|
|
528
|
+
redirect_uri: effectiveRedirectUri
|
|
529
|
+
};
|
|
530
|
+
if (codeVerifier) {
|
|
531
|
+
body.code_verifier = codeVerifier;
|
|
532
|
+
}
|
|
557
533
|
let response;
|
|
558
534
|
try {
|
|
559
|
-
response = await fetch(url,
|
|
535
|
+
response = await fetch(url, {
|
|
536
|
+
method: "POST",
|
|
537
|
+
credentials: "omit",
|
|
538
|
+
headers: { "Content-Type": "application/x-www-form-urlencoded", ...sdkHeaders() },
|
|
539
|
+
body: new URLSearchParams(body).toString()
|
|
540
|
+
});
|
|
560
541
|
} catch (error) {
|
|
561
|
-
logger.error("Network error:", error);
|
|
542
|
+
logger.error("Network error during token exchange:", error);
|
|
562
543
|
return {
|
|
563
544
|
error: {
|
|
564
|
-
code:
|
|
545
|
+
code: errorCodes.NETWORK_ERROR,
|
|
565
546
|
message: error instanceof Error ? error.message : "\u30CD\u30C3\u30C8\u30EF\u30FC\u30AF\u30A8\u30E9\u30FC\u304C\u767A\u751F\u3057\u307E\u3057\u305F"
|
|
566
547
|
}
|
|
567
548
|
};
|
|
568
549
|
}
|
|
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;
|
|
550
|
+
let tokenResponse;
|
|
588
551
|
try {
|
|
589
|
-
|
|
552
|
+
tokenResponse = await response.json();
|
|
590
553
|
} catch (parseError) {
|
|
591
|
-
logger.error("Parse error
|
|
554
|
+
logger.error("Parse error during token exchange:", parseError);
|
|
592
555
|
return {
|
|
593
556
|
error: {
|
|
594
|
-
code:
|
|
595
|
-
message: `\
|
|
557
|
+
code: errorCodes.PARSE_ERROR,
|
|
558
|
+
message: `\u30C8\u30FC\u30AF\u30F3\u30EC\u30B9\u30DD\u30F3\u30B9\u306E\u89E3\u6790\u306B\u5931\u6557\u3057\u307E\u3057\u305F (status: ${response.status})`
|
|
596
559
|
}
|
|
597
560
|
};
|
|
598
561
|
}
|
|
599
|
-
logger.debug("Response:", response.status, raw);
|
|
600
|
-
checkVersionHeader(response, logger);
|
|
601
562
|
if (!response.ok) {
|
|
563
|
+
const errorBody = tokenResponse;
|
|
602
564
|
return {
|
|
603
|
-
error:
|
|
604
|
-
code:
|
|
605
|
-
message: "\
|
|
565
|
+
error: {
|
|
566
|
+
code: errorBody.error ?? errorCodes.TOKEN_EXCHANGE_ERROR,
|
|
567
|
+
message: errorBody.error_description ?? "\u30C8\u30FC\u30AF\u30F3\u4EA4\u63DB\u306B\u5931\u6557\u3057\u307E\u3057\u305F"
|
|
606
568
|
}
|
|
607
569
|
};
|
|
608
570
|
}
|
|
609
|
-
|
|
571
|
+
const validationError = validateTokenResponse(tokenResponse);
|
|
572
|
+
if (validationError) {
|
|
573
|
+
logger.error("Token exchange validation failed:", validationError);
|
|
610
574
|
return {
|
|
611
575
|
error: {
|
|
612
|
-
code:
|
|
613
|
-
message:
|
|
576
|
+
code: errorCodes.TOKEN_EXCHANGE_ERROR,
|
|
577
|
+
message: validationError
|
|
614
578
|
}
|
|
615
579
|
};
|
|
616
580
|
}
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
};
|
|
581
|
+
tokenStore.setTokens({
|
|
582
|
+
accessToken: tokenResponse.access_token,
|
|
583
|
+
refreshToken: tokenResponse.refresh_token,
|
|
584
|
+
expiresAt: Date.now() + tokenResponse.expires_in * MS_PER_SECOND
|
|
585
|
+
});
|
|
586
|
+
logger.debug("Token exchange successful");
|
|
587
|
+
return { data: void 0 };
|
|
624
588
|
}
|
|
625
|
-
function
|
|
626
|
-
|
|
589
|
+
async function refreshAccessToken() {
|
|
590
|
+
const tokens = tokenStore.getTokens();
|
|
591
|
+
if (!tokens) {
|
|
627
592
|
return {
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
"Content-Type": "application/json",
|
|
632
|
-
...sdkHeaders(),
|
|
633
|
-
"X-Service-Api-Key": serviceApiKey,
|
|
634
|
-
...options.headers
|
|
593
|
+
error: {
|
|
594
|
+
code: errorCodes.NO_TOKENS,
|
|
595
|
+
message: "\u30EA\u30D5\u30EC\u30C3\u30B7\u30E5\u30C8\u30FC\u30AF\u30F3\u304C\u3042\u308A\u307E\u305B\u3093"
|
|
635
596
|
}
|
|
636
597
|
};
|
|
637
598
|
}
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
};
|
|
645
|
-
if (tokens) {
|
|
646
|
-
headers["Authorization"] = `Bearer ${tokens.accessToken}`;
|
|
647
|
-
}
|
|
648
|
-
return {
|
|
649
|
-
...options,
|
|
599
|
+
const url = `${baseUrl}${OAUTH_TOKEN_ENDPOINT}`;
|
|
600
|
+
logger.debug("Refreshing access token:", url);
|
|
601
|
+
let response;
|
|
602
|
+
try {
|
|
603
|
+
response = await fetch(url, {
|
|
604
|
+
method: "POST",
|
|
650
605
|
credentials: "omit",
|
|
651
|
-
headers
|
|
606
|
+
headers: { "Content-Type": "application/x-www-form-urlencoded", ...sdkHeaders() },
|
|
607
|
+
body: new URLSearchParams({
|
|
608
|
+
grant_type: "refresh_token",
|
|
609
|
+
refresh_token: tokens.refreshToken,
|
|
610
|
+
client_id: clientId
|
|
611
|
+
}).toString()
|
|
612
|
+
});
|
|
613
|
+
} catch (error) {
|
|
614
|
+
logger.error("Network error during token refresh:", error);
|
|
615
|
+
return {
|
|
616
|
+
error: {
|
|
617
|
+
code: errorCodes.NETWORK_ERROR,
|
|
618
|
+
message: error instanceof Error ? error.message : "\u30CD\u30C3\u30C8\u30EF\u30FC\u30AF\u30A8\u30E9\u30FC\u304C\u767A\u751F\u3057\u307E\u3057\u305F"
|
|
619
|
+
}
|
|
652
620
|
};
|
|
653
621
|
}
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
622
|
+
let tokenResponse;
|
|
623
|
+
try {
|
|
624
|
+
tokenResponse = await response.json();
|
|
625
|
+
} catch (parseError) {
|
|
626
|
+
logger.error("Parse error during token refresh:", parseError);
|
|
627
|
+
return {
|
|
628
|
+
error: {
|
|
629
|
+
code: errorCodes.PARSE_ERROR,
|
|
630
|
+
message: `\u30C8\u30FC\u30AF\u30F3\u30EC\u30B9\u30DD\u30F3\u30B9\u306E\u89E3\u6790\u306B\u5931\u6557\u3057\u307E\u3057\u305F (status: ${response.status})`
|
|
631
|
+
}
|
|
632
|
+
};
|
|
633
|
+
}
|
|
634
|
+
if (!response.ok) {
|
|
635
|
+
const errorBody = tokenResponse;
|
|
636
|
+
logger.error("Token refresh failed:", errorBody);
|
|
637
|
+
const irrecoverableErrors = ["token_revoked", "invalid_grant"];
|
|
638
|
+
if (errorBody.error && irrecoverableErrors.includes(errorBody.error)) {
|
|
639
|
+
tokenStore.clearTokens();
|
|
640
|
+
logger.debug("Cleared tokens due to irrecoverable refresh error:", errorBody.error);
|
|
661
641
|
}
|
|
662
|
-
|
|
642
|
+
return {
|
|
643
|
+
error: {
|
|
644
|
+
code: errorBody.error ?? errorCodes.TOKEN_REFRESH_ERROR,
|
|
645
|
+
message: errorBody.error_description ?? "\u30C8\u30FC\u30AF\u30F3\u30EA\u30D5\u30EC\u30C3\u30B7\u30E5\u306B\u5931\u6557\u3057\u307E\u3057\u305F"
|
|
646
|
+
}
|
|
647
|
+
};
|
|
648
|
+
}
|
|
649
|
+
const validationError = validateTokenResponse(tokenResponse);
|
|
650
|
+
if (validationError) {
|
|
651
|
+
logger.error("Token refresh validation failed:", validationError);
|
|
652
|
+
return {
|
|
653
|
+
error: {
|
|
654
|
+
code: errorCodes.TOKEN_REFRESH_ERROR,
|
|
655
|
+
message: validationError
|
|
656
|
+
}
|
|
657
|
+
};
|
|
658
|
+
}
|
|
659
|
+
tokenStore.setTokens({
|
|
660
|
+
accessToken: tokenResponse.access_token,
|
|
661
|
+
refreshToken: tokenResponse.refresh_token,
|
|
662
|
+
expiresAt: Date.now() + tokenResponse.expires_in * MS_PER_SECOND
|
|
663
|
+
});
|
|
664
|
+
logger.debug("Token refresh successful");
|
|
665
|
+
return { data: void 0 };
|
|
663
666
|
}
|
|
667
|
+
async function signOutToken() {
|
|
668
|
+
const tokens = tokenStore.getTokens();
|
|
669
|
+
tokenStore.clearTokens();
|
|
670
|
+
if (!tokens) {
|
|
671
|
+
logger.debug("No tokens to revoke");
|
|
672
|
+
return { data: void 0 };
|
|
673
|
+
}
|
|
674
|
+
const url = `${baseUrl}${OAUTH_REVOKE_ENDPOINT}`;
|
|
675
|
+
logger.debug("Revoking token:", url);
|
|
676
|
+
try {
|
|
677
|
+
const response = await fetch(url, {
|
|
678
|
+
method: "POST",
|
|
679
|
+
credentials: "omit",
|
|
680
|
+
headers: { "Content-Type": "application/x-www-form-urlencoded", ...sdkHeaders() },
|
|
681
|
+
body: new URLSearchParams({
|
|
682
|
+
token: tokens.accessToken,
|
|
683
|
+
client_id: clientId
|
|
684
|
+
}).toString()
|
|
685
|
+
});
|
|
686
|
+
if (!response.ok) {
|
|
687
|
+
logger.warn(
|
|
688
|
+
"\u30C8\u30FC\u30AF\u30F3\u7121\u52B9\u5316\u30EA\u30AF\u30A8\u30B9\u30C8\u304C\u30B5\u30FC\u30D0\u30FC\u3067\u5931\u6557\u3057\u307E\u3057\u305F:",
|
|
689
|
+
`status=${response.status}`
|
|
690
|
+
);
|
|
691
|
+
}
|
|
692
|
+
} catch (error) {
|
|
693
|
+
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);
|
|
694
|
+
}
|
|
695
|
+
logger.debug("Token sign-out completed");
|
|
696
|
+
return { data: void 0 };
|
|
697
|
+
}
|
|
698
|
+
return { exchangeCodeForTokens, refreshAccessToken, signOutToken };
|
|
699
|
+
}
|
|
700
|
+
|
|
701
|
+
// src/client/session.ts
|
|
702
|
+
var SESSION_ENDPOINT = "/api/v1/auth/session";
|
|
703
|
+
var LOGOUT_ENDPOINT = "/api/v1/auth/signout";
|
|
704
|
+
var OAUTH_USERINFO_ENDPOINT = "/api/v1/oauth/userinfo";
|
|
705
|
+
var NO_CONTENT_STATUS = 204;
|
|
706
|
+
var UNAUTHORIZED_STATUS = 401;
|
|
707
|
+
function normalizeFFIDUser(user) {
|
|
708
|
+
return {
|
|
709
|
+
...user,
|
|
710
|
+
locale: user.locale ?? null,
|
|
711
|
+
timezone: user.timezone ?? null
|
|
712
|
+
};
|
|
713
|
+
}
|
|
714
|
+
function createSessionMethods(deps) {
|
|
715
|
+
const {
|
|
716
|
+
authMode,
|
|
717
|
+
baseUrl,
|
|
718
|
+
serviceCode,
|
|
719
|
+
tokenStore,
|
|
720
|
+
logger,
|
|
721
|
+
fetchWithAuth,
|
|
722
|
+
refreshAccessToken,
|
|
723
|
+
errorCodes
|
|
724
|
+
} = deps;
|
|
664
725
|
async function getSession() {
|
|
665
726
|
if (authMode === "token") {
|
|
666
727
|
return getSessionFromUserinfo();
|
|
@@ -677,19 +738,12 @@ function createFFIDClient(config) {
|
|
|
677
738
|
}
|
|
678
739
|
return result;
|
|
679
740
|
}
|
|
680
|
-
function normalizeFFIDUser(user) {
|
|
681
|
-
return {
|
|
682
|
-
...user,
|
|
683
|
-
locale: user.locale ?? null,
|
|
684
|
-
timezone: user.timezone ?? null
|
|
685
|
-
};
|
|
686
|
-
}
|
|
687
741
|
async function getSessionFromUserinfo() {
|
|
688
742
|
const tokens = tokenStore.getTokens();
|
|
689
743
|
if (!tokens) {
|
|
690
744
|
return {
|
|
691
745
|
error: {
|
|
692
|
-
code:
|
|
746
|
+
code: errorCodes.NO_TOKENS,
|
|
693
747
|
message: "\u30C8\u30FC\u30AF\u30F3\u304C\u4FDD\u5B58\u3055\u308C\u3066\u3044\u307E\u305B\u3093"
|
|
694
748
|
}
|
|
695
749
|
};
|
|
@@ -710,7 +764,7 @@ function createFFIDClient(config) {
|
|
|
710
764
|
logger.error("Network error:", error);
|
|
711
765
|
return {
|
|
712
766
|
error: {
|
|
713
|
-
code:
|
|
767
|
+
code: errorCodes.NETWORK_ERROR,
|
|
714
768
|
message: error instanceof Error ? error.message : "\u30CD\u30C3\u30C8\u30EF\u30FC\u30AF\u30A8\u30E9\u30FC\u304C\u767A\u751F\u3057\u307E\u3057\u305F"
|
|
715
769
|
}
|
|
716
770
|
};
|
|
@@ -733,7 +787,7 @@ function createFFIDClient(config) {
|
|
|
733
787
|
logger.error("Network error on retry:", retryError);
|
|
734
788
|
return {
|
|
735
789
|
error: {
|
|
736
|
-
code:
|
|
790
|
+
code: errorCodes.NETWORK_ERROR,
|
|
737
791
|
message: retryError instanceof Error ? retryError.message : "\u30CD\u30C3\u30C8\u30EF\u30FC\u30AF\u30A8\u30E9\u30FC\u304C\u767A\u751F\u3057\u307E\u3057\u305F"
|
|
738
792
|
}
|
|
739
793
|
};
|
|
@@ -750,7 +804,7 @@ function createFFIDClient(config) {
|
|
|
750
804
|
logger.error("Parse error:", parseError, "Status:", response.status);
|
|
751
805
|
return {
|
|
752
806
|
error: {
|
|
753
|
-
code:
|
|
807
|
+
code: errorCodes.PARSE_ERROR,
|
|
754
808
|
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
809
|
}
|
|
756
810
|
};
|
|
@@ -759,7 +813,7 @@ function createFFIDClient(config) {
|
|
|
759
813
|
const errorBody = rawUserinfo;
|
|
760
814
|
return {
|
|
761
815
|
error: {
|
|
762
|
-
code: errorBody.code ??
|
|
816
|
+
code: errorBody.code ?? errorCodes.UNKNOWN_ERROR,
|
|
763
817
|
message: errorBody.message ?? "\u4E0D\u660E\u306A\u30A8\u30E9\u30FC\u304C\u767A\u751F\u3057\u307E\u3057\u305F"
|
|
764
818
|
}
|
|
765
819
|
};
|
|
@@ -778,16 +832,10 @@ function createFFIDClient(config) {
|
|
|
778
832
|
data: {
|
|
779
833
|
user,
|
|
780
834
|
organizations: [],
|
|
781
|
-
subscriptions: mapUserinfoSubscriptionToSession(userinfo,
|
|
835
|
+
subscriptions: mapUserinfoSubscriptionToSession(userinfo, serviceCode)
|
|
782
836
|
}
|
|
783
837
|
};
|
|
784
838
|
}
|
|
785
|
-
async function signOut() {
|
|
786
|
-
if (authMode === "token") {
|
|
787
|
-
return signOutToken();
|
|
788
|
-
}
|
|
789
|
-
return signOutCookie();
|
|
790
|
-
}
|
|
791
839
|
async function signOutCookie() {
|
|
792
840
|
const url = `${baseUrl}${LOGOUT_ENDPOINT}`;
|
|
793
841
|
logger.debug("Fetching:", url);
|
|
@@ -802,7 +850,7 @@ function createFFIDClient(config) {
|
|
|
802
850
|
logger.error("Network error:", error);
|
|
803
851
|
return {
|
|
804
852
|
error: {
|
|
805
|
-
code:
|
|
853
|
+
code: errorCodes.NETWORK_ERROR,
|
|
806
854
|
message: error instanceof Error ? error.message : "\u30CD\u30C3\u30C8\u30EF\u30FC\u30AF\u30A8\u30E9\u30FC\u304C\u767A\u751F\u3057\u307E\u3057\u305F"
|
|
807
855
|
}
|
|
808
856
|
};
|
|
@@ -816,14 +864,20 @@ function createFFIDClient(config) {
|
|
|
816
864
|
const raw = await response.json();
|
|
817
865
|
return {
|
|
818
866
|
error: raw.error ?? {
|
|
819
|
-
code:
|
|
867
|
+
code: errorCodes.UNKNOWN_ERROR,
|
|
820
868
|
message: "\u4E0D\u660E\u306A\u30A8\u30E9\u30FC\u304C\u767A\u751F\u3057\u307E\u3057\u305F"
|
|
821
869
|
}
|
|
822
870
|
};
|
|
823
|
-
} catch {
|
|
871
|
+
} catch (parseError) {
|
|
872
|
+
logger.error(
|
|
873
|
+
"\u30B5\u30A4\u30F3\u30A2\u30A6\u30C8\u30EC\u30B9\u30DD\u30F3\u30B9\u306E\u89E3\u6790\u306B\u5931\u6557\u3057\u307E\u3057\u305F:",
|
|
874
|
+
parseError,
|
|
875
|
+
"Status:",
|
|
876
|
+
response.status
|
|
877
|
+
);
|
|
824
878
|
return {
|
|
825
879
|
error: {
|
|
826
|
-
code:
|
|
880
|
+
code: errorCodes.PARSE_ERROR,
|
|
827
881
|
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
882
|
}
|
|
829
883
|
};
|
|
@@ -832,125 +886,496 @@ function createFFIDClient(config) {
|
|
|
832
886
|
logger.debug("Response:", response.status);
|
|
833
887
|
return { data: void 0 };
|
|
834
888
|
}
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
889
|
+
return { getSession, signOutCookie };
|
|
890
|
+
}
|
|
891
|
+
|
|
892
|
+
// src/auth/pkce.ts
|
|
893
|
+
var VERIFIER_STORAGE_KEY = "ffid_code_verifier";
|
|
894
|
+
var CODE_VERIFIER_MIN_LENGTH = 43;
|
|
895
|
+
var UNRESERVED_CHARS = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-._~";
|
|
896
|
+
function generateCodeVerifier() {
|
|
897
|
+
const length = CODE_VERIFIER_MIN_LENGTH;
|
|
898
|
+
const randomValues = new Uint8Array(length);
|
|
899
|
+
crypto.getRandomValues(randomValues);
|
|
900
|
+
let verifier = "";
|
|
901
|
+
for (let i = 0; i < length; i++) {
|
|
902
|
+
verifier += UNRESERVED_CHARS[randomValues[i] % UNRESERVED_CHARS.length];
|
|
903
|
+
}
|
|
904
|
+
return verifier;
|
|
905
|
+
}
|
|
906
|
+
async function generateCodeChallenge(verifier) {
|
|
907
|
+
const encoder = new TextEncoder();
|
|
908
|
+
const data = encoder.encode(verifier);
|
|
909
|
+
const digest = await crypto.subtle.digest("SHA-256", data);
|
|
910
|
+
return base64UrlEncode(digest);
|
|
911
|
+
}
|
|
912
|
+
function storeCodeVerifier(verifier, logger) {
|
|
913
|
+
try {
|
|
914
|
+
if (typeof window === "undefined") {
|
|
915
|
+
logger?.warn("storeCodeVerifier: sessionStorage is not available in SSR context");
|
|
916
|
+
return false;
|
|
841
917
|
}
|
|
842
|
-
|
|
843
|
-
|
|
918
|
+
window.sessionStorage.setItem(VERIFIER_STORAGE_KEY, verifier);
|
|
919
|
+
return true;
|
|
920
|
+
} catch (error) {
|
|
921
|
+
logger?.warn("storeCodeVerifier: sessionStorage \u3078\u306E\u4FDD\u5B58\u306B\u5931\u6557\u3057\u307E\u3057\u305F:", error);
|
|
922
|
+
return false;
|
|
923
|
+
}
|
|
924
|
+
}
|
|
925
|
+
function retrieveCodeVerifier(logger) {
|
|
926
|
+
try {
|
|
927
|
+
if (typeof window === "undefined") return null;
|
|
928
|
+
const verifier = window.sessionStorage.getItem(VERIFIER_STORAGE_KEY);
|
|
929
|
+
if (verifier) {
|
|
930
|
+
window.sessionStorage.removeItem(VERIFIER_STORAGE_KEY);
|
|
931
|
+
}
|
|
932
|
+
return verifier;
|
|
933
|
+
} catch (error) {
|
|
934
|
+
logger?.warn("retrieveCodeVerifier: sessionStorage \u304B\u3089\u306E\u53D6\u5F97\u306B\u5931\u6557\u3057\u307E\u3057\u305F:", error);
|
|
935
|
+
return null;
|
|
936
|
+
}
|
|
937
|
+
}
|
|
938
|
+
function base64UrlEncode(buffer) {
|
|
939
|
+
const bytes = new Uint8Array(buffer);
|
|
940
|
+
let binary = "";
|
|
941
|
+
for (let i = 0; i < bytes.length; i++) {
|
|
942
|
+
binary += String.fromCharCode(bytes[i]);
|
|
943
|
+
}
|
|
944
|
+
return btoa(binary).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
|
|
945
|
+
}
|
|
946
|
+
|
|
947
|
+
// src/client/redirect.ts
|
|
948
|
+
var OAUTH_AUTHORIZE_ENDPOINT = "/api/v1/oauth/authorize";
|
|
949
|
+
var STATE_RANDOM_BYTES = 16;
|
|
950
|
+
var HEX_BASE2 = 16;
|
|
951
|
+
function generateRandomState() {
|
|
952
|
+
const array = new Uint8Array(STATE_RANDOM_BYTES);
|
|
953
|
+
crypto.getRandomValues(array);
|
|
954
|
+
return Array.from(array, (byte) => byte.toString(HEX_BASE2).padStart(2, "0")).join("");
|
|
955
|
+
}
|
|
956
|
+
function createRedirectMethods(deps) {
|
|
957
|
+
const {
|
|
958
|
+
authMode,
|
|
959
|
+
baseUrl,
|
|
960
|
+
clientId,
|
|
961
|
+
serviceCode,
|
|
962
|
+
resolvedRedirectUri,
|
|
963
|
+
logger
|
|
964
|
+
} = deps;
|
|
965
|
+
async function redirectToAuthorize() {
|
|
966
|
+
const verifier = generateCodeVerifier();
|
|
967
|
+
storeCodeVerifier(verifier, logger);
|
|
968
|
+
let challenge;
|
|
844
969
|
try {
|
|
845
|
-
await
|
|
846
|
-
method: "POST",
|
|
847
|
-
credentials: "omit",
|
|
848
|
-
headers: { "Content-Type": "application/x-www-form-urlencoded", ...sdkHeaders() },
|
|
849
|
-
body: new URLSearchParams({
|
|
850
|
-
token: tokens.accessToken,
|
|
851
|
-
client_id: clientId
|
|
852
|
-
}).toString()
|
|
853
|
-
});
|
|
970
|
+
challenge = await generateCodeChallenge(verifier);
|
|
854
971
|
} catch (error) {
|
|
855
|
-
|
|
972
|
+
const errorMessage = error instanceof Error ? error.message : "PKCE \u30B3\u30FC\u30C9\u30C1\u30E3\u30EC\u30F3\u30B8\u306E\u751F\u6210\u306B\u5931\u6557\u3057\u307E\u3057\u305F";
|
|
973
|
+
logger.error("PKCE \u30B3\u30FC\u30C9\u30C1\u30E3\u30EC\u30F3\u30B8\u306E\u751F\u6210\u306B\u5931\u6557\u3057\u307E\u3057\u305F:", error);
|
|
974
|
+
return { success: false, error: errorMessage };
|
|
975
|
+
}
|
|
976
|
+
const state = generateRandomState();
|
|
977
|
+
const redirectUri = resolvedRedirectUri ?? window.location.origin + window.location.pathname;
|
|
978
|
+
const params = new URLSearchParams({
|
|
979
|
+
response_type: "code",
|
|
980
|
+
client_id: clientId,
|
|
981
|
+
redirect_uri: redirectUri,
|
|
982
|
+
state,
|
|
983
|
+
code_challenge: challenge,
|
|
984
|
+
code_challenge_method: "S256"
|
|
985
|
+
});
|
|
986
|
+
const authorizeUrl = `${baseUrl}${OAUTH_AUTHORIZE_ENDPOINT}?${params.toString()}`;
|
|
987
|
+
logger.debug("Redirecting to authorize:", authorizeUrl);
|
|
988
|
+
window.location.href = authorizeUrl;
|
|
989
|
+
return { success: true };
|
|
990
|
+
}
|
|
991
|
+
async function redirectToLogin() {
|
|
992
|
+
if (typeof window === "undefined") {
|
|
993
|
+
logger.warn("SSR \u74B0\u5883\u3067\u306F\u30EA\u30C0\u30A4\u30EC\u30AF\u30C8\u3067\u304D\u307E\u305B\u3093");
|
|
994
|
+
return { success: false, error: "SSR \u74B0\u5883\u3067\u306F\u30EA\u30C0\u30A4\u30EC\u30AF\u30C8\u3067\u304D\u307E\u305B\u3093" };
|
|
856
995
|
}
|
|
857
|
-
|
|
858
|
-
|
|
996
|
+
if (authMode === "token") {
|
|
997
|
+
return redirectToAuthorize();
|
|
998
|
+
}
|
|
999
|
+
const currentUrl = window.location.href;
|
|
1000
|
+
const loginUrl = `${baseUrl}/login?redirect=${encodeURIComponent(currentUrl)}&service=${encodeURIComponent(serviceCode)}`;
|
|
1001
|
+
logger.debug("Redirecting to login:", loginUrl);
|
|
1002
|
+
window.location.href = loginUrl;
|
|
1003
|
+
return { success: true };
|
|
859
1004
|
}
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
if (
|
|
865
|
-
|
|
1005
|
+
function getLoginUrl(redirectUrl) {
|
|
1006
|
+
let redirect;
|
|
1007
|
+
if (redirectUrl != null) {
|
|
1008
|
+
redirect = redirectUrl;
|
|
1009
|
+
} else if (typeof window !== "undefined") {
|
|
1010
|
+
redirect = window.location.href;
|
|
1011
|
+
} else {
|
|
1012
|
+
logger.warn("getLoginUrl: SSR \u74B0\u5883\u3067 redirectUrl \u304C\u672A\u6307\u5B9A\u306E\u305F\u3081\u7A7A\u6587\u5B57\u306B\u30D5\u30A9\u30FC\u30EB\u30D0\u30C3\u30AF\u3057\u307E\u3059");
|
|
1013
|
+
redirect = "";
|
|
1014
|
+
}
|
|
1015
|
+
return `${baseUrl}/login?redirect=${encodeURIComponent(redirect)}&service=${encodeURIComponent(serviceCode)}`;
|
|
1016
|
+
}
|
|
1017
|
+
function getSignupUrl(redirectUrl) {
|
|
1018
|
+
let redirect;
|
|
1019
|
+
if (redirectUrl != null) {
|
|
1020
|
+
redirect = redirectUrl;
|
|
1021
|
+
} else if (typeof window !== "undefined") {
|
|
1022
|
+
redirect = window.location.href;
|
|
1023
|
+
} else {
|
|
1024
|
+
logger.warn("getSignupUrl: SSR \u74B0\u5883\u3067 redirectUrl \u304C\u672A\u6307\u5B9A\u306E\u305F\u3081\u7A7A\u6587\u5B57\u306B\u30D5\u30A9\u30FC\u30EB\u30D0\u30C3\u30AF\u3057\u307E\u3059");
|
|
1025
|
+
redirect = "";
|
|
1026
|
+
}
|
|
1027
|
+
return `${baseUrl}/signup?redirect=${encodeURIComponent(redirect)}&service=${encodeURIComponent(serviceCode)}`;
|
|
1028
|
+
}
|
|
1029
|
+
return { redirectToLogin, redirectToAuthorize, getLoginUrl, getSignupUrl };
|
|
1030
|
+
}
|
|
1031
|
+
|
|
1032
|
+
// src/client/password-reset.ts
|
|
1033
|
+
var RESET_PASSWORD_BASE = "/api/v1/auth/reset-password";
|
|
1034
|
+
function isBlank(value) {
|
|
1035
|
+
return !value || !value.trim();
|
|
1036
|
+
}
|
|
1037
|
+
function createPasswordResetMethods(deps) {
|
|
1038
|
+
const { baseUrl, logger, createError, fetchWithAuth, errorCodes } = deps;
|
|
1039
|
+
async function fetchPublic(endpoint, options = {}) {
|
|
1040
|
+
const url = `${baseUrl}${endpoint}`;
|
|
1041
|
+
logger.debug("Fetching (public):", url);
|
|
1042
|
+
let response;
|
|
1043
|
+
try {
|
|
1044
|
+
response = await fetch(url, {
|
|
1045
|
+
...options,
|
|
1046
|
+
credentials: "include",
|
|
1047
|
+
headers: {
|
|
1048
|
+
"Content-Type": "application/json",
|
|
1049
|
+
...sdkHeaders(),
|
|
1050
|
+
...options.headers
|
|
1051
|
+
}
|
|
1052
|
+
});
|
|
1053
|
+
} catch (error) {
|
|
1054
|
+
logger.error("Network error:", error);
|
|
866
1055
|
return {
|
|
867
1056
|
error: {
|
|
868
|
-
code:
|
|
869
|
-
message: "
|
|
1057
|
+
code: errorCodes.NETWORK_ERROR,
|
|
1058
|
+
message: error instanceof Error ? error.message : "\u30CD\u30C3\u30C8\u30EF\u30FC\u30AF\u30A8\u30E9\u30FC\u304C\u767A\u751F\u3057\u307E\u3057\u305F"
|
|
870
1059
|
}
|
|
871
1060
|
};
|
|
872
1061
|
}
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
1062
|
+
let raw;
|
|
1063
|
+
try {
|
|
1064
|
+
raw = await response.json();
|
|
1065
|
+
} catch (parseError) {
|
|
1066
|
+
logger.error("Parse error:", parseError, "Status:", response.status);
|
|
1067
|
+
return {
|
|
1068
|
+
error: {
|
|
1069
|
+
code: errorCodes.PARSE_ERROR,
|
|
1070
|
+
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})`
|
|
1071
|
+
}
|
|
1072
|
+
};
|
|
1073
|
+
}
|
|
1074
|
+
logger.debug("Response (public):", response.status, raw);
|
|
1075
|
+
checkVersionHeader(response, logger);
|
|
1076
|
+
if (!response.ok) {
|
|
1077
|
+
return {
|
|
1078
|
+
error: raw.error ?? {
|
|
1079
|
+
code: errorCodes.UNKNOWN_ERROR,
|
|
1080
|
+
message: "\u30D1\u30B9\u30EF\u30FC\u30C9\u30EA\u30BB\u30C3\u30C8\u306B\u5931\u6557\u3057\u307E\u3057\u305F"
|
|
1081
|
+
}
|
|
1082
|
+
};
|
|
1083
|
+
}
|
|
1084
|
+
if (raw.data === void 0) {
|
|
1085
|
+
return {
|
|
1086
|
+
error: {
|
|
1087
|
+
code: errorCodes.UNKNOWN_ERROR,
|
|
1088
|
+
message: "\u30B5\u30FC\u30D0\u30FC\u304B\u3089\u30C7\u30FC\u30BF\u304C\u8FD4\u3055\u308C\u307E\u305B\u3093\u3067\u3057\u305F"
|
|
1089
|
+
}
|
|
1090
|
+
};
|
|
1091
|
+
}
|
|
1092
|
+
return { data: raw.data };
|
|
1093
|
+
}
|
|
1094
|
+
async function requestPasswordReset(email) {
|
|
1095
|
+
if (isBlank(email)) {
|
|
1096
|
+
return {
|
|
1097
|
+
error: createError("VALIDATION_ERROR", "\u30E1\u30FC\u30EB\u30A2\u30C9\u30EC\u30B9\u306F\u5FC5\u9808\u3067\u3059")
|
|
1098
|
+
};
|
|
1099
|
+
}
|
|
1100
|
+
return fetchPublic(
|
|
1101
|
+
RESET_PASSWORD_BASE,
|
|
1102
|
+
{
|
|
1103
|
+
method: "POST",
|
|
1104
|
+
body: JSON.stringify({ email })
|
|
1105
|
+
}
|
|
1106
|
+
);
|
|
1107
|
+
}
|
|
1108
|
+
async function verifyPasswordResetToken(accessToken) {
|
|
1109
|
+
if (isBlank(accessToken)) {
|
|
1110
|
+
return {
|
|
1111
|
+
error: createError("VALIDATION_ERROR", "\u30A2\u30AF\u30BB\u30B9\u30C8\u30FC\u30AF\u30F3\u306F\u5FC5\u9808\u3067\u3059")
|
|
1112
|
+
};
|
|
881
1113
|
}
|
|
1114
|
+
const query = new URLSearchParams({
|
|
1115
|
+
access_token: accessToken,
|
|
1116
|
+
type: "recovery"
|
|
1117
|
+
});
|
|
1118
|
+
return fetchPublic(
|
|
1119
|
+
`${RESET_PASSWORD_BASE}/verify?${query.toString()}`
|
|
1120
|
+
);
|
|
1121
|
+
}
|
|
1122
|
+
async function establishResetSession(accessToken, refreshToken) {
|
|
1123
|
+
if (isBlank(accessToken)) {
|
|
1124
|
+
return {
|
|
1125
|
+
error: createError("VALIDATION_ERROR", "\u30A2\u30AF\u30BB\u30B9\u30C8\u30FC\u30AF\u30F3\u306F\u5FC5\u9808\u3067\u3059")
|
|
1126
|
+
};
|
|
1127
|
+
}
|
|
1128
|
+
if (isBlank(refreshToken)) {
|
|
1129
|
+
return {
|
|
1130
|
+
error: createError("VALIDATION_ERROR", "\u30EA\u30D5\u30EC\u30C3\u30B7\u30E5\u30C8\u30FC\u30AF\u30F3\u306F\u5FC5\u9808\u3067\u3059")
|
|
1131
|
+
};
|
|
1132
|
+
}
|
|
1133
|
+
return fetchPublic(
|
|
1134
|
+
`${RESET_PASSWORD_BASE}/session`,
|
|
1135
|
+
{
|
|
1136
|
+
method: "POST",
|
|
1137
|
+
body: JSON.stringify({ accessToken, refreshToken })
|
|
1138
|
+
}
|
|
1139
|
+
);
|
|
1140
|
+
}
|
|
1141
|
+
async function confirmPasswordReset(password) {
|
|
1142
|
+
if (isBlank(password)) {
|
|
1143
|
+
return {
|
|
1144
|
+
error: createError("VALIDATION_ERROR", "\u30D1\u30B9\u30EF\u30FC\u30C9\u306F\u5FC5\u9808\u3067\u3059")
|
|
1145
|
+
};
|
|
1146
|
+
}
|
|
1147
|
+
return fetchWithAuth(
|
|
1148
|
+
`${RESET_PASSWORD_BASE}/confirm`,
|
|
1149
|
+
{
|
|
1150
|
+
method: "POST",
|
|
1151
|
+
body: JSON.stringify({ password })
|
|
1152
|
+
}
|
|
1153
|
+
);
|
|
1154
|
+
}
|
|
1155
|
+
return {
|
|
1156
|
+
requestPasswordReset,
|
|
1157
|
+
verifyPasswordResetToken,
|
|
1158
|
+
establishResetSession,
|
|
1159
|
+
confirmPasswordReset
|
|
1160
|
+
};
|
|
1161
|
+
}
|
|
1162
|
+
|
|
1163
|
+
// src/client/otp.ts
|
|
1164
|
+
var OTP_BASE = "/api/v1/auth/otp";
|
|
1165
|
+
function isBlank2(value) {
|
|
1166
|
+
return !value || !value.trim();
|
|
1167
|
+
}
|
|
1168
|
+
function createOtpMethods(deps) {
|
|
1169
|
+
const { baseUrl, logger, createError, errorCodes } = deps;
|
|
1170
|
+
async function fetchPublic(endpoint, options = {}) {
|
|
1171
|
+
const url = `${baseUrl}${endpoint}`;
|
|
1172
|
+
logger.debug("Fetching (public):", url);
|
|
882
1173
|
let response;
|
|
883
1174
|
try {
|
|
884
1175
|
response = await fetch(url, {
|
|
885
|
-
|
|
886
|
-
credentials: "
|
|
887
|
-
headers: {
|
|
888
|
-
|
|
1176
|
+
...options,
|
|
1177
|
+
credentials: "include",
|
|
1178
|
+
headers: {
|
|
1179
|
+
"Content-Type": "application/json",
|
|
1180
|
+
...sdkHeaders(),
|
|
1181
|
+
...options.headers
|
|
1182
|
+
}
|
|
889
1183
|
});
|
|
890
1184
|
} catch (error) {
|
|
891
|
-
logger.error("Network error
|
|
1185
|
+
logger.error("Network error:", error);
|
|
1186
|
+
return {
|
|
1187
|
+
error: {
|
|
1188
|
+
code: errorCodes.NETWORK_ERROR,
|
|
1189
|
+
message: error instanceof Error ? error.message : "\u30CD\u30C3\u30C8\u30EF\u30FC\u30AF\u30A8\u30E9\u30FC\u304C\u767A\u751F\u3057\u307E\u3057\u305F"
|
|
1190
|
+
}
|
|
1191
|
+
};
|
|
1192
|
+
}
|
|
1193
|
+
let raw;
|
|
1194
|
+
try {
|
|
1195
|
+
raw = await response.json();
|
|
1196
|
+
} catch (parseError) {
|
|
1197
|
+
logger.error("Parse error:", parseError, "Status:", response.status);
|
|
1198
|
+
return {
|
|
1199
|
+
error: {
|
|
1200
|
+
code: errorCodes.PARSE_ERROR,
|
|
1201
|
+
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})`
|
|
1202
|
+
}
|
|
1203
|
+
};
|
|
1204
|
+
}
|
|
1205
|
+
logger.debug("Response (public):", response.status, raw);
|
|
1206
|
+
checkVersionHeader(response, logger);
|
|
1207
|
+
if (!response.ok) {
|
|
1208
|
+
return {
|
|
1209
|
+
error: raw.error ?? {
|
|
1210
|
+
code: errorCodes.UNKNOWN_ERROR,
|
|
1211
|
+
message: "OTP\u8A8D\u8A3C\u306B\u5931\u6557\u3057\u307E\u3057\u305F"
|
|
1212
|
+
}
|
|
1213
|
+
};
|
|
1214
|
+
}
|
|
1215
|
+
if (raw.data === void 0) {
|
|
1216
|
+
return {
|
|
1217
|
+
error: {
|
|
1218
|
+
code: errorCodes.UNKNOWN_ERROR,
|
|
1219
|
+
message: "\u30B5\u30FC\u30D0\u30FC\u304B\u3089\u30C7\u30FC\u30BF\u304C\u8FD4\u3055\u308C\u307E\u305B\u3093\u3067\u3057\u305F"
|
|
1220
|
+
}
|
|
1221
|
+
};
|
|
1222
|
+
}
|
|
1223
|
+
return { data: raw.data };
|
|
1224
|
+
}
|
|
1225
|
+
async function sendOtp(email, options) {
|
|
1226
|
+
if (isBlank2(email)) {
|
|
1227
|
+
return {
|
|
1228
|
+
error: createError("VALIDATION_ERROR", "\u30E1\u30FC\u30EB\u30A2\u30C9\u30EC\u30B9\u306F\u5FC5\u9808\u3067\u3059")
|
|
1229
|
+
};
|
|
1230
|
+
}
|
|
1231
|
+
return fetchPublic(
|
|
1232
|
+
`${OTP_BASE}/send`,
|
|
1233
|
+
{
|
|
1234
|
+
method: "POST",
|
|
1235
|
+
body: JSON.stringify({
|
|
1236
|
+
email,
|
|
1237
|
+
...options?.redirectUrl ? { redirectUrl: options.redirectUrl } : {}
|
|
1238
|
+
})
|
|
1239
|
+
}
|
|
1240
|
+
);
|
|
1241
|
+
}
|
|
1242
|
+
async function verifyOtp(params) {
|
|
1243
|
+
if (isBlank2(params.accessToken)) {
|
|
892
1244
|
return {
|
|
893
|
-
error:
|
|
894
|
-
code: FFID_ERROR_CODES.NETWORK_ERROR,
|
|
895
|
-
message: error instanceof Error ? error.message : "\u30CD\u30C3\u30C8\u30EF\u30FC\u30AF\u30A8\u30E9\u30FC\u304C\u767A\u751F\u3057\u307E\u3057\u305F"
|
|
896
|
-
}
|
|
1245
|
+
error: createError("VALIDATION_ERROR", "\u30A2\u30AF\u30BB\u30B9\u30C8\u30FC\u30AF\u30F3\u306F\u5FC5\u9808\u3067\u3059")
|
|
897
1246
|
};
|
|
898
1247
|
}
|
|
899
|
-
|
|
900
|
-
try {
|
|
901
|
-
tokenResponse = await response.json();
|
|
902
|
-
} catch (parseError) {
|
|
903
|
-
logger.error("Parse error during token exchange:", parseError);
|
|
1248
|
+
if (isBlank2(params.refreshToken)) {
|
|
904
1249
|
return {
|
|
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
|
-
}
|
|
1250
|
+
error: createError("VALIDATION_ERROR", "\u30EA\u30D5\u30EC\u30C3\u30B7\u30E5\u30C8\u30FC\u30AF\u30F3\u306F\u5FC5\u9808\u3067\u3059")
|
|
909
1251
|
};
|
|
910
1252
|
}
|
|
911
|
-
|
|
912
|
-
|
|
1253
|
+
return fetchPublic(
|
|
1254
|
+
`${OTP_BASE}/verify`,
|
|
1255
|
+
{
|
|
1256
|
+
method: "POST",
|
|
1257
|
+
body: JSON.stringify({
|
|
1258
|
+
accessToken: params.accessToken,
|
|
1259
|
+
refreshToken: params.refreshToken
|
|
1260
|
+
})
|
|
1261
|
+
}
|
|
1262
|
+
);
|
|
1263
|
+
}
|
|
1264
|
+
return {
|
|
1265
|
+
sendOtp,
|
|
1266
|
+
verifyOtp
|
|
1267
|
+
};
|
|
1268
|
+
}
|
|
1269
|
+
|
|
1270
|
+
// src/client/ffid-client.ts
|
|
1271
|
+
var UNAUTHORIZED_STATUS2 = 401;
|
|
1272
|
+
var SDK_LOG_PREFIX = "[FFID SDK]";
|
|
1273
|
+
var noopLogger = {
|
|
1274
|
+
debug: () => {
|
|
1275
|
+
},
|
|
1276
|
+
info: () => {
|
|
1277
|
+
},
|
|
1278
|
+
warn: (...args) => console.warn(SDK_LOG_PREFIX, ...args),
|
|
1279
|
+
error: (...args) => console.error(SDK_LOG_PREFIX, ...args)
|
|
1280
|
+
};
|
|
1281
|
+
var consoleLogger = {
|
|
1282
|
+
debug: (...args) => console.debug(SDK_LOG_PREFIX, ...args),
|
|
1283
|
+
info: (...args) => console.info(SDK_LOG_PREFIX, ...args),
|
|
1284
|
+
warn: (...args) => console.warn(SDK_LOG_PREFIX, ...args),
|
|
1285
|
+
error: (...args) => console.error(SDK_LOG_PREFIX, ...args)
|
|
1286
|
+
};
|
|
1287
|
+
var FFID_ERROR_CODES = {
|
|
1288
|
+
NETWORK_ERROR: "NETWORK_ERROR",
|
|
1289
|
+
PARSE_ERROR: "PARSE_ERROR",
|
|
1290
|
+
UNKNOWN_ERROR: "UNKNOWN_ERROR",
|
|
1291
|
+
TOKEN_EXCHANGE_ERROR: "TOKEN_EXCHANGE_ERROR",
|
|
1292
|
+
TOKEN_REFRESH_ERROR: "TOKEN_REFRESH_ERROR",
|
|
1293
|
+
NO_TOKENS: "NO_TOKENS",
|
|
1294
|
+
TOKEN_VERIFICATION_ERROR: "TOKEN_VERIFICATION_ERROR"
|
|
1295
|
+
};
|
|
1296
|
+
var EXT_CHECK_ENDPOINT = "/api/v1/subscriptions/ext/check";
|
|
1297
|
+
function createFFIDClient(config) {
|
|
1298
|
+
if (!config.serviceCode || !config.serviceCode.trim()) {
|
|
1299
|
+
throw new Error("FFID Client: serviceCode \u304C\u672A\u8A2D\u5B9A\u3067\u3059");
|
|
1300
|
+
}
|
|
1301
|
+
const baseUrl = config.apiBaseUrl ?? DEFAULT_API_BASE_URL;
|
|
1302
|
+
const authMode = config.authMode ?? "cookie";
|
|
1303
|
+
const clientId = config.clientId ?? config.serviceCode;
|
|
1304
|
+
const resolvedRedirectUri = config.redirectUri ?? null;
|
|
1305
|
+
const serviceApiKey = config.serviceApiKey?.trim();
|
|
1306
|
+
const verifyStrategy = config.verifyStrategy ?? "jwt";
|
|
1307
|
+
const cache = config.cache;
|
|
1308
|
+
const timeout = config.timeout;
|
|
1309
|
+
if (authMode === "service-key" && !serviceApiKey) {
|
|
1310
|
+
throw new Error("FFID Client: service-key \u30E2\u30FC\u30C9\u3067\u306F serviceApiKey \u304C\u5FC5\u9808\u3067\u3059");
|
|
1311
|
+
}
|
|
1312
|
+
if (cache && cache.ttl <= 0) {
|
|
1313
|
+
throw new Error("FFID Client: cache.ttl \u306F\u6B63\u306E\u6570\u5024\u3092\u6307\u5B9A\u3057\u3066\u304F\u3060\u3055\u3044");
|
|
1314
|
+
}
|
|
1315
|
+
if (timeout !== void 0 && timeout <= 0) {
|
|
1316
|
+
throw new Error("FFID Client: timeout \u306F\u6B63\u306E\u6570\u5024\u3092\u6307\u5B9A\u3057\u3066\u304F\u3060\u3055\u3044");
|
|
1317
|
+
}
|
|
1318
|
+
const logger = config.logger ?? (config.debug ? consoleLogger : noopLogger);
|
|
1319
|
+
const tokenStore = authMode === "token" ? createTokenStore() : createTokenStore("memory");
|
|
1320
|
+
function createError(code, message) {
|
|
1321
|
+
return { code, message };
|
|
1322
|
+
}
|
|
1323
|
+
function buildFetchOptions(options) {
|
|
1324
|
+
if (authMode === "service-key") {
|
|
913
1325
|
return {
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
1326
|
+
...options,
|
|
1327
|
+
credentials: "omit",
|
|
1328
|
+
headers: {
|
|
1329
|
+
"Content-Type": "application/json",
|
|
1330
|
+
...sdkHeaders(),
|
|
1331
|
+
"X-Service-Api-Key": serviceApiKey,
|
|
1332
|
+
...options.headers
|
|
917
1333
|
}
|
|
918
1334
|
};
|
|
919
1335
|
}
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
if (!tokens) {
|
|
1336
|
+
if (authMode === "token") {
|
|
1337
|
+
const tokens = tokenStore.getTokens();
|
|
1338
|
+
const headers = {
|
|
1339
|
+
"Content-Type": "application/json",
|
|
1340
|
+
...sdkHeaders(),
|
|
1341
|
+
...options.headers
|
|
1342
|
+
};
|
|
1343
|
+
if (tokens) {
|
|
1344
|
+
headers["Authorization"] = `Bearer ${tokens.accessToken}`;
|
|
1345
|
+
}
|
|
931
1346
|
return {
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
}
|
|
1347
|
+
...options,
|
|
1348
|
+
credentials: "omit",
|
|
1349
|
+
headers
|
|
936
1350
|
};
|
|
937
1351
|
}
|
|
938
|
-
|
|
939
|
-
|
|
1352
|
+
return {
|
|
1353
|
+
...options,
|
|
1354
|
+
credentials: "include",
|
|
1355
|
+
headers: {
|
|
1356
|
+
"Content-Type": "application/json",
|
|
1357
|
+
...sdkHeaders(),
|
|
1358
|
+
...options.headers
|
|
1359
|
+
}
|
|
1360
|
+
};
|
|
1361
|
+
}
|
|
1362
|
+
const { exchangeCodeForTokens, refreshAccessToken, signOutToken } = createOAuthTokenMethods({
|
|
1363
|
+
baseUrl,
|
|
1364
|
+
clientId,
|
|
1365
|
+
resolvedRedirectUri,
|
|
1366
|
+
tokenStore,
|
|
1367
|
+
logger,
|
|
1368
|
+
errorCodes: FFID_ERROR_CODES
|
|
1369
|
+
});
|
|
1370
|
+
async function fetchWithAuth(endpoint, options = {}) {
|
|
1371
|
+
const url = `${baseUrl}${endpoint}`;
|
|
1372
|
+
logger.debug("Fetching:", url);
|
|
1373
|
+
const fetchOptions = buildFetchOptions(options);
|
|
940
1374
|
let response;
|
|
941
1375
|
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
|
-
});
|
|
1376
|
+
response = await fetch(url, fetchOptions);
|
|
952
1377
|
} catch (error) {
|
|
953
|
-
logger.error("Network error
|
|
1378
|
+
logger.error("Network error:", error);
|
|
954
1379
|
return {
|
|
955
1380
|
error: {
|
|
956
1381
|
code: FFID_ERROR_CODES.NETWORK_ERROR,
|
|
@@ -958,88 +1383,83 @@ function createFFIDClient(config) {
|
|
|
958
1383
|
}
|
|
959
1384
|
};
|
|
960
1385
|
}
|
|
961
|
-
|
|
1386
|
+
if (authMode === "token" && response.status === UNAUTHORIZED_STATUS2) {
|
|
1387
|
+
const refreshResult = await refreshAccessToken();
|
|
1388
|
+
if (!refreshResult.error) {
|
|
1389
|
+
logger.debug("Token refreshed, retrying request");
|
|
1390
|
+
const retryOptions = buildFetchOptions(options);
|
|
1391
|
+
try {
|
|
1392
|
+
response = await fetch(url, retryOptions);
|
|
1393
|
+
} catch (retryError) {
|
|
1394
|
+
logger.error("Network error on retry:", retryError);
|
|
1395
|
+
return {
|
|
1396
|
+
error: {
|
|
1397
|
+
code: FFID_ERROR_CODES.NETWORK_ERROR,
|
|
1398
|
+
message: retryError instanceof Error ? retryError.message : "\u30CD\u30C3\u30C8\u30EF\u30FC\u30AF\u30A8\u30E9\u30FC\u304C\u767A\u751F\u3057\u307E\u3057\u305F"
|
|
1399
|
+
}
|
|
1400
|
+
};
|
|
1401
|
+
}
|
|
1402
|
+
} else {
|
|
1403
|
+
logger.warn("Token refresh failed, returning refresh error:", refreshResult.error);
|
|
1404
|
+
return { error: refreshResult.error };
|
|
1405
|
+
}
|
|
1406
|
+
}
|
|
1407
|
+
let raw;
|
|
962
1408
|
try {
|
|
963
|
-
|
|
1409
|
+
raw = await response.json();
|
|
964
1410
|
} catch (parseError) {
|
|
965
|
-
logger.error("Parse error
|
|
1411
|
+
logger.error("Parse error:", parseError, "Status:", response.status);
|
|
966
1412
|
return {
|
|
967
1413
|
error: {
|
|
968
1414
|
code: FFID_ERROR_CODES.PARSE_ERROR,
|
|
969
|
-
message: `\
|
|
1415
|
+
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
1416
|
}
|
|
971
1417
|
};
|
|
972
1418
|
}
|
|
1419
|
+
logger.debug("Response:", response.status, raw);
|
|
1420
|
+
checkVersionHeader(response, logger);
|
|
973
1421
|
if (!response.ok) {
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
1422
|
+
return {
|
|
1423
|
+
error: raw.error ?? {
|
|
1424
|
+
code: FFID_ERROR_CODES.UNKNOWN_ERROR,
|
|
1425
|
+
message: "\u4E0D\u660E\u306A\u30A8\u30E9\u30FC\u304C\u767A\u751F\u3057\u307E\u3057\u305F"
|
|
1426
|
+
}
|
|
1427
|
+
};
|
|
1428
|
+
}
|
|
1429
|
+
if (raw.data === void 0) {
|
|
981
1430
|
return {
|
|
982
1431
|
error: {
|
|
983
|
-
code:
|
|
984
|
-
message:
|
|
1432
|
+
code: FFID_ERROR_CODES.UNKNOWN_ERROR,
|
|
1433
|
+
message: "\u30B5\u30FC\u30D0\u30FC\u304B\u3089\u30C7\u30FC\u30BF\u304C\u8FD4\u3055\u308C\u307E\u305B\u3093\u3067\u3057\u305F"
|
|
985
1434
|
}
|
|
986
1435
|
};
|
|
987
1436
|
}
|
|
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 };
|
|
1437
|
+
return { data: raw.data };
|
|
995
1438
|
}
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1439
|
+
const { getSession, signOutCookie } = createSessionMethods({
|
|
1440
|
+
authMode,
|
|
1441
|
+
baseUrl,
|
|
1442
|
+
serviceCode: config.serviceCode,
|
|
1443
|
+
tokenStore,
|
|
1444
|
+
logger,
|
|
1445
|
+
fetchWithAuth,
|
|
1446
|
+
refreshAccessToken,
|
|
1447
|
+
errorCodes: FFID_ERROR_CODES
|
|
1448
|
+
});
|
|
1449
|
+
async function signOut() {
|
|
1001
1450
|
if (authMode === "token") {
|
|
1002
|
-
return
|
|
1451
|
+
return signOutToken();
|
|
1003
1452
|
}
|
|
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 };
|
|
1453
|
+
return signOutCookie();
|
|
1042
1454
|
}
|
|
1455
|
+
const { redirectToLogin, getLoginUrl, getSignupUrl } = createRedirectMethods({
|
|
1456
|
+
authMode,
|
|
1457
|
+
baseUrl,
|
|
1458
|
+
clientId,
|
|
1459
|
+
serviceCode: config.serviceCode,
|
|
1460
|
+
resolvedRedirectUri,
|
|
1461
|
+
logger
|
|
1462
|
+
});
|
|
1043
1463
|
async function checkSubscription(params) {
|
|
1044
1464
|
if (!params.userId || !params.organizationId) {
|
|
1045
1465
|
return {
|
|
@@ -1059,6 +1479,24 @@ function createFFIDClient(config) {
|
|
|
1059
1479
|
fetchWithAuth,
|
|
1060
1480
|
createError
|
|
1061
1481
|
});
|
|
1482
|
+
const {
|
|
1483
|
+
requestPasswordReset,
|
|
1484
|
+
verifyPasswordResetToken,
|
|
1485
|
+
establishResetSession,
|
|
1486
|
+
confirmPasswordReset
|
|
1487
|
+
} = createPasswordResetMethods({
|
|
1488
|
+
baseUrl,
|
|
1489
|
+
logger,
|
|
1490
|
+
createError,
|
|
1491
|
+
fetchWithAuth,
|
|
1492
|
+
errorCodes: FFID_ERROR_CODES
|
|
1493
|
+
});
|
|
1494
|
+
const { sendOtp, verifyOtp } = createOtpMethods({
|
|
1495
|
+
baseUrl,
|
|
1496
|
+
logger,
|
|
1497
|
+
createError,
|
|
1498
|
+
errorCodes: FFID_ERROR_CODES
|
|
1499
|
+
});
|
|
1062
1500
|
const verifyAccessToken = createVerifyAccessToken({
|
|
1063
1501
|
authMode,
|
|
1064
1502
|
baseUrl,
|
|
@@ -1084,6 +1522,12 @@ function createFFIDClient(config) {
|
|
|
1084
1522
|
createCheckoutSession,
|
|
1085
1523
|
createPortalSession,
|
|
1086
1524
|
verifyAccessToken,
|
|
1525
|
+
requestPasswordReset,
|
|
1526
|
+
verifyPasswordResetToken,
|
|
1527
|
+
establishResetSession,
|
|
1528
|
+
confirmPasswordReset,
|
|
1529
|
+
sendOtp,
|
|
1530
|
+
verifyOtp,
|
|
1087
1531
|
/** Token store (token mode only) */
|
|
1088
1532
|
tokenStore,
|
|
1089
1533
|
/** Resolved auth mode */
|
|
@@ -1096,11 +1540,6 @@ function createFFIDClient(config) {
|
|
|
1096
1540
|
redirectUri: resolvedRedirectUri
|
|
1097
1541
|
};
|
|
1098
1542
|
}
|
|
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
1543
|
var DEFAULT_REFRESH_INTERVAL_MS = 5 * 60 * 1e3;
|
|
1105
1544
|
var TOKEN_REFRESH_RATIO = 0.8;
|
|
1106
1545
|
var FFIDContext = react.createContext(null);
|
|
@@ -1186,7 +1625,9 @@ function FFIDProvider({
|
|
|
1186
1625
|
}
|
|
1187
1626
|
}, [client]);
|
|
1188
1627
|
const login = react.useCallback(() => {
|
|
1189
|
-
client.redirectToLogin()
|
|
1628
|
+
client.redirectToLogin().catch((err) => {
|
|
1629
|
+
client.logger.error("\u30ED\u30B0\u30A4\u30F3\u30EA\u30C0\u30A4\u30EC\u30AF\u30C8\u306B\u5931\u6557\u3057\u307E\u3057\u305F:", err);
|
|
1630
|
+
});
|
|
1190
1631
|
}, [client]);
|
|
1191
1632
|
const logout = react.useCallback(async () => {
|
|
1192
1633
|
client.logger.debug("Signing out...");
|