@seamless-auth/express 0.0.2-beta.4 → 0.0.2-beta.6

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/index.js CHANGED
@@ -4,12 +4,13 @@ import cookieParser from "cookie-parser";
4
4
 
5
5
  // src/internal/cookie.ts
6
6
  import jwt from "jsonwebtoken";
7
- function setSessionCookie(res, payload, ttlSeconds = 300, name = "sa_session") {
7
+ function setSessionCookie(res, payload, domain, ttlSeconds = 300, name = "sa_session") {
8
8
  const COOKIE_SECRET2 = process.env.SEAMLESS_COOKIE_SIGNING_KEY;
9
9
  if (!COOKIE_SECRET2) {
10
10
  console.warn("[SeamlessAuth] Missing SEAMLESS_COOKIE_SIGNING_KEY env var!");
11
11
  throw new Error("Missing required env SEAMLESS_COOKIE_SIGNING_KEY");
12
12
  }
13
+ console.debug("[SeamlessAuth] Domain check... ", domain);
13
14
  const token = jwt.sign(payload, COOKIE_SECRET2, {
14
15
  algorithm: "HS256",
15
16
  expiresIn: `${ttlSeconds}s`
@@ -19,16 +20,18 @@ function setSessionCookie(res, payload, ttlSeconds = 300, name = "sa_session") {
19
20
  secure: process.env.NODE_ENV === "production",
20
21
  sameSite: process.env.NODE_ENV === "production" ? "none" : "lax",
21
22
  path: "/",
23
+ domain,
22
24
  maxAge: ttlSeconds * 1e3
23
25
  });
24
26
  }
25
27
  function clearSessionCookie(res, domain, name = "sa_session") {
26
28
  res.clearCookie(name, { domain, path: "/" });
27
29
  }
28
- function clearAllCookies(res, accesscookieName, registrationCookieName, refreshCookieName) {
29
- res.clearCookie(accesscookieName, { path: "/" });
30
- res.clearCookie(registrationCookieName, { path: "/" });
31
- res.clearCookie(refreshCookieName, { path: "/" });
30
+ function clearAllCookies(res, domain, accesscookieName, registrationCookieName, refreshCookieName) {
31
+ console.debug("[SeamlessAuth] clearing cookies");
32
+ res.clearCookie(accesscookieName, { domain, path: "/" });
33
+ res.clearCookie(registrationCookieName, { domain, path: "/" });
34
+ res.clearCookie(refreshCookieName, { domain, path: "/" });
32
35
  }
33
36
 
34
37
  // src/internal/authFetch.ts
@@ -171,7 +174,8 @@ function createEnsureCookiesMiddleware(opts) {
171
174
  "/logout": { name: opts.accesscookieName, required: true },
172
175
  "/users/me": { name: opts.accesscookieName, required: true }
173
176
  };
174
- return async function ensureCookies(req, res, next, cookieDomain = "") {
177
+ return async function ensureCookies(req, res, next, cookieDomain = opts.cookieDomain || "") {
178
+ console.debug("[SeamlessAuth] Ensuring cookies domain...", cookieDomain);
175
179
  const match = Object.entries(COOKIE_REQUIREMENTS).find(
176
180
  ([path]) => req.path.startsWith(path)
177
181
  );
@@ -191,6 +195,7 @@ function createEnsureCookiesMiddleware(opts) {
191
195
  if (!refreshed?.token) {
192
196
  clearAllCookies(
193
197
  res,
198
+ cookieDomain,
194
199
  name,
195
200
  opts.registrationCookieName,
196
201
  opts.refreshCookieName
@@ -205,12 +210,14 @@ function createEnsureCookiesMiddleware(opts) {
205
210
  token: refreshed.token,
206
211
  roles: refreshed.roles
207
212
  },
213
+ cookieDomain,
208
214
  refreshed.ttl,
209
215
  name
210
216
  );
211
217
  setSessionCookie(
212
218
  res,
213
219
  { sub: refreshed.sub, refreshToken: refreshed.refreshToken },
220
+ cookieDomain,
214
221
  refreshed.refreshTtl,
215
222
  opts.refreshCookieName
216
223
  );
@@ -260,6 +267,7 @@ function createSeamlessAuthServer(opts) {
260
267
  r.use(cookieParser());
261
268
  const {
262
269
  authServerUrl,
270
+ cookieDomain = "",
263
271
  accesscookieName = "seamless-access",
264
272
  registrationCookieName = "seamless-ephemeral",
265
273
  refreshCookieName = "seamless-refresh",
@@ -279,6 +287,7 @@ function createSeamlessAuthServer(opts) {
279
287
  r.use(
280
288
  createEnsureCookiesMiddleware({
281
289
  authServerUrl,
290
+ cookieDomain,
282
291
  accesscookieName,
283
292
  registrationCookieName,
284
293
  refreshCookieName,
@@ -293,6 +302,8 @@ function createSeamlessAuthServer(opts) {
293
302
  r.post("/otp/verify-email-otp", proxy("otp/verify-email-otp"));
294
303
  r.post("/login", login);
295
304
  r.post("/users/update", proxy("users/update"));
305
+ r.post("/users/credentials", proxy("users/credentials"));
306
+ r.delete("/users/credentials", proxy("users/credentials"));
296
307
  r.post("/registration/register", register);
297
308
  r.get("/users/me", me);
298
309
  r.get("/logout", logout);
@@ -311,7 +322,13 @@ function createSeamlessAuthServer(opts) {
311
322
  if (verified.sub !== data.sub) {
312
323
  throw new Error("Signature mismatch with data payload");
313
324
  }
314
- setSessionCookie(res, { sub: data.sub }, data.ttl, preAuthCookieName);
325
+ setSessionCookie(
326
+ res,
327
+ { sub: data.sub },
328
+ cookieDomain,
329
+ data.ttl,
330
+ preAuthCookieName
331
+ );
315
332
  res.status(204).end();
316
333
  }
317
334
  async function register(req, res) {
@@ -321,7 +338,13 @@ function createSeamlessAuthServer(opts) {
321
338
  });
322
339
  const data = await up.json();
323
340
  if (!up.ok) return res.status(up.status).json(data);
324
- setSessionCookie(res, { sub: data.sub }, data.ttl, registrationCookieName);
341
+ setSessionCookie(
342
+ res,
343
+ { sub: data.sub },
344
+ cookieDomain,
345
+ data.ttl,
346
+ registrationCookieName
347
+ );
325
348
  res.status(200).json(data).end();
326
349
  }
327
350
  async function finishLogin(req, res) {
@@ -344,12 +367,14 @@ function createSeamlessAuthServer(opts) {
344
367
  setSessionCookie(
345
368
  res,
346
369
  { sub: data.sub, roles: data.roles },
370
+ cookieDomain,
347
371
  data.ttl,
348
372
  accesscookieName
349
373
  );
350
374
  setSessionCookie(
351
375
  res,
352
376
  { sub: data.sub, refreshToken: data.refreshToken },
377
+ cookieDomain,
353
378
  data.refreshTtl,
354
379
  refreshCookieName
355
380
  );
@@ -369,6 +394,7 @@ function createSeamlessAuthServer(opts) {
369
394
  setSessionCookie(
370
395
  res,
371
396
  { sub: data.sub, roles: data.roles },
397
+ cookieDomain,
372
398
  data.ttl,
373
399
  accesscookieName
374
400
  );
@@ -380,6 +406,7 @@ function createSeamlessAuthServer(opts) {
380
406
  });
381
407
  clearAllCookies(
382
408
  res,
409
+ cookieDomain,
383
410
  accesscookieName,
384
411
  registrationCookieName,
385
412
  refreshCookieName
@@ -391,9 +418,9 @@ function createSeamlessAuthServer(opts) {
391
418
  method: "GET"
392
419
  });
393
420
  const data = await up.json();
394
- clearSessionCookie(res, preAuthCookieName);
421
+ clearSessionCookie(res, cookieDomain, preAuthCookieName);
395
422
  if (!data.user) return res.status(401).json({ error: "unauthenticated" });
396
- res.json({ user: data.user });
423
+ res.json({ user: data.user, credentials: data.credentials });
397
424
  }
398
425
  }
399
426
 
@@ -452,12 +479,14 @@ function requireAuth(cookieName = "seamless-access", refreshCookieName = "seamle
452
479
  token: refreshed.token,
453
480
  roles: refreshed.roles
454
481
  },
482
+ cookieDomain,
455
483
  refreshed.ttl,
456
484
  cookieName
457
485
  );
458
486
  setSessionCookie(
459
487
  res,
460
488
  { sub: refreshed.sub, refreshToken: refreshed.refreshToken },
489
+ req.hostname,
461
490
  refreshed.refreshTtl,
462
491
  refreshCookieName
463
492
  );
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@seamless-auth/express",
3
- "version": "0.0.2-beta.4",
3
+ "version": "0.0.2-beta.6",
4
4
  "description": "Express adapter for Seamless Auth passwordless authentication",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -8,7 +8,7 @@
8
8
  "types": "./dist/index.d.ts",
9
9
  "author": "Fells Code LLC",
10
10
  "scripts": {
11
- "build": "tsup src/index.ts --format esm --out-dir dist --dts --splitting",
11
+ "build": "tsup src/index.ts --format esm --out-dir dist --splitting",
12
12
  "dev": "tsc --watch"
13
13
  },
14
14
  "repository": {
package/dist/index.d.ts DELETED
@@ -1,211 +0,0 @@
1
- import { Router, Request, Response, NextFunction, RequestHandler } from 'express';
2
- import { JwtPayload } from 'jsonwebtoken';
3
-
4
- interface SeamlessAuthServerOptions {
5
- authServerUrl: string;
6
- cookieDomain?: string;
7
- accesscookieName?: string;
8
- registrationCookieName?: string;
9
- refreshCookieName?: string;
10
- preAuthCookieName?: string;
11
- }
12
-
13
- /**
14
- * Creates an Express Router that proxies all authentication traffic to a Seamless Auth server.
15
- *
16
- * This helper wires your API backend to a Seamless Auth instance running in
17
- * "server mode." It automatically forwards login, registration, WebAuthn,
18
- * logout, token refresh, and session validation routes to the auth server
19
- * and handles all cookie management required for a seamless login flow.
20
- *
21
- * ### Responsibilities
22
- * - Proxies all `/auth/*` routes to the upstream Seamless Auth server
23
- * - Manages `access`, `registration`, `pre-auth`, and `refresh` cookies
24
- * - Normalizes cookie settings for cross-domain or same-domain deployments
25
- * - Ensures authentication routes behave consistently across environments
26
- * - Provides shared middleware for auth flows
27
- *
28
- * ### Cookie Types
29
- * - **accessCookie** – long-lived session cookie for authenticated API requests
30
- * - **registrationCookie** – ephemeral cookie used during registration and OTP/WebAuthn flows
31
- * - **preAuthCookie** – short-lived cookie used during login initiation
32
- * - **refreshCookie** – opaque refresh token cookie used to rotate session tokens
33
- *
34
- * All cookie names and their domains may be customized via the `opts` parameter.
35
- *
36
- * ### Example
37
- * ```ts
38
- * app.use("/auth", createSeamlessAuthServer({
39
- * authServerUrl: "https://identifier.seamlessauth.com",
40
- * accesscookieName: "sa_access",
41
- * registrationCookieName: "sa_registration",
42
- * refreshCookieName: "sa_refresh",
43
- * }));
44
- * ```
45
- *
46
- * @param opts - Configuration options for the Seamless Auth proxy:
47
- * - `authServerUrl` — Base URL of your Seamless Auth instance (required)
48
- * - `accesscookieName` — Name of the session access cookie
49
- * - `registrationCookieName` — Name of the ephemeral registration cookie
50
- * - `refreshCookieName` — Name of the refresh token cookie
51
- * - `preAuthCookieName` — Name of the cookie used during login initiation
52
- *
53
- * @returns An Express `Router` preconfigured with all Seamless Auth routes.
54
- */
55
- declare function createSeamlessAuthServer(opts: SeamlessAuthServerOptions): Router;
56
-
57
- /**
58
- * Express middleware that enforces authentication using Seamless Auth cookies.
59
- *
60
- * This guard verifies the signed access cookie generated by the Seamless Auth
61
- * server. If the access cookie is valid and unexpired, the decoded session
62
- * payload is attached to `req.user` and the request proceeds.
63
- *
64
- * If the access cookie is expired or missing *but* a valid refresh cookie is
65
- * present, the middleware automatically attempts a silent token refresh using
66
- * the Seamless Auth server. When successful, new session cookies are issued and
67
- * the request continues with an updated `req.user`.
68
- *
69
- * If neither the access token nor refresh token can validate the session,
70
- * the middleware returns a 401 Unauthorized error and prevents further
71
- * route execution.
72
- *
73
- * ### Responsibilities
74
- * - Validates the Seamless Auth session access cookie
75
- * - Attempts refresh-token–based session renewal when necessary
76
- * - Populates `req.user` with the verified session payload
77
- * - Handles all cookie rewriting during refresh flows
78
- * - Acts as a request-level authentication guard for API routes
79
- *
80
- * ### Cookie Parameters
81
- * - **cookieName** — Name of the access cookie that holds the signed session JWT
82
- * - **refreshCookieName** — Name of the refresh cookie used for silent token refresh
83
- * - **cookieDomain** — Domain or path value applied to issued cookies
84
- *
85
- * ### Example
86
- * ```ts
87
- * // Protect a route
88
- * app.get("/api/me", requireAuth(), (req, res) => {
89
- * res.json({ user: req.user });
90
- * });
91
- *
92
- * // Custom cookie names (if your Seamless Auth server uses overrides)
93
- * app.use(
94
- * "/internal",
95
- * requireAuth("sa_access", "sa_refresh", "mycompany.com"),
96
- * internalRouter
97
- * );
98
- * ```
99
- *
100
- * @param cookieName - The access cookie name. Defaults to `"seamless-access"`.
101
- * @param refreshCookieName - The refresh cookie name used for session rotation. Defaults to `"seamless-refresh"`.
102
- * @param cookieDomain - Domain or path used when rewriting cookies. Defaults to `"/"`.
103
- *
104
- * @returns An Express middleware function that enforces Seamless Auth
105
- * authentication on incoming requests.
106
- */
107
- declare function requireAuth(cookieName?: string, refreshCookieName?: string, cookieDomain?: string): (req: Request, res: Response, next: NextFunction) => Promise<void>;
108
-
109
- /**
110
- * Express middleware that enforces role-based authorization for Seamless Auth sessions.
111
- *
112
- * This guard assumes that `requireAuth()` has already validated the request
113
- * and populated `req.user` with the decoded Seamless Auth session payload.
114
- * It then checks whether the user’s roles include the required role (or any
115
- * of several, when an array is provided).
116
- *
117
- * If the user possesses the required authorization, the request proceeds.
118
- * Otherwise, the middleware responds with a 403 Forbidden error.
119
- *
120
- * ### Responsibilities
121
- * - Validates that `req.user` is present (enforced upstream by `requireAuth`)
122
- * - Ensures the authenticated user includes the specified role(s)
123
- * - Blocks unauthorized access with a standardized JSON 403 response
124
- *
125
- * ### Parameters
126
- * - **requiredRole** — A role (string) or list of roles the user must have.
127
- * If an array is provided, *any* matching role grants access.
128
- * - **cookieName** — Optional name of the access cookie to inspect.
129
- * Defaults to `"seamless-access"`, but typically not needed because
130
- * `requireAuth` is expected to run first.
131
- *
132
- * ### Example
133
- * ```ts
134
- * // Require a single role
135
- * app.get("/admin/users",
136
- * requireAuth(),
137
- * requireRole("admin"),
138
- * (req, res) => {
139
- * res.send("Welcome admin!");
140
- * }
141
- * );
142
- *
143
- * // Allow any of multiple roles
144
- * app.post("/settings",
145
- * requireAuth(),
146
- * requireRole(["admin", "supervisor"]),
147
- * updateSettingsHandler
148
- * );
149
- * ```
150
- *
151
- * @param requiredRole - A role or list of roles required to access the route.
152
- * @param cookieName - Optional access cookie name (defaults to `seamless-access`).
153
- * @returns An Express middleware function enforcing role-based access control.
154
- */
155
- declare function requireRole(role: string, cookieName?: string): RequestHandler;
156
-
157
- interface CookieRequest extends Request {
158
- cookiePayload?: JwtPayload;
159
- }
160
-
161
- /**
162
- * Retrieves the authenticated Seamless Auth user for a request by calling
163
- * the upstream Seamless Auth Server’s introspection endpoint.
164
- *
165
- * This helper is used when server-side code needs the fully hydrated
166
- * Seamless Auth user object (including roles, metadata, and profile fields),
167
- * not just the JWT payload extracted from cookies.
168
- *
169
- * Unlike `requireAuth`, this helper does **not** enforce authentication.
170
- * It simply returns:
171
- * - The resolved user object (if the session is valid)
172
- * - `null` if the session is invalid, expired, or missing
173
- *
174
- * ### Responsibilities
175
- * - Extracts the access cookie (or refresh cookie when needed)
176
- * - Calls the Seamless Auth Server’s `/internal/session/introspect` endpoint
177
- * - Validates whether the session is active
178
- * - Returns the user object or `null` without throwing
179
- *
180
- * ### Use Cases
181
- * - Fetching the current user in internal APIs
182
- * - Enriching backend requests with server-authoritative user information
183
- * - Logging, analytics, auditing
184
- * - Optional-auth routes that behave differently for signed-in users
185
- *
186
- * ### Example
187
- * ```ts
188
- * app.get("/portal/me", async (req, res) => {
189
- * const user = await getSeamlessUser(req, process.env.SA_AUTH_SERVER_URL);
190
- *
191
- * if (!user) {
192
- * return res.json({ user: null });
193
- * }
194
- *
195
- * return res.json({ user });
196
- * });
197
- * ```
198
- *
199
- * ### Returns
200
- * - A full Seamless Auth user object (if active)
201
- * - `null` if not authenticated or session expired
202
- *
203
- * @param req - The Express request object containing auth cookies.
204
- * @param authServerUrl - Base URL of the Seamless Auth instance to introspect against.
205
- * @param cookieName - Name of the access cookie storing the session JWT (`"seamless-access"` by default).
206
- *
207
- * @returns The authenticated user object, or `null` if the session is inactive.
208
- */
209
- declare function getSeamlessUser<T = any>(req: CookieRequest, authServerUrl: string, cookieName?: string): Promise<T | null>;
210
-
211
- export { type SeamlessAuthServerOptions, createSeamlessAuthServer as default, getSeamlessUser, requireAuth, requireRole };