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