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