@msdongcha/auth-kit 0.0.2 → 0.0.3

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/CHANGELOG.md CHANGED
@@ -1,5 +1,13 @@
1
1
  # Changelog
2
2
 
3
+ ## Unreleased
4
+
5
+ ## 0.0.3 - 2026-10-23
6
+
7
+ - `logout()` 在清理本地 TokenStore 前调用 IAM `/api/v1/auth/logout` 撤销 refresh token,并返回明确的 `LOGOUT_FAILED` 错误。
8
+ - 新增 `getMemberships()`,以统一类型返回当前用户的租户成员列表。
9
+ - 新增 `switchTenant()`,在服务端返回完整 token 后原子切换会话,并保护并发切换。
10
+
3
11
  ## 0.0.1
4
12
 
5
13
  - 初始发布:无 UI Auth-kit 核心、配置校验、跳转决策、显式 SessionAdapter 与 refresh single-flight。
package/README.md CHANGED
@@ -105,12 +105,39 @@ const result = await auth.getContext()
105
105
  if (result.ok) console.log(result.value.selectedTenant)
106
106
  ```
107
107
 
108
+ ### `getMemberships()`
109
+
110
+ 使用 `TokenStore` 中的 access token 调用 `GET /api/v1/auth/memberships`,返回 camelCase 的租户成员列表。SDK 会保留 `membershipStatus`;列表中的成员关系不代表服务端一定允许切换。
111
+
112
+ ```ts
113
+ const result = await auth.getMemberships()
114
+ if (result.ok) {
115
+ for (const membership of result.value) {
116
+ console.log(membership.tenantId, membership.isDefault)
117
+ }
118
+ }
119
+ ```
120
+
121
+ ### `switchTenant(membershipId)`
122
+
123
+ 使用当前 access token 调用 `POST /api/v1/auth/switch-tenant`。服务端返回完整的新 access token 和 refresh token 后,SDK 才会原子替换 `TokenStore`;请求失败或响应不完整时保留原会话。
124
+
125
+ 同一客户端实例中,对相同 membership 的并发切换会合并为一次请求;切换未完成时请求其他 membership 会返回 `TENANT_SWITCH_IN_PROGRESS`。
126
+
127
+ ```ts
128
+ const result = await auth.switchTenant('membership-id')
129
+ if (result.ok) {
130
+ console.log(result.value.identity?.roles)
131
+ }
132
+ ```
133
+
108
134
  ### `logout()`
109
135
 
110
- 只清空 `TokenStore`,不发送网络请求。
136
+ 如果 `TokenStore` 中存在 refresh token,则调用 `POST /api/v1/auth/logout` 撤销它,请求体为 `{ refresh_token }`。无论服务端请求成功或失败,SDK 都会清空本地 `TokenStore`;请求失败时返回 `LOGOUT_FAILED`。没有 refresh token 时只执行本地清理。
111
137
 
112
138
  ```ts
113
- await auth.logout()
139
+ const result = await auth.logout()
140
+ if (!result.ok) console.error(result.error)
114
141
  ```
115
142
 
116
143
  ## 其他公开工具
@@ -147,6 +174,6 @@ await coordinator.refresh(() => refreshBusinessSession())
147
174
 
148
175
  ## 公开类型与结果
149
176
 
150
- `AuthKitConfig`、`AuthClient`、`TokenStore`、`TokenSet`、`AuthIdentity`、`AuthContext`、`AuthKitResult`、`LoginResult`、`AuthorizeOptions`、`TenantDecision` 和相关 legacy 类型均从包根导出。由 `create()` 返回的客户端方法返回 `{ ok: true, value }` 或 `{ ok: false, error, retryable? }`;调用方应先判断 `ok` 再读取 `value`。
177
+ `AuthKitConfig`、`AuthClient`、`TokenStore`、`TokenSet`、`AuthIdentity`、`AuthContext`、`TenantMembership`、`AuthKitResult`、`LoginResult`、`AuthorizeOptions`、`TenantDecision` 和相关 legacy 类型均从包根导出。由 `create()` 返回的客户端方法返回 `{ ok: true, value }` 或 `{ ok: false, error, retryable? }`;调用方应先判断 `ok` 再读取 `value`。
151
178
 
152
179
  SDK 可以解包 IAM 的 `{ success, message, data }` 响应信封,也兼容裸 JSON 响应。它不实现 IAM 后端授权、回跳白名单或 JWKS token 校验。
package/dist/index.d.ts CHANGED
@@ -68,7 +68,7 @@ export interface LegacyAuthKitConfig {
68
68
  allowedLoginOrigins?: readonly string[];
69
69
  isLoginHost?: boolean;
70
70
  }
71
- export type AuthErrorCode = 'AUTH_DOMAIN_MISSING' | 'AUTH_DOMAIN_INVALID' | 'CALLBACK_URL_INVALID' | 'RETURN_TO_UNAVAILABLE' | 'AUTH_ENTRY_CALLBACK_LOOP' | 'LOGIN_REQUIRED' | 'INVALID_CREDENTIALS' | 'AUTH_CONTEXT_MISSING' | 'PRINCIPAL_CONTEXT_INVALID' | 'TENANT_CONTEXT_INVALID' | 'TENANT_ACCESS_DENIED' | 'APP_ACCESS_DENIED' | 'CALLBACK_REJECTED' | 'TOKEN_EXPIRED' | 'REFRESH_FAILED' | 'LOGOUT_FAILED' | 'AUTH_PROTOCOL_UNAVAILABLE';
71
+ export type AuthErrorCode = 'AUTH_DOMAIN_MISSING' | 'AUTH_DOMAIN_INVALID' | 'CALLBACK_URL_INVALID' | 'RETURN_TO_UNAVAILABLE' | 'AUTH_ENTRY_CALLBACK_LOOP' | 'LOGIN_REQUIRED' | 'INVALID_CREDENTIALS' | 'AUTH_CONTEXT_MISSING' | 'PRINCIPAL_CONTEXT_INVALID' | 'TENANT_CONTEXT_INVALID' | 'TENANT_ACCESS_DENIED' | 'TENANT_SWITCH_IN_PROGRESS' | 'APP_ACCESS_DENIED' | 'CALLBACK_REJECTED' | 'TOKEN_EXPIRED' | 'REFRESH_FAILED' | 'LOGOUT_FAILED' | 'AUTH_PROTOCOL_UNAVAILABLE';
72
72
  export type AuthKitError = AuthErrorCode | 'LOGIN_ENTRY_URL_MISSING' | 'INVALID_LOGIN_ENTRY_URL' | 'LOGIN_ENTRY_ORIGIN_DENIED' | 'INVALID_CALLBACK' | 'CROSS_APP_CONTEXT' | 'IDENTITY_CONTEXT_MISSING';
73
73
  export type AuthKitResult<T> = {
74
74
  ok: true;
@@ -91,6 +91,13 @@ export interface AuthIdentity {
91
91
  permissions: string[];
92
92
  attrs?: Record<string, unknown>;
93
93
  }
94
+ export interface TenantMembership {
95
+ membershipId: string;
96
+ tenantId: string;
97
+ tenantName?: string;
98
+ membershipStatus: string;
99
+ isDefault: boolean;
100
+ }
94
101
  export interface AuthorizeOptions {
95
102
  returnTo?: string;
96
103
  app?: string;
@@ -109,6 +116,8 @@ export interface AuthClient {
109
116
  refreshToken: () => Promise<AuthKitResult<LoginResult>>;
110
117
  getIdentity: () => Promise<AuthKitResult<AuthIdentity>>;
111
118
  getContext: () => Promise<AuthKitResult<AuthContext>>;
119
+ getMemberships: () => Promise<AuthKitResult<TenantMembership[]>>;
120
+ switchTenant: (membershipId: string) => Promise<AuthKitResult<LoginResult>>;
112
121
  logout: () => Promise<AuthKitResult<void>>;
113
122
  }
114
123
  /** 清理不应保留在 history 或 referrer 的认证参数。 */
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
package/dist/index.js CHANGED
@@ -121,6 +121,37 @@ function normalizeIdentity(value) {
121
121
  ...(attrs ? { attrs } : {}),
122
122
  };
123
123
  }
124
+ function normalizeBoolean(value) {
125
+ if (value === true || value === 1 || value === '1' || value === 'true')
126
+ return true;
127
+ if (value === false || value === 0 || value === '0' || value === 'false')
128
+ return false;
129
+ return undefined;
130
+ }
131
+ function normalizeMemberships(value) {
132
+ if (!isRecord(value) || !Array.isArray(value.memberships))
133
+ return null;
134
+ const memberships = [];
135
+ for (const item of value.memberships) {
136
+ if (!isRecord(item))
137
+ return null;
138
+ const membershipId = stringValue(item.membership_id)?.trim();
139
+ const tenantId = stringValue(item.tenant_id)?.trim();
140
+ const membershipStatus = stringValue(item.membership_status)?.trim();
141
+ const tenantName = stringValue(item.tenant_name)?.trim();
142
+ const isDefault = item.is_default === undefined ? false : normalizeBoolean(item.is_default);
143
+ if (!membershipId || !tenantId || !membershipStatus || isDefault === undefined)
144
+ return null;
145
+ memberships.push({
146
+ membershipId,
147
+ tenantId,
148
+ ...(tenantName ? { tenantName } : {}),
149
+ membershipStatus,
150
+ isDefault,
151
+ });
152
+ }
153
+ return memberships;
154
+ }
124
155
  /** 只消费后端 selected tenant;旧接口仅允许 is_default 兼容,不猜 joined_at 或身份。 */
125
156
  export function resolveTenantDecision(input) {
126
157
  const context = input.context;
@@ -167,6 +198,8 @@ export function create(config) {
167
198
  const tokenStore = config.tokenStore ?? createMemoryStore();
168
199
  const authDomain = config.authDomain.replace(/\/$/, '');
169
200
  let refreshPending = null;
201
+ let membershipsPending = null;
202
+ let switchPending = null;
170
203
  function validate() {
171
204
  if (!config.authDomain)
172
205
  return fail('AUTH_DOMAIN_MISSING');
@@ -260,23 +293,45 @@ export function create(config) {
260
293
  refreshPending = runRefresh().finally(() => { refreshPending = null; });
261
294
  return refreshPending;
262
295
  }
263
- async function getRemoteIdentity() {
296
+ async function getAuthorizedJson(path, init = {}, forbiddenError = 'APP_ACCESS_DENIED', preserveLegacyErrors = false) {
264
297
  const current = tokenStore.get();
265
298
  if (!current?.accessToken)
266
299
  return fail('LOGIN_REQUIRED');
300
+ let response;
301
+ try {
302
+ response = await fetchImpl(`${authDomain}${path}`, {
303
+ ...init,
304
+ headers: {
305
+ Authorization: `Bearer ${current.accessToken}`,
306
+ ...(init.body === undefined ? {} : { 'Content-Type': 'application/json' }),
307
+ },
308
+ });
309
+ }
310
+ catch {
311
+ return fail('AUTH_PROTOCOL_UNAVAILABLE', true);
312
+ }
313
+ if (!response.ok) {
314
+ if (response.status === 401)
315
+ return fail('TOKEN_EXPIRED');
316
+ if (response.status === 403)
317
+ return fail(forbiddenError);
318
+ if (preserveLegacyErrors)
319
+ return responseError(response.status);
320
+ return fail('AUTH_PROTOCOL_UNAVAILABLE', response.status === 429 || response.status >= 500);
321
+ }
267
322
  try {
268
- const response = await fetchImpl(`${authDomain}/api/v1/auth/me`, { headers: { Authorization: `Bearer ${current.accessToken}` } });
269
- if (!response.ok)
270
- return response.status === 401 ? fail('TOKEN_EXPIRED') : responseError(response.status);
271
323
  const responseBody = unwrapResponse(await response.json());
272
324
  if (!responseBody.ok)
273
325
  return fail('AUTH_PROTOCOL_UNAVAILABLE');
274
326
  return ok(responseBody.value);
275
327
  }
276
328
  catch {
277
- return fail('AUTH_PROTOCOL_UNAVAILABLE', true);
329
+ return fail('AUTH_PROTOCOL_UNAVAILABLE', preserveLegacyErrors);
278
330
  }
279
331
  }
332
+ function getRemoteIdentity() {
333
+ return getAuthorizedJson('/api/v1/auth/me', {}, 'APP_ACCESS_DENIED', true);
334
+ }
280
335
  async function getContext() {
281
336
  const remoteIdentity = await getRemoteIdentity();
282
337
  if (!remoteIdentity.ok)
@@ -295,11 +350,74 @@ export function create(config) {
295
350
  const identity = normalizeIdentity(remoteIdentity.value);
296
351
  return identity ? ok(identity) : fail('AUTH_PROTOCOL_UNAVAILABLE');
297
352
  }
353
+ async function runGetMemberships() {
354
+ const result = await getAuthorizedJson('/api/v1/auth/memberships', {}, 'TENANT_ACCESS_DENIED');
355
+ if (!result.ok)
356
+ return result;
357
+ const memberships = normalizeMemberships(result.value);
358
+ return memberships ? ok(memberships) : fail('AUTH_PROTOCOL_UNAVAILABLE');
359
+ }
360
+ function getMemberships() {
361
+ if (!membershipsPending)
362
+ membershipsPending = runGetMemberships().finally(() => { membershipsPending = null; });
363
+ return membershipsPending;
364
+ }
365
+ async function runSwitchTenant(membershipId) {
366
+ const result = await getAuthorizedJson('/api/v1/auth/switch-tenant', {
367
+ method: 'POST',
368
+ body: JSON.stringify({ membership_id: membershipId }),
369
+ }, 'TENANT_ACCESS_DENIED');
370
+ if (!result.ok)
371
+ return result;
372
+ const tokens = toTokenSet(result.value);
373
+ if (!tokens?.refreshToken)
374
+ return fail('AUTH_PROTOCOL_UNAVAILABLE');
375
+ const context = isRecord(result.value) ? normalizeContext(result.value.context ?? result.value.auth_context) ?? undefined : undefined;
376
+ const identity = normalizeIdentity(result.value) ?? undefined;
377
+ tokenStore.set(tokens);
378
+ return ok({ ...tokens, context, identity });
379
+ }
380
+ function switchTenant(rawMembershipId) {
381
+ const membershipId = rawMembershipId.trim();
382
+ if (!membershipId)
383
+ return Promise.resolve(fail('TENANT_CONTEXT_INVALID'));
384
+ if (switchPending) {
385
+ return switchPending.membershipId === membershipId
386
+ ? switchPending.promise
387
+ : Promise.resolve(fail('TENANT_SWITCH_IN_PROGRESS'));
388
+ }
389
+ const promise = runSwitchTenant(membershipId).finally(() => {
390
+ if (switchPending?.promise === promise)
391
+ switchPending = null;
392
+ });
393
+ switchPending = { membershipId, promise };
394
+ return promise;
395
+ }
298
396
  async function logout() {
299
- tokenStore.clear();
300
- return ok(undefined);
397
+ try {
398
+ const current = tokenStore.get();
399
+ if (!current?.refreshToken)
400
+ return ok(undefined);
401
+ const valid = validate();
402
+ if (!valid.ok)
403
+ return valid;
404
+ const response = await fetchImpl(`${authDomain}/api/v1/auth/logout`, {
405
+ method: 'POST',
406
+ headers: { 'Content-Type': 'application/json' },
407
+ body: JSON.stringify({ refresh_token: current.refreshToken }),
408
+ });
409
+ if (!response.ok)
410
+ return fail('LOGOUT_FAILED', response.status >= 408);
411
+ return ok(undefined);
412
+ }
413
+ catch {
414
+ return fail('LOGOUT_FAILED', true);
415
+ }
416
+ finally {
417
+ tokenStore.clear();
418
+ }
301
419
  }
302
- return { validate, buildAuthorizeUrl, login, refreshToken, getIdentity, getContext, logout };
420
+ return { validate, buildAuthorizeUrl, login, refreshToken, getIdentity, getContext, getMemberships, switchTenant, logout };
303
421
  }
304
422
  /** 创建旧版兼容实例;新业务接入请使用 create。 */
305
423
  export function createAuthKit(config) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@msdongcha/auth-kit",
3
- "version": "0.0.2",
3
+ "version": "0.0.3",
4
4
  "description": "Framework-agnostic authentication context, redirect, callback and session coordination utilities.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",