@uln/auth 0.6.0 → 0.6.1
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/dist/client.js +10 -2
- package/dist/flow.d.ts +5 -0
- package/dist/flow.js +11 -2
- package/dist/hono.js +10 -2
- package/dist/index.js +10 -2
- package/dist/middleware.js +10 -2
- package/dist/remote.js +4 -2
- package/dist/service.js +10 -2
- package/dist/token.d.ts +4 -0
- package/dist/token.js +4 -2
- package/dist/types.d.ts +7 -0
- package/dist/version.d.ts +6 -0
- package/dist/version.js +6 -0
- package/package.json +1 -1
- package/src/flow.ts +8 -0
- package/src/token.ts +9 -1
- package/src/types.ts +7 -0
- package/src/version.ts +6 -0
package/dist/client.js
CHANGED
|
@@ -50,7 +50,9 @@ async function createToken(config, user, options = {}) {
|
|
|
50
50
|
kid: config.kid,
|
|
51
51
|
iat: now,
|
|
52
52
|
exp: now + config.maxAgeSeconds,
|
|
53
|
-
...user.audience === undefined ? {} : { aud: user.audience }
|
|
53
|
+
...user.audience === undefined ? {} : { aud: user.audience },
|
|
54
|
+
...user.sessionId === undefined ? {} : { sid: user.sessionId },
|
|
55
|
+
...user.deviceBound ? { dbsc: true } : {}
|
|
54
56
|
};
|
|
55
57
|
const payloadB64 = base64urlEncode(encoder.encode(JSON.stringify(payload)));
|
|
56
58
|
const payloadBytes = encoder.encode(payloadB64);
|
|
@@ -73,7 +75,7 @@ function isTokenPayload(value) {
|
|
|
73
75
|
if (!("sub" in value) || !("email" in value) || !("name" in value) || !("admin" in value) || !("scopes" in value) || !("flags" in value) || !("kid" in value) || !("iat" in value) || !("exp" in value)) {
|
|
74
76
|
return false;
|
|
75
77
|
}
|
|
76
|
-
return typeof value.sub === "string" && typeof value.email === "string" && typeof value.name === "string" && typeof value.admin === "boolean" && isStringArray(value.scopes) && isFlags(value.flags) && typeof value.kid === "string" && Number.isSafeInteger(value.iat) && Number.isSafeInteger(value.exp) && (!("aud" in value) || value.aud === undefined || typeof value.aud === "string");
|
|
78
|
+
return typeof value.sub === "string" && typeof value.email === "string" && typeof value.name === "string" && typeof value.admin === "boolean" && isStringArray(value.scopes) && isFlags(value.flags) && typeof value.kid === "string" && Number.isSafeInteger(value.iat) && Number.isSafeInteger(value.exp) && (!("aud" in value) || value.aud === undefined || typeof value.aud === "string") && (!("sid" in value) || value.sid === undefined || typeof value.sid === "string") && (!("dbsc" in value) || value.dbsc === undefined || typeof value.dbsc === "boolean");
|
|
77
79
|
}
|
|
78
80
|
async function validateToken(config, token) {
|
|
79
81
|
const parts = token.split(".");
|
|
@@ -276,12 +278,16 @@ function extractCookie(cookieHeader, name) {
|
|
|
276
278
|
}
|
|
277
279
|
return null;
|
|
278
280
|
}
|
|
281
|
+
// src/version.ts
|
|
282
|
+
var LIBRARY_VERSION = "0.6.1";
|
|
283
|
+
|
|
279
284
|
// src/flow.ts
|
|
280
285
|
var DEFAULT_AUTH_URL = "https://auth.uln.industries";
|
|
281
286
|
var STATE_COOKIE = "__Host-uln_auth_state";
|
|
282
287
|
var INSECURE_STATE_COOKIE = "uln_auth_state";
|
|
283
288
|
var STATE_PARAM = "uln_state";
|
|
284
289
|
var CHALLENGE_PARAM = "uln_challenge";
|
|
290
|
+
var VERSION_PARAM = "uln_v";
|
|
285
291
|
var STATE_MAX_AGE_SECONDS = 600;
|
|
286
292
|
function base64url(bytes) {
|
|
287
293
|
let binary = "";
|
|
@@ -311,6 +317,7 @@ function stripCallbackParams(url) {
|
|
|
311
317
|
url.searchParams.delete("code");
|
|
312
318
|
url.searchParams.delete(STATE_PARAM);
|
|
313
319
|
url.searchParams.delete(CHALLENGE_PARAM);
|
|
320
|
+
url.searchParams.delete(VERSION_PARAM);
|
|
314
321
|
}
|
|
315
322
|
async function redirectToLogin(currentUrl, authUrl = DEFAULT_AUTH_URL) {
|
|
316
323
|
const returnUrl = new URL(currentUrl);
|
|
@@ -319,6 +326,7 @@ async function redirectToLogin(currentUrl, authUrl = DEFAULT_AUTH_URL) {
|
|
|
319
326
|
const verifier = randomToken();
|
|
320
327
|
returnUrl.searchParams.set(STATE_PARAM, state);
|
|
321
328
|
returnUrl.searchParams.set(CHALLENGE_PARAM, await pkceChallenge(verifier));
|
|
329
|
+
returnUrl.searchParams.set(VERSION_PARAM, LIBRARY_VERSION);
|
|
322
330
|
const loginUrl = new URL("/login", authUrl);
|
|
323
331
|
loginUrl.searchParams.set("redirect", returnUrl.toString());
|
|
324
332
|
loginUrl.searchParams.set("response_type", "code");
|
package/dist/flow.d.ts
CHANGED
|
@@ -17,6 +17,11 @@ export declare const STATE_PARAM = "uln_state";
|
|
|
17
17
|
* code that leaks (history, logs, a shared link) is useless on its own.
|
|
18
18
|
*/
|
|
19
19
|
export declare const CHALLENGE_PARAM = "uln_challenge";
|
|
20
|
+
/**
|
|
21
|
+
* This library's version on the return URL (0.6.1+), so the auth service can track which version each site runs.
|
|
22
|
+
* The auth service strips it from the URL it sends the browser back to.
|
|
23
|
+
*/
|
|
24
|
+
export declare const VERSION_PARAM = "uln_v";
|
|
20
25
|
/** S256 code challenge for a PKCE verifier: base64url(SHA-256(verifier)). */
|
|
21
26
|
export declare function pkceChallenge(verifier: string): Promise<string>;
|
|
22
27
|
/**
|
package/dist/flow.js
CHANGED
|
@@ -74,7 +74,9 @@ async function createToken(config, user, options = {}) {
|
|
|
74
74
|
kid: config.kid,
|
|
75
75
|
iat: now,
|
|
76
76
|
exp: now + config.maxAgeSeconds,
|
|
77
|
-
...user.audience === undefined ? {} : { aud: user.audience }
|
|
77
|
+
...user.audience === undefined ? {} : { aud: user.audience },
|
|
78
|
+
...user.sessionId === undefined ? {} : { sid: user.sessionId },
|
|
79
|
+
...user.deviceBound ? { dbsc: true } : {}
|
|
78
80
|
};
|
|
79
81
|
const payloadB64 = base64urlEncode(encoder.encode(JSON.stringify(payload)));
|
|
80
82
|
const payloadBytes = encoder.encode(payloadB64);
|
|
@@ -97,7 +99,7 @@ function isTokenPayload(value) {
|
|
|
97
99
|
if (!("sub" in value) || !("email" in value) || !("name" in value) || !("admin" in value) || !("scopes" in value) || !("flags" in value) || !("kid" in value) || !("iat" in value) || !("exp" in value)) {
|
|
98
100
|
return false;
|
|
99
101
|
}
|
|
100
|
-
return typeof value.sub === "string" && typeof value.email === "string" && typeof value.name === "string" && typeof value.admin === "boolean" && isStringArray(value.scopes) && isFlags(value.flags) && typeof value.kid === "string" && Number.isSafeInteger(value.iat) && Number.isSafeInteger(value.exp) && (!("aud" in value) || value.aud === undefined || typeof value.aud === "string");
|
|
102
|
+
return typeof value.sub === "string" && typeof value.email === "string" && typeof value.name === "string" && typeof value.admin === "boolean" && isStringArray(value.scopes) && isFlags(value.flags) && typeof value.kid === "string" && Number.isSafeInteger(value.iat) && Number.isSafeInteger(value.exp) && (!("aud" in value) || value.aud === undefined || typeof value.aud === "string") && (!("sid" in value) || value.sid === undefined || typeof value.sid === "string") && (!("dbsc" in value) || value.dbsc === undefined || typeof value.dbsc === "boolean");
|
|
101
103
|
}
|
|
102
104
|
async function validateToken(config, token) {
|
|
103
105
|
const parts = token.split(".");
|
|
@@ -277,12 +279,16 @@ async function fetchJwks(auth) {
|
|
|
277
279
|
return entries;
|
|
278
280
|
}
|
|
279
281
|
|
|
282
|
+
// src/version.ts
|
|
283
|
+
var LIBRARY_VERSION = "0.6.1";
|
|
284
|
+
|
|
280
285
|
// src/flow.ts
|
|
281
286
|
var DEFAULT_AUTH_URL = "https://auth.uln.industries";
|
|
282
287
|
var STATE_COOKIE = "__Host-uln_auth_state";
|
|
283
288
|
var INSECURE_STATE_COOKIE = "uln_auth_state";
|
|
284
289
|
var STATE_PARAM = "uln_state";
|
|
285
290
|
var CHALLENGE_PARAM = "uln_challenge";
|
|
291
|
+
var VERSION_PARAM = "uln_v";
|
|
286
292
|
var STATE_MAX_AGE_SECONDS = 600;
|
|
287
293
|
function base64url(bytes) {
|
|
288
294
|
let binary = "";
|
|
@@ -312,6 +318,7 @@ function stripCallbackParams(url) {
|
|
|
312
318
|
url.searchParams.delete("code");
|
|
313
319
|
url.searchParams.delete(STATE_PARAM);
|
|
314
320
|
url.searchParams.delete(CHALLENGE_PARAM);
|
|
321
|
+
url.searchParams.delete(VERSION_PARAM);
|
|
315
322
|
}
|
|
316
323
|
async function redirectToLogin(currentUrl, authUrl = DEFAULT_AUTH_URL) {
|
|
317
324
|
const returnUrl = new URL(currentUrl);
|
|
@@ -320,6 +327,7 @@ async function redirectToLogin(currentUrl, authUrl = DEFAULT_AUTH_URL) {
|
|
|
320
327
|
const verifier = randomToken();
|
|
321
328
|
returnUrl.searchParams.set(STATE_PARAM, state);
|
|
322
329
|
returnUrl.searchParams.set(CHALLENGE_PARAM, await pkceChallenge(verifier));
|
|
330
|
+
returnUrl.searchParams.set(VERSION_PARAM, LIBRARY_VERSION);
|
|
323
331
|
const loginUrl = new URL("/login", authUrl);
|
|
324
332
|
loginUrl.searchParams.set("redirect", returnUrl.toString());
|
|
325
333
|
loginUrl.searchParams.set("response_type", "code");
|
|
@@ -385,6 +393,7 @@ export {
|
|
|
385
393
|
INSECURE_STATE_COOKIE,
|
|
386
394
|
STATE_COOKIE,
|
|
387
395
|
STATE_PARAM,
|
|
396
|
+
VERSION_PARAM,
|
|
388
397
|
clearRevalidationCache,
|
|
389
398
|
handleLoginCallback,
|
|
390
399
|
pkceChallenge,
|
package/dist/hono.js
CHANGED
|
@@ -74,7 +74,9 @@ async function createToken(config, user, options = {}) {
|
|
|
74
74
|
kid: config.kid,
|
|
75
75
|
iat: now,
|
|
76
76
|
exp: now + config.maxAgeSeconds,
|
|
77
|
-
...user.audience === undefined ? {} : { aud: user.audience }
|
|
77
|
+
...user.audience === undefined ? {} : { aud: user.audience },
|
|
78
|
+
...user.sessionId === undefined ? {} : { sid: user.sessionId },
|
|
79
|
+
...user.deviceBound ? { dbsc: true } : {}
|
|
78
80
|
};
|
|
79
81
|
const payloadB64 = base64urlEncode(encoder.encode(JSON.stringify(payload)));
|
|
80
82
|
const payloadBytes = encoder.encode(payloadB64);
|
|
@@ -97,7 +99,7 @@ function isTokenPayload(value) {
|
|
|
97
99
|
if (!("sub" in value) || !("email" in value) || !("name" in value) || !("admin" in value) || !("scopes" in value) || !("flags" in value) || !("kid" in value) || !("iat" in value) || !("exp" in value)) {
|
|
98
100
|
return false;
|
|
99
101
|
}
|
|
100
|
-
return typeof value.sub === "string" && typeof value.email === "string" && typeof value.name === "string" && typeof value.admin === "boolean" && isStringArray(value.scopes) && isFlags(value.flags) && typeof value.kid === "string" && Number.isSafeInteger(value.iat) && Number.isSafeInteger(value.exp) && (!("aud" in value) || value.aud === undefined || typeof value.aud === "string");
|
|
102
|
+
return typeof value.sub === "string" && typeof value.email === "string" && typeof value.name === "string" && typeof value.admin === "boolean" && isStringArray(value.scopes) && isFlags(value.flags) && typeof value.kid === "string" && Number.isSafeInteger(value.iat) && Number.isSafeInteger(value.exp) && (!("aud" in value) || value.aud === undefined || typeof value.aud === "string") && (!("sid" in value) || value.sid === undefined || typeof value.sid === "string") && (!("dbsc" in value) || value.dbsc === undefined || typeof value.dbsc === "boolean");
|
|
101
103
|
}
|
|
102
104
|
async function validateToken(config, token) {
|
|
103
105
|
const parts = token.split(".");
|
|
@@ -277,12 +279,16 @@ async function fetchJwks(auth) {
|
|
|
277
279
|
return entries;
|
|
278
280
|
}
|
|
279
281
|
|
|
282
|
+
// src/version.ts
|
|
283
|
+
var LIBRARY_VERSION = "0.6.1";
|
|
284
|
+
|
|
280
285
|
// src/flow.ts
|
|
281
286
|
var DEFAULT_AUTH_URL = "https://auth.uln.industries";
|
|
282
287
|
var STATE_COOKIE = "__Host-uln_auth_state";
|
|
283
288
|
var INSECURE_STATE_COOKIE = "uln_auth_state";
|
|
284
289
|
var STATE_PARAM = "uln_state";
|
|
285
290
|
var CHALLENGE_PARAM = "uln_challenge";
|
|
291
|
+
var VERSION_PARAM = "uln_v";
|
|
286
292
|
var STATE_MAX_AGE_SECONDS = 600;
|
|
287
293
|
function base64url(bytes) {
|
|
288
294
|
let binary = "";
|
|
@@ -312,6 +318,7 @@ function stripCallbackParams(url) {
|
|
|
312
318
|
url.searchParams.delete("code");
|
|
313
319
|
url.searchParams.delete(STATE_PARAM);
|
|
314
320
|
url.searchParams.delete(CHALLENGE_PARAM);
|
|
321
|
+
url.searchParams.delete(VERSION_PARAM);
|
|
315
322
|
}
|
|
316
323
|
async function redirectToLogin(currentUrl, authUrl = DEFAULT_AUTH_URL) {
|
|
317
324
|
const returnUrl = new URL(currentUrl);
|
|
@@ -320,6 +327,7 @@ async function redirectToLogin(currentUrl, authUrl = DEFAULT_AUTH_URL) {
|
|
|
320
327
|
const verifier = randomToken();
|
|
321
328
|
returnUrl.searchParams.set(STATE_PARAM, state);
|
|
322
329
|
returnUrl.searchParams.set(CHALLENGE_PARAM, await pkceChallenge(verifier));
|
|
330
|
+
returnUrl.searchParams.set(VERSION_PARAM, LIBRARY_VERSION);
|
|
323
331
|
const loginUrl = new URL("/login", authUrl);
|
|
324
332
|
loginUrl.searchParams.set("redirect", returnUrl.toString());
|
|
325
333
|
loginUrl.searchParams.set("response_type", "code");
|
package/dist/index.js
CHANGED
|
@@ -73,7 +73,9 @@ async function createToken(config, user, options = {}) {
|
|
|
73
73
|
kid: config.kid,
|
|
74
74
|
iat: now,
|
|
75
75
|
exp: now + config.maxAgeSeconds,
|
|
76
|
-
...user.audience === undefined ? {} : { aud: user.audience }
|
|
76
|
+
...user.audience === undefined ? {} : { aud: user.audience },
|
|
77
|
+
...user.sessionId === undefined ? {} : { sid: user.sessionId },
|
|
78
|
+
...user.deviceBound ? { dbsc: true } : {}
|
|
77
79
|
};
|
|
78
80
|
const payloadB64 = base64urlEncode(encoder.encode(JSON.stringify(payload)));
|
|
79
81
|
const payloadBytes = encoder.encode(payloadB64);
|
|
@@ -96,7 +98,7 @@ function isTokenPayload(value) {
|
|
|
96
98
|
if (!("sub" in value) || !("email" in value) || !("name" in value) || !("admin" in value) || !("scopes" in value) || !("flags" in value) || !("kid" in value) || !("iat" in value) || !("exp" in value)) {
|
|
97
99
|
return false;
|
|
98
100
|
}
|
|
99
|
-
return typeof value.sub === "string" && typeof value.email === "string" && typeof value.name === "string" && typeof value.admin === "boolean" && isStringArray(value.scopes) && isFlags(value.flags) && typeof value.kid === "string" && Number.isSafeInteger(value.iat) && Number.isSafeInteger(value.exp) && (!("aud" in value) || value.aud === undefined || typeof value.aud === "string");
|
|
101
|
+
return typeof value.sub === "string" && typeof value.email === "string" && typeof value.name === "string" && typeof value.admin === "boolean" && isStringArray(value.scopes) && isFlags(value.flags) && typeof value.kid === "string" && Number.isSafeInteger(value.iat) && Number.isSafeInteger(value.exp) && (!("aud" in value) || value.aud === undefined || typeof value.aud === "string") && (!("sid" in value) || value.sid === undefined || typeof value.sid === "string") && (!("dbsc" in value) || value.dbsc === undefined || typeof value.dbsc === "boolean");
|
|
100
102
|
}
|
|
101
103
|
async function validateToken(config, token) {
|
|
102
104
|
const parts = token.split(".");
|
|
@@ -276,12 +278,16 @@ async function fetchJwks(auth) {
|
|
|
276
278
|
return entries;
|
|
277
279
|
}
|
|
278
280
|
|
|
281
|
+
// src/version.ts
|
|
282
|
+
var LIBRARY_VERSION = "0.6.1";
|
|
283
|
+
|
|
279
284
|
// src/flow.ts
|
|
280
285
|
var DEFAULT_AUTH_URL = "https://auth.uln.industries";
|
|
281
286
|
var STATE_COOKIE = "__Host-uln_auth_state";
|
|
282
287
|
var INSECURE_STATE_COOKIE = "uln_auth_state";
|
|
283
288
|
var STATE_PARAM = "uln_state";
|
|
284
289
|
var CHALLENGE_PARAM = "uln_challenge";
|
|
290
|
+
var VERSION_PARAM = "uln_v";
|
|
285
291
|
var STATE_MAX_AGE_SECONDS = 600;
|
|
286
292
|
function base64url(bytes) {
|
|
287
293
|
let binary = "";
|
|
@@ -311,6 +317,7 @@ function stripCallbackParams(url) {
|
|
|
311
317
|
url.searchParams.delete("code");
|
|
312
318
|
url.searchParams.delete(STATE_PARAM);
|
|
313
319
|
url.searchParams.delete(CHALLENGE_PARAM);
|
|
320
|
+
url.searchParams.delete(VERSION_PARAM);
|
|
314
321
|
}
|
|
315
322
|
async function redirectToLogin(currentUrl, authUrl = DEFAULT_AUTH_URL) {
|
|
316
323
|
const returnUrl = new URL(currentUrl);
|
|
@@ -319,6 +326,7 @@ async function redirectToLogin(currentUrl, authUrl = DEFAULT_AUTH_URL) {
|
|
|
319
326
|
const verifier = randomToken();
|
|
320
327
|
returnUrl.searchParams.set(STATE_PARAM, state);
|
|
321
328
|
returnUrl.searchParams.set(CHALLENGE_PARAM, await pkceChallenge(verifier));
|
|
329
|
+
returnUrl.searchParams.set(VERSION_PARAM, LIBRARY_VERSION);
|
|
322
330
|
const loginUrl = new URL("/login", authUrl);
|
|
323
331
|
loginUrl.searchParams.set("redirect", returnUrl.toString());
|
|
324
332
|
loginUrl.searchParams.set("response_type", "code");
|
package/dist/middleware.js
CHANGED
|
@@ -74,7 +74,9 @@ async function createToken(config, user, options = {}) {
|
|
|
74
74
|
kid: config.kid,
|
|
75
75
|
iat: now,
|
|
76
76
|
exp: now + config.maxAgeSeconds,
|
|
77
|
-
...user.audience === undefined ? {} : { aud: user.audience }
|
|
77
|
+
...user.audience === undefined ? {} : { aud: user.audience },
|
|
78
|
+
...user.sessionId === undefined ? {} : { sid: user.sessionId },
|
|
79
|
+
...user.deviceBound ? { dbsc: true } : {}
|
|
78
80
|
};
|
|
79
81
|
const payloadB64 = base64urlEncode(encoder.encode(JSON.stringify(payload)));
|
|
80
82
|
const payloadBytes = encoder.encode(payloadB64);
|
|
@@ -97,7 +99,7 @@ function isTokenPayload(value) {
|
|
|
97
99
|
if (!("sub" in value) || !("email" in value) || !("name" in value) || !("admin" in value) || !("scopes" in value) || !("flags" in value) || !("kid" in value) || !("iat" in value) || !("exp" in value)) {
|
|
98
100
|
return false;
|
|
99
101
|
}
|
|
100
|
-
return typeof value.sub === "string" && typeof value.email === "string" && typeof value.name === "string" && typeof value.admin === "boolean" && isStringArray(value.scopes) && isFlags(value.flags) && typeof value.kid === "string" && Number.isSafeInteger(value.iat) && Number.isSafeInteger(value.exp) && (!("aud" in value) || value.aud === undefined || typeof value.aud === "string");
|
|
102
|
+
return typeof value.sub === "string" && typeof value.email === "string" && typeof value.name === "string" && typeof value.admin === "boolean" && isStringArray(value.scopes) && isFlags(value.flags) && typeof value.kid === "string" && Number.isSafeInteger(value.iat) && Number.isSafeInteger(value.exp) && (!("aud" in value) || value.aud === undefined || typeof value.aud === "string") && (!("sid" in value) || value.sid === undefined || typeof value.sid === "string") && (!("dbsc" in value) || value.dbsc === undefined || typeof value.dbsc === "boolean");
|
|
101
103
|
}
|
|
102
104
|
async function validateToken(config, token) {
|
|
103
105
|
const parts = token.split(".");
|
|
@@ -277,12 +279,16 @@ async function fetchJwks(auth) {
|
|
|
277
279
|
return entries;
|
|
278
280
|
}
|
|
279
281
|
|
|
282
|
+
// src/version.ts
|
|
283
|
+
var LIBRARY_VERSION = "0.6.1";
|
|
284
|
+
|
|
280
285
|
// src/flow.ts
|
|
281
286
|
var DEFAULT_AUTH_URL = "https://auth.uln.industries";
|
|
282
287
|
var STATE_COOKIE = "__Host-uln_auth_state";
|
|
283
288
|
var INSECURE_STATE_COOKIE = "uln_auth_state";
|
|
284
289
|
var STATE_PARAM = "uln_state";
|
|
285
290
|
var CHALLENGE_PARAM = "uln_challenge";
|
|
291
|
+
var VERSION_PARAM = "uln_v";
|
|
286
292
|
var STATE_MAX_AGE_SECONDS = 600;
|
|
287
293
|
function base64url(bytes) {
|
|
288
294
|
let binary = "";
|
|
@@ -312,6 +318,7 @@ function stripCallbackParams(url) {
|
|
|
312
318
|
url.searchParams.delete("code");
|
|
313
319
|
url.searchParams.delete(STATE_PARAM);
|
|
314
320
|
url.searchParams.delete(CHALLENGE_PARAM);
|
|
321
|
+
url.searchParams.delete(VERSION_PARAM);
|
|
315
322
|
}
|
|
316
323
|
async function redirectToLogin(currentUrl, authUrl = DEFAULT_AUTH_URL) {
|
|
317
324
|
const returnUrl = new URL(currentUrl);
|
|
@@ -320,6 +327,7 @@ async function redirectToLogin(currentUrl, authUrl = DEFAULT_AUTH_URL) {
|
|
|
320
327
|
const verifier = randomToken();
|
|
321
328
|
returnUrl.searchParams.set(STATE_PARAM, state);
|
|
322
329
|
returnUrl.searchParams.set(CHALLENGE_PARAM, await pkceChallenge(verifier));
|
|
330
|
+
returnUrl.searchParams.set(VERSION_PARAM, LIBRARY_VERSION);
|
|
323
331
|
const loginUrl = new URL("/login", authUrl);
|
|
324
332
|
loginUrl.searchParams.set("redirect", returnUrl.toString());
|
|
325
333
|
loginUrl.searchParams.set("response_type", "code");
|
package/dist/remote.js
CHANGED
|
@@ -50,7 +50,9 @@ async function createToken(config, user, options = {}) {
|
|
|
50
50
|
kid: config.kid,
|
|
51
51
|
iat: now,
|
|
52
52
|
exp: now + config.maxAgeSeconds,
|
|
53
|
-
...user.audience === undefined ? {} : { aud: user.audience }
|
|
53
|
+
...user.audience === undefined ? {} : { aud: user.audience },
|
|
54
|
+
...user.sessionId === undefined ? {} : { sid: user.sessionId },
|
|
55
|
+
...user.deviceBound ? { dbsc: true } : {}
|
|
54
56
|
};
|
|
55
57
|
const payloadB64 = base64urlEncode(encoder.encode(JSON.stringify(payload)));
|
|
56
58
|
const payloadBytes = encoder.encode(payloadB64);
|
|
@@ -73,7 +75,7 @@ function isTokenPayload(value) {
|
|
|
73
75
|
if (!("sub" in value) || !("email" in value) || !("name" in value) || !("admin" in value) || !("scopes" in value) || !("flags" in value) || !("kid" in value) || !("iat" in value) || !("exp" in value)) {
|
|
74
76
|
return false;
|
|
75
77
|
}
|
|
76
|
-
return typeof value.sub === "string" && typeof value.email === "string" && typeof value.name === "string" && typeof value.admin === "boolean" && isStringArray(value.scopes) && isFlags(value.flags) && typeof value.kid === "string" && Number.isSafeInteger(value.iat) && Number.isSafeInteger(value.exp) && (!("aud" in value) || value.aud === undefined || typeof value.aud === "string");
|
|
78
|
+
return typeof value.sub === "string" && typeof value.email === "string" && typeof value.name === "string" && typeof value.admin === "boolean" && isStringArray(value.scopes) && isFlags(value.flags) && typeof value.kid === "string" && Number.isSafeInteger(value.iat) && Number.isSafeInteger(value.exp) && (!("aud" in value) || value.aud === undefined || typeof value.aud === "string") && (!("sid" in value) || value.sid === undefined || typeof value.sid === "string") && (!("dbsc" in value) || value.dbsc === undefined || typeof value.dbsc === "boolean");
|
|
77
79
|
}
|
|
78
80
|
async function validateToken(config, token) {
|
|
79
81
|
const parts = token.split(".");
|
package/dist/service.js
CHANGED
|
@@ -74,7 +74,9 @@ async function createToken(config, user, options = {}) {
|
|
|
74
74
|
kid: config.kid,
|
|
75
75
|
iat: now,
|
|
76
76
|
exp: now + config.maxAgeSeconds,
|
|
77
|
-
...user.audience === undefined ? {} : { aud: user.audience }
|
|
77
|
+
...user.audience === undefined ? {} : { aud: user.audience },
|
|
78
|
+
...user.sessionId === undefined ? {} : { sid: user.sessionId },
|
|
79
|
+
...user.deviceBound ? { dbsc: true } : {}
|
|
78
80
|
};
|
|
79
81
|
const payloadB64 = base64urlEncode(encoder.encode(JSON.stringify(payload)));
|
|
80
82
|
const payloadBytes = encoder.encode(payloadB64);
|
|
@@ -97,7 +99,7 @@ function isTokenPayload(value) {
|
|
|
97
99
|
if (!("sub" in value) || !("email" in value) || !("name" in value) || !("admin" in value) || !("scopes" in value) || !("flags" in value) || !("kid" in value) || !("iat" in value) || !("exp" in value)) {
|
|
98
100
|
return false;
|
|
99
101
|
}
|
|
100
|
-
return typeof value.sub === "string" && typeof value.email === "string" && typeof value.name === "string" && typeof value.admin === "boolean" && isStringArray(value.scopes) && isFlags(value.flags) && typeof value.kid === "string" && Number.isSafeInteger(value.iat) && Number.isSafeInteger(value.exp) && (!("aud" in value) || value.aud === undefined || typeof value.aud === "string");
|
|
102
|
+
return typeof value.sub === "string" && typeof value.email === "string" && typeof value.name === "string" && typeof value.admin === "boolean" && isStringArray(value.scopes) && isFlags(value.flags) && typeof value.kid === "string" && Number.isSafeInteger(value.iat) && Number.isSafeInteger(value.exp) && (!("aud" in value) || value.aud === undefined || typeof value.aud === "string") && (!("sid" in value) || value.sid === undefined || typeof value.sid === "string") && (!("dbsc" in value) || value.dbsc === undefined || typeof value.dbsc === "boolean");
|
|
101
103
|
}
|
|
102
104
|
async function validateToken(config, token) {
|
|
103
105
|
const parts = token.split(".");
|
|
@@ -277,12 +279,16 @@ async function fetchJwks(auth) {
|
|
|
277
279
|
return entries;
|
|
278
280
|
}
|
|
279
281
|
|
|
282
|
+
// src/version.ts
|
|
283
|
+
var LIBRARY_VERSION = "0.6.1";
|
|
284
|
+
|
|
280
285
|
// src/flow.ts
|
|
281
286
|
var DEFAULT_AUTH_URL = "https://auth.uln.industries";
|
|
282
287
|
var STATE_COOKIE = "__Host-uln_auth_state";
|
|
283
288
|
var INSECURE_STATE_COOKIE = "uln_auth_state";
|
|
284
289
|
var STATE_PARAM = "uln_state";
|
|
285
290
|
var CHALLENGE_PARAM = "uln_challenge";
|
|
291
|
+
var VERSION_PARAM = "uln_v";
|
|
286
292
|
var STATE_MAX_AGE_SECONDS = 600;
|
|
287
293
|
function base64url(bytes) {
|
|
288
294
|
let binary = "";
|
|
@@ -312,6 +318,7 @@ function stripCallbackParams(url) {
|
|
|
312
318
|
url.searchParams.delete("code");
|
|
313
319
|
url.searchParams.delete(STATE_PARAM);
|
|
314
320
|
url.searchParams.delete(CHALLENGE_PARAM);
|
|
321
|
+
url.searchParams.delete(VERSION_PARAM);
|
|
315
322
|
}
|
|
316
323
|
async function redirectToLogin(currentUrl, authUrl = DEFAULT_AUTH_URL) {
|
|
317
324
|
const returnUrl = new URL(currentUrl);
|
|
@@ -320,6 +327,7 @@ async function redirectToLogin(currentUrl, authUrl = DEFAULT_AUTH_URL) {
|
|
|
320
327
|
const verifier = randomToken();
|
|
321
328
|
returnUrl.searchParams.set(STATE_PARAM, state);
|
|
322
329
|
returnUrl.searchParams.set(CHALLENGE_PARAM, await pkceChallenge(verifier));
|
|
330
|
+
returnUrl.searchParams.set(VERSION_PARAM, LIBRARY_VERSION);
|
|
323
331
|
const loginUrl = new URL("/login", authUrl);
|
|
324
332
|
loginUrl.searchParams.set("redirect", returnUrl.toString());
|
|
325
333
|
loginUrl.searchParams.set("response_type", "code");
|
package/dist/token.d.ts
CHANGED
|
@@ -18,6 +18,10 @@ export interface CreateTokenInput {
|
|
|
18
18
|
flags: Record<string, string[]>;
|
|
19
19
|
/** The origin the token is for (see `TokenPayload.aud`). */
|
|
20
20
|
audience?: string;
|
|
21
|
+
/** See `TokenPayload.sid`. */
|
|
22
|
+
sessionId?: string;
|
|
23
|
+
/** See `TokenPayload.dbsc`. */
|
|
24
|
+
deviceBound?: boolean;
|
|
21
25
|
}
|
|
22
26
|
export interface CreateTokenOptions {
|
|
23
27
|
/** Unix seconds to use as `iat` instead of now (e.g. to land just after a session revocation cut-off). */
|
package/dist/token.js
CHANGED
|
@@ -50,7 +50,9 @@ async function createToken(config, user, options = {}) {
|
|
|
50
50
|
kid: config.kid,
|
|
51
51
|
iat: now,
|
|
52
52
|
exp: now + config.maxAgeSeconds,
|
|
53
|
-
...user.audience === undefined ? {} : { aud: user.audience }
|
|
53
|
+
...user.audience === undefined ? {} : { aud: user.audience },
|
|
54
|
+
...user.sessionId === undefined ? {} : { sid: user.sessionId },
|
|
55
|
+
...user.deviceBound ? { dbsc: true } : {}
|
|
54
56
|
};
|
|
55
57
|
const payloadB64 = base64urlEncode(encoder.encode(JSON.stringify(payload)));
|
|
56
58
|
const payloadBytes = encoder.encode(payloadB64);
|
|
@@ -73,7 +75,7 @@ function isTokenPayload(value) {
|
|
|
73
75
|
if (!("sub" in value) || !("email" in value) || !("name" in value) || !("admin" in value) || !("scopes" in value) || !("flags" in value) || !("kid" in value) || !("iat" in value) || !("exp" in value)) {
|
|
74
76
|
return false;
|
|
75
77
|
}
|
|
76
|
-
return typeof value.sub === "string" && typeof value.email === "string" && typeof value.name === "string" && typeof value.admin === "boolean" && isStringArray(value.scopes) && isFlags(value.flags) && typeof value.kid === "string" && Number.isSafeInteger(value.iat) && Number.isSafeInteger(value.exp) && (!("aud" in value) || value.aud === undefined || typeof value.aud === "string");
|
|
78
|
+
return typeof value.sub === "string" && typeof value.email === "string" && typeof value.name === "string" && typeof value.admin === "boolean" && isStringArray(value.scopes) && isFlags(value.flags) && typeof value.kid === "string" && Number.isSafeInteger(value.iat) && Number.isSafeInteger(value.exp) && (!("aud" in value) || value.aud === undefined || typeof value.aud === "string") && (!("sid" in value) || value.sid === undefined || typeof value.sid === "string") && (!("dbsc" in value) || value.dbsc === undefined || typeof value.dbsc === "boolean");
|
|
77
79
|
}
|
|
78
80
|
async function validateToken(config, token) {
|
|
79
81
|
const parts = token.split(".");
|
package/dist/types.d.ts
CHANGED
|
@@ -51,6 +51,13 @@ export interface TokenPayload {
|
|
|
51
51
|
* (library < 0.5); current verifiers reject those.
|
|
52
52
|
*/
|
|
53
53
|
aud?: string;
|
|
54
|
+
/** Auth-service sessions only: the server-side session this token belongs to (worker `sessions` table). */
|
|
55
|
+
sid?: string;
|
|
56
|
+
/**
|
|
57
|
+
* Auth-service sessions only: a short-lived token for a Device Bound Session (DBSC), renewed by a proof from a key
|
|
58
|
+
* held by the user's device. Once a session is bound, only these tokens are honoured for it.
|
|
59
|
+
*/
|
|
60
|
+
dbsc?: boolean;
|
|
54
61
|
}
|
|
55
62
|
export interface PublicKeyEntry {
|
|
56
63
|
kid: string;
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* This library's version. Sent to the auth service on every login (`uln_v` on the return URL) so Admin → Sites can show
|
|
3
|
+
* which version each site runs and open upgrade PRs for the ones behind. `src/version.test.ts` keeps it equal to
|
|
4
|
+
* `package.json`.
|
|
5
|
+
*/
|
|
6
|
+
export declare const LIBRARY_VERSION = "0.6.1";
|
package/dist/version.js
ADDED
package/package.json
CHANGED
package/src/flow.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { extractCookie, makeSessionCookie } from "./cookie.ts";
|
|
2
2
|
import { checkTokenRemote, exchangeAuthCode, type FetchLike, hasSiteAccessFor } from "./remote.ts";
|
|
3
3
|
import type { CookieConfig, TokenPayload } from "./types.ts";
|
|
4
|
+
import { LIBRARY_VERSION } from "./version.ts";
|
|
4
5
|
|
|
5
6
|
// The one implementation of the site side of the login flow, shared by `@uln/auth/client`, `/service` and `/hono`.
|
|
6
7
|
|
|
@@ -22,6 +23,11 @@ export const STATE_PARAM = "uln_state";
|
|
|
22
23
|
* code that leaks (history, logs, a shared link) is useless on its own.
|
|
23
24
|
*/
|
|
24
25
|
export const CHALLENGE_PARAM = "uln_challenge";
|
|
26
|
+
/**
|
|
27
|
+
* This library's version on the return URL (0.6.1+), so the auth service can track which version each site runs.
|
|
28
|
+
* The auth service strips it from the URL it sends the browser back to.
|
|
29
|
+
*/
|
|
30
|
+
export const VERSION_PARAM = "uln_v";
|
|
25
31
|
const STATE_MAX_AGE_SECONDS = 600;
|
|
26
32
|
|
|
27
33
|
function base64url(bytes: Uint8Array): string {
|
|
@@ -58,6 +64,7 @@ function stripCallbackParams(url: URL): void {
|
|
|
58
64
|
url.searchParams.delete("code");
|
|
59
65
|
url.searchParams.delete(STATE_PARAM);
|
|
60
66
|
url.searchParams.delete(CHALLENGE_PARAM);
|
|
67
|
+
url.searchParams.delete(VERSION_PARAM);
|
|
61
68
|
}
|
|
62
69
|
|
|
63
70
|
/**
|
|
@@ -72,6 +79,7 @@ export async function redirectToLogin(currentUrl: string, authUrl: string = DEFA
|
|
|
72
79
|
const verifier = randomToken();
|
|
73
80
|
returnUrl.searchParams.set(STATE_PARAM, state);
|
|
74
81
|
returnUrl.searchParams.set(CHALLENGE_PARAM, await pkceChallenge(verifier));
|
|
82
|
+
returnUrl.searchParams.set(VERSION_PARAM, LIBRARY_VERSION);
|
|
75
83
|
|
|
76
84
|
const loginUrl = new URL("/login", authUrl);
|
|
77
85
|
loginUrl.searchParams.set("redirect", returnUrl.toString());
|
package/src/token.ts
CHANGED
|
@@ -55,6 +55,10 @@ export interface CreateTokenInput {
|
|
|
55
55
|
flags: Record<string, string[]>;
|
|
56
56
|
/** The origin the token is for (see `TokenPayload.aud`). */
|
|
57
57
|
audience?: string;
|
|
58
|
+
/** See `TokenPayload.sid`. */
|
|
59
|
+
sessionId?: string;
|
|
60
|
+
/** See `TokenPayload.dbsc`. */
|
|
61
|
+
deviceBound?: boolean;
|
|
58
62
|
}
|
|
59
63
|
|
|
60
64
|
export interface CreateTokenOptions {
|
|
@@ -80,6 +84,8 @@ export async function createToken(
|
|
|
80
84
|
iat: now,
|
|
81
85
|
exp: now + config.maxAgeSeconds,
|
|
82
86
|
...(user.audience === undefined ? {} : { aud: user.audience }),
|
|
87
|
+
...(user.sessionId === undefined ? {} : { sid: user.sessionId }),
|
|
88
|
+
...(user.deviceBound ? { dbsc: true } : {}),
|
|
83
89
|
};
|
|
84
90
|
const payloadB64 = base64urlEncode(encoder.encode(JSON.stringify(payload)));
|
|
85
91
|
const payloadBytes = encoder.encode(payloadB64);
|
|
@@ -126,7 +132,9 @@ export function isTokenPayload(value: unknown): value is TokenPayload {
|
|
|
126
132
|
typeof value.kid === "string" &&
|
|
127
133
|
Number.isSafeInteger(value.iat) &&
|
|
128
134
|
Number.isSafeInteger(value.exp) &&
|
|
129
|
-
(!("aud" in value) || value.aud === undefined || typeof value.aud === "string")
|
|
135
|
+
(!("aud" in value) || value.aud === undefined || typeof value.aud === "string") &&
|
|
136
|
+
(!("sid" in value) || value.sid === undefined || typeof value.sid === "string") &&
|
|
137
|
+
(!("dbsc" in value) || value.dbsc === undefined || typeof value.dbsc === "boolean")
|
|
130
138
|
);
|
|
131
139
|
}
|
|
132
140
|
|
package/src/types.ts
CHANGED
|
@@ -56,6 +56,13 @@ export interface TokenPayload {
|
|
|
56
56
|
* (library < 0.5); current verifiers reject those.
|
|
57
57
|
*/
|
|
58
58
|
aud?: string;
|
|
59
|
+
/** Auth-service sessions only: the server-side session this token belongs to (worker `sessions` table). */
|
|
60
|
+
sid?: string;
|
|
61
|
+
/**
|
|
62
|
+
* Auth-service sessions only: a short-lived token for a Device Bound Session (DBSC), renewed by a proof from a key
|
|
63
|
+
* held by the user's device. Once a session is bound, only these tokens are honoured for it.
|
|
64
|
+
*/
|
|
65
|
+
dbsc?: boolean;
|
|
59
66
|
}
|
|
60
67
|
|
|
61
68
|
export interface PublicKeyEntry {
|
package/src/version.ts
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* This library's version. Sent to the auth service on every login (`uln_v` on the return URL) so Admin → Sites can show
|
|
3
|
+
* which version each site runs and open upgrade PRs for the ones behind. `src/version.test.ts` keeps it equal to
|
|
4
|
+
* `package.json`.
|
|
5
|
+
*/
|
|
6
|
+
export const LIBRARY_VERSION = "0.6.1";
|