@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
|
@@ -7,6 +7,7 @@ var DEFAULT_API_BASE_URL = "https://id.feelflow.net";
|
|
|
7
7
|
|
|
8
8
|
// src/auth/token-store.ts
|
|
9
9
|
var STORAGE_KEY = "ffid_tokens";
|
|
10
|
+
var TOKEN_STORE_LOG_PREFIX = "[FFID SDK] TokenStore:";
|
|
10
11
|
var EXPIRY_BUFFER_SECONDS = 30;
|
|
11
12
|
var EXPIRY_BUFFER_MS = EXPIRY_BUFFER_SECONDS * 1e3;
|
|
12
13
|
function isLocalStorageAvailable() {
|
|
@@ -30,22 +31,33 @@ function createLocalStorageStore() {
|
|
|
30
31
|
const raw = storage.getItem(STORAGE_KEY);
|
|
31
32
|
if (!raw) return null;
|
|
32
33
|
const parsed = JSON.parse(raw);
|
|
33
|
-
if (!isTokenData(parsed))
|
|
34
|
+
if (!isTokenData(parsed)) {
|
|
35
|
+
console.warn(TOKEN_STORE_LOG_PREFIX, "\u30C8\u30FC\u30AF\u30F3\u30C7\u30FC\u30BF\u304C\u4E0D\u6B63\u306A\u5F62\u5F0F\u3067\u3059\u3002\u30B9\u30C8\u30EC\u30FC\u30B8\u3092\u30AF\u30EA\u30A2\u3057\u307E\u3059");
|
|
36
|
+
storage.removeItem(STORAGE_KEY);
|
|
37
|
+
return null;
|
|
38
|
+
}
|
|
34
39
|
return parsed;
|
|
35
|
-
} catch {
|
|
40
|
+
} catch (error) {
|
|
41
|
+
console.warn(TOKEN_STORE_LOG_PREFIX, "\u30C8\u30FC\u30AF\u30F3\u8AAD\u307F\u53D6\u308A\u306B\u5931\u6557\u3057\u307E\u3057\u305F\u3002\u7834\u640D\u30C7\u30FC\u30BF\u3092\u30AF\u30EA\u30A2\u3057\u307E\u3059", error);
|
|
42
|
+
try {
|
|
43
|
+
storage.removeItem(STORAGE_KEY);
|
|
44
|
+
} catch {
|
|
45
|
+
}
|
|
36
46
|
return null;
|
|
37
47
|
}
|
|
38
48
|
},
|
|
39
49
|
setTokens(tokens) {
|
|
40
50
|
try {
|
|
41
51
|
storage.setItem(STORAGE_KEY, JSON.stringify(tokens));
|
|
42
|
-
} catch {
|
|
52
|
+
} catch (error) {
|
|
53
|
+
console.warn(TOKEN_STORE_LOG_PREFIX, "\u30C8\u30FC\u30AF\u30F3\u4FDD\u5B58\u306B\u5931\u6557\u3057\u307E\u3057\u305F\uFF08\u30B9\u30C8\u30EC\u30FC\u30B8\u5BB9\u91CF\u8D85\u904E\u306E\u53EF\u80FD\u6027\uFF09", error);
|
|
43
54
|
}
|
|
44
55
|
},
|
|
45
56
|
clearTokens() {
|
|
46
57
|
try {
|
|
47
58
|
storage.removeItem(STORAGE_KEY);
|
|
48
|
-
} catch {
|
|
59
|
+
} catch (error) {
|
|
60
|
+
console.warn(TOKEN_STORE_LOG_PREFIX, "\u30C8\u30FC\u30AF\u30F3\u524A\u9664\u306B\u5931\u6557\u3057\u307E\u3057\u305F", error);
|
|
49
61
|
}
|
|
50
62
|
},
|
|
51
63
|
isAccessTokenExpired() {
|
|
@@ -88,54 +100,6 @@ function createTokenStore(storageType) {
|
|
|
88
100
|
return createMemoryStore();
|
|
89
101
|
}
|
|
90
102
|
|
|
91
|
-
// src/auth/pkce.ts
|
|
92
|
-
var VERIFIER_STORAGE_KEY = "ffid_code_verifier";
|
|
93
|
-
var CODE_VERIFIER_MIN_LENGTH = 43;
|
|
94
|
-
var UNRESERVED_CHARS = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-._~";
|
|
95
|
-
function generateCodeVerifier() {
|
|
96
|
-
const length = CODE_VERIFIER_MIN_LENGTH;
|
|
97
|
-
const randomValues = new Uint8Array(length);
|
|
98
|
-
crypto.getRandomValues(randomValues);
|
|
99
|
-
let verifier = "";
|
|
100
|
-
for (let i = 0; i < length; i++) {
|
|
101
|
-
verifier += UNRESERVED_CHARS[randomValues[i] % UNRESERVED_CHARS.length];
|
|
102
|
-
}
|
|
103
|
-
return verifier;
|
|
104
|
-
}
|
|
105
|
-
async function generateCodeChallenge(verifier) {
|
|
106
|
-
const encoder = new TextEncoder();
|
|
107
|
-
const data = encoder.encode(verifier);
|
|
108
|
-
const digest = await crypto.subtle.digest("SHA-256", data);
|
|
109
|
-
return base64UrlEncode(digest);
|
|
110
|
-
}
|
|
111
|
-
function storeCodeVerifier(verifier) {
|
|
112
|
-
try {
|
|
113
|
-
if (typeof window === "undefined") return;
|
|
114
|
-
window.sessionStorage.setItem(VERIFIER_STORAGE_KEY, verifier);
|
|
115
|
-
} catch {
|
|
116
|
-
}
|
|
117
|
-
}
|
|
118
|
-
function retrieveCodeVerifier() {
|
|
119
|
-
try {
|
|
120
|
-
if (typeof window === "undefined") return null;
|
|
121
|
-
const verifier = window.sessionStorage.getItem(VERIFIER_STORAGE_KEY);
|
|
122
|
-
if (verifier) {
|
|
123
|
-
window.sessionStorage.removeItem(VERIFIER_STORAGE_KEY);
|
|
124
|
-
}
|
|
125
|
-
return verifier;
|
|
126
|
-
} catch {
|
|
127
|
-
return null;
|
|
128
|
-
}
|
|
129
|
-
}
|
|
130
|
-
function base64UrlEncode(buffer) {
|
|
131
|
-
const bytes = new Uint8Array(buffer);
|
|
132
|
-
let binary = "";
|
|
133
|
-
for (let i = 0; i < bytes.length; i++) {
|
|
134
|
-
binary += String.fromCharCode(bytes[i]);
|
|
135
|
-
}
|
|
136
|
-
return btoa(binary).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
|
|
137
|
-
}
|
|
138
|
-
|
|
139
103
|
// src/client/oauth-userinfo.ts
|
|
140
104
|
var VALID_SUBSCRIPTION_STATUSES = ["trialing", "active", "past_due", "canceled", "paused"];
|
|
141
105
|
function isValidSubscriptionStatus(value) {
|
|
@@ -239,6 +203,7 @@ var OAUTH_INTROSPECT_ENDPOINT = "/api/v1/oauth/introspect";
|
|
|
239
203
|
var CACHE_KEY_PREFIX = "ffid:introspect:";
|
|
240
204
|
var HEX_BASE = 16;
|
|
241
205
|
var HEX_BYTE_WIDTH = 2;
|
|
206
|
+
var TOKEN_LOG_PREFIX_LENGTH = 8;
|
|
242
207
|
function createVerifyAccessToken(deps) {
|
|
243
208
|
const { authMode, baseUrl, serviceCode, serviceApiKey, verifyStrategy, logger, createError, errorCodes, cache, timeout } = deps;
|
|
244
209
|
let jwtVerify2 = null;
|
|
@@ -254,7 +219,7 @@ function createVerifyAccessToken(deps) {
|
|
|
254
219
|
}
|
|
255
220
|
return jwtVerify2;
|
|
256
221
|
}
|
|
257
|
-
async function verifyAccessToken(accessToken) {
|
|
222
|
+
async function verifyAccessToken(accessToken, options) {
|
|
258
223
|
if (authMode !== "service-key") {
|
|
259
224
|
return {
|
|
260
225
|
error: createError(
|
|
@@ -271,12 +236,24 @@ function createVerifyAccessToken(deps) {
|
|
|
271
236
|
)
|
|
272
237
|
};
|
|
273
238
|
}
|
|
239
|
+
if (options?.includeProfile && verifyStrategy === "jwt" && !serviceApiKey) {
|
|
240
|
+
return {
|
|
241
|
+
error: createError(
|
|
242
|
+
errorCodes.TOKEN_VERIFICATION_ERROR,
|
|
243
|
+
"includeProfile: true \u3092\u4F7F\u7528\u3059\u308B\u306B\u306F serviceApiKey \u306E\u8A2D\u5B9A\u304C\u5FC5\u8981\u3067\u3059"
|
|
244
|
+
)
|
|
245
|
+
};
|
|
246
|
+
}
|
|
274
247
|
if (verifyStrategy === "jwt") {
|
|
275
|
-
|
|
248
|
+
const jwtResult = await getJwtVerifier()(accessToken);
|
|
249
|
+
if (!options?.includeProfile || jwtResult.error) {
|
|
250
|
+
return jwtResult;
|
|
251
|
+
}
|
|
252
|
+
return verifyViaIntrospect(accessToken, "profile");
|
|
276
253
|
}
|
|
277
254
|
return verifyViaIntrospect(accessToken);
|
|
278
255
|
}
|
|
279
|
-
async function verifyViaIntrospect(accessToken) {
|
|
256
|
+
async function verifyViaIntrospect(accessToken, cacheKeySuffix) {
|
|
280
257
|
if (!serviceApiKey) {
|
|
281
258
|
return {
|
|
282
259
|
error: createError(
|
|
@@ -287,7 +264,7 @@ function createVerifyAccessToken(deps) {
|
|
|
287
264
|
}
|
|
288
265
|
const url = `${baseUrl}${OAUTH_INTROSPECT_ENDPOINT}`;
|
|
289
266
|
logger.debug("Verifying access token:", url);
|
|
290
|
-
const cacheKey = await buildCacheKey(accessToken);
|
|
267
|
+
const cacheKey = cache ? await buildCacheKey(accessToken, cacheKeySuffix) : "";
|
|
291
268
|
if (cache) {
|
|
292
269
|
try {
|
|
293
270
|
const cached = await cache.adapter.get(cacheKey);
|
|
@@ -389,16 +366,23 @@ function createVerifyAccessToken(deps) {
|
|
|
389
366
|
}
|
|
390
367
|
return { data: userinfo };
|
|
391
368
|
}
|
|
392
|
-
async function buildCacheKey(token) {
|
|
369
|
+
async function buildCacheKey(token, suffix) {
|
|
370
|
+
let key;
|
|
393
371
|
if (typeof globalThis.crypto?.subtle?.digest === "function") {
|
|
394
372
|
const encoder = new TextEncoder();
|
|
395
373
|
const data = encoder.encode(token);
|
|
396
374
|
const hashBuffer = await crypto.subtle.digest("SHA-256", data);
|
|
397
375
|
const hashArray = Array.from(new Uint8Array(hashBuffer));
|
|
398
376
|
const hashHex = hashArray.map((b) => b.toString(HEX_BASE).padStart(HEX_BYTE_WIDTH, "0")).join("");
|
|
399
|
-
|
|
377
|
+
key = CACHE_KEY_PREFIX + hashHex;
|
|
378
|
+
} else {
|
|
379
|
+
const tokenPrefix = token.length > TOKEN_LOG_PREFIX_LENGTH ? token.substring(0, TOKEN_LOG_PREFIX_LENGTH) + "..." : "***";
|
|
380
|
+
logger.warn(
|
|
381
|
+
`crypto.subtle \u304C\u5229\u7528\u3067\u304D\u306A\u3044\u305F\u3081\u3001\u30AD\u30E3\u30C3\u30B7\u30E5\u30AD\u30FC\u306B\u30CF\u30C3\u30B7\u30E5\u5316\u3055\u308C\u3066\u3044\u306A\u3044\u30C8\u30FC\u30AF\u30F3\u3092\u4F7F\u7528\u3057\u307E\u3059 (token prefix: ${tokenPrefix})`
|
|
382
|
+
);
|
|
383
|
+
key = CACHE_KEY_PREFIX + token;
|
|
400
384
|
}
|
|
401
|
-
return
|
|
385
|
+
return suffix ? `${key}:${suffix}` : key;
|
|
402
386
|
}
|
|
403
387
|
return verifyAccessToken;
|
|
404
388
|
}
|
|
@@ -451,9 +435,15 @@ function createBillingMethods(deps) {
|
|
|
451
435
|
}
|
|
452
436
|
|
|
453
437
|
// src/client/version-check.ts
|
|
454
|
-
var SDK_VERSION = "1.
|
|
438
|
+
var SDK_VERSION = "1.13.0";
|
|
455
439
|
var SDK_USER_AGENT = `FFID-SDK/${SDK_VERSION} (TypeScript)`;
|
|
456
440
|
var SDK_VERSION_HEADER = "X-FFID-SDK-Version";
|
|
441
|
+
function sdkHeaders() {
|
|
442
|
+
return {
|
|
443
|
+
"User-Agent": SDK_USER_AGENT,
|
|
444
|
+
[SDK_VERSION_HEADER]: SDK_VERSION
|
|
445
|
+
};
|
|
446
|
+
}
|
|
457
447
|
var LATEST_VERSION_HEADER = "X-FFID-SDK-Latest-Version";
|
|
458
448
|
var SEMVER_PATTERN = /^\d+(\.\d+)*$/;
|
|
459
449
|
var _versionWarningShown = false;
|
|
@@ -487,178 +477,249 @@ npm install @feelflow/ffid-sdk@latest \u3067\u30A2\u30C3\u30D7\u30C7\u30FC\u30C8
|
|
|
487
477
|
}
|
|
488
478
|
}
|
|
489
479
|
|
|
490
|
-
// src/client/
|
|
491
|
-
var NO_CONTENT_STATUS = 204;
|
|
492
|
-
var SESSION_ENDPOINT = "/api/v1/auth/session";
|
|
493
|
-
var LOGOUT_ENDPOINT = "/api/v1/auth/signout";
|
|
480
|
+
// src/client/oauth-token.ts
|
|
494
481
|
var OAUTH_TOKEN_ENDPOINT = "/api/v1/oauth/token";
|
|
495
|
-
var OAUTH_USERINFO_ENDPOINT = "/api/v1/oauth/userinfo";
|
|
496
|
-
var OAUTH_AUTHORIZE_ENDPOINT = "/api/v1/oauth/authorize";
|
|
497
482
|
var OAUTH_REVOKE_ENDPOINT = "/api/v1/oauth/revoke";
|
|
498
|
-
var EXT_CHECK_ENDPOINT = "/api/v1/subscriptions/ext/check";
|
|
499
|
-
var SDK_LOG_PREFIX = "[FFID SDK]";
|
|
500
483
|
var MS_PER_SECOND = 1e3;
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
debug: () => {
|
|
506
|
-
},
|
|
507
|
-
info: () => {
|
|
508
|
-
},
|
|
509
|
-
warn: () => {
|
|
510
|
-
},
|
|
511
|
-
error: (...args) => console.error(SDK_LOG_PREFIX, ...args)
|
|
512
|
-
};
|
|
513
|
-
var consoleLogger = {
|
|
514
|
-
debug: (...args) => console.debug(SDK_LOG_PREFIX, ...args),
|
|
515
|
-
info: (...args) => console.info(SDK_LOG_PREFIX, ...args),
|
|
516
|
-
warn: (...args) => console.warn(SDK_LOG_PREFIX, ...args),
|
|
517
|
-
error: (...args) => console.error(SDK_LOG_PREFIX, ...args)
|
|
518
|
-
};
|
|
519
|
-
var FFID_ERROR_CODES = {
|
|
520
|
-
NETWORK_ERROR: "NETWORK_ERROR",
|
|
521
|
-
PARSE_ERROR: "PARSE_ERROR",
|
|
522
|
-
UNKNOWN_ERROR: "UNKNOWN_ERROR",
|
|
523
|
-
TOKEN_EXCHANGE_ERROR: "TOKEN_EXCHANGE_ERROR",
|
|
524
|
-
TOKEN_REFRESH_ERROR: "TOKEN_REFRESH_ERROR",
|
|
525
|
-
NO_TOKENS: "NO_TOKENS",
|
|
526
|
-
TOKEN_VERIFICATION_ERROR: "TOKEN_VERIFICATION_ERROR"
|
|
527
|
-
};
|
|
528
|
-
function createFFIDClient(config) {
|
|
529
|
-
if (!config.serviceCode || !config.serviceCode.trim()) {
|
|
530
|
-
throw new Error("FFID Client: serviceCode \u304C\u672A\u8A2D\u5B9A\u3067\u3059");
|
|
484
|
+
function validateTokenResponse(tokenResponse) {
|
|
485
|
+
const invalid = [];
|
|
486
|
+
if (!tokenResponse.access_token) {
|
|
487
|
+
invalid.push("access_token");
|
|
531
488
|
}
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
const clientId = config.clientId ?? config.serviceCode;
|
|
535
|
-
const resolvedRedirectUri = config.redirectUri ?? null;
|
|
536
|
-
const serviceApiKey = config.serviceApiKey?.trim();
|
|
537
|
-
const verifyStrategy = config.verifyStrategy ?? "jwt";
|
|
538
|
-
const cache = config.cache;
|
|
539
|
-
const timeout = config.timeout;
|
|
540
|
-
if (authMode === "service-key" && !serviceApiKey) {
|
|
541
|
-
throw new Error("FFID Client: service-key \u30E2\u30FC\u30C9\u3067\u306F serviceApiKey \u304C\u5FC5\u9808\u3067\u3059");
|
|
489
|
+
if (!tokenResponse.refresh_token) {
|
|
490
|
+
invalid.push("refresh_token");
|
|
542
491
|
}
|
|
543
|
-
if (
|
|
544
|
-
|
|
492
|
+
if (typeof tokenResponse.expires_in !== "number" || tokenResponse.expires_in <= 0) {
|
|
493
|
+
invalid.push("expires_in");
|
|
545
494
|
}
|
|
546
|
-
if (
|
|
547
|
-
|
|
495
|
+
if (invalid.length > 0) {
|
|
496
|
+
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(", ")}`;
|
|
548
497
|
}
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
498
|
+
return null;
|
|
499
|
+
}
|
|
500
|
+
function createOAuthTokenMethods(deps) {
|
|
501
|
+
const {
|
|
502
|
+
baseUrl,
|
|
503
|
+
clientId,
|
|
504
|
+
resolvedRedirectUri,
|
|
505
|
+
tokenStore,
|
|
506
|
+
logger,
|
|
507
|
+
errorCodes
|
|
508
|
+
} = deps;
|
|
509
|
+
async function exchangeCodeForTokens(code, codeVerifier) {
|
|
510
|
+
const url = `${baseUrl}${OAUTH_TOKEN_ENDPOINT}`;
|
|
511
|
+
logger.debug("Exchanging code for tokens:", url);
|
|
512
|
+
const effectiveRedirectUri = resolvedRedirectUri ?? (typeof window !== "undefined" ? window.location.origin + window.location.pathname : null);
|
|
513
|
+
if (!effectiveRedirectUri) {
|
|
514
|
+
logger.error("redirectUri is required for token exchange in SSR environments. Set config.redirectUri explicitly.");
|
|
515
|
+
return {
|
|
516
|
+
error: {
|
|
517
|
+
code: errorCodes.TOKEN_EXCHANGE_ERROR,
|
|
518
|
+
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"
|
|
519
|
+
}
|
|
520
|
+
};
|
|
521
|
+
}
|
|
522
|
+
const body = {
|
|
523
|
+
grant_type: "authorization_code",
|
|
524
|
+
code,
|
|
525
|
+
client_id: clientId,
|
|
526
|
+
redirect_uri: effectiveRedirectUri
|
|
527
|
+
};
|
|
528
|
+
if (codeVerifier) {
|
|
529
|
+
body.code_verifier = codeVerifier;
|
|
530
|
+
}
|
|
555
531
|
let response;
|
|
556
532
|
try {
|
|
557
|
-
response = await fetch(url,
|
|
533
|
+
response = await fetch(url, {
|
|
534
|
+
method: "POST",
|
|
535
|
+
credentials: "omit",
|
|
536
|
+
headers: { "Content-Type": "application/x-www-form-urlencoded", ...sdkHeaders() },
|
|
537
|
+
body: new URLSearchParams(body).toString()
|
|
538
|
+
});
|
|
558
539
|
} catch (error) {
|
|
559
|
-
logger.error("Network error:", error);
|
|
540
|
+
logger.error("Network error during token exchange:", error);
|
|
560
541
|
return {
|
|
561
542
|
error: {
|
|
562
|
-
code:
|
|
543
|
+
code: errorCodes.NETWORK_ERROR,
|
|
563
544
|
message: error instanceof Error ? error.message : "\u30CD\u30C3\u30C8\u30EF\u30FC\u30AF\u30A8\u30E9\u30FC\u304C\u767A\u751F\u3057\u307E\u3057\u305F"
|
|
564
545
|
}
|
|
565
546
|
};
|
|
566
547
|
}
|
|
567
|
-
|
|
568
|
-
const refreshResult = await refreshAccessToken();
|
|
569
|
-
if (!refreshResult.error) {
|
|
570
|
-
logger.debug("Token refreshed, retrying request");
|
|
571
|
-
const retryOptions = buildFetchOptions(options);
|
|
572
|
-
try {
|
|
573
|
-
response = await fetch(url, retryOptions);
|
|
574
|
-
} catch (retryError) {
|
|
575
|
-
logger.error("Network error on retry:", retryError);
|
|
576
|
-
return {
|
|
577
|
-
error: {
|
|
578
|
-
code: FFID_ERROR_CODES.NETWORK_ERROR,
|
|
579
|
-
message: retryError instanceof Error ? retryError.message : "\u30CD\u30C3\u30C8\u30EF\u30FC\u30AF\u30A8\u30E9\u30FC\u304C\u767A\u751F\u3057\u307E\u3057\u305F"
|
|
580
|
-
}
|
|
581
|
-
};
|
|
582
|
-
}
|
|
583
|
-
}
|
|
584
|
-
}
|
|
585
|
-
let raw;
|
|
548
|
+
let tokenResponse;
|
|
586
549
|
try {
|
|
587
|
-
|
|
550
|
+
tokenResponse = await response.json();
|
|
588
551
|
} catch (parseError) {
|
|
589
|
-
logger.error("Parse error
|
|
552
|
+
logger.error("Parse error during token exchange:", parseError);
|
|
590
553
|
return {
|
|
591
554
|
error: {
|
|
592
|
-
code:
|
|
593
|
-
message: `\
|
|
555
|
+
code: errorCodes.PARSE_ERROR,
|
|
556
|
+
message: `\u30C8\u30FC\u30AF\u30F3\u30EC\u30B9\u30DD\u30F3\u30B9\u306E\u89E3\u6790\u306B\u5931\u6557\u3057\u307E\u3057\u305F (status: ${response.status})`
|
|
594
557
|
}
|
|
595
558
|
};
|
|
596
559
|
}
|
|
597
|
-
logger.debug("Response:", response.status, raw);
|
|
598
|
-
checkVersionHeader(response, logger);
|
|
599
560
|
if (!response.ok) {
|
|
561
|
+
const errorBody = tokenResponse;
|
|
600
562
|
return {
|
|
601
|
-
error:
|
|
602
|
-
code:
|
|
603
|
-
message: "\
|
|
563
|
+
error: {
|
|
564
|
+
code: errorBody.error ?? errorCodes.TOKEN_EXCHANGE_ERROR,
|
|
565
|
+
message: errorBody.error_description ?? "\u30C8\u30FC\u30AF\u30F3\u4EA4\u63DB\u306B\u5931\u6557\u3057\u307E\u3057\u305F"
|
|
604
566
|
}
|
|
605
567
|
};
|
|
606
568
|
}
|
|
607
|
-
|
|
569
|
+
const validationError = validateTokenResponse(tokenResponse);
|
|
570
|
+
if (validationError) {
|
|
571
|
+
logger.error("Token exchange validation failed:", validationError);
|
|
608
572
|
return {
|
|
609
573
|
error: {
|
|
610
|
-
code:
|
|
611
|
-
message:
|
|
574
|
+
code: errorCodes.TOKEN_EXCHANGE_ERROR,
|
|
575
|
+
message: validationError
|
|
612
576
|
}
|
|
613
577
|
};
|
|
614
578
|
}
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
};
|
|
579
|
+
tokenStore.setTokens({
|
|
580
|
+
accessToken: tokenResponse.access_token,
|
|
581
|
+
refreshToken: tokenResponse.refresh_token,
|
|
582
|
+
expiresAt: Date.now() + tokenResponse.expires_in * MS_PER_SECOND
|
|
583
|
+
});
|
|
584
|
+
logger.debug("Token exchange successful");
|
|
585
|
+
return { data: void 0 };
|
|
622
586
|
}
|
|
623
|
-
function
|
|
624
|
-
|
|
587
|
+
async function refreshAccessToken() {
|
|
588
|
+
const tokens = tokenStore.getTokens();
|
|
589
|
+
if (!tokens) {
|
|
625
590
|
return {
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
"Content-Type": "application/json",
|
|
630
|
-
...sdkHeaders(),
|
|
631
|
-
"X-Service-Api-Key": serviceApiKey,
|
|
632
|
-
...options.headers
|
|
591
|
+
error: {
|
|
592
|
+
code: errorCodes.NO_TOKENS,
|
|
593
|
+
message: "\u30EA\u30D5\u30EC\u30C3\u30B7\u30E5\u30C8\u30FC\u30AF\u30F3\u304C\u3042\u308A\u307E\u305B\u3093"
|
|
633
594
|
}
|
|
634
595
|
};
|
|
635
596
|
}
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
};
|
|
643
|
-
if (tokens) {
|
|
644
|
-
headers["Authorization"] = `Bearer ${tokens.accessToken}`;
|
|
645
|
-
}
|
|
646
|
-
return {
|
|
647
|
-
...options,
|
|
597
|
+
const url = `${baseUrl}${OAUTH_TOKEN_ENDPOINT}`;
|
|
598
|
+
logger.debug("Refreshing access token:", url);
|
|
599
|
+
let response;
|
|
600
|
+
try {
|
|
601
|
+
response = await fetch(url, {
|
|
602
|
+
method: "POST",
|
|
648
603
|
credentials: "omit",
|
|
649
|
-
headers
|
|
604
|
+
headers: { "Content-Type": "application/x-www-form-urlencoded", ...sdkHeaders() },
|
|
605
|
+
body: new URLSearchParams({
|
|
606
|
+
grant_type: "refresh_token",
|
|
607
|
+
refresh_token: tokens.refreshToken,
|
|
608
|
+
client_id: clientId
|
|
609
|
+
}).toString()
|
|
610
|
+
});
|
|
611
|
+
} catch (error) {
|
|
612
|
+
logger.error("Network error during token refresh:", error);
|
|
613
|
+
return {
|
|
614
|
+
error: {
|
|
615
|
+
code: errorCodes.NETWORK_ERROR,
|
|
616
|
+
message: error instanceof Error ? error.message : "\u30CD\u30C3\u30C8\u30EF\u30FC\u30AF\u30A8\u30E9\u30FC\u304C\u767A\u751F\u3057\u307E\u3057\u305F"
|
|
617
|
+
}
|
|
650
618
|
};
|
|
651
619
|
}
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
620
|
+
let tokenResponse;
|
|
621
|
+
try {
|
|
622
|
+
tokenResponse = await response.json();
|
|
623
|
+
} catch (parseError) {
|
|
624
|
+
logger.error("Parse error during token refresh:", parseError);
|
|
625
|
+
return {
|
|
626
|
+
error: {
|
|
627
|
+
code: errorCodes.PARSE_ERROR,
|
|
628
|
+
message: `\u30C8\u30FC\u30AF\u30F3\u30EC\u30B9\u30DD\u30F3\u30B9\u306E\u89E3\u6790\u306B\u5931\u6557\u3057\u307E\u3057\u305F (status: ${response.status})`
|
|
629
|
+
}
|
|
630
|
+
};
|
|
631
|
+
}
|
|
632
|
+
if (!response.ok) {
|
|
633
|
+
const errorBody = tokenResponse;
|
|
634
|
+
logger.error("Token refresh failed:", errorBody);
|
|
635
|
+
const irrecoverableErrors = ["token_revoked", "invalid_grant"];
|
|
636
|
+
if (errorBody.error && irrecoverableErrors.includes(errorBody.error)) {
|
|
637
|
+
tokenStore.clearTokens();
|
|
638
|
+
logger.debug("Cleared tokens due to irrecoverable refresh error:", errorBody.error);
|
|
659
639
|
}
|
|
660
|
-
|
|
640
|
+
return {
|
|
641
|
+
error: {
|
|
642
|
+
code: errorBody.error ?? errorCodes.TOKEN_REFRESH_ERROR,
|
|
643
|
+
message: errorBody.error_description ?? "\u30C8\u30FC\u30AF\u30F3\u30EA\u30D5\u30EC\u30C3\u30B7\u30E5\u306B\u5931\u6557\u3057\u307E\u3057\u305F"
|
|
644
|
+
}
|
|
645
|
+
};
|
|
646
|
+
}
|
|
647
|
+
const validationError = validateTokenResponse(tokenResponse);
|
|
648
|
+
if (validationError) {
|
|
649
|
+
logger.error("Token refresh validation failed:", validationError);
|
|
650
|
+
return {
|
|
651
|
+
error: {
|
|
652
|
+
code: errorCodes.TOKEN_REFRESH_ERROR,
|
|
653
|
+
message: validationError
|
|
654
|
+
}
|
|
655
|
+
};
|
|
656
|
+
}
|
|
657
|
+
tokenStore.setTokens({
|
|
658
|
+
accessToken: tokenResponse.access_token,
|
|
659
|
+
refreshToken: tokenResponse.refresh_token,
|
|
660
|
+
expiresAt: Date.now() + tokenResponse.expires_in * MS_PER_SECOND
|
|
661
|
+
});
|
|
662
|
+
logger.debug("Token refresh successful");
|
|
663
|
+
return { data: void 0 };
|
|
661
664
|
}
|
|
665
|
+
async function signOutToken() {
|
|
666
|
+
const tokens = tokenStore.getTokens();
|
|
667
|
+
tokenStore.clearTokens();
|
|
668
|
+
if (!tokens) {
|
|
669
|
+
logger.debug("No tokens to revoke");
|
|
670
|
+
return { data: void 0 };
|
|
671
|
+
}
|
|
672
|
+
const url = `${baseUrl}${OAUTH_REVOKE_ENDPOINT}`;
|
|
673
|
+
logger.debug("Revoking token:", url);
|
|
674
|
+
try {
|
|
675
|
+
const response = await fetch(url, {
|
|
676
|
+
method: "POST",
|
|
677
|
+
credentials: "omit",
|
|
678
|
+
headers: { "Content-Type": "application/x-www-form-urlencoded", ...sdkHeaders() },
|
|
679
|
+
body: new URLSearchParams({
|
|
680
|
+
token: tokens.accessToken,
|
|
681
|
+
client_id: clientId
|
|
682
|
+
}).toString()
|
|
683
|
+
});
|
|
684
|
+
if (!response.ok) {
|
|
685
|
+
logger.warn(
|
|
686
|
+
"\u30C8\u30FC\u30AF\u30F3\u7121\u52B9\u5316\u30EA\u30AF\u30A8\u30B9\u30C8\u304C\u30B5\u30FC\u30D0\u30FC\u3067\u5931\u6557\u3057\u307E\u3057\u305F:",
|
|
687
|
+
`status=${response.status}`
|
|
688
|
+
);
|
|
689
|
+
}
|
|
690
|
+
} catch (error) {
|
|
691
|
+
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);
|
|
692
|
+
}
|
|
693
|
+
logger.debug("Token sign-out completed");
|
|
694
|
+
return { data: void 0 };
|
|
695
|
+
}
|
|
696
|
+
return { exchangeCodeForTokens, refreshAccessToken, signOutToken };
|
|
697
|
+
}
|
|
698
|
+
|
|
699
|
+
// src/client/session.ts
|
|
700
|
+
var SESSION_ENDPOINT = "/api/v1/auth/session";
|
|
701
|
+
var LOGOUT_ENDPOINT = "/api/v1/auth/signout";
|
|
702
|
+
var OAUTH_USERINFO_ENDPOINT = "/api/v1/oauth/userinfo";
|
|
703
|
+
var NO_CONTENT_STATUS = 204;
|
|
704
|
+
var UNAUTHORIZED_STATUS = 401;
|
|
705
|
+
function normalizeFFIDUser(user) {
|
|
706
|
+
return {
|
|
707
|
+
...user,
|
|
708
|
+
locale: user.locale ?? null,
|
|
709
|
+
timezone: user.timezone ?? null
|
|
710
|
+
};
|
|
711
|
+
}
|
|
712
|
+
function createSessionMethods(deps) {
|
|
713
|
+
const {
|
|
714
|
+
authMode,
|
|
715
|
+
baseUrl,
|
|
716
|
+
serviceCode,
|
|
717
|
+
tokenStore,
|
|
718
|
+
logger,
|
|
719
|
+
fetchWithAuth,
|
|
720
|
+
refreshAccessToken,
|
|
721
|
+
errorCodes
|
|
722
|
+
} = deps;
|
|
662
723
|
async function getSession() {
|
|
663
724
|
if (authMode === "token") {
|
|
664
725
|
return getSessionFromUserinfo();
|
|
@@ -675,19 +736,12 @@ function createFFIDClient(config) {
|
|
|
675
736
|
}
|
|
676
737
|
return result;
|
|
677
738
|
}
|
|
678
|
-
function normalizeFFIDUser(user) {
|
|
679
|
-
return {
|
|
680
|
-
...user,
|
|
681
|
-
locale: user.locale ?? null,
|
|
682
|
-
timezone: user.timezone ?? null
|
|
683
|
-
};
|
|
684
|
-
}
|
|
685
739
|
async function getSessionFromUserinfo() {
|
|
686
740
|
const tokens = tokenStore.getTokens();
|
|
687
741
|
if (!tokens) {
|
|
688
742
|
return {
|
|
689
743
|
error: {
|
|
690
|
-
code:
|
|
744
|
+
code: errorCodes.NO_TOKENS,
|
|
691
745
|
message: "\u30C8\u30FC\u30AF\u30F3\u304C\u4FDD\u5B58\u3055\u308C\u3066\u3044\u307E\u305B\u3093"
|
|
692
746
|
}
|
|
693
747
|
};
|
|
@@ -708,7 +762,7 @@ function createFFIDClient(config) {
|
|
|
708
762
|
logger.error("Network error:", error);
|
|
709
763
|
return {
|
|
710
764
|
error: {
|
|
711
|
-
code:
|
|
765
|
+
code: errorCodes.NETWORK_ERROR,
|
|
712
766
|
message: error instanceof Error ? error.message : "\u30CD\u30C3\u30C8\u30EF\u30FC\u30AF\u30A8\u30E9\u30FC\u304C\u767A\u751F\u3057\u307E\u3057\u305F"
|
|
713
767
|
}
|
|
714
768
|
};
|
|
@@ -731,7 +785,7 @@ function createFFIDClient(config) {
|
|
|
731
785
|
logger.error("Network error on retry:", retryError);
|
|
732
786
|
return {
|
|
733
787
|
error: {
|
|
734
|
-
code:
|
|
788
|
+
code: errorCodes.NETWORK_ERROR,
|
|
735
789
|
message: retryError instanceof Error ? retryError.message : "\u30CD\u30C3\u30C8\u30EF\u30FC\u30AF\u30A8\u30E9\u30FC\u304C\u767A\u751F\u3057\u307E\u3057\u305F"
|
|
736
790
|
}
|
|
737
791
|
};
|
|
@@ -748,7 +802,7 @@ function createFFIDClient(config) {
|
|
|
748
802
|
logger.error("Parse error:", parseError, "Status:", response.status);
|
|
749
803
|
return {
|
|
750
804
|
error: {
|
|
751
|
-
code:
|
|
805
|
+
code: errorCodes.PARSE_ERROR,
|
|
752
806
|
message: `\u30B5\u30FC\u30D0\u30FC\u304B\u3089\u4E0D\u6B63\u306A\u30EC\u30B9\u30DD\u30F3\u30B9\u3092\u53D7\u4FE1\u3057\u307E\u3057\u305F (status: ${response.status})`
|
|
753
807
|
}
|
|
754
808
|
};
|
|
@@ -757,7 +811,7 @@ function createFFIDClient(config) {
|
|
|
757
811
|
const errorBody = rawUserinfo;
|
|
758
812
|
return {
|
|
759
813
|
error: {
|
|
760
|
-
code: errorBody.code ??
|
|
814
|
+
code: errorBody.code ?? errorCodes.UNKNOWN_ERROR,
|
|
761
815
|
message: errorBody.message ?? "\u4E0D\u660E\u306A\u30A8\u30E9\u30FC\u304C\u767A\u751F\u3057\u307E\u3057\u305F"
|
|
762
816
|
}
|
|
763
817
|
};
|
|
@@ -776,16 +830,10 @@ function createFFIDClient(config) {
|
|
|
776
830
|
data: {
|
|
777
831
|
user,
|
|
778
832
|
organizations: [],
|
|
779
|
-
subscriptions: mapUserinfoSubscriptionToSession(userinfo,
|
|
833
|
+
subscriptions: mapUserinfoSubscriptionToSession(userinfo, serviceCode)
|
|
780
834
|
}
|
|
781
835
|
};
|
|
782
836
|
}
|
|
783
|
-
async function signOut() {
|
|
784
|
-
if (authMode === "token") {
|
|
785
|
-
return signOutToken();
|
|
786
|
-
}
|
|
787
|
-
return signOutCookie();
|
|
788
|
-
}
|
|
789
837
|
async function signOutCookie() {
|
|
790
838
|
const url = `${baseUrl}${LOGOUT_ENDPOINT}`;
|
|
791
839
|
logger.debug("Fetching:", url);
|
|
@@ -800,7 +848,7 @@ function createFFIDClient(config) {
|
|
|
800
848
|
logger.error("Network error:", error);
|
|
801
849
|
return {
|
|
802
850
|
error: {
|
|
803
|
-
code:
|
|
851
|
+
code: errorCodes.NETWORK_ERROR,
|
|
804
852
|
message: error instanceof Error ? error.message : "\u30CD\u30C3\u30C8\u30EF\u30FC\u30AF\u30A8\u30E9\u30FC\u304C\u767A\u751F\u3057\u307E\u3057\u305F"
|
|
805
853
|
}
|
|
806
854
|
};
|
|
@@ -814,14 +862,20 @@ function createFFIDClient(config) {
|
|
|
814
862
|
const raw = await response.json();
|
|
815
863
|
return {
|
|
816
864
|
error: raw.error ?? {
|
|
817
|
-
code:
|
|
865
|
+
code: errorCodes.UNKNOWN_ERROR,
|
|
818
866
|
message: "\u4E0D\u660E\u306A\u30A8\u30E9\u30FC\u304C\u767A\u751F\u3057\u307E\u3057\u305F"
|
|
819
867
|
}
|
|
820
868
|
};
|
|
821
|
-
} catch {
|
|
869
|
+
} catch (parseError) {
|
|
870
|
+
logger.error(
|
|
871
|
+
"\u30B5\u30A4\u30F3\u30A2\u30A6\u30C8\u30EC\u30B9\u30DD\u30F3\u30B9\u306E\u89E3\u6790\u306B\u5931\u6557\u3057\u307E\u3057\u305F:",
|
|
872
|
+
parseError,
|
|
873
|
+
"Status:",
|
|
874
|
+
response.status
|
|
875
|
+
);
|
|
822
876
|
return {
|
|
823
877
|
error: {
|
|
824
|
-
code:
|
|
878
|
+
code: errorCodes.PARSE_ERROR,
|
|
825
879
|
message: `\u30B5\u30FC\u30D0\u30FC\u304B\u3089\u4E0D\u6B63\u306A\u30EC\u30B9\u30DD\u30F3\u30B9\u3092\u53D7\u4FE1\u3057\u307E\u3057\u305F (status: ${response.status})`
|
|
826
880
|
}
|
|
827
881
|
};
|
|
@@ -830,125 +884,496 @@ function createFFIDClient(config) {
|
|
|
830
884
|
logger.debug("Response:", response.status);
|
|
831
885
|
return { data: void 0 };
|
|
832
886
|
}
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
887
|
+
return { getSession, signOutCookie };
|
|
888
|
+
}
|
|
889
|
+
|
|
890
|
+
// src/auth/pkce.ts
|
|
891
|
+
var VERIFIER_STORAGE_KEY = "ffid_code_verifier";
|
|
892
|
+
var CODE_VERIFIER_MIN_LENGTH = 43;
|
|
893
|
+
var UNRESERVED_CHARS = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-._~";
|
|
894
|
+
function generateCodeVerifier() {
|
|
895
|
+
const length = CODE_VERIFIER_MIN_LENGTH;
|
|
896
|
+
const randomValues = new Uint8Array(length);
|
|
897
|
+
crypto.getRandomValues(randomValues);
|
|
898
|
+
let verifier = "";
|
|
899
|
+
for (let i = 0; i < length; i++) {
|
|
900
|
+
verifier += UNRESERVED_CHARS[randomValues[i] % UNRESERVED_CHARS.length];
|
|
901
|
+
}
|
|
902
|
+
return verifier;
|
|
903
|
+
}
|
|
904
|
+
async function generateCodeChallenge(verifier) {
|
|
905
|
+
const encoder = new TextEncoder();
|
|
906
|
+
const data = encoder.encode(verifier);
|
|
907
|
+
const digest = await crypto.subtle.digest("SHA-256", data);
|
|
908
|
+
return base64UrlEncode(digest);
|
|
909
|
+
}
|
|
910
|
+
function storeCodeVerifier(verifier, logger) {
|
|
911
|
+
try {
|
|
912
|
+
if (typeof window === "undefined") {
|
|
913
|
+
logger?.warn("storeCodeVerifier: sessionStorage is not available in SSR context");
|
|
914
|
+
return false;
|
|
839
915
|
}
|
|
840
|
-
|
|
841
|
-
|
|
916
|
+
window.sessionStorage.setItem(VERIFIER_STORAGE_KEY, verifier);
|
|
917
|
+
return true;
|
|
918
|
+
} catch (error) {
|
|
919
|
+
logger?.warn("storeCodeVerifier: sessionStorage \u3078\u306E\u4FDD\u5B58\u306B\u5931\u6557\u3057\u307E\u3057\u305F:", error);
|
|
920
|
+
return false;
|
|
921
|
+
}
|
|
922
|
+
}
|
|
923
|
+
function retrieveCodeVerifier(logger) {
|
|
924
|
+
try {
|
|
925
|
+
if (typeof window === "undefined") return null;
|
|
926
|
+
const verifier = window.sessionStorage.getItem(VERIFIER_STORAGE_KEY);
|
|
927
|
+
if (verifier) {
|
|
928
|
+
window.sessionStorage.removeItem(VERIFIER_STORAGE_KEY);
|
|
929
|
+
}
|
|
930
|
+
return verifier;
|
|
931
|
+
} catch (error) {
|
|
932
|
+
logger?.warn("retrieveCodeVerifier: sessionStorage \u304B\u3089\u306E\u53D6\u5F97\u306B\u5931\u6557\u3057\u307E\u3057\u305F:", error);
|
|
933
|
+
return null;
|
|
934
|
+
}
|
|
935
|
+
}
|
|
936
|
+
function base64UrlEncode(buffer) {
|
|
937
|
+
const bytes = new Uint8Array(buffer);
|
|
938
|
+
let binary = "";
|
|
939
|
+
for (let i = 0; i < bytes.length; i++) {
|
|
940
|
+
binary += String.fromCharCode(bytes[i]);
|
|
941
|
+
}
|
|
942
|
+
return btoa(binary).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
|
|
943
|
+
}
|
|
944
|
+
|
|
945
|
+
// src/client/redirect.ts
|
|
946
|
+
var OAUTH_AUTHORIZE_ENDPOINT = "/api/v1/oauth/authorize";
|
|
947
|
+
var STATE_RANDOM_BYTES = 16;
|
|
948
|
+
var HEX_BASE2 = 16;
|
|
949
|
+
function generateRandomState() {
|
|
950
|
+
const array = new Uint8Array(STATE_RANDOM_BYTES);
|
|
951
|
+
crypto.getRandomValues(array);
|
|
952
|
+
return Array.from(array, (byte) => byte.toString(HEX_BASE2).padStart(2, "0")).join("");
|
|
953
|
+
}
|
|
954
|
+
function createRedirectMethods(deps) {
|
|
955
|
+
const {
|
|
956
|
+
authMode,
|
|
957
|
+
baseUrl,
|
|
958
|
+
clientId,
|
|
959
|
+
serviceCode,
|
|
960
|
+
resolvedRedirectUri,
|
|
961
|
+
logger
|
|
962
|
+
} = deps;
|
|
963
|
+
async function redirectToAuthorize() {
|
|
964
|
+
const verifier = generateCodeVerifier();
|
|
965
|
+
storeCodeVerifier(verifier, logger);
|
|
966
|
+
let challenge;
|
|
842
967
|
try {
|
|
843
|
-
await
|
|
844
|
-
method: "POST",
|
|
845
|
-
credentials: "omit",
|
|
846
|
-
headers: { "Content-Type": "application/x-www-form-urlencoded", ...sdkHeaders() },
|
|
847
|
-
body: new URLSearchParams({
|
|
848
|
-
token: tokens.accessToken,
|
|
849
|
-
client_id: clientId
|
|
850
|
-
}).toString()
|
|
851
|
-
});
|
|
968
|
+
challenge = await generateCodeChallenge(verifier);
|
|
852
969
|
} catch (error) {
|
|
853
|
-
|
|
970
|
+
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";
|
|
971
|
+
logger.error("PKCE \u30B3\u30FC\u30C9\u30C1\u30E3\u30EC\u30F3\u30B8\u306E\u751F\u6210\u306B\u5931\u6557\u3057\u307E\u3057\u305F:", error);
|
|
972
|
+
return { success: false, error: errorMessage };
|
|
973
|
+
}
|
|
974
|
+
const state = generateRandomState();
|
|
975
|
+
const redirectUri = resolvedRedirectUri ?? window.location.origin + window.location.pathname;
|
|
976
|
+
const params = new URLSearchParams({
|
|
977
|
+
response_type: "code",
|
|
978
|
+
client_id: clientId,
|
|
979
|
+
redirect_uri: redirectUri,
|
|
980
|
+
state,
|
|
981
|
+
code_challenge: challenge,
|
|
982
|
+
code_challenge_method: "S256"
|
|
983
|
+
});
|
|
984
|
+
const authorizeUrl = `${baseUrl}${OAUTH_AUTHORIZE_ENDPOINT}?${params.toString()}`;
|
|
985
|
+
logger.debug("Redirecting to authorize:", authorizeUrl);
|
|
986
|
+
window.location.href = authorizeUrl;
|
|
987
|
+
return { success: true };
|
|
988
|
+
}
|
|
989
|
+
async function redirectToLogin() {
|
|
990
|
+
if (typeof window === "undefined") {
|
|
991
|
+
logger.warn("SSR \u74B0\u5883\u3067\u306F\u30EA\u30C0\u30A4\u30EC\u30AF\u30C8\u3067\u304D\u307E\u305B\u3093");
|
|
992
|
+
return { success: false, error: "SSR \u74B0\u5883\u3067\u306F\u30EA\u30C0\u30A4\u30EC\u30AF\u30C8\u3067\u304D\u307E\u305B\u3093" };
|
|
854
993
|
}
|
|
855
|
-
|
|
856
|
-
|
|
994
|
+
if (authMode === "token") {
|
|
995
|
+
return redirectToAuthorize();
|
|
996
|
+
}
|
|
997
|
+
const currentUrl = window.location.href;
|
|
998
|
+
const loginUrl = `${baseUrl}/login?redirect=${encodeURIComponent(currentUrl)}&service=${encodeURIComponent(serviceCode)}`;
|
|
999
|
+
logger.debug("Redirecting to login:", loginUrl);
|
|
1000
|
+
window.location.href = loginUrl;
|
|
1001
|
+
return { success: true };
|
|
857
1002
|
}
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
if (
|
|
863
|
-
|
|
1003
|
+
function getLoginUrl(redirectUrl) {
|
|
1004
|
+
let redirect;
|
|
1005
|
+
if (redirectUrl != null) {
|
|
1006
|
+
redirect = redirectUrl;
|
|
1007
|
+
} else if (typeof window !== "undefined") {
|
|
1008
|
+
redirect = window.location.href;
|
|
1009
|
+
} else {
|
|
1010
|
+
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");
|
|
1011
|
+
redirect = "";
|
|
1012
|
+
}
|
|
1013
|
+
return `${baseUrl}/login?redirect=${encodeURIComponent(redirect)}&service=${encodeURIComponent(serviceCode)}`;
|
|
1014
|
+
}
|
|
1015
|
+
function getSignupUrl(redirectUrl) {
|
|
1016
|
+
let redirect;
|
|
1017
|
+
if (redirectUrl != null) {
|
|
1018
|
+
redirect = redirectUrl;
|
|
1019
|
+
} else if (typeof window !== "undefined") {
|
|
1020
|
+
redirect = window.location.href;
|
|
1021
|
+
} else {
|
|
1022
|
+
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");
|
|
1023
|
+
redirect = "";
|
|
1024
|
+
}
|
|
1025
|
+
return `${baseUrl}/signup?redirect=${encodeURIComponent(redirect)}&service=${encodeURIComponent(serviceCode)}`;
|
|
1026
|
+
}
|
|
1027
|
+
return { redirectToLogin, redirectToAuthorize, getLoginUrl, getSignupUrl };
|
|
1028
|
+
}
|
|
1029
|
+
|
|
1030
|
+
// src/client/password-reset.ts
|
|
1031
|
+
var RESET_PASSWORD_BASE = "/api/v1/auth/reset-password";
|
|
1032
|
+
function isBlank(value) {
|
|
1033
|
+
return !value || !value.trim();
|
|
1034
|
+
}
|
|
1035
|
+
function createPasswordResetMethods(deps) {
|
|
1036
|
+
const { baseUrl, logger, createError, fetchWithAuth, errorCodes } = deps;
|
|
1037
|
+
async function fetchPublic(endpoint, options = {}) {
|
|
1038
|
+
const url = `${baseUrl}${endpoint}`;
|
|
1039
|
+
logger.debug("Fetching (public):", url);
|
|
1040
|
+
let response;
|
|
1041
|
+
try {
|
|
1042
|
+
response = await fetch(url, {
|
|
1043
|
+
...options,
|
|
1044
|
+
credentials: "include",
|
|
1045
|
+
headers: {
|
|
1046
|
+
"Content-Type": "application/json",
|
|
1047
|
+
...sdkHeaders(),
|
|
1048
|
+
...options.headers
|
|
1049
|
+
}
|
|
1050
|
+
});
|
|
1051
|
+
} catch (error) {
|
|
1052
|
+
logger.error("Network error:", error);
|
|
864
1053
|
return {
|
|
865
1054
|
error: {
|
|
866
|
-
code:
|
|
867
|
-
message: "
|
|
1055
|
+
code: errorCodes.NETWORK_ERROR,
|
|
1056
|
+
message: error instanceof Error ? error.message : "\u30CD\u30C3\u30C8\u30EF\u30FC\u30AF\u30A8\u30E9\u30FC\u304C\u767A\u751F\u3057\u307E\u3057\u305F"
|
|
868
1057
|
}
|
|
869
1058
|
};
|
|
870
1059
|
}
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
1060
|
+
let raw;
|
|
1061
|
+
try {
|
|
1062
|
+
raw = await response.json();
|
|
1063
|
+
} catch (parseError) {
|
|
1064
|
+
logger.error("Parse error:", parseError, "Status:", response.status);
|
|
1065
|
+
return {
|
|
1066
|
+
error: {
|
|
1067
|
+
code: errorCodes.PARSE_ERROR,
|
|
1068
|
+
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})`
|
|
1069
|
+
}
|
|
1070
|
+
};
|
|
1071
|
+
}
|
|
1072
|
+
logger.debug("Response (public):", response.status, raw);
|
|
1073
|
+
checkVersionHeader(response, logger);
|
|
1074
|
+
if (!response.ok) {
|
|
1075
|
+
return {
|
|
1076
|
+
error: raw.error ?? {
|
|
1077
|
+
code: errorCodes.UNKNOWN_ERROR,
|
|
1078
|
+
message: "\u30D1\u30B9\u30EF\u30FC\u30C9\u30EA\u30BB\u30C3\u30C8\u306B\u5931\u6557\u3057\u307E\u3057\u305F"
|
|
1079
|
+
}
|
|
1080
|
+
};
|
|
1081
|
+
}
|
|
1082
|
+
if (raw.data === void 0) {
|
|
1083
|
+
return {
|
|
1084
|
+
error: {
|
|
1085
|
+
code: errorCodes.UNKNOWN_ERROR,
|
|
1086
|
+
message: "\u30B5\u30FC\u30D0\u30FC\u304B\u3089\u30C7\u30FC\u30BF\u304C\u8FD4\u3055\u308C\u307E\u305B\u3093\u3067\u3057\u305F"
|
|
1087
|
+
}
|
|
1088
|
+
};
|
|
1089
|
+
}
|
|
1090
|
+
return { data: raw.data };
|
|
1091
|
+
}
|
|
1092
|
+
async function requestPasswordReset(email) {
|
|
1093
|
+
if (isBlank(email)) {
|
|
1094
|
+
return {
|
|
1095
|
+
error: createError("VALIDATION_ERROR", "\u30E1\u30FC\u30EB\u30A2\u30C9\u30EC\u30B9\u306F\u5FC5\u9808\u3067\u3059")
|
|
1096
|
+
};
|
|
1097
|
+
}
|
|
1098
|
+
return fetchPublic(
|
|
1099
|
+
RESET_PASSWORD_BASE,
|
|
1100
|
+
{
|
|
1101
|
+
method: "POST",
|
|
1102
|
+
body: JSON.stringify({ email })
|
|
1103
|
+
}
|
|
1104
|
+
);
|
|
1105
|
+
}
|
|
1106
|
+
async function verifyPasswordResetToken(accessToken) {
|
|
1107
|
+
if (isBlank(accessToken)) {
|
|
1108
|
+
return {
|
|
1109
|
+
error: createError("VALIDATION_ERROR", "\u30A2\u30AF\u30BB\u30B9\u30C8\u30FC\u30AF\u30F3\u306F\u5FC5\u9808\u3067\u3059")
|
|
1110
|
+
};
|
|
879
1111
|
}
|
|
1112
|
+
const query = new URLSearchParams({
|
|
1113
|
+
access_token: accessToken,
|
|
1114
|
+
type: "recovery"
|
|
1115
|
+
});
|
|
1116
|
+
return fetchPublic(
|
|
1117
|
+
`${RESET_PASSWORD_BASE}/verify?${query.toString()}`
|
|
1118
|
+
);
|
|
1119
|
+
}
|
|
1120
|
+
async function establishResetSession(accessToken, refreshToken) {
|
|
1121
|
+
if (isBlank(accessToken)) {
|
|
1122
|
+
return {
|
|
1123
|
+
error: createError("VALIDATION_ERROR", "\u30A2\u30AF\u30BB\u30B9\u30C8\u30FC\u30AF\u30F3\u306F\u5FC5\u9808\u3067\u3059")
|
|
1124
|
+
};
|
|
1125
|
+
}
|
|
1126
|
+
if (isBlank(refreshToken)) {
|
|
1127
|
+
return {
|
|
1128
|
+
error: createError("VALIDATION_ERROR", "\u30EA\u30D5\u30EC\u30C3\u30B7\u30E5\u30C8\u30FC\u30AF\u30F3\u306F\u5FC5\u9808\u3067\u3059")
|
|
1129
|
+
};
|
|
1130
|
+
}
|
|
1131
|
+
return fetchPublic(
|
|
1132
|
+
`${RESET_PASSWORD_BASE}/session`,
|
|
1133
|
+
{
|
|
1134
|
+
method: "POST",
|
|
1135
|
+
body: JSON.stringify({ accessToken, refreshToken })
|
|
1136
|
+
}
|
|
1137
|
+
);
|
|
1138
|
+
}
|
|
1139
|
+
async function confirmPasswordReset(password) {
|
|
1140
|
+
if (isBlank(password)) {
|
|
1141
|
+
return {
|
|
1142
|
+
error: createError("VALIDATION_ERROR", "\u30D1\u30B9\u30EF\u30FC\u30C9\u306F\u5FC5\u9808\u3067\u3059")
|
|
1143
|
+
};
|
|
1144
|
+
}
|
|
1145
|
+
return fetchWithAuth(
|
|
1146
|
+
`${RESET_PASSWORD_BASE}/confirm`,
|
|
1147
|
+
{
|
|
1148
|
+
method: "POST",
|
|
1149
|
+
body: JSON.stringify({ password })
|
|
1150
|
+
}
|
|
1151
|
+
);
|
|
1152
|
+
}
|
|
1153
|
+
return {
|
|
1154
|
+
requestPasswordReset,
|
|
1155
|
+
verifyPasswordResetToken,
|
|
1156
|
+
establishResetSession,
|
|
1157
|
+
confirmPasswordReset
|
|
1158
|
+
};
|
|
1159
|
+
}
|
|
1160
|
+
|
|
1161
|
+
// src/client/otp.ts
|
|
1162
|
+
var OTP_BASE = "/api/v1/auth/otp";
|
|
1163
|
+
function isBlank2(value) {
|
|
1164
|
+
return !value || !value.trim();
|
|
1165
|
+
}
|
|
1166
|
+
function createOtpMethods(deps) {
|
|
1167
|
+
const { baseUrl, logger, createError, errorCodes } = deps;
|
|
1168
|
+
async function fetchPublic(endpoint, options = {}) {
|
|
1169
|
+
const url = `${baseUrl}${endpoint}`;
|
|
1170
|
+
logger.debug("Fetching (public):", url);
|
|
880
1171
|
let response;
|
|
881
1172
|
try {
|
|
882
1173
|
response = await fetch(url, {
|
|
883
|
-
|
|
884
|
-
credentials: "
|
|
885
|
-
headers: {
|
|
886
|
-
|
|
1174
|
+
...options,
|
|
1175
|
+
credentials: "include",
|
|
1176
|
+
headers: {
|
|
1177
|
+
"Content-Type": "application/json",
|
|
1178
|
+
...sdkHeaders(),
|
|
1179
|
+
...options.headers
|
|
1180
|
+
}
|
|
887
1181
|
});
|
|
888
1182
|
} catch (error) {
|
|
889
|
-
logger.error("Network error
|
|
1183
|
+
logger.error("Network error:", error);
|
|
1184
|
+
return {
|
|
1185
|
+
error: {
|
|
1186
|
+
code: errorCodes.NETWORK_ERROR,
|
|
1187
|
+
message: error instanceof Error ? error.message : "\u30CD\u30C3\u30C8\u30EF\u30FC\u30AF\u30A8\u30E9\u30FC\u304C\u767A\u751F\u3057\u307E\u3057\u305F"
|
|
1188
|
+
}
|
|
1189
|
+
};
|
|
1190
|
+
}
|
|
1191
|
+
let raw;
|
|
1192
|
+
try {
|
|
1193
|
+
raw = await response.json();
|
|
1194
|
+
} catch (parseError) {
|
|
1195
|
+
logger.error("Parse error:", parseError, "Status:", response.status);
|
|
1196
|
+
return {
|
|
1197
|
+
error: {
|
|
1198
|
+
code: errorCodes.PARSE_ERROR,
|
|
1199
|
+
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})`
|
|
1200
|
+
}
|
|
1201
|
+
};
|
|
1202
|
+
}
|
|
1203
|
+
logger.debug("Response (public):", response.status, raw);
|
|
1204
|
+
checkVersionHeader(response, logger);
|
|
1205
|
+
if (!response.ok) {
|
|
1206
|
+
return {
|
|
1207
|
+
error: raw.error ?? {
|
|
1208
|
+
code: errorCodes.UNKNOWN_ERROR,
|
|
1209
|
+
message: "OTP\u8A8D\u8A3C\u306B\u5931\u6557\u3057\u307E\u3057\u305F"
|
|
1210
|
+
}
|
|
1211
|
+
};
|
|
1212
|
+
}
|
|
1213
|
+
if (raw.data === void 0) {
|
|
1214
|
+
return {
|
|
1215
|
+
error: {
|
|
1216
|
+
code: errorCodes.UNKNOWN_ERROR,
|
|
1217
|
+
message: "\u30B5\u30FC\u30D0\u30FC\u304B\u3089\u30C7\u30FC\u30BF\u304C\u8FD4\u3055\u308C\u307E\u305B\u3093\u3067\u3057\u305F"
|
|
1218
|
+
}
|
|
1219
|
+
};
|
|
1220
|
+
}
|
|
1221
|
+
return { data: raw.data };
|
|
1222
|
+
}
|
|
1223
|
+
async function sendOtp(email, options) {
|
|
1224
|
+
if (isBlank2(email)) {
|
|
1225
|
+
return {
|
|
1226
|
+
error: createError("VALIDATION_ERROR", "\u30E1\u30FC\u30EB\u30A2\u30C9\u30EC\u30B9\u306F\u5FC5\u9808\u3067\u3059")
|
|
1227
|
+
};
|
|
1228
|
+
}
|
|
1229
|
+
return fetchPublic(
|
|
1230
|
+
`${OTP_BASE}/send`,
|
|
1231
|
+
{
|
|
1232
|
+
method: "POST",
|
|
1233
|
+
body: JSON.stringify({
|
|
1234
|
+
email,
|
|
1235
|
+
...options?.redirectUrl ? { redirectUrl: options.redirectUrl } : {}
|
|
1236
|
+
})
|
|
1237
|
+
}
|
|
1238
|
+
);
|
|
1239
|
+
}
|
|
1240
|
+
async function verifyOtp(params) {
|
|
1241
|
+
if (isBlank2(params.accessToken)) {
|
|
890
1242
|
return {
|
|
891
|
-
error:
|
|
892
|
-
code: FFID_ERROR_CODES.NETWORK_ERROR,
|
|
893
|
-
message: error instanceof Error ? error.message : "\u30CD\u30C3\u30C8\u30EF\u30FC\u30AF\u30A8\u30E9\u30FC\u304C\u767A\u751F\u3057\u307E\u3057\u305F"
|
|
894
|
-
}
|
|
1243
|
+
error: createError("VALIDATION_ERROR", "\u30A2\u30AF\u30BB\u30B9\u30C8\u30FC\u30AF\u30F3\u306F\u5FC5\u9808\u3067\u3059")
|
|
895
1244
|
};
|
|
896
1245
|
}
|
|
897
|
-
|
|
898
|
-
try {
|
|
899
|
-
tokenResponse = await response.json();
|
|
900
|
-
} catch (parseError) {
|
|
901
|
-
logger.error("Parse error during token exchange:", parseError);
|
|
1246
|
+
if (isBlank2(params.refreshToken)) {
|
|
902
1247
|
return {
|
|
903
|
-
error:
|
|
904
|
-
code: FFID_ERROR_CODES.PARSE_ERROR,
|
|
905
|
-
message: `\u30C8\u30FC\u30AF\u30F3\u30EC\u30B9\u30DD\u30F3\u30B9\u306E\u89E3\u6790\u306B\u5931\u6557\u3057\u307E\u3057\u305F (status: ${response.status})`
|
|
906
|
-
}
|
|
1248
|
+
error: createError("VALIDATION_ERROR", "\u30EA\u30D5\u30EC\u30C3\u30B7\u30E5\u30C8\u30FC\u30AF\u30F3\u306F\u5FC5\u9808\u3067\u3059")
|
|
907
1249
|
};
|
|
908
1250
|
}
|
|
909
|
-
|
|
910
|
-
|
|
1251
|
+
return fetchPublic(
|
|
1252
|
+
`${OTP_BASE}/verify`,
|
|
1253
|
+
{
|
|
1254
|
+
method: "POST",
|
|
1255
|
+
body: JSON.stringify({
|
|
1256
|
+
accessToken: params.accessToken,
|
|
1257
|
+
refreshToken: params.refreshToken
|
|
1258
|
+
})
|
|
1259
|
+
}
|
|
1260
|
+
);
|
|
1261
|
+
}
|
|
1262
|
+
return {
|
|
1263
|
+
sendOtp,
|
|
1264
|
+
verifyOtp
|
|
1265
|
+
};
|
|
1266
|
+
}
|
|
1267
|
+
|
|
1268
|
+
// src/client/ffid-client.ts
|
|
1269
|
+
var UNAUTHORIZED_STATUS2 = 401;
|
|
1270
|
+
var SDK_LOG_PREFIX = "[FFID SDK]";
|
|
1271
|
+
var noopLogger = {
|
|
1272
|
+
debug: () => {
|
|
1273
|
+
},
|
|
1274
|
+
info: () => {
|
|
1275
|
+
},
|
|
1276
|
+
warn: (...args) => console.warn(SDK_LOG_PREFIX, ...args),
|
|
1277
|
+
error: (...args) => console.error(SDK_LOG_PREFIX, ...args)
|
|
1278
|
+
};
|
|
1279
|
+
var consoleLogger = {
|
|
1280
|
+
debug: (...args) => console.debug(SDK_LOG_PREFIX, ...args),
|
|
1281
|
+
info: (...args) => console.info(SDK_LOG_PREFIX, ...args),
|
|
1282
|
+
warn: (...args) => console.warn(SDK_LOG_PREFIX, ...args),
|
|
1283
|
+
error: (...args) => console.error(SDK_LOG_PREFIX, ...args)
|
|
1284
|
+
};
|
|
1285
|
+
var FFID_ERROR_CODES = {
|
|
1286
|
+
NETWORK_ERROR: "NETWORK_ERROR",
|
|
1287
|
+
PARSE_ERROR: "PARSE_ERROR",
|
|
1288
|
+
UNKNOWN_ERROR: "UNKNOWN_ERROR",
|
|
1289
|
+
TOKEN_EXCHANGE_ERROR: "TOKEN_EXCHANGE_ERROR",
|
|
1290
|
+
TOKEN_REFRESH_ERROR: "TOKEN_REFRESH_ERROR",
|
|
1291
|
+
NO_TOKENS: "NO_TOKENS",
|
|
1292
|
+
TOKEN_VERIFICATION_ERROR: "TOKEN_VERIFICATION_ERROR"
|
|
1293
|
+
};
|
|
1294
|
+
var EXT_CHECK_ENDPOINT = "/api/v1/subscriptions/ext/check";
|
|
1295
|
+
function createFFIDClient(config) {
|
|
1296
|
+
if (!config.serviceCode || !config.serviceCode.trim()) {
|
|
1297
|
+
throw new Error("FFID Client: serviceCode \u304C\u672A\u8A2D\u5B9A\u3067\u3059");
|
|
1298
|
+
}
|
|
1299
|
+
const baseUrl = config.apiBaseUrl ?? DEFAULT_API_BASE_URL;
|
|
1300
|
+
const authMode = config.authMode ?? "cookie";
|
|
1301
|
+
const clientId = config.clientId ?? config.serviceCode;
|
|
1302
|
+
const resolvedRedirectUri = config.redirectUri ?? null;
|
|
1303
|
+
const serviceApiKey = config.serviceApiKey?.trim();
|
|
1304
|
+
const verifyStrategy = config.verifyStrategy ?? "jwt";
|
|
1305
|
+
const cache = config.cache;
|
|
1306
|
+
const timeout = config.timeout;
|
|
1307
|
+
if (authMode === "service-key" && !serviceApiKey) {
|
|
1308
|
+
throw new Error("FFID Client: service-key \u30E2\u30FC\u30C9\u3067\u306F serviceApiKey \u304C\u5FC5\u9808\u3067\u3059");
|
|
1309
|
+
}
|
|
1310
|
+
if (cache && cache.ttl <= 0) {
|
|
1311
|
+
throw new Error("FFID Client: cache.ttl \u306F\u6B63\u306E\u6570\u5024\u3092\u6307\u5B9A\u3057\u3066\u304F\u3060\u3055\u3044");
|
|
1312
|
+
}
|
|
1313
|
+
if (timeout !== void 0 && timeout <= 0) {
|
|
1314
|
+
throw new Error("FFID Client: timeout \u306F\u6B63\u306E\u6570\u5024\u3092\u6307\u5B9A\u3057\u3066\u304F\u3060\u3055\u3044");
|
|
1315
|
+
}
|
|
1316
|
+
const logger = config.logger ?? (config.debug ? consoleLogger : noopLogger);
|
|
1317
|
+
const tokenStore = authMode === "token" ? createTokenStore() : createTokenStore("memory");
|
|
1318
|
+
function createError(code, message) {
|
|
1319
|
+
return { code, message };
|
|
1320
|
+
}
|
|
1321
|
+
function buildFetchOptions(options) {
|
|
1322
|
+
if (authMode === "service-key") {
|
|
911
1323
|
return {
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
1324
|
+
...options,
|
|
1325
|
+
credentials: "omit",
|
|
1326
|
+
headers: {
|
|
1327
|
+
"Content-Type": "application/json",
|
|
1328
|
+
...sdkHeaders(),
|
|
1329
|
+
"X-Service-Api-Key": serviceApiKey,
|
|
1330
|
+
...options.headers
|
|
915
1331
|
}
|
|
916
1332
|
};
|
|
917
1333
|
}
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
if (!tokens) {
|
|
1334
|
+
if (authMode === "token") {
|
|
1335
|
+
const tokens = tokenStore.getTokens();
|
|
1336
|
+
const headers = {
|
|
1337
|
+
"Content-Type": "application/json",
|
|
1338
|
+
...sdkHeaders(),
|
|
1339
|
+
...options.headers
|
|
1340
|
+
};
|
|
1341
|
+
if (tokens) {
|
|
1342
|
+
headers["Authorization"] = `Bearer ${tokens.accessToken}`;
|
|
1343
|
+
}
|
|
929
1344
|
return {
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
}
|
|
1345
|
+
...options,
|
|
1346
|
+
credentials: "omit",
|
|
1347
|
+
headers
|
|
934
1348
|
};
|
|
935
1349
|
}
|
|
936
|
-
|
|
937
|
-
|
|
1350
|
+
return {
|
|
1351
|
+
...options,
|
|
1352
|
+
credentials: "include",
|
|
1353
|
+
headers: {
|
|
1354
|
+
"Content-Type": "application/json",
|
|
1355
|
+
...sdkHeaders(),
|
|
1356
|
+
...options.headers
|
|
1357
|
+
}
|
|
1358
|
+
};
|
|
1359
|
+
}
|
|
1360
|
+
const { exchangeCodeForTokens, refreshAccessToken, signOutToken } = createOAuthTokenMethods({
|
|
1361
|
+
baseUrl,
|
|
1362
|
+
clientId,
|
|
1363
|
+
resolvedRedirectUri,
|
|
1364
|
+
tokenStore,
|
|
1365
|
+
logger,
|
|
1366
|
+
errorCodes: FFID_ERROR_CODES
|
|
1367
|
+
});
|
|
1368
|
+
async function fetchWithAuth(endpoint, options = {}) {
|
|
1369
|
+
const url = `${baseUrl}${endpoint}`;
|
|
1370
|
+
logger.debug("Fetching:", url);
|
|
1371
|
+
const fetchOptions = buildFetchOptions(options);
|
|
938
1372
|
let response;
|
|
939
1373
|
try {
|
|
940
|
-
response = await fetch(url,
|
|
941
|
-
method: "POST",
|
|
942
|
-
credentials: "omit",
|
|
943
|
-
headers: { "Content-Type": "application/x-www-form-urlencoded", ...sdkHeaders() },
|
|
944
|
-
body: new URLSearchParams({
|
|
945
|
-
grant_type: "refresh_token",
|
|
946
|
-
refresh_token: tokens.refreshToken,
|
|
947
|
-
client_id: clientId
|
|
948
|
-
}).toString()
|
|
949
|
-
});
|
|
1374
|
+
response = await fetch(url, fetchOptions);
|
|
950
1375
|
} catch (error) {
|
|
951
|
-
logger.error("Network error
|
|
1376
|
+
logger.error("Network error:", error);
|
|
952
1377
|
return {
|
|
953
1378
|
error: {
|
|
954
1379
|
code: FFID_ERROR_CODES.NETWORK_ERROR,
|
|
@@ -956,88 +1381,83 @@ function createFFIDClient(config) {
|
|
|
956
1381
|
}
|
|
957
1382
|
};
|
|
958
1383
|
}
|
|
959
|
-
|
|
1384
|
+
if (authMode === "token" && response.status === UNAUTHORIZED_STATUS2) {
|
|
1385
|
+
const refreshResult = await refreshAccessToken();
|
|
1386
|
+
if (!refreshResult.error) {
|
|
1387
|
+
logger.debug("Token refreshed, retrying request");
|
|
1388
|
+
const retryOptions = buildFetchOptions(options);
|
|
1389
|
+
try {
|
|
1390
|
+
response = await fetch(url, retryOptions);
|
|
1391
|
+
} catch (retryError) {
|
|
1392
|
+
logger.error("Network error on retry:", retryError);
|
|
1393
|
+
return {
|
|
1394
|
+
error: {
|
|
1395
|
+
code: FFID_ERROR_CODES.NETWORK_ERROR,
|
|
1396
|
+
message: retryError instanceof Error ? retryError.message : "\u30CD\u30C3\u30C8\u30EF\u30FC\u30AF\u30A8\u30E9\u30FC\u304C\u767A\u751F\u3057\u307E\u3057\u305F"
|
|
1397
|
+
}
|
|
1398
|
+
};
|
|
1399
|
+
}
|
|
1400
|
+
} else {
|
|
1401
|
+
logger.warn("Token refresh failed, returning refresh error:", refreshResult.error);
|
|
1402
|
+
return { error: refreshResult.error };
|
|
1403
|
+
}
|
|
1404
|
+
}
|
|
1405
|
+
let raw;
|
|
960
1406
|
try {
|
|
961
|
-
|
|
1407
|
+
raw = await response.json();
|
|
962
1408
|
} catch (parseError) {
|
|
963
|
-
logger.error("Parse error
|
|
1409
|
+
logger.error("Parse error:", parseError, "Status:", response.status);
|
|
964
1410
|
return {
|
|
965
1411
|
error: {
|
|
966
1412
|
code: FFID_ERROR_CODES.PARSE_ERROR,
|
|
967
|
-
message: `\
|
|
1413
|
+
message: `\u30B5\u30FC\u30D0\u30FC\u304B\u3089\u4E0D\u6B63\u306A\u30EC\u30B9\u30DD\u30F3\u30B9\u3092\u53D7\u4FE1\u3057\u307E\u3057\u305F (status: ${response.status})`
|
|
968
1414
|
}
|
|
969
1415
|
};
|
|
970
1416
|
}
|
|
1417
|
+
logger.debug("Response:", response.status, raw);
|
|
1418
|
+
checkVersionHeader(response, logger);
|
|
971
1419
|
if (!response.ok) {
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
1420
|
+
return {
|
|
1421
|
+
error: raw.error ?? {
|
|
1422
|
+
code: FFID_ERROR_CODES.UNKNOWN_ERROR,
|
|
1423
|
+
message: "\u4E0D\u660E\u306A\u30A8\u30E9\u30FC\u304C\u767A\u751F\u3057\u307E\u3057\u305F"
|
|
1424
|
+
}
|
|
1425
|
+
};
|
|
1426
|
+
}
|
|
1427
|
+
if (raw.data === void 0) {
|
|
979
1428
|
return {
|
|
980
1429
|
error: {
|
|
981
|
-
code:
|
|
982
|
-
message:
|
|
1430
|
+
code: FFID_ERROR_CODES.UNKNOWN_ERROR,
|
|
1431
|
+
message: "\u30B5\u30FC\u30D0\u30FC\u304B\u3089\u30C7\u30FC\u30BF\u304C\u8FD4\u3055\u308C\u307E\u305B\u3093\u3067\u3057\u305F"
|
|
983
1432
|
}
|
|
984
1433
|
};
|
|
985
1434
|
}
|
|
986
|
-
|
|
987
|
-
accessToken: tokenResponse.access_token,
|
|
988
|
-
refreshToken: tokenResponse.refresh_token,
|
|
989
|
-
expiresAt: Date.now() + tokenResponse.expires_in * MS_PER_SECOND
|
|
990
|
-
});
|
|
991
|
-
logger.debug("Token refresh successful");
|
|
992
|
-
return { data: void 0 };
|
|
1435
|
+
return { data: raw.data };
|
|
993
1436
|
}
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
1437
|
+
const { getSession, signOutCookie } = createSessionMethods({
|
|
1438
|
+
authMode,
|
|
1439
|
+
baseUrl,
|
|
1440
|
+
serviceCode: config.serviceCode,
|
|
1441
|
+
tokenStore,
|
|
1442
|
+
logger,
|
|
1443
|
+
fetchWithAuth,
|
|
1444
|
+
refreshAccessToken,
|
|
1445
|
+
errorCodes: FFID_ERROR_CODES
|
|
1446
|
+
});
|
|
1447
|
+
async function signOut() {
|
|
999
1448
|
if (authMode === "token") {
|
|
1000
|
-
return
|
|
1449
|
+
return signOutToken();
|
|
1001
1450
|
}
|
|
1002
|
-
|
|
1003
|
-
const loginUrl = `${baseUrl}/login?redirect=${encodeURIComponent(currentUrl)}&service=${encodeURIComponent(config.serviceCode)}`;
|
|
1004
|
-
logger.debug("Redirecting to login:", loginUrl);
|
|
1005
|
-
window.location.href = loginUrl;
|
|
1006
|
-
return true;
|
|
1007
|
-
}
|
|
1008
|
-
function redirectToAuthorize() {
|
|
1009
|
-
const verifier = generateCodeVerifier();
|
|
1010
|
-
storeCodeVerifier(verifier);
|
|
1011
|
-
generateCodeChallenge(verifier).then((challenge) => {
|
|
1012
|
-
const state = generateRandomState();
|
|
1013
|
-
const redirectUri = resolvedRedirectUri ?? window.location.origin + window.location.pathname;
|
|
1014
|
-
const params = new URLSearchParams({
|
|
1015
|
-
response_type: "code",
|
|
1016
|
-
client_id: clientId,
|
|
1017
|
-
redirect_uri: redirectUri,
|
|
1018
|
-
state,
|
|
1019
|
-
code_challenge: challenge,
|
|
1020
|
-
code_challenge_method: "S256"
|
|
1021
|
-
});
|
|
1022
|
-
const authorizeUrl = `${baseUrl}${OAUTH_AUTHORIZE_ENDPOINT}?${params.toString()}`;
|
|
1023
|
-
logger.debug("Redirecting to authorize:", authorizeUrl);
|
|
1024
|
-
window.location.href = authorizeUrl;
|
|
1025
|
-
}).catch((error) => {
|
|
1026
|
-
logger.error("Failed to generate code challenge:", error);
|
|
1027
|
-
});
|
|
1028
|
-
return true;
|
|
1029
|
-
}
|
|
1030
|
-
function getLoginUrl(redirectUrl) {
|
|
1031
|
-
const redirect = redirectUrl ?? (typeof window !== "undefined" ? window.location.href : "");
|
|
1032
|
-
return `${baseUrl}/login?redirect=${encodeURIComponent(redirect)}&service=${encodeURIComponent(config.serviceCode)}`;
|
|
1033
|
-
}
|
|
1034
|
-
function getSignupUrl(redirectUrl) {
|
|
1035
|
-
const redirect = redirectUrl ?? (typeof window !== "undefined" ? window.location.href : "");
|
|
1036
|
-
return `${baseUrl}/signup?redirect=${encodeURIComponent(redirect)}&service=${encodeURIComponent(config.serviceCode)}`;
|
|
1037
|
-
}
|
|
1038
|
-
function createError(code, message) {
|
|
1039
|
-
return { code, message };
|
|
1451
|
+
return signOutCookie();
|
|
1040
1452
|
}
|
|
1453
|
+
const { redirectToLogin, getLoginUrl, getSignupUrl } = createRedirectMethods({
|
|
1454
|
+
authMode,
|
|
1455
|
+
baseUrl,
|
|
1456
|
+
clientId,
|
|
1457
|
+
serviceCode: config.serviceCode,
|
|
1458
|
+
resolvedRedirectUri,
|
|
1459
|
+
logger
|
|
1460
|
+
});
|
|
1041
1461
|
async function checkSubscription(params) {
|
|
1042
1462
|
if (!params.userId || !params.organizationId) {
|
|
1043
1463
|
return {
|
|
@@ -1057,6 +1477,24 @@ function createFFIDClient(config) {
|
|
|
1057
1477
|
fetchWithAuth,
|
|
1058
1478
|
createError
|
|
1059
1479
|
});
|
|
1480
|
+
const {
|
|
1481
|
+
requestPasswordReset,
|
|
1482
|
+
verifyPasswordResetToken,
|
|
1483
|
+
establishResetSession,
|
|
1484
|
+
confirmPasswordReset
|
|
1485
|
+
} = createPasswordResetMethods({
|
|
1486
|
+
baseUrl,
|
|
1487
|
+
logger,
|
|
1488
|
+
createError,
|
|
1489
|
+
fetchWithAuth,
|
|
1490
|
+
errorCodes: FFID_ERROR_CODES
|
|
1491
|
+
});
|
|
1492
|
+
const { sendOtp, verifyOtp } = createOtpMethods({
|
|
1493
|
+
baseUrl,
|
|
1494
|
+
logger,
|
|
1495
|
+
createError,
|
|
1496
|
+
errorCodes: FFID_ERROR_CODES
|
|
1497
|
+
});
|
|
1060
1498
|
const verifyAccessToken = createVerifyAccessToken({
|
|
1061
1499
|
authMode,
|
|
1062
1500
|
baseUrl,
|
|
@@ -1082,6 +1520,12 @@ function createFFIDClient(config) {
|
|
|
1082
1520
|
createCheckoutSession,
|
|
1083
1521
|
createPortalSession,
|
|
1084
1522
|
verifyAccessToken,
|
|
1523
|
+
requestPasswordReset,
|
|
1524
|
+
verifyPasswordResetToken,
|
|
1525
|
+
establishResetSession,
|
|
1526
|
+
confirmPasswordReset,
|
|
1527
|
+
sendOtp,
|
|
1528
|
+
verifyOtp,
|
|
1085
1529
|
/** Token store (token mode only) */
|
|
1086
1530
|
tokenStore,
|
|
1087
1531
|
/** Resolved auth mode */
|
|
@@ -1094,11 +1538,6 @@ function createFFIDClient(config) {
|
|
|
1094
1538
|
redirectUri: resolvedRedirectUri
|
|
1095
1539
|
};
|
|
1096
1540
|
}
|
|
1097
|
-
function generateRandomState() {
|
|
1098
|
-
const array = new Uint8Array(STATE_RANDOM_BYTES);
|
|
1099
|
-
crypto.getRandomValues(array);
|
|
1100
|
-
return Array.from(array, (byte) => byte.toString(HEX_BASE2).padStart(2, "0")).join("");
|
|
1101
|
-
}
|
|
1102
1541
|
var DEFAULT_REFRESH_INTERVAL_MS = 5 * 60 * 1e3;
|
|
1103
1542
|
var TOKEN_REFRESH_RATIO = 0.8;
|
|
1104
1543
|
var FFIDContext = createContext(null);
|
|
@@ -1184,7 +1623,9 @@ function FFIDProvider({
|
|
|
1184
1623
|
}
|
|
1185
1624
|
}, [client]);
|
|
1186
1625
|
const login = useCallback(() => {
|
|
1187
|
-
client.redirectToLogin()
|
|
1626
|
+
client.redirectToLogin().catch((err) => {
|
|
1627
|
+
client.logger.error("\u30ED\u30B0\u30A4\u30F3\u30EA\u30C0\u30A4\u30EC\u30AF\u30C8\u306B\u5931\u6557\u3057\u307E\u3057\u305F:", err);
|
|
1628
|
+
});
|
|
1188
1629
|
}, [client]);
|
|
1189
1630
|
const logout = useCallback(async () => {
|
|
1190
1631
|
client.logger.debug("Signing out...");
|