@micacms/auth-oidc 0.2.2 → 0.4.0
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 +1 -1
- package/dist/admission.d.ts +5 -4
- package/dist/admission.d.ts.map +1 -1
- package/dist/admission.js +22 -15
- package/dist/admission.js.map +1 -1
- package/dist/config.d.ts +8 -2
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +41 -4
- package/dist/config.js.map +1 -1
- package/dist/gate.d.ts +4 -5
- package/dist/gate.d.ts.map +1 -1
- package/dist/gate.js +14 -13
- package/dist/gate.js.map +1 -1
- package/dist/index.d.ts +4 -9
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +7 -11
- package/dist/index.js.map +1 -1
- package/dist/oidc.d.ts +9 -2
- package/dist/oidc.d.ts.map +1 -1
- package/dist/oidc.js +22 -9
- package/dist/oidc.js.map +1 -1
- package/dist/return-to.d.ts +8 -0
- package/dist/return-to.d.ts.map +1 -0
- package/dist/return-to.js +24 -0
- package/dist/return-to.js.map +1 -0
- package/dist/routes.d.ts +2 -2
- package/dist/routes.d.ts.map +1 -1
- package/dist/routes.js +33 -23
- package/dist/routes.js.map +1 -1
- package/dist/session.d.ts +58 -26
- package/dist/session.d.ts.map +1 -1
- package/dist/session.js +137 -72
- package/dist/session.js.map +1 -1
- package/package.json +4 -4
- package/dist/sessions-repository.d.ts +0 -56
- package/dist/sessions-repository.d.ts.map +0 -1
- package/dist/sessions-repository.js +0 -96
- package/dist/sessions-repository.js.map +0 -1
package/dist/oidc.js
CHANGED
|
@@ -24,11 +24,9 @@
|
|
|
24
24
|
* the transaction lasts one redirect, and a table of them would be a table of
|
|
25
25
|
* garbage to collect.
|
|
26
26
|
*/
|
|
27
|
-
import { createHmac, randomBytes, timingSafeEqual } from "node:crypto";
|
|
27
|
+
import { createHmac, hkdfSync, randomBytes, timingSafeEqual } from "node:crypto";
|
|
28
28
|
import * as client from "openid-client";
|
|
29
29
|
import { ApiError } from "@micacms/runtime";
|
|
30
|
-
/** Carries state, PKCE verifier and nonce across the redirect to the provider. */
|
|
31
|
-
export const TRANSACTION_COOKIE = "mica_oidc";
|
|
32
30
|
/**
|
|
33
31
|
* How long a sign-in may take.
|
|
34
32
|
*
|
|
@@ -39,8 +37,10 @@ const TRANSACTION_TTL_MS = 15 * 60 * 1000;
|
|
|
39
37
|
/* ------------------------------------------------------------------ *
|
|
40
38
|
* The transaction cookie
|
|
41
39
|
* ------------------------------------------------------------------ */
|
|
40
|
+
// Its own key, derived like the session's: one secret, never used directly for two purposes (#91).
|
|
41
|
+
const transactionKey = (secret) => Buffer.from(hkdfSync("sha256", secret, "", "mica oidc transaction v1", 32));
|
|
42
42
|
function sign(value, secret) {
|
|
43
|
-
return createHmac("sha256", secret).update(value).digest("base64url");
|
|
43
|
+
return createHmac("sha256", transactionKey(secret)).update(value).digest("base64url");
|
|
44
44
|
}
|
|
45
45
|
function safeEqual(a, b) {
|
|
46
46
|
const left = Buffer.from(a);
|
|
@@ -67,6 +67,21 @@ export function openTransaction(cookie, secret) {
|
|
|
67
67
|
return null;
|
|
68
68
|
}
|
|
69
69
|
}
|
|
70
|
+
/**
|
|
71
|
+
* The address and its `email_verified` flag, always from the same answer, so
|
|
72
|
+
* that a flag cannot vouch for an address it was not sent with (#91). Userinfo
|
|
73
|
+
* fills in an address the ID token lacks, or confirms the token's own address.
|
|
74
|
+
*/
|
|
75
|
+
export function emailFrom(claims, info) {
|
|
76
|
+
const tokenEmail = typeof claims["email"] === "string" ? claims["email"] : "";
|
|
77
|
+
const infoEmail = typeof info?.["email"] === "string" ? info["email"] : "";
|
|
78
|
+
const infoVerified = info?.["email_verified"] === true;
|
|
79
|
+
if (tokenEmail) {
|
|
80
|
+
const confirmed = infoEmail === tokenEmail && infoVerified;
|
|
81
|
+
return { email: tokenEmail, emailVerified: claims["email_verified"] === true || confirmed };
|
|
82
|
+
}
|
|
83
|
+
return { email: infoEmail, emailVerified: Boolean(infoEmail) && infoVerified };
|
|
84
|
+
}
|
|
70
85
|
export function createOidcService(deps) {
|
|
71
86
|
const { config, logger } = deps;
|
|
72
87
|
const redirectUri = new URL("/api/auth/callback", config.MICA_PUBLIC_URL).toString();
|
|
@@ -148,16 +163,14 @@ export function createOidcService(deps) {
|
|
|
148
163
|
// keep them at the userinfo endpoint, and some only release groups
|
|
149
164
|
// there. Ask only when something is missing.
|
|
150
165
|
const groupsClaim = config.MICA_OIDC_GROUPS_CLAIM;
|
|
151
|
-
let
|
|
166
|
+
let { email, emailVerified } = emailFrom(claims);
|
|
152
167
|
let name = typeof claims["name"] === "string" ? claims["name"] : "";
|
|
153
|
-
let emailVerified = claims["email_verified"] === true;
|
|
154
168
|
let groups = readGroups(claims[groupsClaim]);
|
|
155
|
-
if (!email || !name || groups.length === 0) {
|
|
169
|
+
if (!email || !emailVerified || !name || groups.length === 0) {
|
|
156
170
|
try {
|
|
157
171
|
const info = await client.fetchUserInfo(as, tokens.access_token, claims.sub);
|
|
158
|
-
email
|
|
172
|
+
({ email, emailVerified } = emailFrom(claims, info));
|
|
159
173
|
name ||= typeof info.name === "string" ? info.name : "";
|
|
160
|
-
emailVerified ||= info["email_verified"] === true;
|
|
161
174
|
if (groups.length === 0)
|
|
162
175
|
groups = readGroups(info[groupsClaim]);
|
|
163
176
|
}
|
package/dist/oidc.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"oidc.js","sourceRoot":"","sources":["../src/oidc.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AAEH,OAAO,EAAE,UAAU,EAAE,WAAW,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;
|
|
1
|
+
{"version":3,"file":"oidc.js","sourceRoot":"","sources":["../src/oidc.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Where the browser goes after sign-in: a page of this editor, or the start page.
|
|
3
|
+
*
|
|
4
|
+
* Parsed the way a browser parses a `Location`, so that `/\evil.example`, a
|
|
5
|
+
* tab or a line break cannot turn a path into another host (#87).
|
|
6
|
+
*/
|
|
7
|
+
export declare function safeReturnTo(raw: string | undefined, publicUrl: string): string;
|
|
8
|
+
//# sourceMappingURL=return-to.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"return-to.d.ts","sourceRoot":"","sources":["../src/return-to.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,wBAAgB,YAAY,CAAC,GAAG,EAAE,MAAM,GAAG,SAAS,EAAE,SAAS,EAAE,MAAM,GAAG,MAAM,CAc/E"}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Where the browser goes after sign-in: a page of this editor, or the start page.
|
|
3
|
+
*
|
|
4
|
+
* Parsed the way a browser parses a `Location`, so that `/\evil.example`, a
|
|
5
|
+
* tab or a line break cannot turn a path into another host (#87).
|
|
6
|
+
*/
|
|
7
|
+
export function safeReturnTo(raw, publicUrl) {
|
|
8
|
+
const fallback = "/admin/";
|
|
9
|
+
if (!raw || !raw.startsWith("/"))
|
|
10
|
+
return fallback;
|
|
11
|
+
let url;
|
|
12
|
+
try {
|
|
13
|
+
url = new URL(raw, publicUrl);
|
|
14
|
+
}
|
|
15
|
+
catch {
|
|
16
|
+
return fallback;
|
|
17
|
+
}
|
|
18
|
+
if (url.origin !== new URL(publicUrl).origin)
|
|
19
|
+
return fallback;
|
|
20
|
+
if (url.pathname !== "/admin" && !url.pathname.startsWith("/admin/"))
|
|
21
|
+
return fallback;
|
|
22
|
+
return `${url.pathname}${url.search}${url.hash}`;
|
|
23
|
+
}
|
|
24
|
+
//# sourceMappingURL=return-to.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"return-to.js","sourceRoot":"","sources":["../src/return-to.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,MAAM,UAAU,YAAY,CAAC,GAAuB,EAAE,SAAiB;IACrE,MAAM,QAAQ,GAAG,SAAS,CAAC;IAC3B,IAAI,CAAC,GAAG,IAAI,CAAC,GAAG,CAAC,UAAU,CAAC,GAAG,CAAC;QAAE,OAAO,QAAQ,CAAC;IAElD,IAAI,GAAQ,CAAC;IACb,IAAI,CAAC;QACH,GAAG,GAAG,IAAI,GAAG,CAAC,GAAG,EAAE,SAAS,CAAC,CAAC;IAChC,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,QAAQ,CAAC;IAClB,CAAC;IAED,IAAI,GAAG,CAAC,MAAM,KAAK,IAAI,GAAG,CAAC,SAAS,CAAC,CAAC,MAAM;QAAE,OAAO,QAAQ,CAAC;IAC9D,IAAI,GAAG,CAAC,QAAQ,KAAK,QAAQ,IAAI,CAAC,GAAG,CAAC,QAAQ,CAAC,UAAU,CAAC,SAAS,CAAC;QAAE,OAAO,QAAQ,CAAC;IACtF,OAAO,GAAG,GAAG,CAAC,QAAQ,GAAG,GAAG,CAAC,MAAM,GAAG,GAAG,CAAC,IAAI,EAAE,CAAC;AACnD,CAAC"}
|
package/dist/routes.d.ts
CHANGED
|
@@ -15,12 +15,12 @@ import { type Env, type RateLimiter } from "@micacms/runtime";
|
|
|
15
15
|
import type { AdmissionPolicy } from "./admission.js";
|
|
16
16
|
import type { AuthConfig } from "./config.js";
|
|
17
17
|
import { type SessionService } from "./session.js";
|
|
18
|
-
import type { SessionRepository } from "./sessions-repository.js";
|
|
19
18
|
import { type OidcService } from "./oidc.js";
|
|
19
|
+
/** Why a sign-in came back to the login view. The SPA has the words for each. */
|
|
20
|
+
export type SignInRefusal = "rate_limited" | "expired" | "failed" | "not_admitted" | "too_large";
|
|
20
21
|
export interface AuthRoutesDeps {
|
|
21
22
|
config: AuthConfig;
|
|
22
23
|
sessions: SessionService;
|
|
23
|
-
sessionsRepo: SessionRepository;
|
|
24
24
|
oidc: OidcService;
|
|
25
25
|
admission: AdmissionPolicy;
|
|
26
26
|
loginLimiter: RateLimiter;
|
package/dist/routes.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"routes.d.ts","sourceRoot":"","sources":["../src/routes.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAG5B,OAAO,EAAsB,KAAK,GAAG,EAAE,KAAK,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAElF,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,gBAAgB,CAAC;AACtD,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;
|
|
1
|
+
{"version":3,"file":"routes.d.ts","sourceRoot":"","sources":["../src/routes.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAG5B,OAAO,EAAsB,KAAK,GAAG,EAAE,KAAK,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAElF,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,gBAAgB,CAAC;AACtD,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAG9C,OAAO,EAGL,KAAK,cAAc,EACpB,MAAM,cAAc,CAAC;AACtB,OAAO,EAIL,KAAK,WAAW,EACjB,MAAM,WAAW,CAAC;AAWnB,iFAAiF;AACjF,MAAM,MAAM,aAAa,GAAG,cAAc,GAAG,SAAS,GAAG,QAAQ,GAAG,cAAc,GAAG,WAAW,CAAC;AAEjG,MAAM,WAAW,cAAc;IAC7B,MAAM,EAAE,UAAU,CAAC;IACnB,QAAQ,EAAE,cAAc,CAAC;IACzB,IAAI,EAAE,WAAW,CAAC;IAClB,SAAS,EAAE,eAAe,CAAC;IAC3B,YAAY,EAAE,WAAW,CAAC;CAC3B;AAED,wBAAgB,UAAU,CAAC,IAAI,EAAE,cAAc,GAAG,IAAI,CAAC,GAAG,CAAC,CA6I1D"}
|
package/dist/routes.js
CHANGED
|
@@ -14,14 +14,9 @@ import { Hono } from "hono";
|
|
|
14
14
|
import { deleteCookie, getCookie, setCookie } from "hono/cookie";
|
|
15
15
|
import { ApiError, clientIp } from "@micacms/runtime";
|
|
16
16
|
import { requireAccess, sessionOf } from "./gate.js";
|
|
17
|
-
import {
|
|
18
|
-
import {
|
|
19
|
-
|
|
20
|
-
function safeReturnTo(raw) {
|
|
21
|
-
if (!raw || !raw.startsWith("/") || raw.startsWith("//"))
|
|
22
|
-
return "/admin/";
|
|
23
|
-
return raw;
|
|
24
|
-
}
|
|
17
|
+
import { safeReturnTo } from "./return-to.js";
|
|
18
|
+
import { establishSession, } from "./session.js";
|
|
19
|
+
import { openTransaction, sealTransaction, } from "./oidc.js";
|
|
25
20
|
const identityOf = (claims) => ({
|
|
26
21
|
issuer: claims.issuer,
|
|
27
22
|
subject: claims.subject,
|
|
@@ -46,15 +41,15 @@ export function authRoutes(deps) {
|
|
|
46
41
|
}));
|
|
47
42
|
/** Starts the flow. A redirect, not a form post. */
|
|
48
43
|
app.get("/login", async (c) => {
|
|
49
|
-
const returnTo = safeReturnTo(c.req.query("returnTo"));
|
|
50
|
-
// Rate limited on the way out
|
|
51
|
-
// cheap for a client and costs Mica a discovery request.
|
|
44
|
+
const returnTo = safeReturnTo(c.req.query("returnTo"), config.MICA_PUBLIC_URL);
|
|
45
|
+
// Rate limited on the way out and on the way back (in /callback): starting a
|
|
46
|
+
// flow is cheap for a client and costs Mica a discovery request.
|
|
52
47
|
const limit = deps.loginLimiter.check(`oidc:${clientIp(c)}`);
|
|
53
48
|
if (!limit.allowed) {
|
|
54
49
|
throw new ApiError("rate_limited", `Too many sign-in attempts. Try again in ${Math.ceil(limit.retryAfter / 60)} minutes.`, { headers: { "retry-after": String(limit.retryAfter) } });
|
|
55
50
|
}
|
|
56
51
|
const { url, transaction } = await oidc.begin(returnTo);
|
|
57
|
-
setCookie(c,
|
|
52
|
+
setCookie(c, sessions.names.transaction, sealTransaction(transaction, config.sessionSecret), {
|
|
58
53
|
path: "/api/auth",
|
|
59
54
|
httpOnly: true,
|
|
60
55
|
secure: config.cookieSecure,
|
|
@@ -75,20 +70,30 @@ export function authRoutes(deps) {
|
|
|
75
70
|
*/
|
|
76
71
|
app.get("/callback", async (c) => {
|
|
77
72
|
const logger = c.get("logger");
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
73
|
+
// A code, not a sentence: the URL lands in browser history and proxy logs,
|
|
74
|
+
// so it must not carry the person's address (#150). The login view words it.
|
|
75
|
+
const refuse = (code) => c.redirect(`/admin/login?error=${code}`);
|
|
76
|
+
// A replayed transaction cookie would call the token endpoint each time.
|
|
77
|
+
// Its own key, so that one sign-in does not count twice against the start.
|
|
78
|
+
if (!deps.loginLimiter.check(`oidc-callback:${clientIp(c)}`).allowed) {
|
|
79
|
+
return refuse("rate_limited");
|
|
80
|
+
}
|
|
81
|
+
const sealed = getCookie(c, sessions.names.transaction);
|
|
82
|
+
deleteCookie(c, sessions.names.transaction, { path: "/api/auth", secure: config.cookieSecure });
|
|
81
83
|
const transaction = sealed ? openTransaction(sealed, config.sessionSecret) : null;
|
|
82
84
|
if (!transaction) {
|
|
83
85
|
// Expired, tampered with, or a callback nobody asked for.
|
|
84
|
-
return refuse("
|
|
86
|
+
return refuse("expired");
|
|
85
87
|
}
|
|
86
88
|
let claims;
|
|
87
89
|
try {
|
|
88
90
|
claims = await oidc.complete(new URL(c.req.url), transaction);
|
|
89
91
|
}
|
|
90
92
|
catch (error) {
|
|
91
|
-
|
|
93
|
+
logger.warn("Sign-in could not be completed", {
|
|
94
|
+
error: error instanceof Error ? error.message : String(error),
|
|
95
|
+
});
|
|
96
|
+
return refuse("failed");
|
|
92
97
|
}
|
|
93
98
|
const identity = identityOf(claims);
|
|
94
99
|
/**
|
|
@@ -105,17 +110,22 @@ export function authRoutes(deps) {
|
|
|
105
110
|
subject: identity.subject,
|
|
106
111
|
reason: verdict.reason,
|
|
107
112
|
});
|
|
108
|
-
return refuse(
|
|
109
|
-
|
|
113
|
+
return refuse("not_admitted");
|
|
114
|
+
}
|
|
115
|
+
try {
|
|
116
|
+
establishSession(c, sessions, identity);
|
|
117
|
+
}
|
|
118
|
+
catch (error) {
|
|
119
|
+
logger.error("Could not start a session", { subject: identity.subject, error });
|
|
120
|
+
return refuse("too_large");
|
|
110
121
|
}
|
|
111
|
-
establishSession(c, sessions, identity);
|
|
112
122
|
logger.info("Signed in", { subject: identity.subject, issuer: identity.issuer });
|
|
113
123
|
return c.redirect(transaction.returnTo);
|
|
114
124
|
});
|
|
125
|
+
// There is no server-side session to end; clearing the cookies is sign-out.
|
|
115
126
|
app.post("/logout", async (c) => {
|
|
116
|
-
|
|
117
|
-
deleteCookie(c,
|
|
118
|
-
deleteCookie(c, CSRF_COOKIE, sessions.clearedCookieOptions());
|
|
127
|
+
deleteCookie(c, sessions.names.session, sessions.clearedCookieOptions());
|
|
128
|
+
deleteCookie(c, sessions.names.csrf, sessions.clearedCookieOptions());
|
|
119
129
|
// Offered, not followed: ending the provider's session is the provider's
|
|
120
130
|
// decision to make, and the SPA sends the browser there if it is there.
|
|
121
131
|
return c.json({ ok: true, endSession: await oidc.endSessionUrl() });
|
package/dist/routes.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"routes.js","sourceRoot":"","sources":["../src/routes.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAC5B,OAAO,EAAE,YAAY,EAAE,SAAS,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AAEjE,OAAO,EAAE,QAAQ,EAAE,QAAQ,EAA8B,MAAM,kBAAkB,CAAC;AAIlF,OAAO,EAAE,aAAa,EAAE,SAAS,EAAiB,MAAM,WAAW,CAAC;AACpE,OAAO,
|
|
1
|
+
{"version":3,"file":"routes.js","sourceRoot":"","sources":["../src/routes.ts"],"names":[],"mappings":"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"}
|
package/dist/session.d.ts
CHANGED
|
@@ -1,48 +1,80 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Session management.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
4
|
+
* The session is the cookie: who the provider said this is, a CSRF token and
|
|
5
|
+
* an expiry, encrypted with a key derived from SESSION_SECRET. There is no
|
|
6
|
+
* session table, so Mica stores nothing about people at all.
|
|
7
7
|
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
8
|
+
* - AES-256-GCM, so the cookie can be neither read nor altered. Encrypted and
|
|
9
|
+
* not only signed, because it carries an email address and groups.
|
|
10
|
+
* - A fresh random IV per cookie, and a new cookie at every sign-in, so a
|
|
11
|
+
* client-supplied value is never adopted (session fixation).
|
|
12
|
+
* - The idle window slides: an active session is re-issued every few
|
|
13
|
+
* minutes with a later expiry, capped by the absolute lifetime.
|
|
13
14
|
*
|
|
14
|
-
*
|
|
15
|
-
* a
|
|
15
|
+
* What this gives up is server-side revocation of one cookie. Sign-out clears
|
|
16
|
+
* it in the browser; a copy taken before then stays valid until it expires.
|
|
17
|
+
* Rotating SESSION_SECRET ends every session at once. The admission rules are
|
|
18
|
+
* still re-read on every request — see gate.ts and docs/decisions.md.
|
|
16
19
|
*/
|
|
17
20
|
import type { Context } from "hono";
|
|
18
21
|
import { type Env } from "@micacms/runtime";
|
|
19
22
|
import type { AuthConfig } from "./config.js";
|
|
20
|
-
import type { SessionIdentity, SessionRecord, SessionRepository } from "./sessions-repository.js";
|
|
21
23
|
export declare const SESSION_COOKIE = "mica_session";
|
|
22
24
|
export declare const CSRF_COOKIE = "mica_csrf";
|
|
23
25
|
export declare const CSRF_HEADER = "x-csrf-token";
|
|
26
|
+
export declare const TRANSACTION_COOKIE = "mica_oidc";
|
|
27
|
+
export interface CookieNames {
|
|
28
|
+
session: string;
|
|
29
|
+
csrf: string;
|
|
30
|
+
transaction: string;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* With https, the names carry a prefix the browser enforces: `__Host-` means
|
|
34
|
+
* Secure, path `/` and no Domain, so a sibling subdomain cannot plant or
|
|
35
|
+
* overwrite the cookie. The transaction cookie is scoped to `/api/auth`, so it
|
|
36
|
+
* can only be `__Secure-`. Over plain HTTP the cookies are not Secure, and a
|
|
37
|
+
* browser would refuse a prefixed name.
|
|
38
|
+
*/
|
|
39
|
+
export declare function cookieNames(secure: boolean): CookieNames;
|
|
40
|
+
/** What the identity provider stated about the person holding this session. */
|
|
41
|
+
export interface SessionIdentity {
|
|
42
|
+
issuer: string;
|
|
43
|
+
/** Opaque and stable at that issuer. */
|
|
44
|
+
subject: string;
|
|
45
|
+
email: string;
|
|
46
|
+
/** The provider's own `email_verified`. Absent counts as false. */
|
|
47
|
+
emailVerified: boolean;
|
|
48
|
+
name: string;
|
|
49
|
+
/** The groups claim, reduced at sign-in to the groups the rules name. */
|
|
50
|
+
groups: string[];
|
|
51
|
+
}
|
|
52
|
+
export interface SessionRecord {
|
|
53
|
+
identity: SessionIdentity;
|
|
54
|
+
csrfToken: string;
|
|
55
|
+
createdAt: string;
|
|
56
|
+
/** When the session was last issued; drives the sliding idle window. */
|
|
57
|
+
refreshedAt: string;
|
|
58
|
+
expiresAt: string;
|
|
59
|
+
}
|
|
24
60
|
export interface SessionService {
|
|
25
|
-
/**
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
create(input: {
|
|
30
|
-
identity: SessionIdentity | null;
|
|
31
|
-
ip?: string | null;
|
|
32
|
-
userAgent?: string | null;
|
|
33
|
-
}): {
|
|
61
|
+
/** The cookie names, which depend on whether the cookies are Secure. */
|
|
62
|
+
readonly names: CookieNames;
|
|
63
|
+
/** Seals a new session. Throws when the identity cannot fit in a cookie. */
|
|
64
|
+
create(identity: SessionIdentity): {
|
|
34
65
|
record: SessionRecord;
|
|
35
66
|
cookie: string;
|
|
36
|
-
csrfToken: string;
|
|
37
67
|
};
|
|
68
|
+
/** The live session in a cookie, or null for a missing, forged or expired one. */
|
|
38
69
|
resolve(cookieValue: string | undefined): SessionRecord | null;
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
70
|
+
/** A later-expiring copy of an active session, or null while it is fresh. */
|
|
71
|
+
refresh(record: SessionRecord): {
|
|
72
|
+
record: SessionRecord;
|
|
73
|
+
cookie: string;
|
|
74
|
+
} | null;
|
|
42
75
|
sessionCookieOptions(): CookieOptions;
|
|
43
76
|
csrfCookieOptions(): CookieOptions;
|
|
44
77
|
clearedCookieOptions(): CookieOptions;
|
|
45
|
-
purgeExpired(): number;
|
|
46
78
|
}
|
|
47
79
|
export interface CookieOptions {
|
|
48
80
|
path: string;
|
|
@@ -53,7 +85,7 @@ export interface CookieOptions {
|
|
|
53
85
|
}
|
|
54
86
|
/** Constant-time string comparison that tolerates differing lengths. */
|
|
55
87
|
export declare function safeEqual(a: string, b: string): boolean;
|
|
56
|
-
export declare function createSessionService(
|
|
88
|
+
export declare function createSessionService(config: AuthConfig): SessionService;
|
|
57
89
|
/** Issues the session cookies for a browser that has just been identified. */
|
|
58
90
|
export declare function establishSession(c: Context<Env>, sessions: SessionService, identity: SessionIdentity): void;
|
|
59
91
|
//# sourceMappingURL=session.d.ts.map
|
package/dist/session.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"session.d.ts","sourceRoot":"","sources":["../src/session.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"session.d.ts","sourceRoot":"","sources":["../src/session.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAIH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,MAAM,CAAC;AAGpC,OAAO,EAAe,KAAK,GAAG,EAAE,MAAM,kBAAkB,CAAC;AAEzD,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAE9C,eAAO,MAAM,cAAc,iBAAiB,CAAC;AAC7C,eAAO,MAAM,WAAW,cAAc,CAAC;AACvC,eAAO,MAAM,WAAW,iBAAiB,CAAC;AAC1C,eAAO,MAAM,kBAAkB,cAAc,CAAC;AAE9C,MAAM,WAAW,WAAW;IAC1B,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;CACrB;AAED;;;;;;GAMG;AACH,wBAAgB,WAAW,CAAC,MAAM,EAAE,OAAO,GAAG,WAAW,CAQxD;AAWD,+EAA+E;AAC/E,MAAM,WAAW,eAAe;IAC9B,MAAM,EAAE,MAAM,CAAC;IACf,wCAAwC;IACxC,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,MAAM,CAAC;IACd,mEAAmE;IACnE,aAAa,EAAE,OAAO,CAAC;IACvB,IAAI,EAAE,MAAM,CAAC;IACb,yEAAyE;IACzE,MAAM,EAAE,MAAM,EAAE,CAAC;CAClB;AAED,MAAM,WAAW,aAAa;IAC5B,QAAQ,EAAE,eAAe,CAAC;IAC1B,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;IAClB,wEAAwE;IACxE,WAAW,EAAE,MAAM,CAAC;IACpB,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,cAAc;IAC7B,wEAAwE;IACxE,QAAQ,CAAC,KAAK,EAAE,WAAW,CAAC;IAC5B,4EAA4E;IAC5E,MAAM,CAAC,QAAQ,EAAE,eAAe,GAAG;QAAE,MAAM,EAAE,aAAa,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,CAAC;IAC7E,kFAAkF;IAClF,OAAO,CAAC,WAAW,EAAE,MAAM,GAAG,SAAS,GAAG,aAAa,GAAG,IAAI,CAAC;IAC/D,6EAA6E;IAC7E,OAAO,CAAC,MAAM,EAAE,aAAa,GAAG;QAAE,MAAM,EAAE,aAAa,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI,CAAC;IACjF,oBAAoB,IAAI,aAAa,CAAC;IACtC,iBAAiB,IAAI,aAAa,CAAC;IACnC,oBAAoB,IAAI,aAAa,CAAC;CACvC;AAED,MAAM,WAAW,aAAa;IAC5B,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,OAAO,CAAC;IAClB,MAAM,EAAE,OAAO,CAAC;IAChB,QAAQ,EAAE,KAAK,GAAG,QAAQ,GAAG,MAAM,CAAC;IACpC,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,wEAAwE;AACxE,wBAAgB,SAAS,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,GAAG,OAAO,CASvD;AA6CD,wBAAgB,oBAAoB,CAAC,MAAM,EAAE,UAAU,GAAG,cAAc,CAsHvE;AAMD,8EAA8E;AAC9E,wBAAgB,gBAAgB,CAC9B,CAAC,EAAE,OAAO,CAAC,GAAG,CAAC,EACf,QAAQ,EAAE,cAAc,EACxB,QAAQ,EAAE,eAAe,GACxB,IAAI,CAKN"}
|