@seamless-auth/express 0.0.2-beta.1 → 0.0.2-beta.10

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 (40) hide show
  1. package/LICENSE +79 -0
  2. package/LICENSE.md +26 -0
  3. package/README.md +99 -123
  4. package/dist/index.d.ts +190 -7
  5. package/dist/index.js +501 -5
  6. package/package.json +31 -14
  7. package/dist/createServer.d.ts +0 -48
  8. package/dist/createServer.d.ts.map +0 -1
  9. package/dist/createServer.js +0 -164
  10. package/dist/index.d.ts.map +0 -1
  11. package/dist/internal/authFetch.d.ts +0 -9
  12. package/dist/internal/authFetch.d.ts.map +0 -1
  13. package/dist/internal/authFetch.js +0 -37
  14. package/dist/internal/cookie.d.ts +0 -11
  15. package/dist/internal/cookie.d.ts.map +0 -1
  16. package/dist/internal/cookie.js +0 -28
  17. package/dist/internal/getSeamlessUser.d.ts +0 -51
  18. package/dist/internal/getSeamlessUser.d.ts.map +0 -1
  19. package/dist/internal/getSeamlessUser.js +0 -72
  20. package/dist/internal/refreshAccessToken.d.ts +0 -10
  21. package/dist/internal/refreshAccessToken.d.ts.map +0 -1
  22. package/dist/internal/refreshAccessToken.js +0 -44
  23. package/dist/internal/verifyCookieJwt.d.ts +0 -2
  24. package/dist/internal/verifyCookieJwt.d.ts.map +0 -1
  25. package/dist/internal/verifyCookieJwt.js +0 -13
  26. package/dist/internal/verifySignedAuthResponse.d.ts +0 -6
  27. package/dist/internal/verifySignedAuthResponse.d.ts.map +0 -1
  28. package/dist/internal/verifySignedAuthResponse.js +0 -23
  29. package/dist/middleware/ensureCookies.d.ts +0 -8
  30. package/dist/middleware/ensureCookies.d.ts.map +0 -1
  31. package/dist/middleware/ensureCookies.js +0 -78
  32. package/dist/middleware/requireAuth.d.ts +0 -53
  33. package/dist/middleware/requireAuth.d.ts.map +0 -1
  34. package/dist/middleware/requireAuth.js +0 -118
  35. package/dist/middleware/requireRole.d.ts +0 -49
  36. package/dist/middleware/requireRole.d.ts.map +0 -1
  37. package/dist/middleware/requireRole.js +0 -77
  38. package/dist/types.d.ts +0 -9
  39. package/dist/types.d.ts.map +0 -1
  40. package/dist/types.js +0 -1
@@ -1,164 +0,0 @@
1
- import express from "express";
2
- import cookieParser from "cookie-parser";
3
- import { setSessionCookie, clearAllCookies, clearSessionCookie, } from './internal/cookie.js';
4
- import { authFetch } from './internal/authFetch.js';
5
- import { createEnsureCookiesMiddleware } from './middleware/ensureCookies.js';
6
- import { verifySignedAuthResponse } from './internal/verifySignedAuthResponse.js';
7
- /**
8
- * Creates an Express Router that proxies all authentication traffic to a Seamless Auth server.
9
- *
10
- * This helper wires your API backend to a Seamless Auth instance running in
11
- * "server mode." It automatically forwards login, registration, WebAuthn,
12
- * logout, token refresh, and session validation routes to the auth server
13
- * and handles all cookie management required for a seamless login flow.
14
- *
15
- * ### Responsibilities
16
- * - Proxies all `/auth/*` routes to the upstream Seamless Auth server
17
- * - Manages `access`, `registration`, `pre-auth`, and `refresh` cookies
18
- * - Normalizes cookie settings for cross-domain or same-domain deployments
19
- * - Ensures authentication routes behave consistently across environments
20
- * - Provides shared middleware for auth flows
21
- *
22
- * ### Cookie Types
23
- * - **accessCookie** – long-lived session cookie for authenticated API requests
24
- * - **registrationCookie** – ephemeral cookie used during registration and OTP/WebAuthn flows
25
- * - **preAuthCookie** – short-lived cookie used during login initiation
26
- * - **refreshCookie** – opaque refresh token cookie used to rotate session tokens
27
- *
28
- * All cookie names and their domains may be customized via the `opts` parameter.
29
- *
30
- * ### Example
31
- * ```ts
32
- * app.use("/auth", createSeamlessAuthServer({
33
- * authServerUrl: "https://identifier.seamlessauth.com",
34
- * cookieDomain: "mycompany.com",
35
- * accesscookieName: "sa_access",
36
- * registrationCookieName: "sa_registration",
37
- * refreshCookieName: "sa_refresh",
38
- * }));
39
- * ```
40
- *
41
- * @param opts - Configuration options for the Seamless Auth proxy:
42
- * - `authServerUrl` — Base URL of your Seamless Auth instance (required)
43
- * - `cookieDomain` — Domain attribute applied to all auth cookies
44
- * - `accesscookieName` — Name of the session access cookie
45
- * - `registrationCookieName` — Name of the ephemeral registration cookie
46
- * - `refreshCookieName` — Name of the refresh token cookie
47
- * - `preAuthCookieName` — Name of the cookie used during login initiation
48
- *
49
- * @returns An Express `Router` preconfigured with all Seamless Auth routes.
50
- */
51
- export function createSeamlessAuthServer(opts) {
52
- const r = express.Router();
53
- r.use(express.json());
54
- r.use(cookieParser());
55
- const { authServerUrl, cookieDomain = "", accesscookieName = "seamless-access", registrationCookieName = "seamless-ephemeral", refreshCookieName = "seamless-refresh", preAuthCookieName = "seamless-ephemeral", } = opts;
56
- const proxy = (path, method = "POST") => async (req, res) => {
57
- try {
58
- const response = await authFetch(req, `${authServerUrl}/${path}`, {
59
- method,
60
- body: req.body,
61
- });
62
- res.status(response.status).json(await response.json());
63
- }
64
- catch (error) {
65
- console.error(`Failed to proxy to route. Error: ${error}`);
66
- }
67
- };
68
- r.use(createEnsureCookiesMiddleware({
69
- authServerUrl,
70
- cookieDomain,
71
- accesscookieName,
72
- registrationCookieName,
73
- refreshCookieName,
74
- preAuthCookieName,
75
- }));
76
- r.post("/webAuthn/login/start", proxy("webAuthn/login/start"));
77
- r.post("/webAuthn/login/finish", finishLogin);
78
- r.get("/webAuthn/register/start", proxy("webAuthn/register/start", "GET"));
79
- r.post("/webAuthn/register/finish", finishRegister);
80
- r.post("/otp/verify-phone-otp", proxy("otp/verify-phone-otp"));
81
- r.post("/otp/verify-email-otp", proxy("otp/verify-email-otp"));
82
- r.post("/login", login);
83
- r.post("/users/update", proxy("users/update"));
84
- r.post("/registration/register", register);
85
- r.get("/users/me", me);
86
- r.get("/logout", logout);
87
- return r;
88
- async function login(req, res) {
89
- const up = await authFetch(req, `${authServerUrl}/login`, {
90
- method: "POST",
91
- body: req.body,
92
- });
93
- const data = (await up.json());
94
- if (!up.ok)
95
- return res.status(up.status).json(data);
96
- const verified = await verifySignedAuthResponse(data.token, authServerUrl);
97
- if (!verified) {
98
- throw new Error("Invalid signed response from Auth Server");
99
- }
100
- if (verified.sub !== data.sub) {
101
- throw new Error("Signature mismatch with data payload");
102
- }
103
- setSessionCookie(res, { sub: data.sub }, cookieDomain, data.ttl, preAuthCookieName);
104
- res.status(204).end();
105
- }
106
- async function register(req, res) {
107
- const up = await authFetch(req, `${authServerUrl}/registration/register`, {
108
- method: "POST",
109
- body: req.body,
110
- });
111
- const data = (await up.json());
112
- if (!up.ok)
113
- return res.status(up.status).json(data);
114
- setSessionCookie(res, { sub: data.sub }, cookieDomain, data.ttl, registrationCookieName);
115
- res.status(200).json(data).end();
116
- }
117
- async function finishLogin(req, res) {
118
- const up = await authFetch(req, `${authServerUrl}/webAuthn/login/finish`, {
119
- method: "POST",
120
- body: req.body,
121
- });
122
- const data = (await up.json());
123
- if (!up.ok)
124
- return res.status(up.status).json(data);
125
- const verifiedAccessToken = await verifySignedAuthResponse(data.token, authServerUrl);
126
- if (!verifiedAccessToken) {
127
- throw new Error("Invalid signed response from Auth Server");
128
- }
129
- if (verifiedAccessToken.sub !== data.sub) {
130
- throw new Error("Signature mismatch with data payload");
131
- }
132
- setSessionCookie(res, { sub: data.sub, roles: data.roles }, cookieDomain, data.ttl, accesscookieName);
133
- setSessionCookie(res, { sub: data.sub, refreshToken: data.refreshToken }, req.hostname, data.refreshTtl, refreshCookieName);
134
- res.status(200).json(data).end();
135
- }
136
- async function finishRegister(req, res) {
137
- const up = await authFetch(req, `${authServerUrl}/webAuthn/register/finish`, {
138
- method: "POST",
139
- body: req.body,
140
- });
141
- const data = (await up.json());
142
- if (!up.ok)
143
- return res.status(up.status).json(data);
144
- setSessionCookie(res, { sub: data.sub, roles: data.roles }, cookieDomain, data.ttl, accesscookieName);
145
- res.status(204).end();
146
- }
147
- async function logout(req, res) {
148
- await authFetch(req, `${authServerUrl}/logout`, {
149
- method: "GET",
150
- });
151
- clearAllCookies(res, cookieDomain, accesscookieName, registrationCookieName, refreshCookieName);
152
- res.status(204).end();
153
- }
154
- async function me(req, res) {
155
- const up = await authFetch(req, `${authServerUrl}/users/me`, {
156
- method: "GET",
157
- });
158
- const data = (await up.json());
159
- clearSessionCookie(res, cookieDomain, preAuthCookieName);
160
- if (!data.user)
161
- return res.status(401).json({ error: "unauthenticated" });
162
- res.json({ user: data.user });
163
- }
164
- }
@@ -1 +0,0 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,wBAAwB,EAAE,MAAM,gBAAgB,CAAC;AAC1D,OAAO,EAAE,WAAW,EAAE,MAAM,0BAA0B,CAAC;AACvD,OAAO,EAAE,WAAW,EAAE,MAAM,0BAA0B,CAAC;AACvD,OAAO,EAAE,eAAe,EAAE,MAAM,4BAA4B,CAAA;AAC5D,YAAY,EAAE,yBAAyB,EAAE,MAAM,SAAS,CAAC;AAEzD,eAAe,wBAAwB,CAAC"}
@@ -1,9 +0,0 @@
1
- import { CookieRequest } from "../middleware/ensureCookies";
2
- export interface AuthFetchOptions {
3
- method?: "GET" | "POST" | "PUT" | "DELETE";
4
- body?: any;
5
- cookies?: string[];
6
- headers?: Record<string, string>;
7
- }
8
- export declare function authFetch(req: CookieRequest, url: string, { method, body, cookies, headers }?: AuthFetchOptions): Promise<Response>;
9
- //# sourceMappingURL=authFetch.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"authFetch.d.ts","sourceRoot":"","sources":["../../src/internal/authFetch.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,aAAa,EAAE,MAAM,6BAA6B,CAAC;AAE5D,MAAM,WAAW,gBAAgB;IAC/B,MAAM,CAAC,EAAE,KAAK,GAAG,MAAM,GAAG,KAAK,GAAG,QAAQ,CAAC;IAC3C,IAAI,CAAC,EAAE,GAAG,CAAC;IACX,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;IACnB,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CAClC;AAED,wBAAsB,SAAS,CAC7B,GAAG,EAAE,aAAa,EAClB,GAAG,EAAE,MAAM,EACX,EAAE,MAAe,EAAE,IAAI,EAAE,OAAO,EAAE,OAAY,EAAE,GAAE,gBAAqB,qBAiDxE"}
@@ -1,37 +0,0 @@
1
- import jwt from "jsonwebtoken";
2
- export async function authFetch(req, url, { method = "POST", body, cookies, headers = {} } = {}) {
3
- const serviceKey = process.env.SEAMLESS_SERVICE_TOKEN;
4
- if (!serviceKey) {
5
- throw new Error("Cannot sign service token. Missing SEAMLESS_SERVICE_TOKEN");
6
- }
7
- // -------------------------------
8
- // Issue short-lived machine token
9
- // -------------------------------
10
- const token = jwt.sign({
11
- iss: process.env.FRONTEND_URL,
12
- aud: process.env.AUTH_SERVER_URL,
13
- sub: req.cookiePayload?.sub,
14
- roles: req.cookiePayload?.roles ?? [],
15
- iat: Math.floor(Date.now() / 1000),
16
- }, serviceKey, {
17
- expiresIn: "60s", // Short-lived
18
- algorithm: "HS256", // HMAC-based
19
- });
20
- const finalHeaders = {
21
- ...(method !== "GET" && { "Content-Type": "application/json" }),
22
- ...(cookies ? { Cookie: cookies.join("; ") } : {}),
23
- Authorization: `Bearer ${token}`,
24
- ...headers,
25
- };
26
- let finalUrl = url;
27
- if (method === "GET" && body && typeof body === "object") {
28
- const qs = new URLSearchParams(body).toString();
29
- finalUrl += url.includes("?") ? `&${qs}` : `?${qs}`;
30
- }
31
- const res = await fetch(finalUrl, {
32
- method,
33
- headers: finalHeaders,
34
- ...(method !== "GET" && body ? { body: JSON.stringify(body) } : {}),
35
- });
36
- return res;
37
- }
@@ -1,11 +0,0 @@
1
- import { Response } from "express";
2
- export interface CookiePayload {
3
- sub: string;
4
- token?: string;
5
- refreshToken?: string;
6
- roles?: string[];
7
- }
8
- export declare function setSessionCookie(res: Response, payload: CookiePayload, domain?: string, ttlSeconds?: number, name?: string): void;
9
- export declare function clearSessionCookie(res: Response, domain: string, name?: string): void;
10
- export declare function clearAllCookies(res: Response, domain: string, accesscookieName: string, registrationCookieName: string, refreshCookieName: string): void;
11
- //# sourceMappingURL=cookie.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"cookie.d.ts","sourceRoot":"","sources":["../../src/internal/cookie.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AAEnC,MAAM,WAAW,aAAa;IAC5B,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,KAAK,CAAC,EAAE,MAAM,EAAE,CAAC;CAClB;AAED,wBAAgB,gBAAgB,CAC9B,GAAG,EAAE,QAAQ,EACb,OAAO,EAAE,aAAa,EACtB,MAAM,CAAC,EAAE,MAAM,EACf,UAAU,SAAM,EAChB,IAAI,SAAe,QAqBpB;AAED,wBAAgB,kBAAkB,CAChC,GAAG,EAAE,QAAQ,EACb,MAAM,EAAE,MAAM,EACd,IAAI,SAAe,QAGpB;AAED,wBAAgB,eAAe,CAC7B,GAAG,EAAE,QAAQ,EACb,MAAM,EAAE,MAAM,EACd,gBAAgB,EAAE,MAAM,EACxB,sBAAsB,EAAE,MAAM,EAC9B,iBAAiB,EAAE,MAAM,QAK1B"}
@@ -1,28 +0,0 @@
1
- import jwt from "jsonwebtoken";
2
- export function setSessionCookie(res, payload, domain, ttlSeconds = 300, name = "sa_session") {
3
- const COOKIE_SECRET = process.env.SEAMLESS_COOKIE_SIGNING_KEY;
4
- if (!COOKIE_SECRET) {
5
- console.warn("[SeamlessAuth] Missing SEAMLESS_COOKIE_SIGNING_KEY env var!");
6
- throw new Error("Missing required env SEAMLESS_COOKIE_SIGNING_KEY");
7
- }
8
- const token = jwt.sign(payload, COOKIE_SECRET, {
9
- algorithm: "HS256",
10
- expiresIn: `${ttlSeconds}s`,
11
- });
12
- res.cookie(name, token, {
13
- httpOnly: true,
14
- secure: process.env.NODE_ENV === "production",
15
- sameSite: process.env.NODE_ENV === "production" ? "none" : "lax",
16
- path: "/",
17
- domain,
18
- maxAge: ttlSeconds * 1000,
19
- });
20
- }
21
- export function clearSessionCookie(res, domain, name = "sa_session") {
22
- res.clearCookie(name, { domain, path: "/" });
23
- }
24
- export function clearAllCookies(res, domain, accesscookieName, registrationCookieName, refreshCookieName) {
25
- res.clearCookie(accesscookieName, { domain, path: "/" });
26
- res.clearCookie(registrationCookieName, { domain, path: "/" });
27
- res.clearCookie(refreshCookieName, { domain, path: "/" });
28
- }
@@ -1,51 +0,0 @@
1
- import { CookieRequest } from "../middleware/ensureCookies.js";
2
- /**
3
- * Retrieves the authenticated Seamless Auth user for a request by calling
4
- * the upstream Seamless Auth Server’s introspection endpoint.
5
- *
6
- * This helper is used when server-side code needs the fully hydrated
7
- * Seamless Auth user object (including roles, metadata, and profile fields),
8
- * not just the JWT payload extracted from cookies.
9
- *
10
- * Unlike `requireAuth`, this helper does **not** enforce authentication.
11
- * It simply returns:
12
- * - The resolved user object (if the session is valid)
13
- * - `null` if the session is invalid, expired, or missing
14
- *
15
- * ### Responsibilities
16
- * - Extracts the access cookie (or refresh cookie when needed)
17
- * - Calls the Seamless Auth Server’s `/internal/session/introspect` endpoint
18
- * - Validates whether the session is active
19
- * - Returns the user object or `null` without throwing
20
- *
21
- * ### Use Cases
22
- * - Fetching the current user in internal APIs
23
- * - Enriching backend requests with server-authoritative user information
24
- * - Logging, analytics, auditing
25
- * - Optional-auth routes that behave differently for signed-in users
26
- *
27
- * ### Example
28
- * ```ts
29
- * app.get("/portal/me", async (req, res) => {
30
- * const user = await getSeamlessUser(req, process.env.SA_AUTH_SERVER_URL);
31
- *
32
- * if (!user) {
33
- * return res.json({ user: null });
34
- * }
35
- *
36
- * return res.json({ user });
37
- * });
38
- * ```
39
- *
40
- * ### Returns
41
- * - A full Seamless Auth user object (if active)
42
- * - `null` if not authenticated or session expired
43
- *
44
- * @param req - The Express request object containing auth cookies.
45
- * @param authServerUrl - Base URL of the Seamless Auth instance to introspect against.
46
- * @param cookieName - Name of the access cookie storing the session JWT (`"seamless-access"` by default).
47
- *
48
- * @returns The authenticated user object, or `null` if the session is inactive.
49
- */
50
- export declare function getSeamlessUser<T = any>(req: CookieRequest, authServerUrl: string, cookieName?: string): Promise<T | null>;
51
- //# sourceMappingURL=getSeamlessUser.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"getSeamlessUser.d.ts","sourceRoot":"","sources":["../../src/internal/getSeamlessUser.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,aAAa,EAAE,MAAM,gCAAgC,CAAC;AAG/D;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+CG;AACH,wBAAsB,eAAe,CAAC,CAAC,GAAG,GAAG,EAC3C,GAAG,EAAE,aAAa,EAClB,aAAa,EAAE,MAAM,EACrB,UAAU,GAAE,MAA0B,GACrC,OAAO,CAAC,CAAC,GAAG,IAAI,CAAC,CAwBnB"}
@@ -1,72 +0,0 @@
1
- import { authFetch } from "./authFetch.js";
2
- import { verifyCookieJwt } from "./verifyCookieJwt.js";
3
- /**
4
- * Retrieves the authenticated Seamless Auth user for a request by calling
5
- * the upstream Seamless Auth Server’s introspection endpoint.
6
- *
7
- * This helper is used when server-side code needs the fully hydrated
8
- * Seamless Auth user object (including roles, metadata, and profile fields),
9
- * not just the JWT payload extracted from cookies.
10
- *
11
- * Unlike `requireAuth`, this helper does **not** enforce authentication.
12
- * It simply returns:
13
- * - The resolved user object (if the session is valid)
14
- * - `null` if the session is invalid, expired, or missing
15
- *
16
- * ### Responsibilities
17
- * - Extracts the access cookie (or refresh cookie when needed)
18
- * - Calls the Seamless Auth Server’s `/internal/session/introspect` endpoint
19
- * - Validates whether the session is active
20
- * - Returns the user object or `null` without throwing
21
- *
22
- * ### Use Cases
23
- * - Fetching the current user in internal APIs
24
- * - Enriching backend requests with server-authoritative user information
25
- * - Logging, analytics, auditing
26
- * - Optional-auth routes that behave differently for signed-in users
27
- *
28
- * ### Example
29
- * ```ts
30
- * app.get("/portal/me", async (req, res) => {
31
- * const user = await getSeamlessUser(req, process.env.SA_AUTH_SERVER_URL);
32
- *
33
- * if (!user) {
34
- * return res.json({ user: null });
35
- * }
36
- *
37
- * return res.json({ user });
38
- * });
39
- * ```
40
- *
41
- * ### Returns
42
- * - A full Seamless Auth user object (if active)
43
- * - `null` if not authenticated or session expired
44
- *
45
- * @param req - The Express request object containing auth cookies.
46
- * @param authServerUrl - Base URL of the Seamless Auth instance to introspect against.
47
- * @param cookieName - Name of the access cookie storing the session JWT (`"seamless-access"` by default).
48
- *
49
- * @returns The authenticated user object, or `null` if the session is inactive.
50
- */
51
- export async function getSeamlessUser(req, authServerUrl, cookieName = "seamless-access") {
52
- try {
53
- const payload = verifyCookieJwt(req.cookies[cookieName]);
54
- if (!payload) {
55
- throw new Error("Missing cookie");
56
- }
57
- req.cookiePayload = payload;
58
- const response = await authFetch(req, `${authServerUrl}/users/me`, {
59
- method: "GET",
60
- });
61
- if (!response.ok) {
62
- console.warn(`[SeamlessAuth] Auth server responded ${response.status}`);
63
- return null;
64
- }
65
- const data = (await response.json());
66
- return data.user;
67
- }
68
- catch (err) {
69
- console.error("[SeamlessAuth] getSeamlessUser failed:", err);
70
- return null;
71
- }
72
- }
@@ -1,10 +0,0 @@
1
- import { CookieRequest } from "../middleware/ensureCookies.js";
2
- export declare function refreshAccessToken(req: CookieRequest, authServerUrl: string, refreshToken: string): Promise<{
3
- sub: string;
4
- token: string;
5
- refreshToken: string;
6
- roles: string[];
7
- ttl: number;
8
- refreshTtl: number;
9
- } | null>;
10
- //# sourceMappingURL=refreshAccessToken.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"refreshAccessToken.d.ts","sourceRoot":"","sources":["../../src/internal/refreshAccessToken.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,gCAAgC,CAAC;AAG/D,wBAAsB,kBAAkB,CACtC,GAAG,EAAE,aAAa,EAClB,aAAa,EAAE,MAAM,EACrB,YAAY,EAAE,MAAM,GACnB,OAAO,CAAC;IACT,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,EAAE,MAAM,CAAC;IACd,YAAY,EAAE,MAAM,CAAC;IACrB,KAAK,EAAE,MAAM,EAAE,CAAC;IAChB,GAAG,EAAE,MAAM,CAAC;IACZ,UAAU,EAAE,MAAM,CAAC;CACpB,GAAG,IAAI,CAAC,CAwDR"}
@@ -1,44 +0,0 @@
1
- import jwt from "jsonwebtoken";
2
- export async function refreshAccessToken(req, authServerUrl, refreshToken) {
3
- try {
4
- const COOKIE_SECRET = process.env.SEAMLESS_COOKIE_SIGNING_KEY;
5
- if (!COOKIE_SECRET) {
6
- console.warn("[SeamlessAuth] SEAMLESS_COOKIE_SIGNING_KEY missing — requireAuth will always fail.");
7
- throw new Error("Missing required env SEAMLESS_COOKIE_SIGNING_KEY");
8
- }
9
- const serviceKey = process.env.SEAMLESS_SERVICE_TOKEN;
10
- if (!serviceKey) {
11
- throw new Error("Cannot sign service token. Missing SEAMLESS_SERVICE_TOKEN");
12
- }
13
- // unwrap token with local key and rewrap with service key
14
- const payload = jwt.verify(refreshToken, COOKIE_SECRET, {
15
- algorithms: ["HS256"],
16
- });
17
- const token = jwt.sign({
18
- // Minimal, safe fields
19
- iss: process.env.FRONTEND_URL,
20
- aud: process.env.AUTH_SERVER,
21
- sub: payload.sub,
22
- refreshToken: payload.refreshToken,
23
- iat: Math.floor(Date.now() / 1000),
24
- }, serviceKey, {
25
- expiresIn: "60s", // Short-lived = safer
26
- algorithm: "HS256", // HMAC-based
27
- keyid: "dev-main", // For future rotation
28
- });
29
- const response = await fetch(`${authServerUrl}/refresh`, {
30
- method: "GET",
31
- headers: { Authorization: `Bearer ${token}` },
32
- });
33
- if (!response.ok) {
34
- console.error("[SeamlessAuth] Refresh token request failed:", response.status);
35
- return null;
36
- }
37
- const data = await response.json();
38
- return data;
39
- }
40
- catch (err) {
41
- console.error("[SeamlessAuth] refreshAccessToken error:", err);
42
- return null;
43
- }
44
- }
@@ -1,2 +0,0 @@
1
- export declare function verifyCookieJwt<T = any>(token: string): T | null;
2
- //# sourceMappingURL=verifyCookieJwt.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"verifyCookieJwt.d.ts","sourceRoot":"","sources":["../../src/internal/verifyCookieJwt.ts"],"names":[],"mappings":"AAIA,wBAAgB,eAAe,CAAC,CAAC,GAAG,GAAG,EAAE,KAAK,EAAE,MAAM,GAAG,CAAC,GAAG,IAAI,CAShE"}
@@ -1,13 +0,0 @@
1
- import jwt from "jsonwebtoken";
2
- const COOKIE_SECRET = process.env.SEAMLESS_COOKIE_SIGNING_KEY;
3
- export function verifyCookieJwt(token) {
4
- try {
5
- return jwt.verify(token, COOKIE_SECRET, {
6
- algorithms: ["HS256"],
7
- });
8
- }
9
- catch (err) {
10
- console.error("[SeamlessAuth] Cookie JWT verification failed:", err);
11
- return null;
12
- }
13
- }
@@ -1,6 +0,0 @@
1
- /**
2
- * Verifies a signed response JWT from a Seamless Auth server.
3
- * Uses the Auth Server's JWKS endpoint to dynamically fetch public keys.
4
- */
5
- export declare function verifySignedAuthResponse<T = any>(token: string, authServerUrl: string): Promise<T | null>;
6
- //# sourceMappingURL=verifySignedAuthResponse.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"verifySignedAuthResponse.d.ts","sourceRoot":"","sources":["../../src/internal/verifySignedAuthResponse.ts"],"names":[],"mappings":"AAEA;;;GAGG;AACH,wBAAsB,wBAAwB,CAAC,CAAC,GAAG,GAAG,EACpD,KAAK,EAAE,MAAM,EACb,aAAa,EAAE,MAAM,GACpB,OAAO,CAAC,CAAC,GAAG,IAAI,CAAC,CAmBnB"}
@@ -1,23 +0,0 @@
1
- import { createRemoteJWKSet, jwtVerify } from "jose";
2
- /**
3
- * Verifies a signed response JWT from a Seamless Auth server.
4
- * Uses the Auth Server's JWKS endpoint to dynamically fetch public keys.
5
- */
6
- export async function verifySignedAuthResponse(token, authServerUrl) {
7
- try {
8
- // Construct JWKS URL from auth server
9
- const jwksUrl = new URL("/.well-known/jwks.json", authServerUrl).toString();
10
- // Create a remote JWKS verifier (auto-caches)
11
- const JWKS = createRemoteJWKSet(new URL(jwksUrl));
12
- // Verify signature and algorithm
13
- const { payload } = await jwtVerify(token, JWKS, {
14
- algorithms: ["RS256"],
15
- issuer: authServerUrl,
16
- });
17
- return payload;
18
- }
19
- catch (err) {
20
- console.error("[SeamlessAuth] Failed to verify signed auth response:", err);
21
- return null;
22
- }
23
- }
@@ -1,8 +0,0 @@
1
- import { NextFunction, Request, Response } from "express";
2
- import { SeamlessAuthServerOptions } from "../types";
3
- import { JwtPayload } from "jsonwebtoken";
4
- export interface CookieRequest extends Request {
5
- cookiePayload?: JwtPayload;
6
- }
7
- export declare function createEnsureCookiesMiddleware(opts: SeamlessAuthServerOptions): (req: CookieRequest, res: Response, next: NextFunction, cookieDomain?: string) => Promise<void | Response<any, Record<string, any>>>;
8
- //# sourceMappingURL=ensureCookies.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"ensureCookies.d.ts","sourceRoot":"","sources":["../../src/middleware/ensureCookies.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AAC1D,OAAO,EAAE,yBAAyB,EAAE,MAAM,UAAU,CAAC;AAGrD,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAI1C,MAAM,WAAW,aAAc,SAAQ,OAAO;IAC5C,aAAa,CAAC,EAAE,UAAU,CAAC;CAC5B;AAED,wBAAgB,6BAA6B,CAAC,IAAI,EAAE,yBAAyB,IA4BzE,KAAK,aAAa,EAClB,KAAK,QAAQ,EACb,MAAM,YAAY,EAClB,qBAAiB,wDAqFpB"}
@@ -1,78 +0,0 @@
1
- import { verifyCookieJwt } from "../internal/verifyCookieJwt.js";
2
- import { refreshAccessToken } from "../internal/refreshAccessToken";
3
- import { clearAllCookies, setSessionCookie } from "../internal/cookie";
4
- export function createEnsureCookiesMiddleware(opts) {
5
- const COOKIE_REQUIREMENTS = {
6
- "/webAuthn/login/finish": { name: opts.preAuthCookieName, required: true },
7
- "/webAuthn/login/start": { name: opts.preAuthCookieName, required: true },
8
- "/webAuthn/register/start": {
9
- name: opts.registrationCookieName,
10
- required: true,
11
- },
12
- "/webAuthn/register/finish": {
13
- name: opts.registrationCookieName,
14
- required: true,
15
- },
16
- "/otp/verify-email-otp": {
17
- name: opts.registrationCookieName,
18
- required: true,
19
- },
20
- "/otp/verify-phone-otp": {
21
- name: opts.registrationCookieName,
22
- required: true,
23
- },
24
- "/logout": { name: opts.accesscookieName, required: true },
25
- "/users/me": { name: opts.accesscookieName, required: true },
26
- };
27
- return async function ensureCookies(req, res, next, cookieDomain = "") {
28
- const match = Object.entries(COOKIE_REQUIREMENTS).find(([path]) => req.path.startsWith(path));
29
- if (!match)
30
- return next();
31
- const [, { name, required }] = match;
32
- const AUTH_SERVER_URL = process.env.AUTH_SERVER;
33
- const cookieValue = req.cookies?.[name];
34
- const refreshCookieValue = req.cookies?.[opts.refreshCookieName];
35
- if (required && !cookieValue) {
36
- if (refreshCookieValue) {
37
- console.log("[SeamlessAuth] Access token expired — attempting refresh");
38
- const refreshed = await refreshAccessToken(req, AUTH_SERVER_URL, refreshCookieValue);
39
- if (!refreshed?.token) {
40
- clearAllCookies(res, cookieDomain, name, opts.registrationCookieName, opts.refreshCookieName);
41
- res.status(401).json({ error: "Refresh failed" });
42
- return;
43
- }
44
- // Update cookie with new access token
45
- setSessionCookie(res, {
46
- sub: refreshed.sub,
47
- token: refreshed.token,
48
- roles: refreshed.roles,
49
- }, cookieDomain, refreshed.ttl, name);
50
- setSessionCookie(res, { sub: refreshed.sub, refreshToken: refreshed.refreshToken }, cookieDomain, refreshed.refreshTtl, opts.refreshCookieName);
51
- // Let requireAuth() attempt refresh
52
- req.cookiePayload = {
53
- sub: refreshed.sub,
54
- roles: refreshed.roles,
55
- };
56
- return next();
57
- }
58
- // No required cookie AND no refresh cookie
59
- return res.status(400).json({
60
- error: `Missing required cookie "${name}" for route ${req.path}`,
61
- hint: "Did you forget to call /auth/login/start first?",
62
- });
63
- }
64
- //
65
- // If cookie exists, verify it normally
66
- //
67
- if (cookieValue) {
68
- const payload = verifyCookieJwt(cookieValue);
69
- if (!payload) {
70
- return res
71
- .status(401)
72
- .json({ error: `Invalid or expired ${name} cookie` });
73
- }
74
- req.cookiePayload = payload;
75
- }
76
- next();
77
- };
78
- }
@@ -1,53 +0,0 @@
1
- import { Request, Response, NextFunction } from "express";
2
- /**
3
- * Express middleware that enforces authentication using Seamless Auth cookies.
4
- *
5
- * This guard verifies the signed access cookie generated by the Seamless Auth
6
- * server. If the access cookie is valid and unexpired, the decoded session
7
- * payload is attached to `req.user` and the request proceeds.
8
- *
9
- * If the access cookie is expired or missing *but* a valid refresh cookie is
10
- * present, the middleware automatically attempts a silent token refresh using
11
- * the Seamless Auth server. When successful, new session cookies are issued and
12
- * the request continues with an updated `req.user`.
13
- *
14
- * If neither the access token nor refresh token can validate the session,
15
- * the middleware returns a 401 Unauthorized error and prevents further
16
- * route execution.
17
- *
18
- * ### Responsibilities
19
- * - Validates the Seamless Auth session access cookie
20
- * - Attempts refresh-token–based session renewal when necessary
21
- * - Populates `req.user` with the verified session payload
22
- * - Handles all cookie rewriting during refresh flows
23
- * - Acts as a request-level authentication guard for API routes
24
- *
25
- * ### Cookie Parameters
26
- * - **cookieName** — Name of the access cookie that holds the signed session JWT
27
- * - **refreshCookieName** — Name of the refresh cookie used for silent token refresh
28
- * - **cookieDomain** — Domain or path value applied to issued cookies
29
- *
30
- * ### Example
31
- * ```ts
32
- * // Protect a route
33
- * app.get("/api/me", requireAuth(), (req, res) => {
34
- * res.json({ user: req.user });
35
- * });
36
- *
37
- * // Custom cookie names (if your Seamless Auth server uses overrides)
38
- * app.use(
39
- * "/internal",
40
- * requireAuth("sa_access", "sa_refresh", "mycompany.com"),
41
- * internalRouter
42
- * );
43
- * ```
44
- *
45
- * @param cookieName - The access cookie name. Defaults to `"seamless-access"`.
46
- * @param refreshCookieName - The refresh cookie name used for session rotation. Defaults to `"seamless-refresh"`.
47
- * @param cookieDomain - Domain or path used when rewriting cookies. Defaults to `"/"`.
48
- *
49
- * @returns An Express middleware function that enforces Seamless Auth
50
- * authentication on incoming requests.
51
- */
52
- export declare function requireAuth(cookieName?: string, refreshCookieName?: string, cookieDomain?: string): (req: Request, res: Response, next: NextFunction) => Promise<void>;
53
- //# sourceMappingURL=requireAuth.d.ts.map