@feelflow/ffid-sdk 5.18.0 → 5.21.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/README.md +77 -0
- package/dist/agency/index.cjs +4 -2
- package/dist/agency/index.js +4 -2
- package/dist/announcements/index.cjs +2 -1
- package/dist/announcements/index.js +2 -1
- package/dist/{chunk-55S5VUWR.cjs → chunk-RDQ4R3XC.cjs} +190 -49
- package/dist/{chunk-L2H56C4W.js → chunk-U57ZDFEN.js} +190 -49
- package/dist/components/index.cjs +8 -8
- 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/{ffid-client-Cwk6cG3b.d.cts → ffid-client-BQPhm-XL.d.cts} +268 -9
- package/dist/{ffid-client-w8Twi5lD.d.ts → ffid-client-Bx8y4SwC.d.ts} +268 -9
- package/dist/{index-Bkul-fRw.d.cts → index--wRiPlpi.d.cts} +54 -8
- package/dist/{index-Bkul-fRw.d.ts → index--wRiPlpi.d.ts} +54 -8
- package/dist/index.cjs +42 -42
- package/dist/index.d.cts +217 -4
- package/dist/index.d.ts +217 -4
- package/dist/index.js +2 -2
- package/dist/legal/index.cjs +2 -1
- package/dist/legal/index.js +2 -1
- package/dist/server/index.cjs +188 -48
- package/dist/server/index.d.cts +2 -2
- package/dist/server/index.d.ts +2 -2
- package/dist/server/index.js +188 -48
- package/dist/server/test/index.d.cts +1 -1
- package/dist/server/test/index.d.ts +1 -1
- package/dist/webhooks/index.cjs +2 -2
- package/dist/webhooks/index.js +2 -2
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -396,6 +396,83 @@ await ffid.updateMemberRole({
|
|
|
396
396
|
- `role` は `admin` / `member` / `viewer` のみ指定可能です。`owner` は追加・昇格 API では扱いません。
|
|
397
397
|
- agency client と同じ呼び味が必要な場合は `ffid.addMember(organizationId, { email, role })` も使えます。
|
|
398
398
|
|
|
399
|
+
### Provisioning(users / organizations)
|
|
400
|
+
|
|
401
|
+
外部サービスからの一括移行向けに、**service-key 認証**でユーザー・組織を冪等にプロビジョニングするメソッド。REST を直叩きせず `createFFIDClient` 経由で呼べる(SDK-first、#3790 / #4127)。
|
|
402
|
+
|
|
403
|
+
- **`authMode: 'service-key'`(`X-Service-Api-Key`)専用**。`token`(Bearer)/ `cookie` モードで呼ぶとサーバーへ往復せず即座に `VALIDATION_ERROR` を返す(サーバー側エンドポイントが service-key 認証のみを受け付けるため)。
|
|
404
|
+
- 必要 scope: `provisionUser` → `user:provision`、`provisionOrganization` → `organization:write`。
|
|
405
|
+
|
|
406
|
+
```ts
|
|
407
|
+
import { createFFIDClient } from '@feelflow/ffid-sdk/server'
|
|
408
|
+
|
|
409
|
+
const ffid = createFFIDClient({
|
|
410
|
+
serviceCode: 'praxis',
|
|
411
|
+
scope: '',
|
|
412
|
+
authMode: 'service-key',
|
|
413
|
+
serviceApiKey: process.env.FFID_SERVICE_API_KEY!,
|
|
414
|
+
})
|
|
415
|
+
|
|
416
|
+
// 1. ユーザーを冪等に作成(新規なら created:true / 既存なら created:false)
|
|
417
|
+
const userRes = await ffid.provisionUser({
|
|
418
|
+
email: 'owner@example.com',
|
|
419
|
+
profile: { displayName: '山田 太郎', companyName: 'Example Inc.' },
|
|
420
|
+
})
|
|
421
|
+
if (userRes.error) throw new Error(userRes.error.message)
|
|
422
|
+
if (!userRes.data.dryRun) {
|
|
423
|
+
console.log(userRes.data.created ? '新規作成' : '既存ユーザー', userRes.data.user.id)
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
// 2. その owner の組織を作成し、メンバーを冪等に追加
|
|
427
|
+
const orgRes = await ffid.provisionOrganization({
|
|
428
|
+
name: 'Example 組織',
|
|
429
|
+
ownerEmail: 'owner@example.com',
|
|
430
|
+
members: [
|
|
431
|
+
{ email: 'member1@example.com', role: 'member' },
|
|
432
|
+
{ email: 'member2@example.com', role: 'viewer' },
|
|
433
|
+
],
|
|
434
|
+
})
|
|
435
|
+
if (orgRes.error) {
|
|
436
|
+
// owner が未登録なら error.code === 'OWNER_NOT_FOUND'(先に provisionUser が必要)
|
|
437
|
+
throw new Error(orgRes.error.message)
|
|
438
|
+
}
|
|
439
|
+
if (!orgRes.data.dryRun) {
|
|
440
|
+
for (const m of orgRes.data.members) {
|
|
441
|
+
console.log(m.email, m.status) // 'added' | 'already_member' | 'user_not_found'
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
```
|
|
445
|
+
|
|
446
|
+
#### 冪等セマンティクス
|
|
447
|
+
|
|
448
|
+
- `provisionUser`: **既存メールは `created: false`**(HTTP 200、no-op)、新規メールは `created: true`(HTTP 201、パスワードレス・メール確認済み)。メールはサーバー側で normalize(trim + lowercase)されるため、大文字小文字違いの再送でも同一ユーザーに解決する。新規作成で `profile` を渡した場合のみ `profileWritten` が付き、`false` は「ユーザーは作成されたがプロフィール書き込みに失敗」→ 再送を推奨。
|
|
449
|
+
- `provisionOrganization`: owner が同名の組織を既に持つ場合は `created: false`(HTTP 200)。`members` は冪等に追加され、各要素の `status` が `added` / `already_member` / `user_not_found` を返す。
|
|
450
|
+
|
|
451
|
+
#### dryRun
|
|
452
|
+
|
|
453
|
+
`dryRun: true` を渡すと **一切書き込まず**、実行時に何が起きるかだけを返す。レスポンスは `dryRun` 判別のタグ付きユニオン。
|
|
454
|
+
|
|
455
|
+
```ts
|
|
456
|
+
const preview = await ffid.provisionUser({ email: 'owner@example.com', dryRun: true })
|
|
457
|
+
if (!preview.error && preview.data.dryRun) {
|
|
458
|
+
console.log(preview.data.wouldCreate) // true = 実行すれば新規作成される
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
const orgPreview = await ffid.provisionOrganization({
|
|
462
|
+
name: 'Example 組織',
|
|
463
|
+
ownerEmail: 'owner@example.com',
|
|
464
|
+
members: [{ email: 'member1@example.com', role: 'member' }],
|
|
465
|
+
dryRun: true,
|
|
466
|
+
})
|
|
467
|
+
if (!orgPreview.error && orgPreview.data.dryRun) {
|
|
468
|
+
console.log(orgPreview.data.wouldCreate) // 組織を新規作成するか
|
|
469
|
+
// per-member plan status: 'would_add' | 'already_member' | 'user_not_found'
|
|
470
|
+
orgPreview.data.members.forEach((m) => console.log(m.email, m.status))
|
|
471
|
+
}
|
|
472
|
+
```
|
|
473
|
+
|
|
474
|
+
`if (res.data.dryRun)` で TypeScript が型を絞り込むため、dry-run 分岐では `wouldCreate`、実行分岐では `created` がそれぞれ型安全に参照できる。
|
|
475
|
+
|
|
399
476
|
### getProfile() / updateProfile()
|
|
400
477
|
|
|
401
478
|
ログイン中ユーザー自身のプロフィールを取得・更新するメソッド(`createFFIDClient` から呼び出し)。
|
package/dist/agency/index.cjs
CHANGED
|
@@ -16,8 +16,10 @@ var noopLogger = {
|
|
|
16
16
|
},
|
|
17
17
|
info: () => {
|
|
18
18
|
},
|
|
19
|
-
warn: (
|
|
20
|
-
|
|
19
|
+
warn: () => {
|
|
20
|
+
},
|
|
21
|
+
error: () => {
|
|
22
|
+
}
|
|
21
23
|
};
|
|
22
24
|
var consoleLogger = {
|
|
23
25
|
debug: (...args) => console.debug(SDK_LOG_PREFIX, ...args),
|
package/dist/agency/index.js
CHANGED
|
@@ -15,8 +15,10 @@ var noopLogger = {
|
|
|
15
15
|
},
|
|
16
16
|
info: () => {
|
|
17
17
|
},
|
|
18
|
-
warn: (
|
|
19
|
-
|
|
18
|
+
warn: () => {
|
|
19
|
+
},
|
|
20
|
+
error: () => {
|
|
21
|
+
}
|
|
20
22
|
};
|
|
21
23
|
var consoleLogger = {
|
|
22
24
|
debug: (...args) => console.debug(SDK_LOG_PREFIX, ...args),
|
|
@@ -266,10 +266,12 @@ var OAUTH_INTROSPECT_ENDPOINT = "/api/v1/oauth/introspect";
|
|
|
266
266
|
var CACHE_KEY_PREFIX = "ffid:introspect:";
|
|
267
267
|
var HEX_BASE = 16;
|
|
268
268
|
var HEX_BYTE_WIDTH = 2;
|
|
269
|
-
var TOKEN_LOG_PREFIX_LENGTH = 8;
|
|
270
269
|
function createVerifyAccessToken(deps) {
|
|
271
270
|
const { authMode, baseUrl, serviceCode, serviceApiKey, verifyStrategy, logger, createError, errorCodes, cache, timeout } = deps;
|
|
272
271
|
let jwtVerify2 = null;
|
|
272
|
+
let cacheReadFailureEscalated = false;
|
|
273
|
+
let cacheWriteFailureEscalated = false;
|
|
274
|
+
let missingSubtleCryptoLogged = false;
|
|
273
275
|
function getJwtVerifier() {
|
|
274
276
|
if (!jwtVerify2) {
|
|
275
277
|
jwtVerify2 = createJwtVerifier({
|
|
@@ -329,8 +331,9 @@ function createVerifyAccessToken(deps) {
|
|
|
329
331
|
}
|
|
330
332
|
const url = `${baseUrl}${OAUTH_INTROSPECT_ENDPOINT}`;
|
|
331
333
|
logger.debug("Verifying access token:", url);
|
|
332
|
-
const cacheKey = cache ? await buildCacheKey(accessToken, cacheKeySuffix) :
|
|
333
|
-
|
|
334
|
+
const cacheKey = cache ? await buildCacheKey(accessToken, cacheKeySuffix) : null;
|
|
335
|
+
let verificationCacheDegraded = cache !== void 0 && cacheKey === null;
|
|
336
|
+
if (cache && cacheKey !== null) {
|
|
334
337
|
try {
|
|
335
338
|
const cached = await cache.adapter.get(cacheKey);
|
|
336
339
|
if (cached && typeof cached === "object" && "sub" in cached) {
|
|
@@ -338,7 +341,16 @@ function createVerifyAccessToken(deps) {
|
|
|
338
341
|
return { data: cached };
|
|
339
342
|
}
|
|
340
343
|
} catch (cacheError) {
|
|
341
|
-
|
|
344
|
+
verificationCacheDegraded = true;
|
|
345
|
+
if (!cacheReadFailureEscalated) {
|
|
346
|
+
cacheReadFailureEscalated = true;
|
|
347
|
+
logger.error(
|
|
348
|
+
"Cache read failed \u2014 falling back to API call. Redis \u7B49\u306E cache adapter \u969C\u5BB3\u6642\u306F\u5168 verification \u304C introspect \u306B degrade \u3057\u307E\u3059\uFF08\u3053\u306E error \u306F instance \u3054\u3068\u306B\u521D\u56DE\u306E\u307F\u3001\u4EE5\u964D\u306F warn\uFF09:",
|
|
349
|
+
cacheError
|
|
350
|
+
);
|
|
351
|
+
} else {
|
|
352
|
+
logger.warn("Cache read failed, falling back to API call:", cacheError);
|
|
353
|
+
}
|
|
342
354
|
}
|
|
343
355
|
}
|
|
344
356
|
const fetchOptions = {
|
|
@@ -433,31 +445,40 @@ function createVerifyAccessToken(deps) {
|
|
|
433
445
|
}
|
|
434
446
|
} : base;
|
|
435
447
|
const userinfo = normalizeUserinfo(raw);
|
|
436
|
-
if (cache) {
|
|
448
|
+
if (cache && cacheKey !== null) {
|
|
437
449
|
try {
|
|
438
450
|
await cache.adapter.set(cacheKey, userinfo, cache.ttl);
|
|
439
451
|
} catch (cacheError) {
|
|
440
|
-
|
|
452
|
+
verificationCacheDegraded = true;
|
|
453
|
+
if (!cacheWriteFailureEscalated) {
|
|
454
|
+
cacheWriteFailureEscalated = true;
|
|
455
|
+
logger.error(
|
|
456
|
+
"Cache write failed \u2014 result still returned. cache adapter \u969C\u5BB3\u6642\u306F introspect \u7D50\u679C\u304C\u30AD\u30E3\u30C3\u30B7\u30E5\u3055\u308C\u305A\u5168 verification \u304C API \u547C\u3073\u51FA\u3057\u306B degrade \u3057\u307E\u3059\uFF08\u3053\u306E error \u306F instance \u3054\u3068\u306B\u521D\u56DE\u306E\u307F\u3001\u4EE5\u964D\u306F warn\uFF09:",
|
|
457
|
+
cacheError
|
|
458
|
+
);
|
|
459
|
+
} else {
|
|
460
|
+
logger.warn("Cache write failed (result still returned):", cacheError);
|
|
461
|
+
}
|
|
441
462
|
}
|
|
442
463
|
}
|
|
443
|
-
return { data: userinfo };
|
|
464
|
+
return verificationCacheDegraded ? { data: userinfo, meta: { verificationCacheDegraded: true } } : { data: userinfo };
|
|
444
465
|
}
|
|
445
466
|
async function buildCacheKey(token, suffix) {
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
}
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
467
|
+
if (typeof globalThis.crypto?.subtle?.digest !== "function") {
|
|
468
|
+
if (!missingSubtleCryptoLogged) {
|
|
469
|
+
missingSubtleCryptoLogged = true;
|
|
470
|
+
logger.error(
|
|
471
|
+
"crypto.subtle \u304C\u5229\u7528\u3067\u304D\u306A\u3044\u305F\u3081 introspect \u7D50\u679C\u306E\u30AD\u30E3\u30C3\u30B7\u30E5\u3092\u7121\u52B9\u5316\u3057\u307E\u3057\u305F\uFF08\u30CF\u30C3\u30B7\u30E5\u5316\u3055\u308C\u3066\u3044\u306A\u3044\u30C8\u30FC\u30AF\u30F3\u3092 cache key \u306B\u4F7F\u308F\u306A\u3044\u305F\u3081\u306E\u63AA\u7F6E #4119\uFF09\u3002\u5168 verification \u304C API \u547C\u3073\u51FA\u3057\u306B\u306A\u308A\u307E\u3059"
|
|
472
|
+
);
|
|
473
|
+
}
|
|
474
|
+
return null;
|
|
475
|
+
}
|
|
476
|
+
const encoder = new TextEncoder();
|
|
477
|
+
const data = encoder.encode(token);
|
|
478
|
+
const hashBuffer = await crypto.subtle.digest("SHA-256", data);
|
|
479
|
+
const hashArray = Array.from(new Uint8Array(hashBuffer));
|
|
480
|
+
const hashHex = hashArray.map((b) => b.toString(HEX_BASE).padStart(HEX_BYTE_WIDTH, "0")).join("");
|
|
481
|
+
const key = CACHE_KEY_PREFIX + hashHex;
|
|
461
482
|
return suffix ? `${key}:${suffix}` : key;
|
|
462
483
|
}
|
|
463
484
|
return verifyAccessToken;
|
|
@@ -875,6 +896,93 @@ function createMembersMethods(deps) {
|
|
|
875
896
|
return { listMembers, addMember, updateMemberRole, removeMember };
|
|
876
897
|
}
|
|
877
898
|
|
|
899
|
+
// src/client/provisioning-methods.ts
|
|
900
|
+
var USER_PROVISION_ENDPOINT = "/api/v1/ext/users/provision";
|
|
901
|
+
var ORGANIZATION_PROVISION_ENDPOINT = "/api/v1/ext/organizations/provision";
|
|
902
|
+
var MAX_PROVISION_MEMBERS = 100;
|
|
903
|
+
var ASSIGNABLE_MEMBER_ROLES = {
|
|
904
|
+
admin: true,
|
|
905
|
+
member: true,
|
|
906
|
+
viewer: true
|
|
907
|
+
};
|
|
908
|
+
function isAssignableMemberRole(role) {
|
|
909
|
+
return Object.hasOwn(ASSIGNABLE_MEMBER_ROLES, role);
|
|
910
|
+
}
|
|
911
|
+
function createProvisioningMethods(deps) {
|
|
912
|
+
const { fetchWithAuth, createError, authMode } = deps;
|
|
913
|
+
function serviceKeyModeError() {
|
|
914
|
+
if (authMode === "service-key") return null;
|
|
915
|
+
return createError(
|
|
916
|
+
"VALIDATION_ERROR",
|
|
917
|
+
"provisionUser / provisionOrganization \u306F service-key \u8A8D\u8A3C\uFF08X-Service-Api-Key\uFF09\u3067\u306E\u307F\u5229\u7528\u3067\u304D\u307E\u3059\u3002Bearer / cookie \u30E2\u30FC\u30C9\u3067\u306F\u547C\u3073\u51FA\u305B\u307E\u305B\u3093"
|
|
918
|
+
);
|
|
919
|
+
}
|
|
920
|
+
async function provisionUser(params) {
|
|
921
|
+
const modeError = serviceKeyModeError();
|
|
922
|
+
if (modeError) return { error: modeError };
|
|
923
|
+
if (!params.email || !params.email.trim()) {
|
|
924
|
+
return { error: createError("VALIDATION_ERROR", "email \u306F\u5FC5\u9808\u3067\u3059") };
|
|
925
|
+
}
|
|
926
|
+
const body = { email: params.email.trim() };
|
|
927
|
+
if (params.profile !== void 0) body.profile = params.profile;
|
|
928
|
+
if (params.dryRun !== void 0) body.dryRun = params.dryRun;
|
|
929
|
+
return fetchWithAuth(USER_PROVISION_ENDPOINT, {
|
|
930
|
+
method: "POST",
|
|
931
|
+
body: JSON.stringify(body)
|
|
932
|
+
});
|
|
933
|
+
}
|
|
934
|
+
async function provisionOrganization(params) {
|
|
935
|
+
const modeError = serviceKeyModeError();
|
|
936
|
+
if (modeError) return { error: modeError };
|
|
937
|
+
if (!params.name || !params.name.trim()) {
|
|
938
|
+
return { error: createError("VALIDATION_ERROR", "name \u306F\u5FC5\u9808\u3067\u3059") };
|
|
939
|
+
}
|
|
940
|
+
if (!params.ownerEmail || !params.ownerEmail.trim()) {
|
|
941
|
+
return { error: createError("VALIDATION_ERROR", "ownerEmail \u306F\u5FC5\u9808\u3067\u3059") };
|
|
942
|
+
}
|
|
943
|
+
if (params.members) {
|
|
944
|
+
if (params.members.length > MAX_PROVISION_MEMBERS) {
|
|
945
|
+
return {
|
|
946
|
+
error: createError(
|
|
947
|
+
"VALIDATION_ERROR",
|
|
948
|
+
`members \u306F1\u30EA\u30AF\u30A8\u30B9\u30C8\u3042\u305F\u308A\u6700\u5927 ${MAX_PROVISION_MEMBERS} \u4EF6\u307E\u3067\u3067\u3059`
|
|
949
|
+
)
|
|
950
|
+
};
|
|
951
|
+
}
|
|
952
|
+
for (const member of params.members) {
|
|
953
|
+
if (!member.email || !member.email.trim()) {
|
|
954
|
+
return { error: createError("VALIDATION_ERROR", "members[].email \u306F\u5FC5\u9808\u3067\u3059") };
|
|
955
|
+
}
|
|
956
|
+
if (!isAssignableMemberRole(member.role)) {
|
|
957
|
+
return {
|
|
958
|
+
error: createError(
|
|
959
|
+
"VALIDATION_ERROR",
|
|
960
|
+
"members[].role \u306F admin\u3001member\u3001viewer \u306E\u3044\u305A\u308C\u304B\u3092\u6307\u5B9A\u3057\u3066\u304F\u3060\u3055\u3044"
|
|
961
|
+
)
|
|
962
|
+
};
|
|
963
|
+
}
|
|
964
|
+
}
|
|
965
|
+
}
|
|
966
|
+
const body = {
|
|
967
|
+
name: params.name.trim(),
|
|
968
|
+
ownerEmail: params.ownerEmail.trim()
|
|
969
|
+
};
|
|
970
|
+
if (params.billingEmail !== void 0) body.billingEmail = params.billingEmail.trim();
|
|
971
|
+
if (params.members !== void 0) {
|
|
972
|
+
body.members = params.members.map((member) => ({
|
|
973
|
+
email: member.email.trim(),
|
|
974
|
+
role: member.role
|
|
975
|
+
}));
|
|
976
|
+
}
|
|
977
|
+
if (params.dryRun !== void 0) body.dryRun = params.dryRun;
|
|
978
|
+
return fetchWithAuth(ORGANIZATION_PROVISION_ENDPOINT, {
|
|
979
|
+
method: "POST",
|
|
980
|
+
body: JSON.stringify(body)
|
|
981
|
+
});
|
|
982
|
+
}
|
|
983
|
+
return { provisionUser, provisionOrganization };
|
|
984
|
+
}
|
|
985
|
+
|
|
878
986
|
// src/client/seat-methods.ts
|
|
879
987
|
function seatsEndpoint(subscriptionId) {
|
|
880
988
|
return `/api/v1/subscriptions/ext/${encodeURIComponent(subscriptionId)}/seats/assignments`;
|
|
@@ -1119,7 +1227,7 @@ function createNonContractMethods(deps) {
|
|
|
1119
1227
|
}
|
|
1120
1228
|
|
|
1121
1229
|
// src/client/version-check.ts
|
|
1122
|
-
var SDK_VERSION = "5.
|
|
1230
|
+
var SDK_VERSION = "5.21.0";
|
|
1123
1231
|
var SDK_USER_AGENT = `FFID-SDK/${SDK_VERSION} (TypeScript)`;
|
|
1124
1232
|
var SDK_VERSION_HEADER = "X-FFID-SDK-Version";
|
|
1125
1233
|
function sdkHeaders() {
|
|
@@ -1361,33 +1469,48 @@ function createOAuthTokenMethods(deps) {
|
|
|
1361
1469
|
async function signOutToken() {
|
|
1362
1470
|
const tokens = tokenStore.getTokens();
|
|
1363
1471
|
tokenStore.clearTokens();
|
|
1472
|
+
const allRevoked = {
|
|
1473
|
+
revoked: true,
|
|
1474
|
+
accessTokenRevoked: true,
|
|
1475
|
+
refreshTokenRevoked: true
|
|
1476
|
+
};
|
|
1364
1477
|
if (!tokens) {
|
|
1365
1478
|
logger.debug("No tokens to revoke");
|
|
1366
|
-
return { data:
|
|
1479
|
+
return { data: allRevoked };
|
|
1367
1480
|
}
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
|
|
1377
|
-
|
|
1378
|
-
|
|
1379
|
-
|
|
1380
|
-
|
|
1381
|
-
|
|
1382
|
-
|
|
1383
|
-
|
|
1384
|
-
);
|
|
1481
|
+
async function revokeOAuthToken(token, tokenTypeHint) {
|
|
1482
|
+
const url = `${baseUrl}${OAUTH_REVOKE_ENDPOINT}`;
|
|
1483
|
+
try {
|
|
1484
|
+
const response = await fetch(url, {
|
|
1485
|
+
method: "POST",
|
|
1486
|
+
credentials: "omit",
|
|
1487
|
+
headers: { "Content-Type": "application/x-www-form-urlencoded", ...sdkHeaders() },
|
|
1488
|
+
body: new URLSearchParams({
|
|
1489
|
+
token,
|
|
1490
|
+
client_id: clientId,
|
|
1491
|
+
token_type_hint: tokenTypeHint
|
|
1492
|
+
}).toString()
|
|
1493
|
+
});
|
|
1494
|
+
return response.ok;
|
|
1495
|
+
} catch {
|
|
1496
|
+
return false;
|
|
1385
1497
|
}
|
|
1386
|
-
}
|
|
1387
|
-
|
|
1498
|
+
}
|
|
1499
|
+
logger.debug("Revoking tokens:", `${baseUrl}${OAUTH_REVOKE_ENDPOINT}`);
|
|
1500
|
+
const accessTokenRevoked = await revokeOAuthToken(tokens.accessToken, "access_token");
|
|
1501
|
+
if (!accessTokenRevoked) {
|
|
1502
|
+
logger.warn("\u30A2\u30AF\u30BB\u30B9\u30C8\u30FC\u30AF\u30F3\u306E\u7121\u52B9\u5316\u30EA\u30AF\u30A8\u30B9\u30C8\u304C\u30B5\u30FC\u30D0\u30FC\u3067\u5931\u6557\u3057\u307E\u3057\u305F");
|
|
1503
|
+
}
|
|
1504
|
+
const refreshTokenRevoked = tokens.refreshToken ? await revokeOAuthToken(tokens.refreshToken, "refresh_token") : true;
|
|
1505
|
+
if (tokens.refreshToken && !refreshTokenRevoked) {
|
|
1506
|
+
logger.warn("\u30EA\u30D5\u30EC\u30C3\u30B7\u30E5\u30C8\u30FC\u30AF\u30F3\u306E\u7121\u52B9\u5316\u30EA\u30AF\u30A8\u30B9\u30C8\u304C\u30B5\u30FC\u30D0\u30FC\u3067\u5931\u6557\u3057\u307E\u3057\u305F");
|
|
1507
|
+
}
|
|
1508
|
+
const revoked = accessTokenRevoked && refreshTokenRevoked;
|
|
1509
|
+
if (!revoked) {
|
|
1510
|
+
logger.warn("\u30C8\u30FC\u30AF\u30F3\u7121\u52B9\u5316\u304C\u4E00\u90E8\u307E\u305F\u306F\u5168\u90E8\u5931\u6557\u3057\u307E\u3057\u305F\uFF08\u30ED\u30FC\u30AB\u30EB\u30C8\u30FC\u30AF\u30F3\u306F\u30AF\u30EA\u30A2\u6E08\u307F\uFF09");
|
|
1388
1511
|
}
|
|
1389
1512
|
logger.debug("Token sign-out completed");
|
|
1390
|
-
return { data:
|
|
1513
|
+
return { data: { revoked, accessTokenRevoked, refreshTokenRevoked } };
|
|
1391
1514
|
}
|
|
1392
1515
|
return { exchangeCodeForTokens, refreshAccessToken, signOutToken };
|
|
1393
1516
|
}
|
|
@@ -1551,7 +1674,7 @@ function createSessionMethods(deps) {
|
|
|
1551
1674
|
}
|
|
1552
1675
|
if (response.status === NO_CONTENT_STATUS) {
|
|
1553
1676
|
logger.debug("Response: 204 No Content");
|
|
1554
|
-
return { data:
|
|
1677
|
+
return { data: { revoked: true } };
|
|
1555
1678
|
}
|
|
1556
1679
|
if (!response.ok) {
|
|
1557
1680
|
try {
|
|
@@ -1578,7 +1701,7 @@ function createSessionMethods(deps) {
|
|
|
1578
1701
|
}
|
|
1579
1702
|
}
|
|
1580
1703
|
logger.debug("Response:", response.status);
|
|
1581
|
-
return { data:
|
|
1704
|
+
return { data: { revoked: true } };
|
|
1582
1705
|
}
|
|
1583
1706
|
return { getSession, signOutCookie };
|
|
1584
1707
|
}
|
|
@@ -1796,6 +1919,9 @@ var REDIRECT_LOOP_WINDOW_MS = 6e4;
|
|
|
1796
1919
|
var REDIRECT_LOOP_THRESHOLD = 3;
|
|
1797
1920
|
var storageReadFailureLogged = false;
|
|
1798
1921
|
var storageWriteFailureLogged = false;
|
|
1922
|
+
function isLoopDetectionDegraded() {
|
|
1923
|
+
return storageReadFailureLogged || storageWriteFailureLogged;
|
|
1924
|
+
}
|
|
1799
1925
|
function logStorageReadFailure(logger, err) {
|
|
1800
1926
|
if (storageReadFailureLogged) return;
|
|
1801
1927
|
storageReadFailureLogged = true;
|
|
@@ -1940,9 +2066,16 @@ function createRedirectMethods(deps) {
|
|
|
1940
2066
|
}
|
|
1941
2067
|
const state = generateRandomState();
|
|
1942
2068
|
if (!storeState(state, logger)) {
|
|
2069
|
+
cleanupVerifierStorage(logger);
|
|
2070
|
+
cleanupStateStorage(logger);
|
|
1943
2071
|
logger.error(
|
|
1944
|
-
"redirectToAuthorize: OAuth state \u3092\u4FDD\u5B58\u3067\u304D\u307E\u305B\u3093\u3067\u3057\u305F\uFF08storage \u5229\u7528\u4E0D\u53EF\uFF09\u3002\
|
|
2072
|
+
"redirectToAuthorize: OAuth state \u3092\u4FDD\u5B58\u3067\u304D\u307E\u305B\u3093\u3067\u3057\u305F\uFF08storage \u5229\u7528\u4E0D\u53EF\uFF09\u3002\u8A8D\u53EF\u30EA\u30C0\u30A4\u30EC\u30AF\u30C8\u3092\u4E2D\u6B62\u3057\u307E\u3057\u305F"
|
|
1945
2073
|
);
|
|
2074
|
+
return {
|
|
2075
|
+
success: false,
|
|
2076
|
+
code: "state_persistence_failed",
|
|
2077
|
+
error: "OAuth state \u3092\u4FDD\u5B58\u3067\u304D\u307E\u305B\u3093\u3067\u3057\u305F\uFF08storage \u5229\u7528\u4E0D\u53EF\uFF09\u3002\u8A8D\u53EF\u30EA\u30C0\u30A4\u30EC\u30AF\u30C8\u3092\u4E2D\u6B62\u3057\u307E\u3057\u305F"
|
|
2078
|
+
};
|
|
1946
2079
|
}
|
|
1947
2080
|
const redirectUri = resolvedRedirectUri ?? window.location.origin + window.location.pathname;
|
|
1948
2081
|
const params = new URLSearchParams({
|
|
@@ -1977,7 +2110,7 @@ function createRedirectMethods(deps) {
|
|
|
1977
2110
|
rollbackLastRedirectAttempt(authorizeKey, logger);
|
|
1978
2111
|
throw err;
|
|
1979
2112
|
}
|
|
1980
|
-
return { success: true };
|
|
2113
|
+
return isLoopDetectionDegraded() ? { success: true, loopDetectionDisabled: true } : { success: true };
|
|
1981
2114
|
}
|
|
1982
2115
|
async function redirectToLogin(options) {
|
|
1983
2116
|
if (typeof window === "undefined") {
|
|
@@ -3130,6 +3263,11 @@ function createFFIDClient(config) {
|
|
|
3130
3263
|
createError,
|
|
3131
3264
|
serviceCode: config.serviceCode
|
|
3132
3265
|
});
|
|
3266
|
+
const { provisionUser, provisionOrganization } = createProvisioningMethods({
|
|
3267
|
+
fetchWithAuth,
|
|
3268
|
+
createError,
|
|
3269
|
+
authMode
|
|
3270
|
+
});
|
|
3133
3271
|
const { getProfile, updateProfile } = createProfileMethods({
|
|
3134
3272
|
fetchWithAuth,
|
|
3135
3273
|
createError
|
|
@@ -3225,6 +3363,8 @@ function createFFIDClient(config) {
|
|
|
3225
3363
|
addMember,
|
|
3226
3364
|
updateMemberRole,
|
|
3227
3365
|
removeMember,
|
|
3366
|
+
provisionUser,
|
|
3367
|
+
provisionOrganization,
|
|
3228
3368
|
getProfile,
|
|
3229
3369
|
updateProfile,
|
|
3230
3370
|
// Non-contract ext API coverage (#3783)
|
|
@@ -4237,7 +4377,8 @@ var quietLogger = {
|
|
|
4237
4377
|
},
|
|
4238
4378
|
warn: () => {
|
|
4239
4379
|
},
|
|
4240
|
-
error: (
|
|
4380
|
+
error: () => {
|
|
4381
|
+
}
|
|
4241
4382
|
};
|
|
4242
4383
|
var consoleLogger2 = {
|
|
4243
4384
|
debug: (...args) => console.debug(SDK_LOG_PREFIX2, ...args),
|