@seamless-auth/express 0.0.1-beta.3 → 0.0.2-beta.1
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 +12 -11
- package/dist/createServer.d.ts +45 -0
- package/dist/createServer.d.ts.map +1 -0
- package/dist/createServer.js +44 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/internal/authFetch.d.ts +2 -1
- package/dist/internal/authFetch.d.ts.map +1 -0
- package/dist/internal/authFetch.js +3 -5
- package/dist/internal/cookie.d.ts +1 -0
- package/dist/internal/cookie.d.ts.map +1 -0
- package/dist/internal/cookie.js +5 -4
- package/dist/internal/getSeamlessUser.d.ts +46 -5
- package/dist/internal/getSeamlessUser.d.ts.map +1 -0
- package/dist/internal/getSeamlessUser.js +46 -6
- package/dist/internal/refreshAccessToken.d.ts +1 -0
- package/dist/internal/refreshAccessToken.d.ts.map +1 -0
- package/dist/internal/refreshAccessToken.js +6 -5
- package/dist/internal/verifyCookieJwt.d.ts +1 -0
- package/dist/internal/verifyCookieJwt.d.ts.map +1 -0
- package/dist/internal/verifySignedAuthResponse.d.ts +1 -0
- package/dist/internal/verifySignedAuthResponse.d.ts.map +1 -0
- package/dist/middleware/ensureCookies.d.ts +1 -0
- package/dist/middleware/ensureCookies.d.ts.map +1 -0
- package/dist/middleware/ensureCookies.js +2 -12
- package/dist/middleware/requireAuth.d.ts +49 -4
- package/dist/middleware/requireAuth.d.ts.map +1 -0
- package/dist/middleware/requireAuth.js +58 -10
- package/dist/middleware/requireRole.d.ts +44 -3
- package/dist/middleware/requireRole.d.ts.map +1 -0
- package/dist/middleware/requireRole.js +49 -8
- package/dist/types.d.ts +1 -0
- package/dist/types.d.ts.map +1 -0
- package/package.json +2 -1
package/README.md
CHANGED
|
@@ -96,13 +96,12 @@ Everything happens securely between your API and a private Seamless Auth Server.
|
|
|
96
96
|
|
|
97
97
|
## Environment Variables
|
|
98
98
|
|
|
99
|
-
| Variable | Description
|
|
100
|
-
| ----------------------------- |
|
|
101
|
-
| `AUTH_SERVER_URL` | Base URL of your Seamless Auth Server
|
|
102
|
-
| `SEAMLESS_COOKIE_SIGNING_KEY` | Secret key for signing JWT cookies
|
|
103
|
-
| `SEAMLESS_SERVICE_TOKEN` | Private key for API → Auth Server JWTs
|
|
104
|
-
| `
|
|
105
|
-
| `COOKIE_DOMAIN` | Domain for cookies | `.client.com` |
|
|
99
|
+
| Variable | Description | Example |
|
|
100
|
+
| ----------------------------- | --------------------------------------------- | ------------------------------ |
|
|
101
|
+
| `AUTH_SERVER_URL` | Base URL of your Seamless Auth Server | `https://auth.client.com` |
|
|
102
|
+
| `SEAMLESS_COOKIE_SIGNING_KEY` | Secret key for signing JWT cookies | `base64:...` |
|
|
103
|
+
| `SEAMLESS_SERVICE_TOKEN` | Private key for API → Auth Server JWTs | `Obtained via the auth portal` |
|
|
104
|
+
| `FRONTEND_URL` | URL of your website or https://localhost:5001 | `https://mySite.com` |
|
|
106
105
|
|
|
107
106
|
---
|
|
108
107
|
|
|
@@ -188,7 +187,7 @@ User shape
|
|
|
188
187
|
→ sets short-lived pre-auth cookie.
|
|
189
188
|
|
|
190
189
|
2. **Frontend** → `/auth/webauthn/finish`
|
|
191
|
-
→ API proxies, validates, sets access cookie (`
|
|
190
|
+
→ API proxies, validates, sets access cookie (`seamless-access`).
|
|
192
191
|
|
|
193
192
|
3. **Subsequent API calls** → `/api/...`
|
|
194
193
|
→ `requireAuth()` verifies cookie and attaches user.
|
|
@@ -206,9 +205,10 @@ In order to develop with your Seamless Auth server instance, you will need to ha
|
|
|
206
205
|
Example env:
|
|
207
206
|
|
|
208
207
|
```bash
|
|
209
|
-
AUTH_SERVER_URL=
|
|
210
|
-
|
|
211
|
-
|
|
208
|
+
AUTH_SERVER_URL=https://<identifier>.seamlessauth.com # Found in the portal
|
|
209
|
+
SEAMLESS_SERVICE_TOKEN=32byte-secret # Created and rotated in the portal
|
|
210
|
+
FRONTEND_URL=https://yourSite.com # Must match the value you have for Frontend Domain in the portal (Can be localhost:5001 when application is in demo mode)
|
|
211
|
+
SEAMLESS_COOKIE_SIGNING_KEY=local-secret-key # A key you make for signing your API's distributed cookies
|
|
212
212
|
```
|
|
213
213
|
|
|
214
214
|
---
|
|
@@ -266,3 +266,4 @@ app.get("/api/test", requireAuth(), (req, res) => res.json({ ok: true }));
|
|
|
266
266
|
|
|
267
267
|
MIT © 2025 Fells Code LLC
|
|
268
268
|
Part of the **Seamless Auth** ecosystem.
|
|
269
|
+
https://seamlessauth.com
|
package/dist/createServer.d.ts
CHANGED
|
@@ -1,3 +1,48 @@
|
|
|
1
1
|
import { Router } from "express";
|
|
2
2
|
import type { SeamlessAuthServerOptions } from "./types";
|
|
3
|
+
/**
|
|
4
|
+
* Creates an Express Router that proxies all authentication traffic to a Seamless Auth server.
|
|
5
|
+
*
|
|
6
|
+
* This helper wires your API backend to a Seamless Auth instance running in
|
|
7
|
+
* "server mode." It automatically forwards login, registration, WebAuthn,
|
|
8
|
+
* logout, token refresh, and session validation routes to the auth server
|
|
9
|
+
* and handles all cookie management required for a seamless login flow.
|
|
10
|
+
*
|
|
11
|
+
* ### Responsibilities
|
|
12
|
+
* - Proxies all `/auth/*` routes to the upstream Seamless Auth server
|
|
13
|
+
* - Manages `access`, `registration`, `pre-auth`, and `refresh` cookies
|
|
14
|
+
* - Normalizes cookie settings for cross-domain or same-domain deployments
|
|
15
|
+
* - Ensures authentication routes behave consistently across environments
|
|
16
|
+
* - Provides shared middleware for auth flows
|
|
17
|
+
*
|
|
18
|
+
* ### Cookie Types
|
|
19
|
+
* - **accessCookie** – long-lived session cookie for authenticated API requests
|
|
20
|
+
* - **registrationCookie** – ephemeral cookie used during registration and OTP/WebAuthn flows
|
|
21
|
+
* - **preAuthCookie** – short-lived cookie used during login initiation
|
|
22
|
+
* - **refreshCookie** – opaque refresh token cookie used to rotate session tokens
|
|
23
|
+
*
|
|
24
|
+
* All cookie names and their domains may be customized via the `opts` parameter.
|
|
25
|
+
*
|
|
26
|
+
* ### Example
|
|
27
|
+
* ```ts
|
|
28
|
+
* app.use("/auth", createSeamlessAuthServer({
|
|
29
|
+
* authServerUrl: "https://identifier.seamlessauth.com",
|
|
30
|
+
* cookieDomain: "mycompany.com",
|
|
31
|
+
* accesscookieName: "sa_access",
|
|
32
|
+
* registrationCookieName: "sa_registration",
|
|
33
|
+
* refreshCookieName: "sa_refresh",
|
|
34
|
+
* }));
|
|
35
|
+
* ```
|
|
36
|
+
*
|
|
37
|
+
* @param opts - Configuration options for the Seamless Auth proxy:
|
|
38
|
+
* - `authServerUrl` — Base URL of your Seamless Auth instance (required)
|
|
39
|
+
* - `cookieDomain` — Domain attribute applied to all auth cookies
|
|
40
|
+
* - `accesscookieName` — Name of the session access cookie
|
|
41
|
+
* - `registrationCookieName` — Name of the ephemeral registration cookie
|
|
42
|
+
* - `refreshCookieName` — Name of the refresh token cookie
|
|
43
|
+
* - `preAuthCookieName` — Name of the cookie used during login initiation
|
|
44
|
+
*
|
|
45
|
+
* @returns An Express `Router` preconfigured with all Seamless Auth routes.
|
|
46
|
+
*/
|
|
3
47
|
export declare function createSeamlessAuthServer(opts: SeamlessAuthServerOptions): Router;
|
|
48
|
+
//# sourceMappingURL=createServer.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"createServer.d.ts","sourceRoot":"","sources":["../src/createServer.ts"],"names":[],"mappings":"AAAA,OAAgB,EAAqB,MAAM,EAAE,MAAM,SAAS,CAAC;AAQ7D,OAAO,KAAK,EAAE,yBAAyB,EAAE,MAAM,SAAS,CAAC;AAIzD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2CG;AACH,wBAAgB,wBAAwB,CACtC,IAAI,EAAE,yBAAyB,GAC9B,MAAM,CA0LR"}
|
package/dist/createServer.js
CHANGED
|
@@ -4,6 +4,50 @@ import { setSessionCookie, clearAllCookies, clearSessionCookie, } from './intern
|
|
|
4
4
|
import { authFetch } from './internal/authFetch.js';
|
|
5
5
|
import { createEnsureCookiesMiddleware } from './middleware/ensureCookies.js';
|
|
6
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
|
+
*/
|
|
7
51
|
export function createSeamlessAuthServer(opts) {
|
|
8
52
|
const r = express.Router();
|
|
9
53
|
r.use(express.json());
|
package/dist/index.d.ts
CHANGED
|
@@ -0,0 +1 @@
|
|
|
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,8 +1,9 @@
|
|
|
1
1
|
import { CookieRequest } from "../middleware/ensureCookies";
|
|
2
2
|
export interface AuthFetchOptions {
|
|
3
|
-
method?: "GET" | "POST" | "PUT" | "
|
|
3
|
+
method?: "GET" | "POST" | "PUT" | "DELETE";
|
|
4
4
|
body?: any;
|
|
5
5
|
cookies?: string[];
|
|
6
6
|
headers?: Record<string, string>;
|
|
7
7
|
}
|
|
8
8
|
export declare function authFetch(req: CookieRequest, url: string, { method, body, cookies, headers }?: AuthFetchOptions): Promise<Response>;
|
|
9
|
+
//# sourceMappingURL=authFetch.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
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,6 +1,6 @@
|
|
|
1
1
|
import jwt from "jsonwebtoken";
|
|
2
|
-
const serviceKey = process.env.SEAMLESS_SERVICE_TOKEN;
|
|
3
2
|
export async function authFetch(req, url, { method = "POST", body, cookies, headers = {} } = {}) {
|
|
3
|
+
const serviceKey = process.env.SEAMLESS_SERVICE_TOKEN;
|
|
4
4
|
if (!serviceKey) {
|
|
5
5
|
throw new Error("Cannot sign service token. Missing SEAMLESS_SERVICE_TOKEN");
|
|
6
6
|
}
|
|
@@ -8,16 +8,14 @@ export async function authFetch(req, url, { method = "POST", body, cookies, head
|
|
|
8
8
|
// Issue short-lived machine token
|
|
9
9
|
// -------------------------------
|
|
10
10
|
const token = jwt.sign({
|
|
11
|
-
// Minimal, safe fields
|
|
12
11
|
iss: process.env.FRONTEND_URL,
|
|
13
|
-
aud: process.env.
|
|
12
|
+
aud: process.env.AUTH_SERVER_URL,
|
|
14
13
|
sub: req.cookiePayload?.sub,
|
|
15
14
|
roles: req.cookiePayload?.roles ?? [],
|
|
16
15
|
iat: Math.floor(Date.now() / 1000),
|
|
17
16
|
}, serviceKey, {
|
|
18
|
-
expiresIn: "60s", // Short-lived
|
|
17
|
+
expiresIn: "60s", // Short-lived
|
|
19
18
|
algorithm: "HS256", // HMAC-based
|
|
20
|
-
keyid: "dev-main", // For future rotation
|
|
21
19
|
});
|
|
22
20
|
const finalHeaders = {
|
|
23
21
|
...(method !== "GET" && { "Content-Type": "application/json" }),
|
|
@@ -8,3 +8,4 @@ export interface CookiePayload {
|
|
|
8
8
|
export declare function setSessionCookie(res: Response, payload: CookiePayload, domain?: string, ttlSeconds?: number, name?: string): void;
|
|
9
9
|
export declare function clearSessionCookie(res: Response, domain: string, name?: string): void;
|
|
10
10
|
export declare function clearAllCookies(res: Response, domain: string, accesscookieName: string, registrationCookieName: string, refreshCookieName: string): void;
|
|
11
|
+
//# sourceMappingURL=cookie.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
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"}
|
package/dist/internal/cookie.js
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import jwt from "jsonwebtoken";
|
|
2
|
-
const COOKIE_SECRET = process.env.SEAMLESS_COOKIE_SIGNING_KEY;
|
|
3
|
-
if (!COOKIE_SECRET) {
|
|
4
|
-
console.warn("[SeamlessAuth] Missing SEAMLESS_COOKIE_SIGNING_KEY env var!");
|
|
5
|
-
}
|
|
6
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
|
+
}
|
|
7
8
|
const token = jwt.sign(payload, COOKIE_SECRET, {
|
|
8
9
|
algorithm: "HS256",
|
|
9
10
|
expiresIn: `${ttlSeconds}s`,
|
|
@@ -1,10 +1,51 @@
|
|
|
1
1
|
import { CookieRequest } from "../middleware/ensureCookies.js";
|
|
2
2
|
/**
|
|
3
|
-
* Retrieves the Seamless Auth user
|
|
4
|
-
*
|
|
3
|
+
* Retrieves the authenticated Seamless Auth user for a request by calling
|
|
4
|
+
* the upstream Seamless Auth Server’s introspection endpoint.
|
|
5
5
|
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
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.
|
|
9
49
|
*/
|
|
10
50
|
export declare function getSeamlessUser<T = any>(req: CookieRequest, authServerUrl: string, cookieName?: string): Promise<T | null>;
|
|
51
|
+
//# sourceMappingURL=getSeamlessUser.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
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,14 +1,54 @@
|
|
|
1
1
|
import { authFetch } from "./authFetch.js";
|
|
2
2
|
import { verifyCookieJwt } from "./verifyCookieJwt.js";
|
|
3
3
|
/**
|
|
4
|
-
* Retrieves the Seamless Auth user
|
|
5
|
-
*
|
|
4
|
+
* Retrieves the authenticated Seamless Auth user for a request by calling
|
|
5
|
+
* the upstream Seamless Auth Server’s introspection endpoint.
|
|
6
6
|
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
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.
|
|
10
50
|
*/
|
|
11
|
-
export async function getSeamlessUser(req, authServerUrl, cookieName = "seamless-
|
|
51
|
+
export async function getSeamlessUser(req, authServerUrl, cookieName = "seamless-access") {
|
|
12
52
|
try {
|
|
13
53
|
const payload = verifyCookieJwt(req.cookies[cookieName]);
|
|
14
54
|
if (!payload) {
|
|
@@ -0,0 +1 @@
|
|
|
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,11 +1,12 @@
|
|
|
1
1
|
import jwt from "jsonwebtoken";
|
|
2
|
-
const COOKIE_SECRET = process.env.SEAMLESS_COOKIE_SIGNING_KEY;
|
|
3
|
-
if (!COOKIE_SECRET) {
|
|
4
|
-
console.warn("[SeamlessAuth] SEAMLESS_COOKIE_SIGNING_KEY missing — requireAuth will always fail.");
|
|
5
|
-
}
|
|
6
|
-
const serviceKey = process.env.SEAMLESS_SERVICE_TOKEN;
|
|
7
2
|
export async function refreshAccessToken(req, authServerUrl, refreshToken) {
|
|
8
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;
|
|
9
10
|
if (!serviceKey) {
|
|
10
11
|
throw new Error("Cannot sign service token. Missing SEAMLESS_SERVICE_TOKEN");
|
|
11
12
|
}
|
|
@@ -0,0 +1 @@
|
|
|
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"}
|
|
@@ -0,0 +1 @@
|
|
|
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"}
|
|
@@ -5,3 +5,4 @@ export interface CookieRequest extends Request {
|
|
|
5
5
|
cookiePayload?: JwtPayload;
|
|
6
6
|
}
|
|
7
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
|
|
@@ -0,0 +1 @@
|
|
|
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,11 +1,6 @@
|
|
|
1
1
|
import { verifyCookieJwt } from "../internal/verifyCookieJwt.js";
|
|
2
2
|
import { refreshAccessToken } from "../internal/refreshAccessToken";
|
|
3
3
|
import { clearAllCookies, setSessionCookie } from "../internal/cookie";
|
|
4
|
-
const AUTH_SERVER_URL = process.env.AUTH_SERVER;
|
|
5
|
-
const COOKIE_SECRET = process.env.SEAMLESS_COOKIE_SIGNING_KEY;
|
|
6
|
-
if (!COOKIE_SECRET) {
|
|
7
|
-
console.warn("[SeamlessAuth] SEAMLESS_COOKIE_SIGNING_KEY missing — requireAuth will always fail.");
|
|
8
|
-
}
|
|
9
4
|
export function createEnsureCookiesMiddleware(opts) {
|
|
10
5
|
const COOKIE_REQUIREMENTS = {
|
|
11
6
|
"/webAuthn/login/finish": { name: opts.preAuthCookieName, required: true },
|
|
@@ -34,14 +29,9 @@ export function createEnsureCookiesMiddleware(opts) {
|
|
|
34
29
|
if (!match)
|
|
35
30
|
return next();
|
|
36
31
|
const [, { name, required }] = match;
|
|
32
|
+
const AUTH_SERVER_URL = process.env.AUTH_SERVER;
|
|
37
33
|
const cookieValue = req.cookies?.[name];
|
|
38
34
|
const refreshCookieValue = req.cookies?.[opts.refreshCookieName];
|
|
39
|
-
//
|
|
40
|
-
// --- NEW REFRESH-AWARE LOGIC ---
|
|
41
|
-
//
|
|
42
|
-
// If required cookie is missing BUT refresh cookie exists,
|
|
43
|
-
// allow request to proceed. requireAuth() will perform refresh.
|
|
44
|
-
//
|
|
45
35
|
if (required && !cookieValue) {
|
|
46
36
|
if (refreshCookieValue) {
|
|
47
37
|
console.log("[SeamlessAuth] Access token expired — attempting refresh");
|
|
@@ -65,7 +55,7 @@ export function createEnsureCookiesMiddleware(opts) {
|
|
|
65
55
|
};
|
|
66
56
|
return next();
|
|
67
57
|
}
|
|
68
|
-
// No required cookie AND no refresh cookie
|
|
58
|
+
// No required cookie AND no refresh cookie
|
|
69
59
|
return res.status(400).json({
|
|
70
60
|
error: `Missing required cookie "${name}" for route ${req.path}`,
|
|
71
61
|
hint: "Did you forget to call /auth/login/start first?",
|
|
@@ -1,8 +1,53 @@
|
|
|
1
1
|
import { Request, Response, NextFunction } from "express";
|
|
2
2
|
/**
|
|
3
|
-
* Express middleware that
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
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.
|
|
7
51
|
*/
|
|
8
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
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"requireAuth.d.ts","sourceRoot":"","sources":["../../src/middleware/requireAuth.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AAM1D;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiDG;AACH,wBAAgB,WAAW,CACzB,UAAU,SAAoB,EAC9B,iBAAiB,SAAqB,EACtC,YAAY,SAAM,IAahB,KAAK,OAAO,EACZ,KAAK,QAAQ,EACb,MAAM,YAAY,KACjB,OAAO,CAAC,IAAI,CAAC,CA+EjB"}
|
|
@@ -1,20 +1,68 @@
|
|
|
1
1
|
import jwt from "jsonwebtoken";
|
|
2
2
|
import { refreshAccessToken } from "../internal/refreshAccessToken.js";
|
|
3
3
|
import { setSessionCookie } from "../internal/cookie.js";
|
|
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
|
-
}
|
|
8
|
-
const AUTH_SERVER_URL = process.env.AUTH_SERVER;
|
|
9
4
|
/**
|
|
10
|
-
* Express middleware that
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
5
|
+
* Express middleware that enforces authentication using Seamless Auth cookies.
|
|
6
|
+
*
|
|
7
|
+
* This guard verifies the signed access cookie generated by the Seamless Auth
|
|
8
|
+
* server. If the access cookie is valid and unexpired, the decoded session
|
|
9
|
+
* payload is attached to `req.user` and the request proceeds.
|
|
10
|
+
*
|
|
11
|
+
* If the access cookie is expired or missing *but* a valid refresh cookie is
|
|
12
|
+
* present, the middleware automatically attempts a silent token refresh using
|
|
13
|
+
* the Seamless Auth server. When successful, new session cookies are issued and
|
|
14
|
+
* the request continues with an updated `req.user`.
|
|
15
|
+
*
|
|
16
|
+
* If neither the access token nor refresh token can validate the session,
|
|
17
|
+
* the middleware returns a 401 Unauthorized error and prevents further
|
|
18
|
+
* route execution.
|
|
19
|
+
*
|
|
20
|
+
* ### Responsibilities
|
|
21
|
+
* - Validates the Seamless Auth session access cookie
|
|
22
|
+
* - Attempts refresh-token–based session renewal when necessary
|
|
23
|
+
* - Populates `req.user` with the verified session payload
|
|
24
|
+
* - Handles all cookie rewriting during refresh flows
|
|
25
|
+
* - Acts as a request-level authentication guard for API routes
|
|
26
|
+
*
|
|
27
|
+
* ### Cookie Parameters
|
|
28
|
+
* - **cookieName** — Name of the access cookie that holds the signed session JWT
|
|
29
|
+
* - **refreshCookieName** — Name of the refresh cookie used for silent token refresh
|
|
30
|
+
* - **cookieDomain** — Domain or path value applied to issued cookies
|
|
31
|
+
*
|
|
32
|
+
* ### Example
|
|
33
|
+
* ```ts
|
|
34
|
+
* // Protect a route
|
|
35
|
+
* app.get("/api/me", requireAuth(), (req, res) => {
|
|
36
|
+
* res.json({ user: req.user });
|
|
37
|
+
* });
|
|
38
|
+
*
|
|
39
|
+
* // Custom cookie names (if your Seamless Auth server uses overrides)
|
|
40
|
+
* app.use(
|
|
41
|
+
* "/internal",
|
|
42
|
+
* requireAuth("sa_access", "sa_refresh", "mycompany.com"),
|
|
43
|
+
* internalRouter
|
|
44
|
+
* );
|
|
45
|
+
* ```
|
|
46
|
+
*
|
|
47
|
+
* @param cookieName - The access cookie name. Defaults to `"seamless-access"`.
|
|
48
|
+
* @param refreshCookieName - The refresh cookie name used for session rotation. Defaults to `"seamless-refresh"`.
|
|
49
|
+
* @param cookieDomain - Domain or path used when rewriting cookies. Defaults to `"/"`.
|
|
50
|
+
*
|
|
51
|
+
* @returns An Express middleware function that enforces Seamless Auth
|
|
52
|
+
* authentication on incoming requests.
|
|
14
53
|
*/
|
|
15
|
-
export function requireAuth(cookieName = "seamless-
|
|
54
|
+
export function requireAuth(cookieName = "seamless-access", refreshCookieName = "seamless-refresh", cookieDomain = "/") {
|
|
55
|
+
const COOKIE_SECRET = process.env.SEAMLESS_COOKIE_SIGNING_KEY;
|
|
56
|
+
if (!COOKIE_SECRET) {
|
|
57
|
+
console.warn("[SeamlessAuth] SEAMLESS_COOKIE_SIGNING_KEY missing — requireAuth will always fail.");
|
|
58
|
+
throw new Error("Missing required env SEAMLESS_COOKIE_SIGNING_KEY");
|
|
59
|
+
}
|
|
60
|
+
const AUTH_SERVER_URL = process.env.AUTH_SERVER_URL;
|
|
16
61
|
return async (req, res, next) => {
|
|
17
62
|
try {
|
|
63
|
+
if (!COOKIE_SECRET) {
|
|
64
|
+
throw new Error("Missing required SEAMLESS_COOKIE_SIGNING_KEY env");
|
|
65
|
+
}
|
|
18
66
|
const token = req.cookies?.[cookieName];
|
|
19
67
|
if (!token) {
|
|
20
68
|
res.status(401).json({ error: "Missing access cookie" });
|
|
@@ -1,8 +1,49 @@
|
|
|
1
1
|
import { RequestHandler } from "express";
|
|
2
2
|
/**
|
|
3
|
-
* Express middleware
|
|
3
|
+
* Express middleware that enforces role-based authorization for Seamless Auth sessions.
|
|
4
4
|
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
5
|
+
* This guard assumes that `requireAuth()` has already validated the request
|
|
6
|
+
* and populated `req.user` with the decoded Seamless Auth session payload.
|
|
7
|
+
* It then checks whether the user’s roles include the required role (or any
|
|
8
|
+
* of several, when an array is provided).
|
|
9
|
+
*
|
|
10
|
+
* If the user possesses the required authorization, the request proceeds.
|
|
11
|
+
* Otherwise, the middleware responds with a 403 Forbidden error.
|
|
12
|
+
*
|
|
13
|
+
* ### Responsibilities
|
|
14
|
+
* - Validates that `req.user` is present (enforced upstream by `requireAuth`)
|
|
15
|
+
* - Ensures the authenticated user includes the specified role(s)
|
|
16
|
+
* - Blocks unauthorized access with a standardized JSON 403 response
|
|
17
|
+
*
|
|
18
|
+
* ### Parameters
|
|
19
|
+
* - **requiredRole** — A role (string) or list of roles the user must have.
|
|
20
|
+
* If an array is provided, *any* matching role grants access.
|
|
21
|
+
* - **cookieName** — Optional name of the access cookie to inspect.
|
|
22
|
+
* Defaults to `"seamless-access"`, but typically not needed because
|
|
23
|
+
* `requireAuth` is expected to run first.
|
|
24
|
+
*
|
|
25
|
+
* ### Example
|
|
26
|
+
* ```ts
|
|
27
|
+
* // Require a single role
|
|
28
|
+
* app.get("/admin/users",
|
|
29
|
+
* requireAuth(),
|
|
30
|
+
* requireRole("admin"),
|
|
31
|
+
* (req, res) => {
|
|
32
|
+
* res.send("Welcome admin!");
|
|
33
|
+
* }
|
|
34
|
+
* );
|
|
35
|
+
*
|
|
36
|
+
* // Allow any of multiple roles
|
|
37
|
+
* app.post("/settings",
|
|
38
|
+
* requireAuth(),
|
|
39
|
+
* requireRole(["admin", "supervisor"]),
|
|
40
|
+
* updateSettingsHandler
|
|
41
|
+
* );
|
|
42
|
+
* ```
|
|
43
|
+
*
|
|
44
|
+
* @param requiredRole - A role or list of roles required to access the route.
|
|
45
|
+
* @param cookieName - Optional access cookie name (defaults to `seamless-access`).
|
|
46
|
+
* @returns An Express middleware function enforcing role-based access control.
|
|
7
47
|
*/
|
|
8
48
|
export declare function requireRole(role: string, cookieName?: string): RequestHandler;
|
|
49
|
+
//# sourceMappingURL=requireRole.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"requireRole.d.ts","sourceRoot":"","sources":["../../src/middleware/requireRole.ts"],"names":[],"mappings":"AAAA,OAAO,EAAmC,cAAc,EAAE,MAAM,SAAS,CAAC;AAG1E;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6CG;AACH,wBAAgB,WAAW,CACzB,IAAI,EAAE,MAAM,EACZ,UAAU,SAAoB,GAC7B,cAAc,CAiChB"}
|
|
@@ -1,17 +1,58 @@
|
|
|
1
1
|
import jwt from "jsonwebtoken";
|
|
2
|
-
const COOKIE_SECRET = process.env.SEAMLESS_COOKIE_SIGNING_KEY;
|
|
3
|
-
if (!COOKIE_SECRET) {
|
|
4
|
-
console.warn("[PortalAPI] Missing SEAMLESS_COOKIE_SIGNING_KEY — role checks will fail.");
|
|
5
|
-
}
|
|
6
2
|
/**
|
|
7
|
-
* Express middleware
|
|
3
|
+
* Express middleware that enforces role-based authorization for Seamless Auth sessions.
|
|
4
|
+
*
|
|
5
|
+
* This guard assumes that `requireAuth()` has already validated the request
|
|
6
|
+
* and populated `req.user` with the decoded Seamless Auth session payload.
|
|
7
|
+
* It then checks whether the user’s roles include the required role (or any
|
|
8
|
+
* of several, when an array is provided).
|
|
9
|
+
*
|
|
10
|
+
* If the user possesses the required authorization, the request proceeds.
|
|
11
|
+
* Otherwise, the middleware responds with a 403 Forbidden error.
|
|
12
|
+
*
|
|
13
|
+
* ### Responsibilities
|
|
14
|
+
* - Validates that `req.user` is present (enforced upstream by `requireAuth`)
|
|
15
|
+
* - Ensures the authenticated user includes the specified role(s)
|
|
16
|
+
* - Blocks unauthorized access with a standardized JSON 403 response
|
|
8
17
|
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
18
|
+
* ### Parameters
|
|
19
|
+
* - **requiredRole** — A role (string) or list of roles the user must have.
|
|
20
|
+
* If an array is provided, *any* matching role grants access.
|
|
21
|
+
* - **cookieName** — Optional name of the access cookie to inspect.
|
|
22
|
+
* Defaults to `"seamless-access"`, but typically not needed because
|
|
23
|
+
* `requireAuth` is expected to run first.
|
|
24
|
+
*
|
|
25
|
+
* ### Example
|
|
26
|
+
* ```ts
|
|
27
|
+
* // Require a single role
|
|
28
|
+
* app.get("/admin/users",
|
|
29
|
+
* requireAuth(),
|
|
30
|
+
* requireRole("admin"),
|
|
31
|
+
* (req, res) => {
|
|
32
|
+
* res.send("Welcome admin!");
|
|
33
|
+
* }
|
|
34
|
+
* );
|
|
35
|
+
*
|
|
36
|
+
* // Allow any of multiple roles
|
|
37
|
+
* app.post("/settings",
|
|
38
|
+
* requireAuth(),
|
|
39
|
+
* requireRole(["admin", "supervisor"]),
|
|
40
|
+
* updateSettingsHandler
|
|
41
|
+
* );
|
|
42
|
+
* ```
|
|
43
|
+
*
|
|
44
|
+
* @param requiredRole - A role or list of roles required to access the route.
|
|
45
|
+
* @param cookieName - Optional access cookie name (defaults to `seamless-access`).
|
|
46
|
+
* @returns An Express middleware function enforcing role-based access control.
|
|
11
47
|
*/
|
|
12
48
|
export function requireRole(role, cookieName = "seamless-access") {
|
|
13
49
|
return (req, res, next) => {
|
|
14
50
|
try {
|
|
51
|
+
const COOKIE_SECRET = process.env.SEAMLESS_COOKIE_SIGNING_KEY;
|
|
52
|
+
if (!COOKIE_SECRET) {
|
|
53
|
+
console.warn("[SeamlessAuth] SEAMLESS_COOKIE_SIGNING_KEY missing — requireRole will always fail.");
|
|
54
|
+
throw new Error("Missing required env SEAMLESS_COOKIE_SIGNING_KEY");
|
|
55
|
+
}
|
|
15
56
|
const token = req.cookies?.[cookieName];
|
|
16
57
|
if (!token) {
|
|
17
58
|
res.status(401).json({ error: "Missing access cookie" });
|
|
@@ -29,7 +70,7 @@ export function requireRole(role, cookieName = "seamless-access") {
|
|
|
29
70
|
next();
|
|
30
71
|
}
|
|
31
72
|
catch (err) {
|
|
32
|
-
console.error(`[
|
|
73
|
+
console.error(`[RequireRole] requireRole(${role}) failed:`, err.message);
|
|
33
74
|
res.status(401).json({ error: "Invalid or expired access cookie" });
|
|
34
75
|
}
|
|
35
76
|
};
|
package/dist/types.d.ts
CHANGED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,yBAAyB;IACxC,aAAa,EAAE,MAAM,CAAC;IACtB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,sBAAsB,CAAC,EAAE,MAAM,CAAC;IAChC,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,iBAAiB,CAAC,EAAE,MAAM,CAAC;CAC5B"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@seamless-auth/express",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.2-beta.1",
|
|
4
4
|
"description": "Express adapter for Seamless Auth passwordless authentication",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -24,6 +24,7 @@
|
|
|
24
24
|
},
|
|
25
25
|
"dependencies": {
|
|
26
26
|
"cookie-parser": "^1.4.6",
|
|
27
|
+
"dotenv": "^17.2.3",
|
|
27
28
|
"jose": "^6.1.1",
|
|
28
29
|
"jsonwebtoken": "^9.0.2",
|
|
29
30
|
"node-fetch": "^3.3.2"
|