@absolutejs/auth 0.69.0 → 0.69.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 +4 -0
- package/dist/client/createAuthClient.d.ts +1 -1
- package/dist/client/index.d.ts +1 -0
- package/dist/client/index.js +412 -380
- package/dist/client/index.js.map +6 -5
- package/dist/client/mobile.d.ts +1 -0
- package/dist/client/mobile.js +601 -0
- package/dist/client/mobile.js.map +11 -0
- package/dist/client/runtimeTransport.d.ts +3 -0
- package/package.json +10 -2
package/dist/client/index.js
CHANGED
|
@@ -46,396 +46,130 @@ var __export = (target, all) => {
|
|
|
46
46
|
var __esm = (fn, res) => () => (fn && (res = fn(fn = 0)), res);
|
|
47
47
|
var __require = import.meta.require;
|
|
48
48
|
|
|
49
|
-
// src/client/
|
|
50
|
-
var
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
passkeyRegisterOptions: "/auth/webauthn/register/options",
|
|
64
|
-
passkeyRegisterVerify: "/auth/webauthn/register/verify",
|
|
65
|
-
passkeyRemove: "/auth/webauthn/credentials",
|
|
66
|
-
passwordReset: "/auth/reset-password",
|
|
67
|
-
passwordResetRequest: "/auth/reset-password/request",
|
|
68
|
-
register: "/auth/register",
|
|
69
|
-
sessions: "/auth/sessions",
|
|
70
|
-
signout: "/oauth2/signout",
|
|
71
|
-
status: "/oauth2/status"
|
|
72
|
-
};
|
|
73
|
-
var succeed = (data) => ({
|
|
74
|
-
data,
|
|
75
|
-
error: null
|
|
76
|
-
});
|
|
77
|
-
var fail = (error) => ({
|
|
78
|
-
data: null,
|
|
79
|
-
error
|
|
80
|
-
});
|
|
81
|
-
var errorFor = (response, body) => ({
|
|
82
|
-
body,
|
|
83
|
-
message: typeof body === "string" ? body : readMessage(body) ?? response.statusText,
|
|
84
|
-
status: response.status
|
|
85
|
-
});
|
|
86
|
-
var createAuthClient = ({
|
|
87
|
-
baseUrl = "",
|
|
88
|
-
credentials = "same-origin",
|
|
89
|
-
fetch: fetchImpl = fetch,
|
|
90
|
-
routes,
|
|
91
|
-
transport
|
|
92
|
-
} = {}) => {
|
|
93
|
-
const resolvedFetch = transport?.fetch ?? fetchImpl;
|
|
94
|
-
const signInEmail = transport?.signInEmail;
|
|
95
|
-
const signOut = transport?.signOut;
|
|
96
|
-
const signUpEmail = transport?.signUpEmail;
|
|
97
|
-
const transportStatus = transport?.status;
|
|
98
|
-
const resolvedRoutes = {
|
|
99
|
-
...DEFAULT_ROUTES,
|
|
100
|
-
...routes
|
|
101
|
-
};
|
|
102
|
-
const request = async (path, init) => {
|
|
103
|
-
try {
|
|
104
|
-
const response = await resolvedFetch(`${baseUrl}${path}`, {
|
|
105
|
-
credentials,
|
|
106
|
-
...init
|
|
107
|
-
});
|
|
108
|
-
const text = await response.text();
|
|
109
|
-
if (!response.ok)
|
|
110
|
-
return fail(errorFor(response, safeJson(text)));
|
|
111
|
-
const data = JSON.parse(text === "" ? "null" : text);
|
|
112
|
-
return succeed(data);
|
|
113
|
-
} catch (caught) {
|
|
114
|
-
const message = caught instanceof Error ? caught.message : "network";
|
|
115
|
-
return fail({ body: null, message, status: 0 });
|
|
116
|
-
}
|
|
117
|
-
};
|
|
118
|
-
const runTransport = async (operation) => {
|
|
119
|
-
try {
|
|
120
|
-
return succeed(await operation());
|
|
121
|
-
} catch (caught) {
|
|
122
|
-
const message = caught instanceof Error ? caught.message : "authentication";
|
|
123
|
-
return fail({ body: null, message, status: 0 });
|
|
124
|
-
}
|
|
125
|
-
};
|
|
126
|
-
const post = (path, body, method = "POST") => request(path, {
|
|
127
|
-
body: body === undefined ? undefined : JSON.stringify(body),
|
|
128
|
-
headers: body === undefined ? undefined : { "content-type": "application/json" },
|
|
129
|
-
method
|
|
49
|
+
// src/client/runtimeTransport.ts
|
|
50
|
+
var AUTH_TRANSPORT_REGISTRY = Symbol.for("@absolutejs/auth/client-runtime-transport");
|
|
51
|
+
var host = globalThis;
|
|
52
|
+
var isRegistry = (value) => typeof value === "object" && value !== null && Array.isArray(Reflect.get(value, "installations"));
|
|
53
|
+
var registry = (() => {
|
|
54
|
+
const existing = host[AUTH_TRANSPORT_REGISTRY];
|
|
55
|
+
if (isRegistry(existing))
|
|
56
|
+
return existing;
|
|
57
|
+
const created = { installations: [] };
|
|
58
|
+
Object.defineProperty(host, AUTH_TRANSPORT_REGISTRY, {
|
|
59
|
+
configurable: false,
|
|
60
|
+
enumerable: false,
|
|
61
|
+
value: created,
|
|
62
|
+
writable: false
|
|
130
63
|
});
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
setup: () => post(resolvedRoutes.mfaSetup),
|
|
142
|
-
status: () => get(resolvedRoutes.mfaManagement),
|
|
143
|
-
verifySetup: (body) => post(resolvedRoutes.mfaVerifySetup, body)
|
|
144
|
-
},
|
|
145
|
-
passkeys: {
|
|
146
|
-
authenticateOptions: () => post(resolvedRoutes.passkeyAuthenticateOptions),
|
|
147
|
-
authenticateVerify: (response) => post(resolvedRoutes.passkeyAuthenticateVerify, response),
|
|
148
|
-
list: () => get(resolvedRoutes.passkeyList),
|
|
149
|
-
registerOptions: () => post(resolvedRoutes.passkeyRegisterOptions),
|
|
150
|
-
registerVerify: (response) => post(resolvedRoutes.passkeyRegisterVerify, response),
|
|
151
|
-
remove: (credentialId) => del(`${resolvedRoutes.passkeyRemove}/${encodeURIComponent(credentialId)}`)
|
|
152
|
-
},
|
|
153
|
-
passwordless: {
|
|
154
|
-
requestMagicLink: (body) => post(resolvedRoutes.magicLinkRequest, body),
|
|
155
|
-
verifyMagicLink: (body) => post(resolvedRoutes.magicLinkVerify, body)
|
|
156
|
-
},
|
|
157
|
-
passwordReset: {
|
|
158
|
-
confirm: (body) => post(resolvedRoutes.passwordReset, body),
|
|
159
|
-
request: (body) => post(resolvedRoutes.passwordResetRequest, body)
|
|
160
|
-
},
|
|
161
|
-
sessions: {
|
|
162
|
-
list: () => get(resolvedRoutes.sessions),
|
|
163
|
-
revoke: (sessionId) => del(`${resolvedRoutes.sessions}/${encodeURIComponent(sessionId)}`)
|
|
164
|
-
},
|
|
165
|
-
signIn: {
|
|
166
|
-
email: (body) => signInEmail ? runTransport(() => signInEmail(body)) : post(resolvedRoutes.login, body)
|
|
167
|
-
},
|
|
168
|
-
signUp: {
|
|
169
|
-
email: (body) => signUpEmail ? runTransport(() => signUpEmail(body)) : post(resolvedRoutes.register, body)
|
|
170
|
-
},
|
|
171
|
-
signOut: () => signOut ? runTransport(() => signOut()) : del(resolvedRoutes.signout),
|
|
172
|
-
status: () => transportStatus ? runTransport(() => transportStatus()) : get(resolvedRoutes.status)
|
|
64
|
+
return created;
|
|
65
|
+
})();
|
|
66
|
+
var getAuthClientRuntimeTransport = () => registry.installations.at(-1)?.transport;
|
|
67
|
+
var installAuthClientRuntimeTransport = (transport) => {
|
|
68
|
+
const installation = { transport };
|
|
69
|
+
registry.installations.push(installation);
|
|
70
|
+
return () => {
|
|
71
|
+
const index = registry.installations.indexOf(installation);
|
|
72
|
+
if (index >= 0)
|
|
73
|
+
registry.installations.splice(index, 1);
|
|
173
74
|
};
|
|
174
75
|
};
|
|
175
|
-
|
|
76
|
+
|
|
77
|
+
// src/client/mobile.ts
|
|
78
|
+
class MobileAuthError extends Error {
|
|
79
|
+
code;
|
|
80
|
+
cause;
|
|
81
|
+
constructor(code, message, options) {
|
|
82
|
+
super(message);
|
|
83
|
+
this.name = "MobileAuthError";
|
|
84
|
+
this.code = code;
|
|
85
|
+
this.cause = options?.cause;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
var PENDING_KEY = "oidc.pending";
|
|
89
|
+
var REFRESH_KEY = "oidc.refresh";
|
|
90
|
+
var DEFAULT_CLOCK_SKEW_MS = 30000;
|
|
91
|
+
var PENDING_TTL_MS = 10 * 60000;
|
|
92
|
+
var RANDOM_BYTES = 32;
|
|
93
|
+
var isRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
94
|
+
var base64Url = (value) => {
|
|
95
|
+
let binary = "";
|
|
96
|
+
for (const byte of value)
|
|
97
|
+
binary += String.fromCharCode(byte);
|
|
98
|
+
return btoa(binary).replaceAll("+", "-").replaceAll("/", "_").replace(/=+$/u, "");
|
|
99
|
+
};
|
|
100
|
+
var decodeBase64Url = (value) => {
|
|
101
|
+
const normalized = value.replaceAll("-", "+").replaceAll("_", "/");
|
|
102
|
+
const padded = normalized.padEnd(normalized.length + (4 - normalized.length % 4) % 4, "=");
|
|
103
|
+
const binary = atob(padded);
|
|
104
|
+
return Uint8Array.from(binary, (character) => character.charCodeAt(0));
|
|
105
|
+
};
|
|
106
|
+
var randomValue = () => {
|
|
107
|
+
const value = new Uint8Array(RANDOM_BYTES);
|
|
108
|
+
crypto.getRandomValues(value);
|
|
109
|
+
return base64Url(value);
|
|
110
|
+
};
|
|
111
|
+
var pkceChallenge = async (verifier) => base64Url(new Uint8Array(await crypto.subtle.digest("SHA-256", new TextEncoder().encode(verifier))));
|
|
112
|
+
var normalizeIssuer = (value) => {
|
|
113
|
+
const issuer = new URL(value);
|
|
114
|
+
const loopback = issuer.protocol === "http:" && ["127.0.0.1", "[::1]", "localhost"].includes(issuer.hostname);
|
|
115
|
+
if (issuer.protocol !== "https:" && !loopback)
|
|
116
|
+
throw new TypeError("Mobile auth issuer must use HTTPS.");
|
|
117
|
+
if (issuer.username || issuer.password || issuer.search || issuer.hash)
|
|
118
|
+
throw new TypeError("Mobile auth issuer cannot contain credentials, query, or fragment.");
|
|
119
|
+
issuer.pathname = issuer.pathname.replace(/\/$/u, "");
|
|
120
|
+
return issuer.href.replace(/\/$/u, "");
|
|
121
|
+
};
|
|
122
|
+
var exactRedirect = (actualValue, expectedValue) => {
|
|
123
|
+
const actual = new URL(actualValue);
|
|
124
|
+
const expected = new URL(expectedValue);
|
|
125
|
+
return actual.protocol === expected.protocol && actual.host === expected.host && actual.pathname === expected.pathname && actual.username === "" && actual.password === "";
|
|
126
|
+
};
|
|
127
|
+
var parsePending = (value) => {
|
|
128
|
+
if (value === null)
|
|
129
|
+
return;
|
|
176
130
|
try {
|
|
177
|
-
|
|
131
|
+
const parsed = JSON.parse(value);
|
|
132
|
+
if (!isRecord(parsed) || typeof parsed.createdAt !== "number" || typeof parsed.nonce !== "string" || typeof parsed.state !== "string" || typeof parsed.verifier !== "string")
|
|
133
|
+
return;
|
|
134
|
+
return {
|
|
135
|
+
createdAt: parsed.createdAt,
|
|
136
|
+
nonce: parsed.nonce,
|
|
137
|
+
state: parsed.state,
|
|
138
|
+
verifier: parsed.verifier
|
|
139
|
+
};
|
|
178
140
|
} catch {
|
|
179
|
-
return
|
|
141
|
+
return;
|
|
180
142
|
}
|
|
181
143
|
};
|
|
182
|
-
var
|
|
183
|
-
if (typeof
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
144
|
+
var parseTokenResponse = (value) => {
|
|
145
|
+
if (!isRecord(value) || typeof value.access_token !== "string" || typeof value.expires_in !== "number" || !Number.isFinite(value.expires_in) || value.expires_in <= 0 || typeof value.id_token !== "string" || typeof value.refresh_token !== "string" || typeof value.token_type !== "string")
|
|
146
|
+
throw new MobileAuthError("token", "The token response is malformed.");
|
|
147
|
+
if (value.token_type.toLowerCase() !== "bearer")
|
|
148
|
+
throw new MobileAuthError("token", `Unsupported mobile token type ${value.token_type}; DPoP is not enabled for this client.`);
|
|
149
|
+
return {
|
|
150
|
+
access_token: value.access_token,
|
|
151
|
+
expires_in: value.expires_in,
|
|
152
|
+
id_token: value.id_token,
|
|
153
|
+
refresh_token: value.refresh_token,
|
|
154
|
+
scope: typeof value.scope === "string" ? value.scope : undefined,
|
|
155
|
+
token_type: value.token_type
|
|
156
|
+
};
|
|
187
157
|
};
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
var DEFAULT_REASON = "session_expired";
|
|
191
|
-
var DEFAULT_REASON_PARAM = "reason";
|
|
192
|
-
var DEFAULT_RETURN_URL_PARAM = "returnUrl";
|
|
193
|
-
var DEFAULT_SIGN_IN_PATH = "/signin";
|
|
194
|
-
var DEFAULT_STATUS_PATH = "/oauth2/status";
|
|
195
|
-
var HTTP_UNAUTHORIZED = 401;
|
|
196
|
-
var activeGuard = null;
|
|
197
|
-
var requestUrl = (input, origin) => {
|
|
198
|
-
const raw = input instanceof Request ? input.url : String(input);
|
|
158
|
+
var responseBody = async (response) => {
|
|
159
|
+
const text = await response.text();
|
|
199
160
|
try {
|
|
200
|
-
return
|
|
161
|
+
return JSON.parse(text);
|
|
201
162
|
} catch {
|
|
202
|
-
return
|
|
163
|
+
return text;
|
|
203
164
|
}
|
|
204
165
|
};
|
|
205
|
-
var
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
const current = new URL(currentHref);
|
|
213
|
-
const returnTo = `${current.pathname}${current.search}${current.hash}`;
|
|
214
|
-
const destination = new URL(signInPath, current.origin);
|
|
215
|
-
destination.searchParams.set(reasonParam, reason);
|
|
216
|
-
destination.searchParams.set(returnUrlParam, returnTo);
|
|
217
|
-
return destination.origin === current.origin ? `${destination.pathname}${destination.search}${destination.hash}` : destination.toString();
|
|
218
|
-
};
|
|
219
|
-
var installSessionExpiryGuard = (config = {}) => {
|
|
220
|
-
if (typeof window === "undefined" || typeof document === "undefined") {
|
|
221
|
-
return { check: async () => false, dispose: () => {
|
|
222
|
-
return;
|
|
223
|
-
} };
|
|
224
|
-
}
|
|
225
|
-
if (activeGuard)
|
|
226
|
-
return activeGuard;
|
|
227
|
-
const {
|
|
228
|
-
checkIntervalMs = DEFAULT_CHECK_INTERVAL_MS,
|
|
229
|
-
isProtectedRequest,
|
|
230
|
-
onExpired,
|
|
231
|
-
protectedPaths = [],
|
|
232
|
-
reason = DEFAULT_REASON,
|
|
233
|
-
reasonParam = DEFAULT_REASON_PARAM,
|
|
234
|
-
returnUrlParam = DEFAULT_RETURN_URL_PARAM,
|
|
235
|
-
signInPath = DEFAULT_SIGN_IN_PATH,
|
|
236
|
-
statusPath = DEFAULT_STATUS_PATH
|
|
237
|
-
} = config;
|
|
238
|
-
const nativeFetch = window.fetch.bind(window);
|
|
239
|
-
const originalFetch = window.fetch;
|
|
240
|
-
let checking = false;
|
|
241
|
-
let disposed = false;
|
|
242
|
-
let expired = false;
|
|
243
|
-
let lastCheckedAt = Date.now();
|
|
244
|
-
const expire = () => {
|
|
245
|
-
if (expired || disposed)
|
|
246
|
-
return;
|
|
247
|
-
expired = true;
|
|
248
|
-
const currentHref = window.location.href;
|
|
249
|
-
const signInUrl = buildSessionExpiredSignInUrl({
|
|
250
|
-
currentHref,
|
|
251
|
-
reason,
|
|
252
|
-
reasonParam,
|
|
253
|
-
returnUrlParam,
|
|
254
|
-
signInPath
|
|
255
|
-
});
|
|
256
|
-
const current = new URL(currentHref);
|
|
257
|
-
const returnTo = `${current.pathname}${current.search}${current.hash}`;
|
|
258
|
-
if (onExpired) {
|
|
259
|
-
onExpired({ returnTo, signInUrl });
|
|
260
|
-
return;
|
|
261
|
-
}
|
|
262
|
-
window.location.assign(signInUrl);
|
|
263
|
-
};
|
|
264
|
-
const protectedRequest = (input) => {
|
|
265
|
-
const url = requestUrl(input, window.location.origin);
|
|
266
|
-
if (!url || url.origin !== window.location.origin)
|
|
267
|
-
return false;
|
|
268
|
-
if (new URL(statusPath, window.location.origin).pathname === url.pathname)
|
|
269
|
-
return false;
|
|
270
|
-
return isProtectedRequest?.(url) === true || protectedPaths.some((path) => url.pathname.startsWith(path));
|
|
271
|
-
};
|
|
272
|
-
const guardedFetch = new Proxy(originalFetch, {
|
|
273
|
-
apply: async (target, thisArg, args) => {
|
|
274
|
-
const [input] = args;
|
|
275
|
-
const response = await Reflect.apply(target, thisArg, args);
|
|
276
|
-
if (response.status === HTTP_UNAUTHORIZED && protectedRequest(input)) {
|
|
277
|
-
expire();
|
|
278
|
-
}
|
|
279
|
-
return response;
|
|
280
|
-
}
|
|
281
|
-
});
|
|
282
|
-
window.fetch = guardedFetch;
|
|
283
|
-
const check = async () => {
|
|
284
|
-
if (checking || expired || disposed)
|
|
285
|
-
return false;
|
|
286
|
-
checking = true;
|
|
287
|
-
lastCheckedAt = Date.now();
|
|
288
|
-
try {
|
|
289
|
-
const response = await nativeFetch(statusPath, {
|
|
290
|
-
cache: "no-store",
|
|
291
|
-
credentials: "include",
|
|
292
|
-
headers: { accept: "application/json" }
|
|
293
|
-
});
|
|
294
|
-
if (!response.ok)
|
|
295
|
-
return false;
|
|
296
|
-
const payload = await response.json();
|
|
297
|
-
const sessionExpired = typeof payload === "object" && payload !== null && Reflect.get(payload, "user") === null;
|
|
298
|
-
if (!sessionExpired)
|
|
299
|
-
return false;
|
|
300
|
-
expire();
|
|
301
|
-
return true;
|
|
302
|
-
} catch {
|
|
303
|
-
return false;
|
|
304
|
-
} finally {
|
|
305
|
-
checking = false;
|
|
306
|
-
}
|
|
307
|
-
};
|
|
308
|
-
const checkIfDue = () => {
|
|
309
|
-
if (document.visibilityState !== "visible" || Date.now() - lastCheckedAt < checkIntervalMs)
|
|
310
|
-
return;
|
|
311
|
-
check();
|
|
312
|
-
};
|
|
313
|
-
const checkPersistedSession = (event) => {
|
|
314
|
-
if (!event.persisted)
|
|
315
|
-
return;
|
|
316
|
-
check();
|
|
317
|
-
};
|
|
318
|
-
const dispose = () => {
|
|
319
|
-
if (disposed)
|
|
320
|
-
return;
|
|
321
|
-
disposed = true;
|
|
322
|
-
document.removeEventListener("visibilitychange", checkIfDue);
|
|
323
|
-
window.removeEventListener("focus", checkIfDue);
|
|
324
|
-
window.removeEventListener("pageshow", checkPersistedSession);
|
|
325
|
-
if (window.fetch === guardedFetch)
|
|
326
|
-
window.fetch = originalFetch;
|
|
327
|
-
activeGuard = null;
|
|
328
|
-
};
|
|
329
|
-
document.addEventListener("visibilitychange", checkIfDue);
|
|
330
|
-
window.addEventListener("focus", checkIfDue);
|
|
331
|
-
window.addEventListener("pageshow", checkPersistedSession);
|
|
332
|
-
activeGuard = { check, dispose };
|
|
333
|
-
return activeGuard;
|
|
334
|
-
};
|
|
335
|
-
var isProtectedSessionRequest = ({
|
|
336
|
-
input,
|
|
337
|
-
origin,
|
|
338
|
-
protectedPaths
|
|
339
|
-
}) => {
|
|
340
|
-
const url = requestUrl(input, origin);
|
|
341
|
-
return url?.origin === origin && protectedPaths.some((path) => url.pathname.startsWith(path));
|
|
342
|
-
};
|
|
343
|
-
// src/client/mobile.ts
|
|
344
|
-
class MobileAuthError extends Error {
|
|
345
|
-
code;
|
|
346
|
-
cause;
|
|
347
|
-
constructor(code, message, options) {
|
|
348
|
-
super(message);
|
|
349
|
-
this.name = "MobileAuthError";
|
|
350
|
-
this.code = code;
|
|
351
|
-
this.cause = options?.cause;
|
|
352
|
-
}
|
|
353
|
-
}
|
|
354
|
-
var PENDING_KEY = "oidc.pending";
|
|
355
|
-
var REFRESH_KEY = "oidc.refresh";
|
|
356
|
-
var DEFAULT_CLOCK_SKEW_MS = 30000;
|
|
357
|
-
var PENDING_TTL_MS = 10 * 60000;
|
|
358
|
-
var RANDOM_BYTES = 32;
|
|
359
|
-
var isRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
360
|
-
var base64Url = (value) => {
|
|
361
|
-
let binary = "";
|
|
362
|
-
for (const byte of value)
|
|
363
|
-
binary += String.fromCharCode(byte);
|
|
364
|
-
return btoa(binary).replaceAll("+", "-").replaceAll("/", "_").replace(/=+$/u, "");
|
|
365
|
-
};
|
|
366
|
-
var decodeBase64Url = (value) => {
|
|
367
|
-
const normalized = value.replaceAll("-", "+").replaceAll("_", "/");
|
|
368
|
-
const padded = normalized.padEnd(normalized.length + (4 - normalized.length % 4) % 4, "=");
|
|
369
|
-
const binary = atob(padded);
|
|
370
|
-
return Uint8Array.from(binary, (character) => character.charCodeAt(0));
|
|
371
|
-
};
|
|
372
|
-
var randomValue = () => {
|
|
373
|
-
const value = new Uint8Array(RANDOM_BYTES);
|
|
374
|
-
crypto.getRandomValues(value);
|
|
375
|
-
return base64Url(value);
|
|
376
|
-
};
|
|
377
|
-
var pkceChallenge = async (verifier) => base64Url(new Uint8Array(await crypto.subtle.digest("SHA-256", new TextEncoder().encode(verifier))));
|
|
378
|
-
var normalizeIssuer = (value) => {
|
|
379
|
-
const issuer = new URL(value);
|
|
380
|
-
const loopback = issuer.protocol === "http:" && ["127.0.0.1", "[::1]", "localhost"].includes(issuer.hostname);
|
|
381
|
-
if (issuer.protocol !== "https:" && !loopback)
|
|
382
|
-
throw new TypeError("Mobile auth issuer must use HTTPS.");
|
|
383
|
-
if (issuer.username || issuer.password || issuer.search || issuer.hash)
|
|
384
|
-
throw new TypeError("Mobile auth issuer cannot contain credentials, query, or fragment.");
|
|
385
|
-
issuer.pathname = issuer.pathname.replace(/\/$/u, "");
|
|
386
|
-
return issuer.href.replace(/\/$/u, "");
|
|
387
|
-
};
|
|
388
|
-
var exactRedirect = (actualValue, expectedValue) => {
|
|
389
|
-
const actual = new URL(actualValue);
|
|
390
|
-
const expected = new URL(expectedValue);
|
|
391
|
-
return actual.protocol === expected.protocol && actual.host === expected.host && actual.pathname === expected.pathname && actual.username === "" && actual.password === "";
|
|
392
|
-
};
|
|
393
|
-
var parsePending = (value) => {
|
|
394
|
-
if (value === null)
|
|
395
|
-
return;
|
|
396
|
-
try {
|
|
397
|
-
const parsed = JSON.parse(value);
|
|
398
|
-
if (!isRecord(parsed) || typeof parsed.createdAt !== "number" || typeof parsed.nonce !== "string" || typeof parsed.state !== "string" || typeof parsed.verifier !== "string")
|
|
399
|
-
return;
|
|
400
|
-
return {
|
|
401
|
-
createdAt: parsed.createdAt,
|
|
402
|
-
nonce: parsed.nonce,
|
|
403
|
-
state: parsed.state,
|
|
404
|
-
verifier: parsed.verifier
|
|
405
|
-
};
|
|
406
|
-
} catch {
|
|
407
|
-
return;
|
|
408
|
-
}
|
|
409
|
-
};
|
|
410
|
-
var parseTokenResponse = (value) => {
|
|
411
|
-
if (!isRecord(value) || typeof value.access_token !== "string" || typeof value.expires_in !== "number" || !Number.isFinite(value.expires_in) || value.expires_in <= 0 || typeof value.id_token !== "string" || typeof value.refresh_token !== "string" || typeof value.token_type !== "string")
|
|
412
|
-
throw new MobileAuthError("token", "The token response is malformed.");
|
|
413
|
-
if (value.token_type.toLowerCase() !== "bearer")
|
|
414
|
-
throw new MobileAuthError("token", `Unsupported mobile token type ${value.token_type}; DPoP is not enabled for this client.`);
|
|
415
|
-
return {
|
|
416
|
-
access_token: value.access_token,
|
|
417
|
-
expires_in: value.expires_in,
|
|
418
|
-
id_token: value.id_token,
|
|
419
|
-
refresh_token: value.refresh_token,
|
|
420
|
-
scope: typeof value.scope === "string" ? value.scope : undefined,
|
|
421
|
-
token_type: value.token_type
|
|
422
|
-
};
|
|
423
|
-
};
|
|
424
|
-
var responseBody = async (response) => {
|
|
425
|
-
const text = await response.text();
|
|
426
|
-
try {
|
|
427
|
-
return JSON.parse(text);
|
|
428
|
-
} catch {
|
|
429
|
-
return text;
|
|
430
|
-
}
|
|
431
|
-
};
|
|
432
|
-
var requireEndpoint = (value, name, issuer) => {
|
|
433
|
-
if (typeof value !== "string")
|
|
434
|
-
throw new MobileAuthError("discovery", `OIDC discovery is missing ${name}.`);
|
|
435
|
-
const endpoint = new URL(value);
|
|
436
|
-
if (endpoint.protocol !== "https:" && new URL(issuer).protocol === "https:")
|
|
437
|
-
throw new MobileAuthError("discovery", `OIDC discovery ${name} must use HTTPS.`);
|
|
438
|
-
return endpoint.href;
|
|
166
|
+
var requireEndpoint = (value, name, issuer) => {
|
|
167
|
+
if (typeof value !== "string")
|
|
168
|
+
throw new MobileAuthError("discovery", `OIDC discovery is missing ${name}.`);
|
|
169
|
+
const endpoint = new URL(value);
|
|
170
|
+
if (endpoint.protocol !== "https:" && new URL(issuer).protocol === "https:")
|
|
171
|
+
throw new MobileAuthError("discovery", `OIDC discovery ${name} must use HTTPS.`);
|
|
172
|
+
return endpoint.href;
|
|
439
173
|
};
|
|
440
174
|
var parseDiscovery = (value, issuer) => {
|
|
441
175
|
if (!isRecord(value) || value.issuer !== issuer)
|
|
@@ -831,7 +565,7 @@ var createMobileAuthClient = (config) => {
|
|
|
831
565
|
};
|
|
832
566
|
};
|
|
833
567
|
var createMobileAuthTransport = (client) => ({
|
|
834
|
-
fetch: client.
|
|
568
|
+
fetch: client.fetchOptional,
|
|
835
569
|
signInEmail: async ({ email }) => {
|
|
836
570
|
await client.signIn({
|
|
837
571
|
authorizationParameters: { login_hint: email }
|
|
@@ -856,6 +590,302 @@ var createMobileAuthTransport = (client) => ({
|
|
|
856
590
|
return { user };
|
|
857
591
|
}
|
|
858
592
|
});
|
|
593
|
+
|
|
594
|
+
// src/client/createAuthClient.ts
|
|
595
|
+
var DEFAULT_ROUTES = {
|
|
596
|
+
emailVerify: "/auth/verify-email",
|
|
597
|
+
emailVerifyRequest: "/auth/verify-email/request",
|
|
598
|
+
login: "/auth/login",
|
|
599
|
+
magicLinkRequest: "/auth/passwordless/magic-link",
|
|
600
|
+
magicLinkVerify: "/auth/passwordless/magic-link/verify",
|
|
601
|
+
mfaChallenge: "/auth/mfa/totp/challenge",
|
|
602
|
+
mfaManagement: "/auth/mfa",
|
|
603
|
+
mfaSetup: "/auth/mfa/totp/setup",
|
|
604
|
+
mfaVerifySetup: "/auth/mfa/totp/verify",
|
|
605
|
+
passkeyAuthenticateOptions: "/auth/webauthn/authenticate/options",
|
|
606
|
+
passkeyAuthenticateVerify: "/auth/webauthn/authenticate/verify",
|
|
607
|
+
passkeyList: "/auth/webauthn/credentials",
|
|
608
|
+
passkeyRegisterOptions: "/auth/webauthn/register/options",
|
|
609
|
+
passkeyRegisterVerify: "/auth/webauthn/register/verify",
|
|
610
|
+
passkeyRemove: "/auth/webauthn/credentials",
|
|
611
|
+
passwordReset: "/auth/reset-password",
|
|
612
|
+
passwordResetRequest: "/auth/reset-password/request",
|
|
613
|
+
register: "/auth/register",
|
|
614
|
+
sessions: "/auth/sessions",
|
|
615
|
+
signout: "/oauth2/signout",
|
|
616
|
+
status: "/oauth2/status"
|
|
617
|
+
};
|
|
618
|
+
var succeed = (data) => ({
|
|
619
|
+
data,
|
|
620
|
+
error: null
|
|
621
|
+
});
|
|
622
|
+
var fail = (error) => ({
|
|
623
|
+
data: null,
|
|
624
|
+
error
|
|
625
|
+
});
|
|
626
|
+
var errorFor = (response, body) => ({
|
|
627
|
+
body,
|
|
628
|
+
message: typeof body === "string" ? body : readMessage(body) ?? response.statusText,
|
|
629
|
+
status: response.status
|
|
630
|
+
});
|
|
631
|
+
var createAuthClient = ({
|
|
632
|
+
baseUrl = "",
|
|
633
|
+
credentials = "same-origin",
|
|
634
|
+
fetch: fetchImpl = fetch,
|
|
635
|
+
routes,
|
|
636
|
+
transport: configuredTransport
|
|
637
|
+
} = {}) => {
|
|
638
|
+
const transport = configuredTransport ?? getAuthClientRuntimeTransport();
|
|
639
|
+
const resolvedFetch = transport?.fetch ?? fetchImpl;
|
|
640
|
+
const signInEmail = transport?.signInEmail;
|
|
641
|
+
const signOut = transport?.signOut;
|
|
642
|
+
const signUpEmail = transport?.signUpEmail;
|
|
643
|
+
const transportStatus = transport?.status;
|
|
644
|
+
const resolvedRoutes = {
|
|
645
|
+
...DEFAULT_ROUTES,
|
|
646
|
+
...routes
|
|
647
|
+
};
|
|
648
|
+
const request = async (path, init) => {
|
|
649
|
+
try {
|
|
650
|
+
const response = await resolvedFetch(`${baseUrl}${path}`, {
|
|
651
|
+
credentials,
|
|
652
|
+
...init
|
|
653
|
+
});
|
|
654
|
+
const text = await response.text();
|
|
655
|
+
if (!response.ok)
|
|
656
|
+
return fail(errorFor(response, safeJson(text)));
|
|
657
|
+
const data = JSON.parse(text === "" ? "null" : text);
|
|
658
|
+
return succeed(data);
|
|
659
|
+
} catch (caught) {
|
|
660
|
+
const message = caught instanceof Error ? caught.message : "network";
|
|
661
|
+
return fail({ body: null, message, status: 0 });
|
|
662
|
+
}
|
|
663
|
+
};
|
|
664
|
+
const runTransport = async (operation) => {
|
|
665
|
+
try {
|
|
666
|
+
return succeed(await operation());
|
|
667
|
+
} catch (caught) {
|
|
668
|
+
const message = caught instanceof Error ? caught.message : "authentication";
|
|
669
|
+
return fail({ body: null, message, status: 0 });
|
|
670
|
+
}
|
|
671
|
+
};
|
|
672
|
+
const post = (path, body, method = "POST") => request(path, {
|
|
673
|
+
body: body === undefined ? undefined : JSON.stringify(body),
|
|
674
|
+
headers: body === undefined ? undefined : { "content-type": "application/json" },
|
|
675
|
+
method
|
|
676
|
+
});
|
|
677
|
+
const get = (path) => request(path, { method: "GET" });
|
|
678
|
+
const del = (path) => request(path, { method: "DELETE" });
|
|
679
|
+
return {
|
|
680
|
+
emailVerification: {
|
|
681
|
+
request: (body) => post(resolvedRoutes.emailVerifyRequest, body),
|
|
682
|
+
verify: (body) => post(resolvedRoutes.emailVerify, body)
|
|
683
|
+
},
|
|
684
|
+
mfa: {
|
|
685
|
+
challenge: (body) => post(resolvedRoutes.mfaChallenge, body),
|
|
686
|
+
disable: () => del(resolvedRoutes.mfaManagement),
|
|
687
|
+
setup: () => post(resolvedRoutes.mfaSetup),
|
|
688
|
+
status: () => get(resolvedRoutes.mfaManagement),
|
|
689
|
+
verifySetup: (body) => post(resolvedRoutes.mfaVerifySetup, body)
|
|
690
|
+
},
|
|
691
|
+
passkeys: {
|
|
692
|
+
authenticateOptions: () => post(resolvedRoutes.passkeyAuthenticateOptions),
|
|
693
|
+
authenticateVerify: (response) => post(resolvedRoutes.passkeyAuthenticateVerify, response),
|
|
694
|
+
list: () => get(resolvedRoutes.passkeyList),
|
|
695
|
+
registerOptions: () => post(resolvedRoutes.passkeyRegisterOptions),
|
|
696
|
+
registerVerify: (response) => post(resolvedRoutes.passkeyRegisterVerify, response),
|
|
697
|
+
remove: (credentialId) => del(`${resolvedRoutes.passkeyRemove}/${encodeURIComponent(credentialId)}`)
|
|
698
|
+
},
|
|
699
|
+
passwordless: {
|
|
700
|
+
requestMagicLink: (body) => post(resolvedRoutes.magicLinkRequest, body),
|
|
701
|
+
verifyMagicLink: (body) => post(resolvedRoutes.magicLinkVerify, body)
|
|
702
|
+
},
|
|
703
|
+
passwordReset: {
|
|
704
|
+
confirm: (body) => post(resolvedRoutes.passwordReset, body),
|
|
705
|
+
request: (body) => post(resolvedRoutes.passwordResetRequest, body)
|
|
706
|
+
},
|
|
707
|
+
sessions: {
|
|
708
|
+
list: () => get(resolvedRoutes.sessions),
|
|
709
|
+
revoke: (sessionId) => del(`${resolvedRoutes.sessions}/${encodeURIComponent(sessionId)}`)
|
|
710
|
+
},
|
|
711
|
+
signIn: {
|
|
712
|
+
email: (body) => signInEmail ? runTransport(() => signInEmail(body)) : post(resolvedRoutes.login, body)
|
|
713
|
+
},
|
|
714
|
+
signUp: {
|
|
715
|
+
email: (body) => signUpEmail ? runTransport(() => signUpEmail(body)) : post(resolvedRoutes.register, body)
|
|
716
|
+
},
|
|
717
|
+
signOut: () => signOut ? runTransport(() => signOut()) : del(resolvedRoutes.signout),
|
|
718
|
+
status: () => transportStatus ? runTransport(() => transportStatus()) : get(resolvedRoutes.status)
|
|
719
|
+
};
|
|
720
|
+
};
|
|
721
|
+
var safeJson = (text) => {
|
|
722
|
+
try {
|
|
723
|
+
return JSON.parse(text);
|
|
724
|
+
} catch {
|
|
725
|
+
return text;
|
|
726
|
+
}
|
|
727
|
+
};
|
|
728
|
+
var readMessage = (body) => {
|
|
729
|
+
if (typeof body !== "object" || body === null)
|
|
730
|
+
return;
|
|
731
|
+
const message = Reflect.get(body, "message");
|
|
732
|
+
return typeof message === "string" ? message : undefined;
|
|
733
|
+
};
|
|
734
|
+
// src/client/sessionExpiry.ts
|
|
735
|
+
var DEFAULT_CHECK_INTERVAL_MS = 30000;
|
|
736
|
+
var DEFAULT_REASON = "session_expired";
|
|
737
|
+
var DEFAULT_REASON_PARAM = "reason";
|
|
738
|
+
var DEFAULT_RETURN_URL_PARAM = "returnUrl";
|
|
739
|
+
var DEFAULT_SIGN_IN_PATH = "/signin";
|
|
740
|
+
var DEFAULT_STATUS_PATH = "/oauth2/status";
|
|
741
|
+
var HTTP_UNAUTHORIZED = 401;
|
|
742
|
+
var activeGuard = null;
|
|
743
|
+
var requestUrl = (input, origin) => {
|
|
744
|
+
const raw = input instanceof Request ? input.url : String(input);
|
|
745
|
+
try {
|
|
746
|
+
return new URL(raw, origin);
|
|
747
|
+
} catch {
|
|
748
|
+
return null;
|
|
749
|
+
}
|
|
750
|
+
};
|
|
751
|
+
var buildSessionExpiredSignInUrl = ({
|
|
752
|
+
currentHref,
|
|
753
|
+
reason = DEFAULT_REASON,
|
|
754
|
+
reasonParam = DEFAULT_REASON_PARAM,
|
|
755
|
+
returnUrlParam = DEFAULT_RETURN_URL_PARAM,
|
|
756
|
+
signInPath = DEFAULT_SIGN_IN_PATH
|
|
757
|
+
}) => {
|
|
758
|
+
const current = new URL(currentHref);
|
|
759
|
+
const returnTo = `${current.pathname}${current.search}${current.hash}`;
|
|
760
|
+
const destination = new URL(signInPath, current.origin);
|
|
761
|
+
destination.searchParams.set(reasonParam, reason);
|
|
762
|
+
destination.searchParams.set(returnUrlParam, returnTo);
|
|
763
|
+
return destination.origin === current.origin ? `${destination.pathname}${destination.search}${destination.hash}` : destination.toString();
|
|
764
|
+
};
|
|
765
|
+
var installSessionExpiryGuard = (config = {}) => {
|
|
766
|
+
if (typeof window === "undefined" || typeof document === "undefined") {
|
|
767
|
+
return { check: async () => false, dispose: () => {
|
|
768
|
+
return;
|
|
769
|
+
} };
|
|
770
|
+
}
|
|
771
|
+
if (activeGuard)
|
|
772
|
+
return activeGuard;
|
|
773
|
+
const {
|
|
774
|
+
checkIntervalMs = DEFAULT_CHECK_INTERVAL_MS,
|
|
775
|
+
isProtectedRequest,
|
|
776
|
+
onExpired,
|
|
777
|
+
protectedPaths = [],
|
|
778
|
+
reason = DEFAULT_REASON,
|
|
779
|
+
reasonParam = DEFAULT_REASON_PARAM,
|
|
780
|
+
returnUrlParam = DEFAULT_RETURN_URL_PARAM,
|
|
781
|
+
signInPath = DEFAULT_SIGN_IN_PATH,
|
|
782
|
+
statusPath = DEFAULT_STATUS_PATH
|
|
783
|
+
} = config;
|
|
784
|
+
const nativeFetch = window.fetch.bind(window);
|
|
785
|
+
const originalFetch = window.fetch;
|
|
786
|
+
let checking = false;
|
|
787
|
+
let disposed = false;
|
|
788
|
+
let expired = false;
|
|
789
|
+
let lastCheckedAt = Date.now();
|
|
790
|
+
const expire = () => {
|
|
791
|
+
if (expired || disposed)
|
|
792
|
+
return;
|
|
793
|
+
expired = true;
|
|
794
|
+
const currentHref = window.location.href;
|
|
795
|
+
const signInUrl = buildSessionExpiredSignInUrl({
|
|
796
|
+
currentHref,
|
|
797
|
+
reason,
|
|
798
|
+
reasonParam,
|
|
799
|
+
returnUrlParam,
|
|
800
|
+
signInPath
|
|
801
|
+
});
|
|
802
|
+
const current = new URL(currentHref);
|
|
803
|
+
const returnTo = `${current.pathname}${current.search}${current.hash}`;
|
|
804
|
+
if (onExpired) {
|
|
805
|
+
onExpired({ returnTo, signInUrl });
|
|
806
|
+
return;
|
|
807
|
+
}
|
|
808
|
+
window.location.assign(signInUrl);
|
|
809
|
+
};
|
|
810
|
+
const protectedRequest = (input) => {
|
|
811
|
+
const url = requestUrl(input, window.location.origin);
|
|
812
|
+
if (!url || url.origin !== window.location.origin)
|
|
813
|
+
return false;
|
|
814
|
+
if (new URL(statusPath, window.location.origin).pathname === url.pathname)
|
|
815
|
+
return false;
|
|
816
|
+
return isProtectedRequest?.(url) === true || protectedPaths.some((path) => url.pathname.startsWith(path));
|
|
817
|
+
};
|
|
818
|
+
const guardedFetch = new Proxy(originalFetch, {
|
|
819
|
+
apply: async (target, thisArg, args) => {
|
|
820
|
+
const [input] = args;
|
|
821
|
+
const response = await Reflect.apply(target, thisArg, args);
|
|
822
|
+
if (response.status === HTTP_UNAUTHORIZED && protectedRequest(input)) {
|
|
823
|
+
expire();
|
|
824
|
+
}
|
|
825
|
+
return response;
|
|
826
|
+
}
|
|
827
|
+
});
|
|
828
|
+
window.fetch = guardedFetch;
|
|
829
|
+
const check = async () => {
|
|
830
|
+
if (checking || expired || disposed)
|
|
831
|
+
return false;
|
|
832
|
+
checking = true;
|
|
833
|
+
lastCheckedAt = Date.now();
|
|
834
|
+
try {
|
|
835
|
+
const response = await nativeFetch(statusPath, {
|
|
836
|
+
cache: "no-store",
|
|
837
|
+
credentials: "include",
|
|
838
|
+
headers: { accept: "application/json" }
|
|
839
|
+
});
|
|
840
|
+
if (!response.ok)
|
|
841
|
+
return false;
|
|
842
|
+
const payload = await response.json();
|
|
843
|
+
const sessionExpired = typeof payload === "object" && payload !== null && Reflect.get(payload, "user") === null;
|
|
844
|
+
if (!sessionExpired)
|
|
845
|
+
return false;
|
|
846
|
+
expire();
|
|
847
|
+
return true;
|
|
848
|
+
} catch {
|
|
849
|
+
return false;
|
|
850
|
+
} finally {
|
|
851
|
+
checking = false;
|
|
852
|
+
}
|
|
853
|
+
};
|
|
854
|
+
const checkIfDue = () => {
|
|
855
|
+
if (document.visibilityState !== "visible" || Date.now() - lastCheckedAt < checkIntervalMs)
|
|
856
|
+
return;
|
|
857
|
+
check();
|
|
858
|
+
};
|
|
859
|
+
const checkPersistedSession = (event) => {
|
|
860
|
+
if (!event.persisted)
|
|
861
|
+
return;
|
|
862
|
+
check();
|
|
863
|
+
};
|
|
864
|
+
const dispose = () => {
|
|
865
|
+
if (disposed)
|
|
866
|
+
return;
|
|
867
|
+
disposed = true;
|
|
868
|
+
document.removeEventListener("visibilitychange", checkIfDue);
|
|
869
|
+
window.removeEventListener("focus", checkIfDue);
|
|
870
|
+
window.removeEventListener("pageshow", checkPersistedSession);
|
|
871
|
+
if (window.fetch === guardedFetch)
|
|
872
|
+
window.fetch = originalFetch;
|
|
873
|
+
activeGuard = null;
|
|
874
|
+
};
|
|
875
|
+
document.addEventListener("visibilitychange", checkIfDue);
|
|
876
|
+
window.addEventListener("focus", checkIfDue);
|
|
877
|
+
window.addEventListener("pageshow", checkPersistedSession);
|
|
878
|
+
activeGuard = { check, dispose };
|
|
879
|
+
return activeGuard;
|
|
880
|
+
};
|
|
881
|
+
var isProtectedSessionRequest = ({
|
|
882
|
+
input,
|
|
883
|
+
origin,
|
|
884
|
+
protectedPaths
|
|
885
|
+
}) => {
|
|
886
|
+
const url = requestUrl(input, origin);
|
|
887
|
+
return url?.origin === origin && protectedPaths.some((path) => url.pathname.startsWith(path));
|
|
888
|
+
};
|
|
859
889
|
// src/redirect.ts
|
|
860
890
|
var isSafeLocalPath = (value) => /^\/(?![/\\])/.test(value);
|
|
861
891
|
var toSafeLocalPath = (value, fallback = "/") => value !== undefined && isSafeLocalPath(value) ? value : fallback;
|
|
@@ -914,6 +944,8 @@ export {
|
|
|
914
944
|
isSafeLocalPath,
|
|
915
945
|
isProtectedSessionRequest,
|
|
916
946
|
installSessionExpiryGuard,
|
|
947
|
+
installAuthClientRuntimeTransport,
|
|
948
|
+
getAuthClientRuntimeTransport,
|
|
917
949
|
createMobileAuthTransport,
|
|
918
950
|
createMobileAuthClient,
|
|
919
951
|
createAuthClient,
|
|
@@ -921,5 +953,5 @@ export {
|
|
|
921
953
|
MobileAuthError
|
|
922
954
|
};
|
|
923
955
|
|
|
924
|
-
//# debugId=
|
|
956
|
+
//# debugId=45D0F7CD268E6FD764756E2164756E21
|
|
925
957
|
//# sourceMappingURL=index.js.map
|