@msdongcha/auth-kit 0.0.1 → 0.0.2

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 CHANGED
@@ -1,25 +1,152 @@
1
1
  # @msdongcha/auth-kit
2
2
 
3
- 无 UI、framework-agnostic 的认证域名校验、统一登录地址、账号密码登录、refresh、可信上下文消费与 logout 工具。它不包含 React、Router、页面、表单、样式或后端授权实现。
3
+ ## 初始化
4
+
5
+ ```bash
6
+ pnpm add @msdongcha/auth-kit
7
+ ```
8
+
9
+ `TokenStore` 由业务应用实现。SDK 只通过它读取、轮换和清理 token;不会读取 React、Router、页面状态或浏览器存储。
4
10
 
5
11
  ```ts
6
- import { create } from '@msdongcha/auth-kit'
12
+ import { create, type TokenStore } from '@msdongcha/auth-kit'
13
+
14
+ const tokenStore: TokenStore = {
15
+ get: () => {
16
+ const accessToken = sessionStorage.getItem('access_token')
17
+ const refreshToken = sessionStorage.getItem('refresh_token')
18
+ return accessToken ? { accessToken, refreshToken: refreshToken ?? undefined } : null
19
+ },
20
+ set: ({ accessToken, refreshToken }) => {
21
+ sessionStorage.setItem('access_token', accessToken)
22
+ if (refreshToken) sessionStorage.setItem('refresh_token', refreshToken)
23
+ else sessionStorage.removeItem('refresh_token')
24
+ },
25
+ clear: () => {
26
+ sessionStorage.removeItem('access_token')
27
+ sessionStorage.removeItem('refresh_token')
28
+ },
29
+ }
7
30
 
8
31
  const auth = create({
9
32
  authDomain: 'https://iam.example.com',
10
- appCode: 'business-web',
11
- allowedCallbackOrigins: ['https://business.example.com'],
33
+ app: 'business-web',
34
+ tokenStore,
12
35
  })
36
+ ```
37
+
38
+ `authDomain` 必须是 HTTPS;仅当 `runtime` 为 `development` 或 `test` 时允许 localhost HTTP。`app` 是认证入口使用的应用标识。`fetchImpl` 用于注入 fetch 实现或测试替身。
39
+
40
+ 不要配置 `allowedCallbackOrigins`。回跳地址的白名单只由 IAM 服务端配置和校验,SDK 不在客户端复制这项授权决策。
41
+
42
+ ## `create(config)` 返回的客户端
13
43
 
14
- const authorizeUrl = auth.buildAuthorizeUrl({
15
- callbackUrl: 'https://business.example.com/auth/callback',
44
+ ### `validate()`
45
+
46
+ 校验认证域名及当前 runtime 是否允许该 URL。
47
+
48
+ ```ts
49
+ const result = auth.validate()
50
+ if (!result.ok) console.error(result.error)
51
+ ```
52
+
53
+ ### `buildAuthorizeUrl(options?)`
54
+
55
+ 创建 IAM 登录地址,只输出 `app`、`return_to` 和可选的 `state` 参数。
56
+
57
+ ```ts
58
+ const result = auth.buildAuthorizeUrl({
59
+ returnTo: 'https://business.example.com/orders',
60
+ state: 'csrf-state',
16
61
  })
62
+
63
+ if (result.ok) window.location.assign(result.value)
64
+ ```
65
+
66
+ 显式传入 `returnTo` 时优先使用。浏览器中省略它时,SDK 使用当前页面的绝对 URL,并删除查询或 hash 中的 `access_token`、`refresh_token`、`code`、`id_token` 和 `token` 参数。非浏览器环境必须显式传入有效的 `returnTo`,否则返回 `RETURN_TO_UNAVAILABLE`。SDK 只校验 URL 格式与 runtime;最终回跳是否允许由 IAM 服务端决定。
67
+
68
+ `appCode`、`clientId` 和 `callbackUrl` 仍是迁移期的兼容别名,分别归一化为 `app` 和 `returnTo`;SDK 不会输出 `client_id` 或 `callback_url`。
69
+
70
+ ### `login(username, password)`
71
+
72
+ 向 `POST /api/v1/auth/token` 发送 `{ username, password }`,将返回的 token 写入 `TokenStore`,并返回 token、可选的可信上下文和身份信息。
73
+
74
+ ```ts
75
+ const result = await auth.login('alice', 'secret')
76
+ if (!result.ok) throw new Error(result.error)
17
77
  ```
18
78
 
19
- `getContext()` 需要后端返回可信 `principal_kind`、`selected_tenant`、`default_target` 和 `app_access`。当前服务端响应缺失这些字段时,SDK 返回 `AUTH_CONTEXT_MISSING` 或 `APP_ACCESS_DENIED`,不会从 roles 或 memberships 猜测。
79
+ ### `refreshToken()`
80
+
81
+ 使用 `TokenStore` 中的 refresh token 调用 `POST /api/v1/auth/refresh`。同一客户端实例中的并发刷新会合并为一次请求;成功后轮换 token,失败时清空 token。
82
+
83
+ ```ts
84
+ const result = await auth.refreshToken()
85
+ if (!result.ok) window.location.assign('/login')
86
+ ```
87
+
88
+ ### `getIdentity()`
89
+
90
+ 从 `TokenStore` 读取 access token,以 Bearer token 调用 `GET /api/v1/auth/me`,返回 IAM 远程响应中的 user、roles 和 permissions。SDK 不使用 JWKS,也不在本地解析 token。
91
+
92
+ ```ts
93
+ const result = await auth.getIdentity()
94
+ if (result.ok && result.value.permissions.includes('orders:read')) {
95
+ // 渲染订单入口
96
+ }
97
+ ```
98
+
99
+ ### `getContext()`
100
+
101
+ 同样通过 `GET /api/v1/auth/me` 获取远程数据,但只在响应包含可信的 `principal_kind`、`selected_tenant`、`default_target` 和 `app_access` 时返回 `AuthContext`。缺少这些字段时返回 `AUTH_CONTEXT_MISSING`;`app_access.allowed` 为 false 时返回 `APP_ACCESS_DENIED`。不会从 roles 或 memberships 推断上下文。
102
+
103
+ ```ts
104
+ const result = await auth.getContext()
105
+ if (result.ok) console.log(result.value.selectedTenant)
106
+ ```
107
+
108
+ ### `logout()`
109
+
110
+ 只清空 `TokenStore`,不发送网络请求。
111
+
112
+ ```ts
113
+ await auth.logout()
114
+ ```
115
+
116
+ ## 其他公开工具
117
+
118
+ ### `sanitizeCallbackUrl(url)`
119
+
120
+ 返回已删除认证参数的路径、查询和 hash,适合回调页在写入 TokenStore 后清理浏览器地址。
121
+
122
+ ```ts
123
+ window.history.replaceState(null, '', sanitizeCallbackUrl(window.location.href))
124
+ ```
125
+
126
+ ### `resolveTenantDecision(input)`
127
+
128
+ 优先消费 `AuthContext` 中受信任的 selected tenant。只有显式设置 `allowLegacyDefault: true` 时,才兼容一个激活的 legacy `isDefault` membership。
129
+
130
+ ```ts
131
+ const decision = resolveTenantDecision({ context: authContext })
132
+ if (decision.ok) useTenant(decision.value.selectedTenant.id)
133
+ ```
134
+
135
+ ### `createAuthKit(config)`
136
+
137
+ 旧版兼容入口。它返回 `validateConfiguration()`、`buildAuthorizeUrl()`、`decideDestination(context)` 和 `applySession(result, adapter?)`。新业务使用 `create()`;此入口仅保留给仍依赖旧配置和返回形状的调用方。
138
+
139
+ ### `createRefreshCoordinator()`
140
+
141
+ 创建旧版 refresh single-flight 协调器。`refresh(task)` 会让同一实例中并发的布尔刷新任务共用一个 Promise。
142
+
143
+ ```ts
144
+ const coordinator = createRefreshCoordinator()
145
+ await coordinator.refresh(() => refreshBusinessSession())
146
+ ```
20
147
 
21
- `getIdentity()` 是旧 `/api/v1/auth/me` 的兼容读取入口,只用于 IAM 内部页面展示 user、tenant、membership、roles 和 permissions;它不能替代可信 `AuthContext`,也不能放行外部业务 callback。
148
+ ## 公开类型与结果
22
149
 
23
- 应用应通过一个 `TokenStore` adapter 将 SDK 接到自己的 session store。SDK 负责 login/refresh HTTP 与 token rotation,应用业务 client 只负责 Bearer 注入和一次重试。
150
+ `AuthKitConfig`、`AuthClient`、`TokenStore`、`TokenSet`、`AuthIdentity`、`AuthContext`、`AuthKitResult`、`LoginResult`、`AuthorizeOptions`、`TenantDecision` 和相关 legacy 类型均从包根导出。由 `create()` 返回的客户端方法返回 `{ ok: true, value }` 或 `{ ok: false, error, retryable? }`;调用方应先判断 `ok` 再读取 `value`。
24
151
 
25
- `createAuthKit` 和 `createRefreshCoordinator` 保留为旧版本兼容入口,新业务请使用 `create`。
152
+ SDK 可以解包 IAM 的 `{ success, message, data }` 响应信封,也兼容裸 JSON 响应。它不实现 IAM 后端授权、回跳白名单或 JWKS token 校验。
package/dist/index.d.ts CHANGED
@@ -48,9 +48,11 @@ export interface SessionAdapter<T = void> {
48
48
  }
49
49
  export interface AuthKitConfig {
50
50
  authDomain: string;
51
+ app?: string;
52
+ /** @deprecated 请改用 app。 */
51
53
  appCode?: string;
54
+ /** @deprecated 请改用 app。 */
52
55
  clientId?: string;
53
- allowedCallbackOrigins?: readonly string[];
54
56
  tokenStore?: TokenStore;
55
57
  fetchImpl?: typeof fetch;
56
58
  runtime?: 'development' | 'test' | 'staging' | 'production';
@@ -66,7 +68,7 @@ export interface LegacyAuthKitConfig {
66
68
  allowedLoginOrigins?: readonly string[];
67
69
  isLoginHost?: boolean;
68
70
  }
69
- export type AuthErrorCode = 'AUTH_DOMAIN_MISSING' | 'AUTH_DOMAIN_INVALID' | 'CALLBACK_URL_INVALID' | 'CALLBACK_ORIGIN_DENIED' | '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' | 'APP_ACCESS_DENIED' | 'CALLBACK_REJECTED' | 'TOKEN_EXPIRED' | 'REFRESH_FAILED' | 'LOGOUT_FAILED' | 'AUTH_PROTOCOL_UNAVAILABLE';
70
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';
71
73
  export type AuthKitResult<T> = {
72
74
  ok: true;
@@ -90,21 +92,26 @@ export interface AuthIdentity {
90
92
  attrs?: Record<string, unknown>;
91
93
  }
92
94
  export interface AuthorizeOptions {
93
- callbackUrl: string;
95
+ returnTo?: string;
96
+ app?: string;
97
+ /** @deprecated 请改用 returnTo。 */
98
+ callbackUrl?: string;
99
+ /** @deprecated 请改用 app。 */
94
100
  appCode?: string;
101
+ /** @deprecated 请改用 app。 */
95
102
  clientId?: string;
96
103
  state?: string;
97
104
  }
98
105
  export interface AuthClient {
99
106
  validate: () => AuthKitResult<void>;
100
- buildAuthorizeUrl: (options: AuthorizeOptions) => AuthKitResult<string>;
107
+ buildAuthorizeUrl: (options?: AuthorizeOptions) => AuthKitResult<string>;
101
108
  login: (username: string, password: string) => Promise<AuthKitResult<LoginResult>>;
102
109
  refreshToken: () => Promise<AuthKitResult<LoginResult>>;
103
110
  getIdentity: () => Promise<AuthKitResult<AuthIdentity>>;
104
111
  getContext: () => Promise<AuthKitResult<AuthContext>>;
105
112
  logout: () => Promise<AuthKitResult<void>>;
106
113
  }
107
- /** 清理回调 URL 中不应保留在 history 或 referrer 的认证参数。 */
114
+ /** 清理不应保留在 history 或 referrer 的认证参数。 */
108
115
  export declare function sanitizeCallbackUrl(value: string): string;
109
116
  export interface LegacyMembershipCandidate {
110
117
  membershipId: string;
@@ -129,7 +136,7 @@ export declare function createAuthKit(config: LegacyAuthKitConfig): {
129
136
  validateConfiguration: () => AuthKitResult<{
130
137
  loginEntryUrl: string;
131
138
  }>;
132
- buildAuthorizeUrl: (requestedTenantId?: string) => AuthKitResult<{
139
+ buildAuthorizeUrl: (_requestedTenantId?: string) => AuthKitResult<{
133
140
  url: string;
134
141
  }>;
135
142
  decideDestination: (context: AuthContext) => AuthKitResult<{
@@ -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
@@ -1,9 +1,24 @@
1
- /** 清理回调 URL 中不应保留在 history 或 referrer 的认证参数。 */
1
+ const SENSITIVE_CALLBACK_PARAMS = ['access_token', 'refresh_token', 'code', 'id_token', 'token'];
2
+ function removeSensitiveCallbackParams(url) {
3
+ for (const param of SENSITIVE_CALLBACK_PARAMS)
4
+ url.searchParams.delete(param);
5
+ const hash = url.hash.slice(1);
6
+ const queryIndex = hash.indexOf('?');
7
+ const hashQuery = queryIndex >= 0 ? hash.slice(queryIndex + 1) : hash.includes('=') ? hash : null;
8
+ if (!hashQuery)
9
+ return url;
10
+ const hashParams = new URLSearchParams(hashQuery);
11
+ for (const param of SENSITIVE_CALLBACK_PARAMS)
12
+ hashParams.delete(param);
13
+ const sanitizedHashQuery = hashParams.toString();
14
+ url.hash = queryIndex >= 0
15
+ ? sanitizedHashQuery ? `${hash.slice(0, queryIndex)}?${sanitizedHashQuery}` : hash.slice(0, queryIndex)
16
+ : sanitizedHashQuery;
17
+ return url;
18
+ }
19
+ /** 清理不应保留在 history 或 referrer 的认证参数。 */
2
20
  export function sanitizeCallbackUrl(value) {
3
- const url = new URL(value);
4
- url.searchParams.delete('access_token');
5
- url.searchParams.delete('refresh_token');
6
- url.searchParams.delete('code');
21
+ const url = removeSensitiveCallbackParams(new URL(value));
7
22
  return `${url.pathname}${url.search}${url.hash}`;
8
23
  }
9
24
  function parseHttpUrl(value, allowLocalhost = true) {
@@ -19,6 +34,16 @@ function parseHttpUrl(value, allowLocalhost = true) {
19
34
  return null;
20
35
  }
21
36
  }
37
+ function currentBrowserUrl() {
38
+ if (typeof window === 'undefined')
39
+ return null;
40
+ try {
41
+ return removeSensitiveCallbackParams(new URL(window.location.href)).toString();
42
+ }
43
+ catch {
44
+ return null;
45
+ }
46
+ }
22
47
  function ok(value) {
23
48
  return { ok: true, value };
24
49
  }
@@ -28,6 +53,12 @@ function fail(error, retryable = false) {
28
53
  function isRecord(value) {
29
54
  return Boolean(value && typeof value === 'object');
30
55
  }
56
+ /** 认证服务与业务接口统一使用响应信封,SDK 对外仍返回既有领域对象。 */
57
+ function unwrapResponse(value) {
58
+ if (!isRecord(value) || !('success' in value))
59
+ return { ok: true, value };
60
+ return value.success === true ? { ok: true, value: value.data } : { ok: false };
61
+ }
31
62
  function stringValue(value) {
32
63
  return typeof value === 'string' && value.length > 0 ? value : undefined;
33
64
  }
@@ -143,20 +174,20 @@ export function create(config) {
143
174
  return fail('AUTH_DOMAIN_INVALID');
144
175
  return ok(undefined);
145
176
  }
146
- function buildAuthorizeUrl(options) {
177
+ function buildAuthorizeUrl(options = {}) {
147
178
  const valid = validate();
148
179
  if (!valid.ok)
149
180
  return valid;
150
- const callbackUrl = parseHttpUrl(options.callbackUrl, config.runtime === 'development' || config.runtime === 'test');
181
+ const url = new URL('/auth/login', `${authDomain}/`);
182
+ const app = options.app ?? config.app ?? options.appCode ?? options.clientId ?? config.appCode ?? config.clientId ?? '';
183
+ const configuredReturnTo = options.returnTo ?? options.callbackUrl;
184
+ const returnTo = configuredReturnTo ?? currentBrowserUrl();
185
+ if (returnTo === null)
186
+ return fail('RETURN_TO_UNAVAILABLE');
187
+ const callbackUrl = parseHttpUrl(returnTo, config.runtime === 'development' || config.runtime === 'test');
151
188
  if (!callbackUrl)
152
189
  return fail('CALLBACK_URL_INVALID');
153
- const allowedOrigins = config.allowedCallbackOrigins ?? [];
154
- if (allowedOrigins.length > 0 && !allowedOrigins.includes(callbackUrl.origin))
155
- return fail('CALLBACK_ORIGIN_DENIED');
156
- const url = new URL('/auth/login', `${authDomain}/`);
157
- url.searchParams.set('app', options.appCode ?? config.appCode ?? '');
158
- url.searchParams.set('client_id', options.clientId ?? config.clientId ?? '');
159
- url.searchParams.set('callback_url', callbackUrl.toString());
190
+ url.searchParams.set('app', app);
160
191
  url.searchParams.set('return_to', callbackUrl.toString());
161
192
  if (options.state)
162
193
  url.searchParams.set('state', options.state);
@@ -176,7 +207,10 @@ export function create(config) {
176
207
  });
177
208
  if (!response.ok)
178
209
  return responseError(response.status);
179
- const body = await response.json();
210
+ const responseBody = unwrapResponse(await response.json());
211
+ if (!responseBody.ok)
212
+ return fail('AUTH_PROTOCOL_UNAVAILABLE');
213
+ const body = responseBody.value;
180
214
  const tokens = toTokenSet(body);
181
215
  if (!tokens)
182
216
  return fail('AUTH_PROTOCOL_UNAVAILABLE');
@@ -202,7 +236,12 @@ export function create(config) {
202
236
  tokenStore.clear();
203
237
  return response.status === 401 ? fail('TOKEN_EXPIRED') : responseError(response.status);
204
238
  }
205
- const body = await response.json();
239
+ const responseBody = unwrapResponse(await response.json());
240
+ if (!responseBody.ok) {
241
+ tokenStore.clear();
242
+ return fail('AUTH_PROTOCOL_UNAVAILABLE');
243
+ }
244
+ const body = responseBody.value;
206
245
  const tokens = toTokenSet(body);
207
246
  if (!tokens) {
208
247
  tokenStore.clear();
@@ -221,7 +260,7 @@ export function create(config) {
221
260
  refreshPending = runRefresh().finally(() => { refreshPending = null; });
222
261
  return refreshPending;
223
262
  }
224
- async function getContext() {
263
+ async function getRemoteIdentity() {
225
264
  const current = tokenStore.get();
226
265
  if (!current?.accessToken)
227
266
  return fail('LOGIN_REQUIRED');
@@ -229,31 +268,32 @@ export function create(config) {
229
268
  const response = await fetchImpl(`${authDomain}/api/v1/auth/me`, { headers: { Authorization: `Bearer ${current.accessToken}` } });
230
269
  if (!response.ok)
231
270
  return response.status === 401 ? fail('TOKEN_EXPIRED') : responseError(response.status);
232
- const context = normalizeContext(await response.json());
233
- if (!context)
234
- return fail('AUTH_CONTEXT_MISSING');
235
- if (!context.appAccess?.allowed)
236
- return fail('APP_ACCESS_DENIED');
237
- return ok(context);
271
+ const responseBody = unwrapResponse(await response.json());
272
+ if (!responseBody.ok)
273
+ return fail('AUTH_PROTOCOL_UNAVAILABLE');
274
+ return ok(responseBody.value);
238
275
  }
239
276
  catch {
240
277
  return fail('AUTH_PROTOCOL_UNAVAILABLE', true);
241
278
  }
242
279
  }
280
+ async function getContext() {
281
+ const remoteIdentity = await getRemoteIdentity();
282
+ if (!remoteIdentity.ok)
283
+ return remoteIdentity;
284
+ const context = normalizeContext(remoteIdentity.value);
285
+ if (!context)
286
+ return fail('AUTH_CONTEXT_MISSING');
287
+ if (!context.appAccess?.allowed)
288
+ return fail('APP_ACCESS_DENIED');
289
+ return ok(context);
290
+ }
243
291
  async function getIdentity() {
244
- const current = tokenStore.get();
245
- if (!current?.accessToken)
246
- return fail('LOGIN_REQUIRED');
247
- try {
248
- const response = await fetchImpl(`${authDomain}/api/v1/auth/me`, { headers: { Authorization: `Bearer ${current.accessToken}` } });
249
- if (!response.ok)
250
- return response.status === 401 ? fail('TOKEN_EXPIRED') : responseError(response.status);
251
- const identity = normalizeIdentity(await response.json());
252
- return identity ? ok(identity) : fail('AUTH_PROTOCOL_UNAVAILABLE');
253
- }
254
- catch {
255
- return fail('AUTH_PROTOCOL_UNAVAILABLE', true);
256
- }
292
+ const remoteIdentity = await getRemoteIdentity();
293
+ if (!remoteIdentity.ok)
294
+ return remoteIdentity;
295
+ const identity = normalizeIdentity(remoteIdentity.value);
296
+ return identity ? ok(identity) : fail('AUTH_PROTOCOL_UNAVAILABLE');
257
297
  }
258
298
  async function logout() {
259
299
  tokenStore.clear();
@@ -276,18 +316,15 @@ export function createAuthKit(config) {
276
316
  return fail('LOGIN_ENTRY_ORIGIN_DENIED');
277
317
  return ok({ loginEntryUrl: entryUrl.toString() });
278
318
  }
279
- function buildAuthorizeUrl(requestedTenantId) {
319
+ function buildAuthorizeUrl(_requestedTenantId) {
280
320
  const valid = validateConfiguration();
281
321
  if (!valid.ok)
282
322
  return valid;
283
323
  if (config.isLoginHost)
284
324
  return fail('AUTH_ENTRY_CALLBACK_LOOP');
285
325
  const url = new URL(valid.value.loginEntryUrl);
286
- url.searchParams.set('client_id', config.clientId);
287
326
  url.searchParams.set('app', config.appCode);
288
327
  url.searchParams.set('return_to', callbackUrl.toString());
289
- if (requestedTenantId)
290
- url.searchParams.set('requested_tenant_id', requestedTenantId);
291
328
  return ok({ url: url.toString() });
292
329
  }
293
330
  function decideDestination(context) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@msdongcha/auth-kit",
3
- "version": "0.0.1",
3
+ "version": "0.0.2",
4
4
  "description": "Framework-agnostic authentication context, redirect, callback and session coordination utilities.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",