@iqauth/sdk 2.0.2 → 2.0.4
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 +384 -181
- package/dist/browser.d.mts +1 -1
- package/dist/browser.d.ts +1 -1
- package/dist/browser.js +37 -2
- package/dist/browser.mjs +1 -1
- package/dist/{chunk-5HF3OBNO.mjs → chunk-JQRTY5MY.mjs} +29 -5
- package/dist/{chunk-YDO2RDWQ.mjs → chunk-S3M2IXCE.mjs} +37 -2
- package/dist/express.js +29 -5
- package/dist/express.mjs +1 -1
- package/dist/fastify.js +29 -5
- package/dist/fastify.mjs +1 -1
- package/dist/hono.js +29 -5
- package/dist/hono.mjs +1 -1
- package/dist/next.js +29 -5
- package/dist/next.mjs +1 -1
- package/dist/react.d.mts +1 -1
- package/dist/react.d.ts +1 -1
- package/dist/react.js +37 -2
- package/dist/react.mjs +1 -1
- package/dist/server/handlers.d.mts +29 -2
- package/dist/server/handlers.d.ts +29 -2
- package/dist/server/handlers.js +29 -5
- package/dist/server/handlers.mjs +1 -1
- package/dist/server.js +29 -5
- package/dist/server.mjs +1 -1
- package/dist/{signIn-C8f6qVjD.d.mts → signIn-CEMdUAwd.d.mts} +22 -0
- package/dist/{signIn-Cy2lbEXb.d.ts → signIn-VRNzlNyG.d.ts} +22 -0
- package/docs/BROWSER_SESSION_MIGRATION.md +34 -0
- package/package.json +1 -1
package/dist/browser.d.mts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export { C as CallbackResult, S as SessionManager, d as SessionManagerOptions, a as SessionSnapshot, e as SessionStatus, b as SignInOptions, c as SignOutOptions, f as buildSignInUrl, h as handleAuthCallback, r as redirectToSignIn, s as signIn, g as signOut } from './signIn-
|
|
1
|
+
export { C as CallbackResult, S as SessionManager, d as SessionManagerOptions, a as SessionSnapshot, e as SessionStatus, b as SignInOptions, c as SignOutOptions, f as buildSignInUrl, h as handleAuthCallback, r as redirectToSignIn, s as signIn, g as signOut } from './signIn-CEMdUAwd.mjs';
|
|
2
2
|
export { K as KeyMode, b as ParsedPublishableKey, P as PublishableKeyPayload, e as encodePublishableKey, i as isPublishableKey, a as isSecretKey, p as parsePublishableKey } from './publishableKey-B5DIK81A.mjs';
|
|
3
3
|
export { a as ErrorCode, E as ErrorCodes, I as IQAuthError } from './errors-CDdl24MP.mjs';
|
|
4
4
|
import './types-Cxl3bQHt.mjs';
|
package/dist/browser.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export { C as CallbackResult, S as SessionManager, d as SessionManagerOptions, a as SessionSnapshot, e as SessionStatus, b as SignInOptions, c as SignOutOptions, f as buildSignInUrl, h as handleAuthCallback, r as redirectToSignIn, s as signIn, g as signOut } from './signIn-
|
|
1
|
+
export { C as CallbackResult, S as SessionManager, d as SessionManagerOptions, a as SessionSnapshot, e as SessionStatus, b as SignInOptions, c as SignOutOptions, f as buildSignInUrl, h as handleAuthCallback, r as redirectToSignIn, s as signIn, g as signOut } from './signIn-VRNzlNyG.js';
|
|
2
2
|
export { K as KeyMode, b as ParsedPublishableKey, P as PublishableKeyPayload, e as encodePublishableKey, i as isPublishableKey, a as isSecretKey, p as parsePublishableKey } from './publishableKey-B5DIK81A.js';
|
|
3
3
|
export { a as ErrorCode, E as ErrorCodes, I as IQAuthError } from './errors-CDdl24MP.js';
|
|
4
4
|
import './types-Cxl3bQHt.js';
|
package/dist/browser.js
CHANGED
|
@@ -270,8 +270,9 @@ var SessionManager = class {
|
|
|
270
270
|
this.refreshPath = options.refreshPath ?? DEFAULT_REFRESH_PATH;
|
|
271
271
|
this.userinfoPath = options.userinfoPath ?? DEFAULT_USERINFO_PATH;
|
|
272
272
|
this.useCookies = options.useCookies ?? true;
|
|
273
|
-
this.
|
|
274
|
-
this.
|
|
273
|
+
this.serverManagedSession = options.serverManagedSession ?? false;
|
|
274
|
+
this.proactiveRefresh = this.serverManagedSession ? false : options.proactiveRefresh ?? true;
|
|
275
|
+
this.tokenStore = options.tokenStore ?? (this.serverManagedSession ? NO_OP_STORE : this.useCookies ? defaultCookieStore() : NO_OP_STORE);
|
|
275
276
|
this.crossTabLockTimeoutMs = options.crossTabLockTimeoutMs ?? 4e3;
|
|
276
277
|
this.fetchImpl = options.fetchImpl ?? (typeof fetch !== "undefined" ? fetch.bind(globalThis) : (() => {
|
|
277
278
|
throw new Error("global fetch is not available; pass fetchImpl");
|
|
@@ -315,6 +316,40 @@ var SessionManager = class {
|
|
|
315
316
|
async bootstrap() {
|
|
316
317
|
if (this.bootstrapped) return;
|
|
317
318
|
this.bootstrapped = true;
|
|
319
|
+
if (this.serverManagedSession) {
|
|
320
|
+
try {
|
|
321
|
+
const res = await this.fetchImpl(`${this.issuer}${this.userinfoPath}`, {
|
|
322
|
+
method: "GET",
|
|
323
|
+
credentials: "include",
|
|
324
|
+
headers: { Accept: "application/json" }
|
|
325
|
+
});
|
|
326
|
+
if (!res.ok) {
|
|
327
|
+
this.setStatus("unauthenticated");
|
|
328
|
+
return;
|
|
329
|
+
}
|
|
330
|
+
const body = await res.json().catch(() => ({}));
|
|
331
|
+
const data = body?.data;
|
|
332
|
+
const user = data?.user ?? claimsToSessionUser(data?.claims ?? null);
|
|
333
|
+
if (!user) {
|
|
334
|
+
this.setStatus("unauthenticated");
|
|
335
|
+
return;
|
|
336
|
+
}
|
|
337
|
+
this.update({
|
|
338
|
+
status: "authenticated",
|
|
339
|
+
accessToken: null,
|
|
340
|
+
user,
|
|
341
|
+
claims: data?.claims ?? null,
|
|
342
|
+
tenantId: data?.tenantId ?? user.tenantId ?? this.key.tenantId,
|
|
343
|
+
error: null,
|
|
344
|
+
version: this.snapshot.version + 1
|
|
345
|
+
});
|
|
346
|
+
this.broadcast("session:update");
|
|
347
|
+
return;
|
|
348
|
+
} catch {
|
|
349
|
+
this.setStatus("unauthenticated");
|
|
350
|
+
return;
|
|
351
|
+
}
|
|
352
|
+
}
|
|
318
353
|
const stored = await Promise.resolve(this.tokenStore.read());
|
|
319
354
|
if (!stored) {
|
|
320
355
|
this.setStatus("unauthenticated");
|
package/dist/browser.mjs
CHANGED
|
@@ -3,6 +3,22 @@ import {
|
|
|
3
3
|
} from "./chunk-5WFR6Y33.mjs";
|
|
4
4
|
|
|
5
5
|
// src/server/handlers.ts
|
|
6
|
+
var TERMINAL_REFRESH_ERROR_CODES = /* @__PURE__ */ new Set([
|
|
7
|
+
"TOKEN_REVOKED",
|
|
8
|
+
"SESSION_REVOKED",
|
|
9
|
+
"INVALID_GRANT",
|
|
10
|
+
"invalid_grant",
|
|
11
|
+
"USER_DEACTIVATED",
|
|
12
|
+
"USER_DISABLED",
|
|
13
|
+
"TENANT_SUSPENDED"
|
|
14
|
+
]);
|
|
15
|
+
function shouldClearCookiesOnFailure(policy, status, errorCode) {
|
|
16
|
+
if (policy === "always") return true;
|
|
17
|
+
if (policy === "never") return false;
|
|
18
|
+
if (status === 410) return true;
|
|
19
|
+
if (errorCode && TERMINAL_REFRESH_ERROR_CODES.has(errorCode)) return true;
|
|
20
|
+
return false;
|
|
21
|
+
}
|
|
6
22
|
var ACCESS_TOKEN_TTL_SECONDS = 60 * 15;
|
|
7
23
|
var REFRESH_TOKEN_TTL_SECONDS = 60 * 60 * 24 * 30;
|
|
8
24
|
function resolve(config) {
|
|
@@ -30,7 +46,8 @@ function resolve(config) {
|
|
|
30
46
|
throw new Error("global fetch is unavailable; pass fetchImpl");
|
|
31
47
|
})),
|
|
32
48
|
appId: parsed.appId,
|
|
33
|
-
tenantId: parsed.tenantId
|
|
49
|
+
tenantId: parsed.tenantId,
|
|
50
|
+
clearCookiesOnRefreshFailure: config.clearCookiesOnRefreshFailure ?? "terminal-only"
|
|
34
51
|
};
|
|
35
52
|
}
|
|
36
53
|
function makeCookie(cfg, name, value, maxAge, httpOnly = true) {
|
|
@@ -126,7 +143,7 @@ async function handleRefresh(config, input) {
|
|
|
126
143
|
return {
|
|
127
144
|
status: 401,
|
|
128
145
|
body: { success: false, error: { code: "TOKEN_INVALID", message: "Missing refresh token" } },
|
|
129
|
-
cookies: clearCookies(cfg)
|
|
146
|
+
cookies: cfg.clearCookiesOnRefreshFailure === "always" ? clearCookies(cfg) : []
|
|
130
147
|
};
|
|
131
148
|
}
|
|
132
149
|
const res = await cfg.fetchImpl(`${cfg.issuer}${cfg.refreshPath}`, {
|
|
@@ -136,16 +153,23 @@ async function handleRefresh(config, input) {
|
|
|
136
153
|
});
|
|
137
154
|
const json = await res.json().catch(() => ({}));
|
|
138
155
|
if (!res.ok || !json.success || !json.data?.accessToken) {
|
|
156
|
+
const status = res.status || 401;
|
|
157
|
+
const errorCode = json.error?.code || "TOKEN_INVALID";
|
|
158
|
+
const shouldClear = shouldClearCookiesOnFailure(
|
|
159
|
+
cfg.clearCookiesOnRefreshFailure,
|
|
160
|
+
status,
|
|
161
|
+
errorCode
|
|
162
|
+
);
|
|
139
163
|
return {
|
|
140
|
-
status
|
|
164
|
+
status,
|
|
141
165
|
body: {
|
|
142
166
|
success: false,
|
|
143
167
|
error: {
|
|
144
|
-
code:
|
|
168
|
+
code: errorCode,
|
|
145
169
|
message: json.error?.message || "Refresh failed"
|
|
146
170
|
}
|
|
147
171
|
},
|
|
148
|
-
cookies: clearCookies(cfg)
|
|
172
|
+
cookies: shouldClear ? clearCookies(cfg) : []
|
|
149
173
|
};
|
|
150
174
|
}
|
|
151
175
|
const cookies = [
|
|
@@ -137,8 +137,9 @@ var SessionManager = class {
|
|
|
137
137
|
this.refreshPath = options.refreshPath ?? DEFAULT_REFRESH_PATH;
|
|
138
138
|
this.userinfoPath = options.userinfoPath ?? DEFAULT_USERINFO_PATH;
|
|
139
139
|
this.useCookies = options.useCookies ?? true;
|
|
140
|
-
this.
|
|
141
|
-
this.
|
|
140
|
+
this.serverManagedSession = options.serverManagedSession ?? false;
|
|
141
|
+
this.proactiveRefresh = this.serverManagedSession ? false : options.proactiveRefresh ?? true;
|
|
142
|
+
this.tokenStore = options.tokenStore ?? (this.serverManagedSession ? NO_OP_STORE : this.useCookies ? defaultCookieStore() : NO_OP_STORE);
|
|
142
143
|
this.crossTabLockTimeoutMs = options.crossTabLockTimeoutMs ?? 4e3;
|
|
143
144
|
this.fetchImpl = options.fetchImpl ?? (typeof fetch !== "undefined" ? fetch.bind(globalThis) : (() => {
|
|
144
145
|
throw new Error("global fetch is not available; pass fetchImpl");
|
|
@@ -182,6 +183,40 @@ var SessionManager = class {
|
|
|
182
183
|
async bootstrap() {
|
|
183
184
|
if (this.bootstrapped) return;
|
|
184
185
|
this.bootstrapped = true;
|
|
186
|
+
if (this.serverManagedSession) {
|
|
187
|
+
try {
|
|
188
|
+
const res = await this.fetchImpl(`${this.issuer}${this.userinfoPath}`, {
|
|
189
|
+
method: "GET",
|
|
190
|
+
credentials: "include",
|
|
191
|
+
headers: { Accept: "application/json" }
|
|
192
|
+
});
|
|
193
|
+
if (!res.ok) {
|
|
194
|
+
this.setStatus("unauthenticated");
|
|
195
|
+
return;
|
|
196
|
+
}
|
|
197
|
+
const body = await res.json().catch(() => ({}));
|
|
198
|
+
const data = body?.data;
|
|
199
|
+
const user = data?.user ?? claimsToSessionUser(data?.claims ?? null);
|
|
200
|
+
if (!user) {
|
|
201
|
+
this.setStatus("unauthenticated");
|
|
202
|
+
return;
|
|
203
|
+
}
|
|
204
|
+
this.update({
|
|
205
|
+
status: "authenticated",
|
|
206
|
+
accessToken: null,
|
|
207
|
+
user,
|
|
208
|
+
claims: data?.claims ?? null,
|
|
209
|
+
tenantId: data?.tenantId ?? user.tenantId ?? this.key.tenantId,
|
|
210
|
+
error: null,
|
|
211
|
+
version: this.snapshot.version + 1
|
|
212
|
+
});
|
|
213
|
+
this.broadcast("session:update");
|
|
214
|
+
return;
|
|
215
|
+
} catch {
|
|
216
|
+
this.setStatus("unauthenticated");
|
|
217
|
+
return;
|
|
218
|
+
}
|
|
219
|
+
}
|
|
185
220
|
const stored = await Promise.resolve(this.tokenStore.read());
|
|
186
221
|
if (!stored) {
|
|
187
222
|
this.setStatus("unauthenticated");
|
package/dist/express.js
CHANGED
|
@@ -1980,6 +1980,22 @@ function iqAuthMiddleware(clientOrOptions, options = {}) {
|
|
|
1980
1980
|
}
|
|
1981
1981
|
|
|
1982
1982
|
// src/server/handlers.ts
|
|
1983
|
+
var TERMINAL_REFRESH_ERROR_CODES = /* @__PURE__ */ new Set([
|
|
1984
|
+
"TOKEN_REVOKED",
|
|
1985
|
+
"SESSION_REVOKED",
|
|
1986
|
+
"INVALID_GRANT",
|
|
1987
|
+
"invalid_grant",
|
|
1988
|
+
"USER_DEACTIVATED",
|
|
1989
|
+
"USER_DISABLED",
|
|
1990
|
+
"TENANT_SUSPENDED"
|
|
1991
|
+
]);
|
|
1992
|
+
function shouldClearCookiesOnFailure(policy, status, errorCode) {
|
|
1993
|
+
if (policy === "always") return true;
|
|
1994
|
+
if (policy === "never") return false;
|
|
1995
|
+
if (status === 410) return true;
|
|
1996
|
+
if (errorCode && TERMINAL_REFRESH_ERROR_CODES.has(errorCode)) return true;
|
|
1997
|
+
return false;
|
|
1998
|
+
}
|
|
1983
1999
|
var ACCESS_TOKEN_TTL_SECONDS = 60 * 15;
|
|
1984
2000
|
var REFRESH_TOKEN_TTL_SECONDS = 60 * 60 * 24 * 30;
|
|
1985
2001
|
function resolve(config) {
|
|
@@ -2007,7 +2023,8 @@ function resolve(config) {
|
|
|
2007
2023
|
throw new Error("global fetch is unavailable; pass fetchImpl");
|
|
2008
2024
|
})),
|
|
2009
2025
|
appId: parsed.appId,
|
|
2010
|
-
tenantId: parsed.tenantId
|
|
2026
|
+
tenantId: parsed.tenantId,
|
|
2027
|
+
clearCookiesOnRefreshFailure: config.clearCookiesOnRefreshFailure ?? "terminal-only"
|
|
2011
2028
|
};
|
|
2012
2029
|
}
|
|
2013
2030
|
function makeCookie(cfg, name, value, maxAge, httpOnly = true) {
|
|
@@ -2093,7 +2110,7 @@ async function handleRefresh(config, input) {
|
|
|
2093
2110
|
return {
|
|
2094
2111
|
status: 401,
|
|
2095
2112
|
body: { success: false, error: { code: "TOKEN_INVALID", message: "Missing refresh token" } },
|
|
2096
|
-
cookies: clearCookies(cfg)
|
|
2113
|
+
cookies: cfg.clearCookiesOnRefreshFailure === "always" ? clearCookies(cfg) : []
|
|
2097
2114
|
};
|
|
2098
2115
|
}
|
|
2099
2116
|
const res = await cfg.fetchImpl(`${cfg.issuer}${cfg.refreshPath}`, {
|
|
@@ -2103,16 +2120,23 @@ async function handleRefresh(config, input) {
|
|
|
2103
2120
|
});
|
|
2104
2121
|
const json = await res.json().catch(() => ({}));
|
|
2105
2122
|
if (!res.ok || !json.success || !json.data?.accessToken) {
|
|
2123
|
+
const status = res.status || 401;
|
|
2124
|
+
const errorCode = json.error?.code || "TOKEN_INVALID";
|
|
2125
|
+
const shouldClear = shouldClearCookiesOnFailure(
|
|
2126
|
+
cfg.clearCookiesOnRefreshFailure,
|
|
2127
|
+
status,
|
|
2128
|
+
errorCode
|
|
2129
|
+
);
|
|
2106
2130
|
return {
|
|
2107
|
-
status
|
|
2131
|
+
status,
|
|
2108
2132
|
body: {
|
|
2109
2133
|
success: false,
|
|
2110
2134
|
error: {
|
|
2111
|
-
code:
|
|
2135
|
+
code: errorCode,
|
|
2112
2136
|
message: json.error?.message || "Refresh failed"
|
|
2113
2137
|
}
|
|
2114
2138
|
},
|
|
2115
|
-
cookies: clearCookies(cfg)
|
|
2139
|
+
cookies: shouldClear ? clearCookies(cfg) : []
|
|
2116
2140
|
};
|
|
2117
2141
|
}
|
|
2118
2142
|
const cookies = [
|
package/dist/express.mjs
CHANGED
package/dist/fastify.js
CHANGED
|
@@ -1781,6 +1781,22 @@ function parsePublishableKey(raw) {
|
|
|
1781
1781
|
}
|
|
1782
1782
|
|
|
1783
1783
|
// src/server/handlers.ts
|
|
1784
|
+
var TERMINAL_REFRESH_ERROR_CODES = /* @__PURE__ */ new Set([
|
|
1785
|
+
"TOKEN_REVOKED",
|
|
1786
|
+
"SESSION_REVOKED",
|
|
1787
|
+
"INVALID_GRANT",
|
|
1788
|
+
"invalid_grant",
|
|
1789
|
+
"USER_DEACTIVATED",
|
|
1790
|
+
"USER_DISABLED",
|
|
1791
|
+
"TENANT_SUSPENDED"
|
|
1792
|
+
]);
|
|
1793
|
+
function shouldClearCookiesOnFailure(policy, status, errorCode) {
|
|
1794
|
+
if (policy === "always") return true;
|
|
1795
|
+
if (policy === "never") return false;
|
|
1796
|
+
if (status === 410) return true;
|
|
1797
|
+
if (errorCode && TERMINAL_REFRESH_ERROR_CODES.has(errorCode)) return true;
|
|
1798
|
+
return false;
|
|
1799
|
+
}
|
|
1784
1800
|
var ACCESS_TOKEN_TTL_SECONDS = 60 * 15;
|
|
1785
1801
|
var REFRESH_TOKEN_TTL_SECONDS = 60 * 60 * 24 * 30;
|
|
1786
1802
|
function resolve(config) {
|
|
@@ -1808,7 +1824,8 @@ function resolve(config) {
|
|
|
1808
1824
|
throw new Error("global fetch is unavailable; pass fetchImpl");
|
|
1809
1825
|
})),
|
|
1810
1826
|
appId: parsed.appId,
|
|
1811
|
-
tenantId: parsed.tenantId
|
|
1827
|
+
tenantId: parsed.tenantId,
|
|
1828
|
+
clearCookiesOnRefreshFailure: config.clearCookiesOnRefreshFailure ?? "terminal-only"
|
|
1812
1829
|
};
|
|
1813
1830
|
}
|
|
1814
1831
|
function makeCookie(cfg, name, value, maxAge, httpOnly = true) {
|
|
@@ -1904,7 +1921,7 @@ async function handleRefresh(config, input) {
|
|
|
1904
1921
|
return {
|
|
1905
1922
|
status: 401,
|
|
1906
1923
|
body: { success: false, error: { code: "TOKEN_INVALID", message: "Missing refresh token" } },
|
|
1907
|
-
cookies: clearCookies(cfg)
|
|
1924
|
+
cookies: cfg.clearCookiesOnRefreshFailure === "always" ? clearCookies(cfg) : []
|
|
1908
1925
|
};
|
|
1909
1926
|
}
|
|
1910
1927
|
const res = await cfg.fetchImpl(`${cfg.issuer}${cfg.refreshPath}`, {
|
|
@@ -1914,16 +1931,23 @@ async function handleRefresh(config, input) {
|
|
|
1914
1931
|
});
|
|
1915
1932
|
const json = await res.json().catch(() => ({}));
|
|
1916
1933
|
if (!res.ok || !json.success || !json.data?.accessToken) {
|
|
1934
|
+
const status = res.status || 401;
|
|
1935
|
+
const errorCode = json.error?.code || "TOKEN_INVALID";
|
|
1936
|
+
const shouldClear = shouldClearCookiesOnFailure(
|
|
1937
|
+
cfg.clearCookiesOnRefreshFailure,
|
|
1938
|
+
status,
|
|
1939
|
+
errorCode
|
|
1940
|
+
);
|
|
1917
1941
|
return {
|
|
1918
|
-
status
|
|
1942
|
+
status,
|
|
1919
1943
|
body: {
|
|
1920
1944
|
success: false,
|
|
1921
1945
|
error: {
|
|
1922
|
-
code:
|
|
1946
|
+
code: errorCode,
|
|
1923
1947
|
message: json.error?.message || "Refresh failed"
|
|
1924
1948
|
}
|
|
1925
1949
|
},
|
|
1926
|
-
cookies: clearCookies(cfg)
|
|
1950
|
+
cookies: shouldClear ? clearCookies(cfg) : []
|
|
1927
1951
|
};
|
|
1928
1952
|
}
|
|
1929
1953
|
const cookies = [
|
package/dist/fastify.mjs
CHANGED
package/dist/hono.js
CHANGED
|
@@ -1780,6 +1780,22 @@ function parsePublishableKey(raw) {
|
|
|
1780
1780
|
}
|
|
1781
1781
|
|
|
1782
1782
|
// src/server/handlers.ts
|
|
1783
|
+
var TERMINAL_REFRESH_ERROR_CODES = /* @__PURE__ */ new Set([
|
|
1784
|
+
"TOKEN_REVOKED",
|
|
1785
|
+
"SESSION_REVOKED",
|
|
1786
|
+
"INVALID_GRANT",
|
|
1787
|
+
"invalid_grant",
|
|
1788
|
+
"USER_DEACTIVATED",
|
|
1789
|
+
"USER_DISABLED",
|
|
1790
|
+
"TENANT_SUSPENDED"
|
|
1791
|
+
]);
|
|
1792
|
+
function shouldClearCookiesOnFailure(policy, status, errorCode) {
|
|
1793
|
+
if (policy === "always") return true;
|
|
1794
|
+
if (policy === "never") return false;
|
|
1795
|
+
if (status === 410) return true;
|
|
1796
|
+
if (errorCode && TERMINAL_REFRESH_ERROR_CODES.has(errorCode)) return true;
|
|
1797
|
+
return false;
|
|
1798
|
+
}
|
|
1783
1799
|
var ACCESS_TOKEN_TTL_SECONDS = 60 * 15;
|
|
1784
1800
|
var REFRESH_TOKEN_TTL_SECONDS = 60 * 60 * 24 * 30;
|
|
1785
1801
|
function resolve(config) {
|
|
@@ -1807,7 +1823,8 @@ function resolve(config) {
|
|
|
1807
1823
|
throw new Error("global fetch is unavailable; pass fetchImpl");
|
|
1808
1824
|
})),
|
|
1809
1825
|
appId: parsed.appId,
|
|
1810
|
-
tenantId: parsed.tenantId
|
|
1826
|
+
tenantId: parsed.tenantId,
|
|
1827
|
+
clearCookiesOnRefreshFailure: config.clearCookiesOnRefreshFailure ?? "terminal-only"
|
|
1811
1828
|
};
|
|
1812
1829
|
}
|
|
1813
1830
|
function makeCookie(cfg, name, value, maxAge, httpOnly = true) {
|
|
@@ -1903,7 +1920,7 @@ async function handleRefresh(config, input) {
|
|
|
1903
1920
|
return {
|
|
1904
1921
|
status: 401,
|
|
1905
1922
|
body: { success: false, error: { code: "TOKEN_INVALID", message: "Missing refresh token" } },
|
|
1906
|
-
cookies: clearCookies(cfg)
|
|
1923
|
+
cookies: cfg.clearCookiesOnRefreshFailure === "always" ? clearCookies(cfg) : []
|
|
1907
1924
|
};
|
|
1908
1925
|
}
|
|
1909
1926
|
const res = await cfg.fetchImpl(`${cfg.issuer}${cfg.refreshPath}`, {
|
|
@@ -1913,16 +1930,23 @@ async function handleRefresh(config, input) {
|
|
|
1913
1930
|
});
|
|
1914
1931
|
const json = await res.json().catch(() => ({}));
|
|
1915
1932
|
if (!res.ok || !json.success || !json.data?.accessToken) {
|
|
1933
|
+
const status = res.status || 401;
|
|
1934
|
+
const errorCode = json.error?.code || "TOKEN_INVALID";
|
|
1935
|
+
const shouldClear = shouldClearCookiesOnFailure(
|
|
1936
|
+
cfg.clearCookiesOnRefreshFailure,
|
|
1937
|
+
status,
|
|
1938
|
+
errorCode
|
|
1939
|
+
);
|
|
1916
1940
|
return {
|
|
1917
|
-
status
|
|
1941
|
+
status,
|
|
1918
1942
|
body: {
|
|
1919
1943
|
success: false,
|
|
1920
1944
|
error: {
|
|
1921
|
-
code:
|
|
1945
|
+
code: errorCode,
|
|
1922
1946
|
message: json.error?.message || "Refresh failed"
|
|
1923
1947
|
}
|
|
1924
1948
|
},
|
|
1925
|
-
cookies: clearCookies(cfg)
|
|
1949
|
+
cookies: shouldClear ? clearCookies(cfg) : []
|
|
1926
1950
|
};
|
|
1927
1951
|
}
|
|
1928
1952
|
const cookies = [
|
package/dist/hono.mjs
CHANGED
package/dist/next.js
CHANGED
|
@@ -66,6 +66,22 @@ function parsePublishableKey(raw) {
|
|
|
66
66
|
}
|
|
67
67
|
|
|
68
68
|
// src/server/handlers.ts
|
|
69
|
+
var TERMINAL_REFRESH_ERROR_CODES = /* @__PURE__ */ new Set([
|
|
70
|
+
"TOKEN_REVOKED",
|
|
71
|
+
"SESSION_REVOKED",
|
|
72
|
+
"INVALID_GRANT",
|
|
73
|
+
"invalid_grant",
|
|
74
|
+
"USER_DEACTIVATED",
|
|
75
|
+
"USER_DISABLED",
|
|
76
|
+
"TENANT_SUSPENDED"
|
|
77
|
+
]);
|
|
78
|
+
function shouldClearCookiesOnFailure(policy, status, errorCode) {
|
|
79
|
+
if (policy === "always") return true;
|
|
80
|
+
if (policy === "never") return false;
|
|
81
|
+
if (status === 410) return true;
|
|
82
|
+
if (errorCode && TERMINAL_REFRESH_ERROR_CODES.has(errorCode)) return true;
|
|
83
|
+
return false;
|
|
84
|
+
}
|
|
69
85
|
var ACCESS_TOKEN_TTL_SECONDS = 60 * 15;
|
|
70
86
|
var REFRESH_TOKEN_TTL_SECONDS = 60 * 60 * 24 * 30;
|
|
71
87
|
function resolve(config) {
|
|
@@ -93,7 +109,8 @@ function resolve(config) {
|
|
|
93
109
|
throw new Error("global fetch is unavailable; pass fetchImpl");
|
|
94
110
|
})),
|
|
95
111
|
appId: parsed.appId,
|
|
96
|
-
tenantId: parsed.tenantId
|
|
112
|
+
tenantId: parsed.tenantId,
|
|
113
|
+
clearCookiesOnRefreshFailure: config.clearCookiesOnRefreshFailure ?? "terminal-only"
|
|
97
114
|
};
|
|
98
115
|
}
|
|
99
116
|
function makeCookie(cfg, name, value, maxAge, httpOnly = true) {
|
|
@@ -189,7 +206,7 @@ async function handleRefresh(config, input) {
|
|
|
189
206
|
return {
|
|
190
207
|
status: 401,
|
|
191
208
|
body: { success: false, error: { code: "TOKEN_INVALID", message: "Missing refresh token" } },
|
|
192
|
-
cookies: clearCookies(cfg)
|
|
209
|
+
cookies: cfg.clearCookiesOnRefreshFailure === "always" ? clearCookies(cfg) : []
|
|
193
210
|
};
|
|
194
211
|
}
|
|
195
212
|
const res = await cfg.fetchImpl(`${cfg.issuer}${cfg.refreshPath}`, {
|
|
@@ -199,16 +216,23 @@ async function handleRefresh(config, input) {
|
|
|
199
216
|
});
|
|
200
217
|
const json = await res.json().catch(() => ({}));
|
|
201
218
|
if (!res.ok || !json.success || !json.data?.accessToken) {
|
|
219
|
+
const status = res.status || 401;
|
|
220
|
+
const errorCode = json.error?.code || "TOKEN_INVALID";
|
|
221
|
+
const shouldClear = shouldClearCookiesOnFailure(
|
|
222
|
+
cfg.clearCookiesOnRefreshFailure,
|
|
223
|
+
status,
|
|
224
|
+
errorCode
|
|
225
|
+
);
|
|
202
226
|
return {
|
|
203
|
-
status
|
|
227
|
+
status,
|
|
204
228
|
body: {
|
|
205
229
|
success: false,
|
|
206
230
|
error: {
|
|
207
|
-
code:
|
|
231
|
+
code: errorCode,
|
|
208
232
|
message: json.error?.message || "Refresh failed"
|
|
209
233
|
}
|
|
210
234
|
},
|
|
211
|
-
cookies: clearCookies(cfg)
|
|
235
|
+
cookies: shouldClear ? clearCookies(cfg) : []
|
|
212
236
|
};
|
|
213
237
|
}
|
|
214
238
|
const cookies = [
|
package/dist/next.mjs
CHANGED
package/dist/react.d.mts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
2
2
|
import * as React from 'react';
|
|
3
3
|
import { ReactNode } from 'react';
|
|
4
|
-
import { S as SessionManager, a as SessionSnapshot, b as SignInOptions, c as SignOutOptions, C as CallbackResult } from './signIn-
|
|
4
|
+
import { S as SessionManager, a as SessionSnapshot, b as SignInOptions, c as SignOutOptions, C as CallbackResult } from './signIn-CEMdUAwd.mjs';
|
|
5
5
|
import { d as SessionUser, J as JwtClaims } from './types-Cxl3bQHt.mjs';
|
|
6
6
|
import './publishableKey-B5DIK81A.mjs';
|
|
7
7
|
|
package/dist/react.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
2
2
|
import * as React from 'react';
|
|
3
3
|
import { ReactNode } from 'react';
|
|
4
|
-
import { S as SessionManager, a as SessionSnapshot, b as SignInOptions, c as SignOutOptions, C as CallbackResult } from './signIn-
|
|
4
|
+
import { S as SessionManager, a as SessionSnapshot, b as SignInOptions, c as SignOutOptions, C as CallbackResult } from './signIn-VRNzlNyG.js';
|
|
5
5
|
import { d as SessionUser, J as JwtClaims } from './types-Cxl3bQHt.js';
|
|
6
6
|
import './publishableKey-B5DIK81A.js';
|
|
7
7
|
|
package/dist/react.js
CHANGED
|
@@ -215,8 +215,9 @@ var SessionManager = class {
|
|
|
215
215
|
this.refreshPath = options.refreshPath ?? DEFAULT_REFRESH_PATH;
|
|
216
216
|
this.userinfoPath = options.userinfoPath ?? DEFAULT_USERINFO_PATH;
|
|
217
217
|
this.useCookies = options.useCookies ?? true;
|
|
218
|
-
this.
|
|
219
|
-
this.
|
|
218
|
+
this.serverManagedSession = options.serverManagedSession ?? false;
|
|
219
|
+
this.proactiveRefresh = this.serverManagedSession ? false : options.proactiveRefresh ?? true;
|
|
220
|
+
this.tokenStore = options.tokenStore ?? (this.serverManagedSession ? NO_OP_STORE : this.useCookies ? defaultCookieStore() : NO_OP_STORE);
|
|
220
221
|
this.crossTabLockTimeoutMs = options.crossTabLockTimeoutMs ?? 4e3;
|
|
221
222
|
this.fetchImpl = options.fetchImpl ?? (typeof fetch !== "undefined" ? fetch.bind(globalThis) : (() => {
|
|
222
223
|
throw new Error("global fetch is not available; pass fetchImpl");
|
|
@@ -260,6 +261,40 @@ var SessionManager = class {
|
|
|
260
261
|
async bootstrap() {
|
|
261
262
|
if (this.bootstrapped) return;
|
|
262
263
|
this.bootstrapped = true;
|
|
264
|
+
if (this.serverManagedSession) {
|
|
265
|
+
try {
|
|
266
|
+
const res = await this.fetchImpl(`${this.issuer}${this.userinfoPath}`, {
|
|
267
|
+
method: "GET",
|
|
268
|
+
credentials: "include",
|
|
269
|
+
headers: { Accept: "application/json" }
|
|
270
|
+
});
|
|
271
|
+
if (!res.ok) {
|
|
272
|
+
this.setStatus("unauthenticated");
|
|
273
|
+
return;
|
|
274
|
+
}
|
|
275
|
+
const body = await res.json().catch(() => ({}));
|
|
276
|
+
const data = body?.data;
|
|
277
|
+
const user = data?.user ?? claimsToSessionUser(data?.claims ?? null);
|
|
278
|
+
if (!user) {
|
|
279
|
+
this.setStatus("unauthenticated");
|
|
280
|
+
return;
|
|
281
|
+
}
|
|
282
|
+
this.update({
|
|
283
|
+
status: "authenticated",
|
|
284
|
+
accessToken: null,
|
|
285
|
+
user,
|
|
286
|
+
claims: data?.claims ?? null,
|
|
287
|
+
tenantId: data?.tenantId ?? user.tenantId ?? this.key.tenantId,
|
|
288
|
+
error: null,
|
|
289
|
+
version: this.snapshot.version + 1
|
|
290
|
+
});
|
|
291
|
+
this.broadcast("session:update");
|
|
292
|
+
return;
|
|
293
|
+
} catch {
|
|
294
|
+
this.setStatus("unauthenticated");
|
|
295
|
+
return;
|
|
296
|
+
}
|
|
297
|
+
}
|
|
263
298
|
const stored = await Promise.resolve(this.tokenStore.read());
|
|
264
299
|
if (!stored) {
|
|
265
300
|
this.setStatus("unauthenticated");
|
package/dist/react.mjs
CHANGED
|
@@ -63,14 +63,33 @@ interface IQAuthHelperConfig {
|
|
|
63
63
|
logoutPath?: string;
|
|
64
64
|
/** Optional fetch implementation override. */
|
|
65
65
|
fetchImpl?: typeof fetch;
|
|
66
|
+
/**
|
|
67
|
+
* Policy for clearing the access + refresh cookies when `/refresh` fails.
|
|
68
|
+
*
|
|
69
|
+
* - `"terminal-only"` (default, recommended): only clear cookies when the
|
|
70
|
+
* issuer indicates the session is unrecoverable
|
|
71
|
+
* (`TOKEN_REVOKED`, `SESSION_REVOKED`, `INVALID_GRANT`,
|
|
72
|
+
* `USER_DEACTIVATED`, or HTTP 410 Gone). Transient failures
|
|
73
|
+
* (`TOKEN_INVALID` from a rotated-out token, `TOKEN_EXPIRED`, network
|
|
74
|
+
* errors, 5xx) leave cookies intact so the next legitimate request can
|
|
75
|
+
* either succeed against a still-valid access cookie or be redirected
|
|
76
|
+
* cleanly to sign-in by the middleware. Fixes the multi-tab /
|
|
77
|
+
* proactive-refresh race that previously silently signed users out.
|
|
78
|
+
* - `"always"`: pre-2.0.3 behavior — wipe both cookies on any non-2xx.
|
|
79
|
+
* Use only if you have an external reason to depend on the old semantics.
|
|
80
|
+
* - `"never"`: leave cookies untouched on every failure path. Suitable for
|
|
81
|
+
* apps that manage cookie lifecycle entirely outside the SDK helpers.
|
|
82
|
+
*/
|
|
83
|
+
clearCookiesOnRefreshFailure?: "terminal-only" | "always" | "never";
|
|
66
84
|
}
|
|
67
|
-
interface ResolvedConfig extends Required<Omit<IQAuthHelperConfig, "secretKey" | "cookieDomain" | "issuer" | "fetchImpl">> {
|
|
85
|
+
interface ResolvedConfig extends Required<Omit<IQAuthHelperConfig, "secretKey" | "cookieDomain" | "issuer" | "fetchImpl" | "clearCookiesOnRefreshFailure">> {
|
|
68
86
|
secretKey?: string;
|
|
69
87
|
cookieDomain?: string;
|
|
70
88
|
issuer: string;
|
|
71
89
|
fetchImpl: typeof fetch;
|
|
72
90
|
appId: string;
|
|
73
91
|
tenantId: string;
|
|
92
|
+
clearCookiesOnRefreshFailure: "terminal-only" | "always" | "never";
|
|
74
93
|
}
|
|
75
94
|
/**
|
|
76
95
|
* Serialize a cookie directive to a Set-Cookie header value. Adapters that
|
|
@@ -84,7 +103,15 @@ declare function handleCallback(config: IQAuthHelperConfig, input: {
|
|
|
84
103
|
codeVerifier?: string;
|
|
85
104
|
redirectUri?: string;
|
|
86
105
|
}): Promise<HandlerResponse>;
|
|
87
|
-
/** POST /api/iqauth/refresh — rotate refresh + access cookies.
|
|
106
|
+
/** POST /api/iqauth/refresh — rotate refresh + access cookies.
|
|
107
|
+
*
|
|
108
|
+
* Cookie-clearing policy is governed by `config.clearCookiesOnRefreshFailure`
|
|
109
|
+
* (default `"terminal-only"`). Prior to 2.0.3 this helper wiped both cookies
|
|
110
|
+
* on any non-2xx, which converted survivable refresh-token races (multi-tab,
|
|
111
|
+
* proactive-refresh timer, React StrictMode double-mount) into silent forced
|
|
112
|
+
* sign-outs. The default behavior now preserves cookies on transient failures
|
|
113
|
+
* and only clears them when the issuer signals the session is truly dead.
|
|
114
|
+
*/
|
|
88
115
|
declare function handleRefresh(config: IQAuthHelperConfig, input: {
|
|
89
116
|
refreshToken?: string;
|
|
90
117
|
}): Promise<HandlerResponse>;
|