@jxsuite/auth 0.4.2 → 0.5.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.
Files changed (2) hide show
  1. package/package.json +6 -6
  2. package/src/config.ts +92 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jxsuite/auth",
3
- "version": "0.4.2",
3
+ "version": "0.5.0",
4
4
  "description": "Better Auth sessions, sign-in flows, and table permissions for Jx sites over /_jx/auth",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -38,13 +38,13 @@
38
38
  "upgrade": "bunx npm-check-updates -u && bun install"
39
39
  },
40
40
  "dependencies": {
41
- "@jxsuite/connector": "^0.4.2",
42
- "@jxsuite/schema": "^1.5.0",
43
- "better-auth": "^1.6.25",
44
- "kysely": "^0.29.4"
41
+ "@jxsuite/connector": "^0.5.0",
42
+ "@jxsuite/schema": "^1.6.0",
43
+ "better-auth": "^1.7.1",
44
+ "kysely": "^0.29.5"
45
45
  },
46
46
  "devDependencies": {
47
- "@happy-dom/global-registrator": "^20.11.1",
47
+ "@happy-dom/global-registrator": "^20.11.2",
48
48
  "ajv": "^8.20.0"
49
49
  },
50
50
  "jx": "./jx-extension.json"
package/src/config.ts CHANGED
@@ -16,6 +16,87 @@ export const AUTH_BASE_PATH = "/_jx/auth";
16
16
  /** Default NAME of the env var holding the Better Auth signing secret. */
17
17
  export const DEFAULT_SECRET_ENV = "BETTER_AUTH_SECRET";
18
18
 
19
+ /**
20
+ * How long a session lives, and how often activity extends it (seconds).
21
+ *
22
+ * These are Better Auth's own defaults, restated so the lifetime is **this project's** decision and
23
+ * shows up in a diff when it changes. A session lifetime that lives only in a dependency's default
24
+ * is one nobody chose.
25
+ */
26
+ export const SESSION_EXPIRES_SECONDS = 60 * 60 * 24 * 7;
27
+ export const SESSION_UPDATE_SECONDS = 60 * 60 * 24;
28
+
29
+ /** Auth-route rate limit: requests per IP per window (seconds). */
30
+ export const RATE_LIMIT_MAX = 100;
31
+ export const RATE_LIMIT_WINDOW_SECONDS = 10;
32
+
33
+ /** Better Auth's own cookie-name prefix. Changing it signs every existing session out. */
34
+ const COOKIE_PREFIX = "better-auth";
35
+
36
+ /** The cookies `createCookieGetter` mints, all of them same-host and `Path=/`. */
37
+ const COOKIE_IDS = ["session_token", "session_data", "dont_remember", "account_data"];
38
+
39
+ /**
40
+ * Whether this deployment's cookies must carry `Secure` — and therefore a name prefix.
41
+ *
42
+ * **Default secure, and only step down when the origin is positively known to be plain HTTP.**
43
+ * Better Auth's own rule falls back to `NODE_ENV === "production"`, which is _false on Cloudflare
44
+ * Workers_ — where `NODE_ENV` is simply unset — so the library's default silently produced
45
+ * unprefixed, non-`Secure` session cookies on exactly the platform Jx deploys to. The one host that
46
+ * genuinely serves auth over plain HTTP is the local dev server, and it pins `BETTER_AUTH_URL` to
47
+ * its own origin so this reads `http:` there rather than guessing.
48
+ *
49
+ * @param {Env} env
50
+ * @returns {boolean}
51
+ */
52
+ export function cookiesAreSecure(env: Env): boolean {
53
+ const baseURL = env["BETTER_AUTH_URL"];
54
+ if (typeof baseURL === "string" && baseURL !== "") {
55
+ return !baseURL.startsWith("http://");
56
+ }
57
+ return true;
58
+ }
59
+
60
+ /**
61
+ * The `advanced` block: cookie names and attributes (RFC 6265bis §4.1.3).
62
+ *
63
+ * **`__Host-`, not `__Secure-`.** `__Secure-` only promises the cookie was set with `Secure`; a
64
+ * page on a sibling origin — `evil.example.com` against `example.com` — can still overwrite it by
65
+ * setting a `Domain`. `__Host-` forbids `Domain`, pins `Path=/`, and is therefore the prefix that
66
+ * actually stops a session-fixation write. Better Auth's defaults already set `Path=/` and no
67
+ * `Domain`, so the stronger prefix costs nothing.
68
+ *
69
+ * Getting there means turning **off** `useSecureCookies` and putting `Secure` back by hand: the
70
+ * library prepends `__Secure-` to whatever name it is given, so leaving it on would mint
71
+ * `__Secure-__Host-…`, a name no browser accepts. `secure: true` in `defaultCookieAttributes`
72
+ * restores the attribute the flag would have set. `tests/config.test.ts` asserts the resulting
73
+ * names, so a library upgrade that changes this is loud rather than silent.
74
+ *
75
+ * **`Partitioned` is not set, and must not default on.** CHIPS is for cookies in a _third-party_
76
+ * context; Jx auth cookies are first-party to the site that serves them. Setting it would force
77
+ * `SameSite=None; Secure` and partition the session per top-level site — signing a visitor out
78
+ * whenever the embedding page changed.
79
+ *
80
+ * @param {boolean} secure
81
+ * @returns {Record<string, unknown>}
82
+ */
83
+ function advancedCookieOptions(secure: boolean): Record<string, unknown> {
84
+ if (!secure) {
85
+ // Plain-HTTP dev origin: a prefixed cookie without `Secure` is rejected outright.
86
+ return { cookiePrefix: COOKIE_PREFIX, useSecureCookies: false };
87
+ }
88
+ const cookies: Record<string, { name: string }> = {};
89
+ for (const id of COOKIE_IDS) {
90
+ cookies[id] = { name: `__Host-${COOKIE_PREFIX}.${id}` };
91
+ }
92
+ return {
93
+ cookiePrefix: COOKIE_PREFIX,
94
+ cookies,
95
+ defaultCookieAttributes: { secure: true },
96
+ useSecureCookies: false,
97
+ };
98
+ }
99
+
19
100
  /** One social provider entry: env-var NAMES for the OAuth credentials, never values. */
20
101
  export interface AuthProviderDef {
21
102
  clientIdEnv?: string;
@@ -119,8 +200,19 @@ export function buildAuthOptions(
119
200
  // Without BETTER_AUTH_URL Better Auth derives the origin from each incoming request, which
120
201
  // Is correct for same-origin /_jx/auth mounts; setting it pins callbacks for OAuth flows.
121
202
  ...(typeof baseURL === "string" && baseURL !== "" ? { baseURL } : {}),
203
+ advanced: advancedCookieOptions(cookiesAreSecure(env)),
122
204
  basePath: AUTH_BASE_PATH,
123
205
  emailAndPassword: { enabled: section.methods?.emailPassword ?? true },
206
+ /*
207
+ * Rate limiting is ON everywhere, not just in production. Better Auth gates its own default on
208
+ * `NODE_ENV === "production"`, which is unset on Workers — so the limit was off in the one
209
+ * place it matters. It is also on in dev deliberately: 100 requests per 10 seconds is far
210
+ * beyond anything a person does by hand, and a limit that is disabled while you develop is a
211
+ * limit nobody ever finds out is broken. Storage is Better Auth's in-memory default, which on
212
+ * a serverless runtime is per-isolate — a speed bump for credential stuffing, not a wall.
213
+ */
214
+ rateLimit: { enabled: true, max: RATE_LIMIT_MAX, window: RATE_LIMIT_WINDOW_SECONDS },
215
+ session: { expiresIn: SESSION_EXPIRES_SECONDS, updateAge: SESSION_UPDATE_SECONDS },
124
216
  ...(Object.keys(socialProviders).length > 0 ? { socialProviders } : {}),
125
217
  ...(roles.length > 0
126
218
  ? {