@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/dist/session.js CHANGED
@@ -1,31 +1,51 @@
1
1
  /**
2
2
  * Session management.
3
3
  *
4
- * Server-side sessions in SQLite, addressed by an opaque random token held in a
5
- * Secure/HttpOnly/SameSite cookie. No JWTs: revocation has to be immediate, and
6
- * a row delete is the simplest way to get it.
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
- * Three layers of defence on the cookie value:
9
- * 1. the token is 256 bits from `crypto.randomBytes`;
10
- * 2. it is HMAC-signed with SESSION_SECRET, so forged or truncated cookies
11
- * are rejected without touching the database;
12
- * 3. only its SHA-256 is stored, so a database dump yields no usable session.
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
- * Session fixation is handled by never adopting a client-supplied identifier —
15
- * a token is minted at login and the previous one deleted.
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
- import { createHash, createHmac, timingSafeEqual } from "node:crypto";
18
- import { getCookie, setCookie } from "hono/cookie";
19
- import { clientIp, randomToken } from "@micacms/runtime";
20
+ import { createCipheriv, createDecipheriv, hkdfSync, randomBytes, timingSafeEqual } from "node:crypto";
21
+ import { setCookie } from "hono/cookie";
22
+ import { randomToken } from "@micacms/runtime";
20
23
  export const SESSION_COOKIE = "mica_session";
21
24
  export const CSRF_COOKIE = "mica_csrf";
22
25
  export const CSRF_HEADER = "x-csrf-token";
23
- /** Writing `last_seen_at` on every request would be a write per page view. */
24
- const TOUCH_INTERVAL_MS = 5 * 60 * 1000;
25
- const sha256 = (value) => createHash("sha256").update(value).digest("hex");
26
- function sign(token, secret) {
27
- return createHmac("sha256", secret).update(token).digest("base64url");
26
+ export const TRANSACTION_COOKIE = "mica_oidc";
27
+ /**
28
+ * With https, the names carry a prefix the browser enforces: `__Host-` means
29
+ * Secure, path `/` and no Domain, so a sibling subdomain cannot plant or
30
+ * overwrite the cookie. The transaction cookie is scoped to `/api/auth`, so it
31
+ * can only be `__Secure-`. Over plain HTTP the cookies are not Secure, and a
32
+ * browser would refuse a prefixed name.
33
+ */
34
+ export function cookieNames(secure) {
35
+ return secure
36
+ ? {
37
+ session: `__Host-${SESSION_COOKIE}`,
38
+ csrf: `__Host-${CSRF_COOKIE}`,
39
+ transaction: `__Secure-${TRANSACTION_COOKIE}`,
40
+ }
41
+ : { session: SESSION_COOKIE, csrf: CSRF_COOKIE, transaction: TRANSACTION_COOKIE };
28
42
  }
43
+ /** How often an active session is re-issued with a later idle expiry. */
44
+ const REFRESH_INTERVAL_MS = 5 * 60 * 1000;
45
+ /** Browsers drop cookies over 4096 bytes; refuse rather than sign in to nothing. */
46
+ const MAX_COOKIE_BYTES = 4000;
47
+ const IV_BYTES = 12;
48
+ const TAG_BYTES = 16;
29
49
  /** Constant-time string comparison that tolerates differing lengths. */
30
50
  export function safeEqual(a, b) {
31
51
  const left = Buffer.from(a);
@@ -37,64 +57,118 @@ export function safeEqual(a, b) {
37
57
  }
38
58
  return timingSafeEqual(left, right);
39
59
  }
40
- export function createSessionService(sessions, config) {
60
+ /**
61
+ * Keycloak sends `/mica-editors` where the rule says `mica-editors`; both
62
+ * spellings are the same group — see admission.ts.
63
+ */
64
+ const groupName = (group) => group.replace(/^\//, "");
65
+ /** Defensive read of a decrypted payload: it was written by us, but checked anyway. */
66
+ function parseRecord(json) {
67
+ try {
68
+ const value = JSON.parse(json);
69
+ const identity = value.identity;
70
+ if (!identity ||
71
+ typeof identity.issuer !== "string" ||
72
+ typeof identity.subject !== "string" ||
73
+ typeof value.csrfToken !== "string" ||
74
+ typeof value.createdAt !== "string" ||
75
+ typeof value.refreshedAt !== "string" ||
76
+ typeof value.expiresAt !== "string") {
77
+ return null;
78
+ }
79
+ return {
80
+ identity: {
81
+ issuer: identity.issuer,
82
+ subject: identity.subject,
83
+ email: typeof identity.email === "string" ? identity.email : "",
84
+ emailVerified: identity.emailVerified === true,
85
+ name: typeof identity.name === "string" ? identity.name : "",
86
+ groups: Array.isArray(identity.groups)
87
+ ? identity.groups.filter((group) => typeof group === "string")
88
+ : [],
89
+ },
90
+ csrfToken: value.csrfToken,
91
+ createdAt: value.createdAt,
92
+ refreshedAt: value.refreshedAt,
93
+ expiresAt: value.expiresAt,
94
+ };
95
+ }
96
+ catch {
97
+ return null;
98
+ }
99
+ }
100
+ export function createSessionService(config) {
41
101
  const ttlMs = config.SESSION_TTL_HOURS * 3600_000;
42
102
  const idleMs = config.SESSION_IDLE_HOURS * 3600_000;
103
+ const key = Buffer.from(hkdfSync("sha256", config.sessionSecret, "", "mica session cookie v1", 32));
104
+ // Only the groups a rule names are kept: an Entra token can carry hundreds,
105
+ // and a cookie holds 4 KB. A group added to the rules later counts from the
106
+ // next sign-in.
107
+ const admitted = new Set(config.admitGroups.map(groupName));
43
108
  /** Expiry is the earlier of the idle window and the absolute lifetime. */
44
109
  const nextExpiry = (createdAt, now) => new Date(Math.min(now + idleMs, createdAt + ttlMs)).toISOString();
45
- const unwrap = (cookieValue) => {
46
- if (!cookieValue)
47
- return null;
48
- const separator = cookieValue.lastIndexOf(".");
49
- if (separator <= 0)
110
+ const seal = (record) => {
111
+ const iv = randomBytes(IV_BYTES);
112
+ const cipher = createCipheriv("aes-256-gcm", key, iv);
113
+ const body = Buffer.concat([cipher.update(JSON.stringify(record), "utf8"), cipher.final()]);
114
+ const cookie = Buffer.concat([iv, body, cipher.getAuthTag()]).toString("base64url");
115
+ if (cookie.length > MAX_COOKIE_BYTES) {
116
+ throw new Error("The session does not fit in a cookie; the identity is too large.");
117
+ }
118
+ return cookie;
119
+ };
120
+ const open = (cookieValue) => {
121
+ const raw = Buffer.from(cookieValue, "base64url");
122
+ if (raw.length <= IV_BYTES + TAG_BYTES)
50
123
  return null;
51
- const token = cookieValue.slice(0, separator);
52
- const signature = cookieValue.slice(separator + 1);
53
- if (!safeEqual(signature, sign(token, config.sessionSecret)))
124
+ try {
125
+ const decipher = createDecipheriv("aes-256-gcm", key, raw.subarray(0, IV_BYTES));
126
+ decipher.setAuthTag(raw.subarray(raw.length - TAG_BYTES));
127
+ const json = Buffer.concat([
128
+ decipher.update(raw.subarray(IV_BYTES, raw.length - TAG_BYTES)),
129
+ decipher.final(),
130
+ ]).toString("utf8");
131
+ return parseRecord(json);
132
+ }
133
+ catch {
134
+ // Forged, truncated, or sealed with a secret that has since rotated.
54
135
  return null;
55
- return token;
136
+ }
56
137
  };
57
138
  return {
58
- create({ identity, ip, userAgent }) {
59
- const token = randomToken(32);
60
- const csrfToken = randomToken(32);
139
+ names: cookieNames(config.cookieSecure),
140
+ create(identity) {
61
141
  const now = Date.now();
62
- const record = sessions.create({
63
- id: sha256(token),
64
- identity,
65
- csrfToken,
142
+ const record = {
143
+ identity: {
144
+ ...identity,
145
+ groups: identity.groups.filter((group) => admitted.has(groupName(group))),
146
+ },
147
+ csrfToken: randomToken(32),
148
+ createdAt: new Date(now).toISOString(),
149
+ refreshedAt: new Date(now).toISOString(),
66
150
  expiresAt: nextExpiry(now, now),
67
- ip: ip ?? null,
68
- userAgent: userAgent ?? null,
69
- });
70
- return { record, cookie: `${token}.${sign(token, config.sessionSecret)}`, csrfToken };
151
+ };
152
+ return { record, cookie: seal(record) };
71
153
  },
72
154
  resolve(cookieValue) {
73
- const token = unwrap(cookieValue);
74
- if (!token)
155
+ if (!cookieValue)
75
156
  return null;
76
- const record = sessions.find(sha256(token));
77
- if (!record)
157
+ const record = open(cookieValue);
158
+ if (!record || Date.parse(record.expiresAt) <= Date.now())
78
159
  return null;
79
- const now = Date.now();
80
- if (Date.parse(record.expiresAt) <= now) {
81
- sessions.delete(record.id);
82
- return null;
83
- }
84
- if (now - Date.parse(record.lastSeenAt) > TOUCH_INTERVAL_MS) {
85
- const expiresAt = nextExpiry(Date.parse(record.createdAt), now);
86
- sessions.touch(record.id, new Date(now).toISOString(), expiresAt);
87
- return { ...record, lastSeenAt: new Date(now).toISOString(), expiresAt };
88
- }
89
160
  return record;
90
161
  },
91
- destroy(cookieValue) {
92
- const token = unwrap(cookieValue);
93
- if (token)
94
- sessions.delete(sha256(token));
95
- },
96
- destroyAllForIdentity(issuer, subject) {
97
- sessions.deleteForIdentity(issuer, subject);
162
+ refresh(record) {
163
+ const now = Date.now();
164
+ if (now - Date.parse(record.refreshedAt) < REFRESH_INTERVAL_MS)
165
+ return null;
166
+ const next = {
167
+ ...record,
168
+ refreshedAt: new Date(now).toISOString(),
169
+ expiresAt: nextExpiry(Date.parse(record.createdAt), now),
170
+ };
171
+ return { record: next, cookie: seal(next) };
98
172
  },
99
173
  sessionCookieOptions() {
100
174
  return {
@@ -127,9 +201,6 @@ export function createSessionService(sessions, config) {
127
201
  maxAge: 0,
128
202
  };
129
203
  },
130
- purgeExpired() {
131
- return sessions.deleteExpired();
132
- },
133
204
  };
134
205
  }
135
206
  /* ------------------------------------------------------------------ *
@@ -137,14 +208,8 @@ export function createSessionService(sessions, config) {
137
208
  * ------------------------------------------------------------------ */
138
209
  /** Issues the session cookies for a browser that has just been identified. */
139
210
  export function establishSession(c, sessions, identity) {
140
- // Session fixation: discard whatever identifier the client already holds.
141
- sessions.destroy(getCookie(c, SESSION_COOKIE));
142
- const { cookie, csrfToken } = sessions.create({
143
- identity,
144
- ip: clientIp(c),
145
- userAgent: c.req.header("user-agent") ?? null,
146
- });
147
- setCookie(c, SESSION_COOKIE, cookie, sessions.sessionCookieOptions());
148
- setCookie(c, CSRF_COOKIE, csrfToken, sessions.csrfCookieOptions());
211
+ const { record, cookie } = sessions.create(identity);
212
+ setCookie(c, sessions.names.session, cookie, sessions.sessionCookieOptions());
213
+ setCookie(c, sessions.names.csrf, record.csrfToken, sessions.csrfCookieOptions());
149
214
  }
150
215
  //# sourceMappingURL=session.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"session.js","sourceRoot":"","sources":["../src/session.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"session.js","sourceRoot":"","sources":["../src/session.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@micacms/auth-oidc",
3
- "version": "0.2.2",
3
+ "version": "0.4.0",
4
4
  "type": "module",
5
5
  "description": "The server-only OIDC gate for Mica: sign-in, sessions and admission around @micacms/runtime. Never enters a browser bundle, and never a dependency of the local editor.",
6
6
  "license": "MIT",
@@ -25,11 +25,11 @@
25
25
  "dist"
26
26
  ],
27
27
  "dependencies": {
28
- "hono": "^4.6.20",
28
+ "hono": "^4.13.7",
29
29
  "openid-client": "^6.8.5",
30
30
  "zod": "^4.3.6",
31
- "@micacms/core": "0.2.2",
32
- "@micacms/runtime": "0.2.2"
31
+ "@micacms/runtime": "0.4.0",
32
+ "@micacms/core": "0.4.0"
33
33
  },
34
34
  "devDependencies": {
35
35
  "@types/node": "^22.20.1",
@@ -1,56 +0,0 @@
1
- /**
2
- * Session repository.
3
- *
4
- * The primary key is the SHA-256 of the cookie token, never the token itself:
5
- * a leaked database backup therefore does not hand an attacker usable sessions.
6
- *
7
- * A row carries who the provider said this is, because there is no user table
8
- * to look it up in. That is the whole of Mica's identity storage.
9
- */
10
- import type { SqlDatabase } from "@micacms/runtime";
11
- /**
12
- * What the identity provider stated about the person holding this session.
13
- *
14
- * `null` where there is no provider: local mode has no user, and the row is
15
- * then a CSRF token with an expiry.
16
- */
17
- export interface SessionIdentity {
18
- issuer: string;
19
- /** Opaque and stable at that issuer. The account key, and never the email. */
20
- subject: string;
21
- email: string;
22
- /** The provider's own `email_verified`. Absent counts as false. */
23
- emailVerified: boolean;
24
- name: string;
25
- /** The groups claim as it arrived, or empty when the provider sends none. */
26
- groups: string[];
27
- }
28
- export interface SessionRecord {
29
- id: string;
30
- identity: SessionIdentity | null;
31
- csrfToken: string;
32
- createdAt: string;
33
- lastSeenAt: string;
34
- expiresAt: string;
35
- ip: string | null;
36
- userAgent: string | null;
37
- }
38
- export declare function createSessionRepository(db: SqlDatabase): {
39
- create(input: {
40
- id: string;
41
- identity: SessionIdentity | null;
42
- csrfToken: string;
43
- expiresAt: string;
44
- ip?: string | null;
45
- userAgent?: string | null;
46
- }): SessionRecord;
47
- find(id: string): SessionRecord | undefined;
48
- touch(id: string, lastSeenAt: string, expiresAt: string): void;
49
- delete(id: string): boolean;
50
- /** Every session this person holds, on every device. */
51
- deleteForIdentity(issuer: string, subject: string): number;
52
- deleteExpired(now?: string): number;
53
- count(): number;
54
- };
55
- export type SessionRepository = ReturnType<typeof createSessionRepository>;
56
- //# sourceMappingURL=sessions-repository.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"sessions-repository.d.ts","sourceRoot":"","sources":["../src/sessions-repository.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAEpD;;;;;GAKG;AACH,MAAM,WAAW,eAAe;IAC9B,MAAM,EAAE,MAAM,CAAC;IACf,8EAA8E;IAC9E,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,MAAM,CAAC;IACd,mEAAmE;IACnE,aAAa,EAAE,OAAO,CAAC;IACvB,IAAI,EAAE,MAAM,CAAC;IACb,6EAA6E;IAC7E,MAAM,EAAE,MAAM,EAAE,CAAC;CAClB;AAED,MAAM,WAAW,aAAa;IAC5B,EAAE,EAAE,MAAM,CAAC;IACX,QAAQ,EAAE,eAAe,GAAG,IAAI,CAAC;IACjC,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,MAAM,CAAC;IACnB,SAAS,EAAE,MAAM,CAAC;IAClB,EAAE,EAAE,MAAM,GAAG,IAAI,CAAC;IAClB,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;CAC1B;AA2DD,wBAAgB,uBAAuB,CAAC,EAAE,EAAE,WAAW;kBAErC;QACZ,EAAE,EAAE,MAAM,CAAC;QACX,QAAQ,EAAE,eAAe,GAAG,IAAI,CAAC;QACjC,SAAS,EAAE,MAAM,CAAC;QAClB,SAAS,EAAE,MAAM,CAAC;QAClB,EAAE,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;QACnB,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;KAC3B,GAAG,aAAa;aAwBR,MAAM,GAAG,aAAa,GAAG,SAAS;cAKjC,MAAM,cAAc,MAAM,aAAa,MAAM,GAAG,IAAI;eAQnD,MAAM,GAAG,OAAO;IAI3B,wDAAwD;8BAC9B,MAAM,WAAW,MAAM,GAAG,MAAM;iCAKX,MAAM;aAI5C,MAAM;EAIlB;AAED,MAAM,MAAM,iBAAiB,GAAG,UAAU,CAAC,OAAO,uBAAuB,CAAC,CAAC"}
@@ -1,96 +0,0 @@
1
- /**
2
- * Session repository.
3
- *
4
- * The primary key is the SHA-256 of the cookie token, never the token itself:
5
- * a leaked database backup therefore does not hand an attacker usable sessions.
6
- *
7
- * A row carries who the provider said this is, because there is no user table
8
- * to look it up in. That is the whole of Mica's identity storage.
9
- */
10
- /**
11
- * The groups column holds whatever the provider sent, serialised. It is read
12
- * back defensively — a claim that was a string, or an object, or absent, must
13
- * not throw four weeks later on a request that has nothing to do with it.
14
- */
15
- function parseGroups(value) {
16
- try {
17
- const parsed = JSON.parse(value);
18
- return Array.isArray(parsed) ? parsed.filter((entry) => typeof entry === "string") : [];
19
- }
20
- catch {
21
- return [];
22
- }
23
- }
24
- function toRecord(row) {
25
- return {
26
- id: row.id,
27
- identity: row.issuer && row.subject
28
- ? {
29
- issuer: row.issuer,
30
- subject: row.subject,
31
- email: row.email ?? "",
32
- emailVerified: row.email_verified === 1,
33
- name: row.name ?? "",
34
- groups: parseGroups(row.groups),
35
- }
36
- : null,
37
- csrfToken: row.csrf_token,
38
- createdAt: row.created_at,
39
- lastSeenAt: row.last_seen_at,
40
- expiresAt: row.expires_at,
41
- ip: row.ip,
42
- userAgent: row.user_agent,
43
- };
44
- }
45
- const COLUMNS = "id, issuer, subject, email, email_verified, name, groups, csrf_token, " +
46
- "created_at, last_seen_at, expires_at, ip, user_agent";
47
- export function createSessionRepository(db) {
48
- return {
49
- create(input) {
50
- const now = new Date().toISOString();
51
- db.run(`INSERT INTO sessions
52
- (${COLUMNS}) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, [
53
- input.id,
54
- input.identity?.issuer ?? null,
55
- input.identity?.subject ?? null,
56
- input.identity?.email ?? null,
57
- input.identity?.emailVerified ? 1 : 0,
58
- input.identity?.name ?? null,
59
- JSON.stringify(input.identity?.groups ?? []),
60
- input.csrfToken,
61
- now,
62
- now,
63
- input.expiresAt,
64
- input.ip ?? null,
65
- input.userAgent ?? null,
66
- ]);
67
- return this.find(input.id);
68
- },
69
- find(id) {
70
- const row = db.get(`SELECT ${COLUMNS} FROM sessions WHERE id = ?`, [id]);
71
- return row && toRecord(row);
72
- },
73
- touch(id, lastSeenAt, expiresAt) {
74
- db.run("UPDATE sessions SET last_seen_at = ?, expires_at = ? WHERE id = ?", [
75
- lastSeenAt,
76
- expiresAt,
77
- id,
78
- ]);
79
- },
80
- delete(id) {
81
- return db.run("DELETE FROM sessions WHERE id = ?", [id]).changes > 0;
82
- },
83
- /** Every session this person holds, on every device. */
84
- deleteForIdentity(issuer, subject) {
85
- return db.run("DELETE FROM sessions WHERE issuer = ? AND subject = ?", [issuer, subject])
86
- .changes;
87
- },
88
- deleteExpired(now = new Date().toISOString()) {
89
- return db.run("DELETE FROM sessions WHERE expires_at <= ?", [now]).changes;
90
- },
91
- count() {
92
- return db.get("SELECT COUNT(*) AS count FROM sessions")?.count ?? 0;
93
- },
94
- };
95
- }
96
- //# sourceMappingURL=sessions-repository.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"sessions-repository.js","sourceRoot":"","sources":["../src/sessions-repository.ts"],"names":[],"mappings":"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"}