@seamless-auth/express 0.0.1 → 0.0.2-beta.2
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 +13 -12
- package/dist/index.js +564 -5
- package/package.json +3 -2
- package/dist/createServer.d.ts +0 -4
- package/dist/createServer.d.ts.map +0 -1
- package/dist/createServer.js +0 -120
- package/dist/index.d.ts +0 -7
- package/dist/index.d.ts.map +0 -1
- package/dist/internal/authFetch.d.ts +0 -9
- package/dist/internal/authFetch.d.ts.map +0 -1
- package/dist/internal/authFetch.js +0 -41
- package/dist/internal/cookie.d.ts +0 -11
- package/dist/internal/cookie.d.ts.map +0 -1
- package/dist/internal/cookie.js +0 -28
- package/dist/internal/getSeamlessUser.d.ts +0 -11
- package/dist/internal/getSeamlessUser.d.ts.map +0 -1
- package/dist/internal/getSeamlessUser.js +0 -32
- package/dist/internal/refreshAccessToken.d.ts +0 -10
- package/dist/internal/refreshAccessToken.d.ts.map +0 -1
- package/dist/internal/refreshAccessToken.js +0 -44
- package/dist/internal/verifyCookieJwt.d.ts +0 -2
- package/dist/internal/verifyCookieJwt.d.ts.map +0 -1
- package/dist/internal/verifyCookieJwt.js +0 -13
- package/dist/internal/verifySignedAuthResponse.d.ts +0 -6
- package/dist/internal/verifySignedAuthResponse.d.ts.map +0 -1
- package/dist/internal/verifySignedAuthResponse.js +0 -23
- package/dist/middleware/ensureCookies.d.ts +0 -8
- package/dist/middleware/ensureCookies.d.ts.map +0 -1
- package/dist/middleware/ensureCookies.js +0 -84
- package/dist/middleware/requireAuth.d.ts +0 -9
- package/dist/middleware/requireAuth.d.ts.map +0 -1
- package/dist/middleware/requireAuth.js +0 -74
- package/dist/middleware/requireRole.d.ts +0 -9
- package/dist/middleware/requireRole.d.ts.map +0 -1
- package/dist/middleware/requireRole.js +0 -37
- package/dist/types.d.ts +0 -9
- package/dist/types.d.ts.map +0 -1
- package/dist/types.js +0 -1
package/dist/createServer.js
DELETED
|
@@ -1,120 +0,0 @@
|
|
|
1
|
-
import express from "express";
|
|
2
|
-
import cookieParser from "cookie-parser";
|
|
3
|
-
import { setSessionCookie, clearAllCookies, clearSessionCookie, } from "./internal/cookie";
|
|
4
|
-
import { authFetch } from "./internal/authFetch";
|
|
5
|
-
import { createEnsureCookiesMiddleware } from "./middleware/ensureCookies";
|
|
6
|
-
import { verifySignedAuthResponse } from "./internal/verifySignedAuthResponse";
|
|
7
|
-
export function createSeamlessAuthServer(opts) {
|
|
8
|
-
const r = express.Router();
|
|
9
|
-
r.use(express.json());
|
|
10
|
-
r.use(cookieParser());
|
|
11
|
-
const { authServerUrl, cookieDomain = "", accesscookieName = "seamless-access", registrationCookieName = "seamless-ephemeral", refreshCookieName = "seamless-refresh", preAuthCookieName = "seamless-ephemeral", } = opts;
|
|
12
|
-
const proxy = (path, method = "POST") => async (req, res) => {
|
|
13
|
-
try {
|
|
14
|
-
const response = await authFetch(req, `${authServerUrl}/${path}`, {
|
|
15
|
-
method,
|
|
16
|
-
body: req.body,
|
|
17
|
-
});
|
|
18
|
-
res.status(response.status).json(await response.json());
|
|
19
|
-
}
|
|
20
|
-
catch (error) {
|
|
21
|
-
console.error(`Failed to proxy to route. Error: ${error}`);
|
|
22
|
-
}
|
|
23
|
-
};
|
|
24
|
-
r.use(createEnsureCookiesMiddleware({
|
|
25
|
-
authServerUrl,
|
|
26
|
-
cookieDomain,
|
|
27
|
-
accesscookieName,
|
|
28
|
-
registrationCookieName,
|
|
29
|
-
refreshCookieName,
|
|
30
|
-
preAuthCookieName,
|
|
31
|
-
}));
|
|
32
|
-
r.post("/webAuthn/login/start", proxy("webAuthn/login/start"));
|
|
33
|
-
r.post("/webAuthn/login/finish", finishLogin);
|
|
34
|
-
r.get("/webAuthn/register/start", proxy("webAuthn/register/start", "GET"));
|
|
35
|
-
r.post("/webAuthn/register/finish", finishRegister);
|
|
36
|
-
r.post("/otp/verify-phone-otp", proxy("otp/verify-phone-otp"));
|
|
37
|
-
r.post("/otp/verify-email-otp", proxy("otp/verify-email-otp"));
|
|
38
|
-
r.post("/login", login);
|
|
39
|
-
r.post("/users/update", proxy("users/update"));
|
|
40
|
-
r.post("/registration/register", register);
|
|
41
|
-
r.get("/users/me", me);
|
|
42
|
-
r.get("/logout", logout);
|
|
43
|
-
return r;
|
|
44
|
-
async function login(req, res) {
|
|
45
|
-
const up = await authFetch(req, `${authServerUrl}/login`, {
|
|
46
|
-
method: "POST",
|
|
47
|
-
body: req.body,
|
|
48
|
-
});
|
|
49
|
-
const data = (await up.json());
|
|
50
|
-
if (!up.ok)
|
|
51
|
-
return res.status(up.status).json(data);
|
|
52
|
-
const verified = await verifySignedAuthResponse(data.token, authServerUrl);
|
|
53
|
-
if (!verified) {
|
|
54
|
-
throw new Error("Invalid signed response from Auth Server");
|
|
55
|
-
}
|
|
56
|
-
if (verified.sub !== data.sub) {
|
|
57
|
-
throw new Error("Signature mismatch with data payload");
|
|
58
|
-
}
|
|
59
|
-
setSessionCookie(res, { sub: data.sub }, cookieDomain, data.ttl, preAuthCookieName);
|
|
60
|
-
res.status(204).end();
|
|
61
|
-
}
|
|
62
|
-
async function register(req, res) {
|
|
63
|
-
const up = await authFetch(req, `${authServerUrl}/registration/register`, {
|
|
64
|
-
method: "POST",
|
|
65
|
-
body: req.body,
|
|
66
|
-
});
|
|
67
|
-
const data = (await up.json());
|
|
68
|
-
if (!up.ok)
|
|
69
|
-
return res.status(up.status).json(data);
|
|
70
|
-
setSessionCookie(res, { sub: data.sub }, cookieDomain, data.ttl, registrationCookieName);
|
|
71
|
-
res.status(200).json(data).end();
|
|
72
|
-
}
|
|
73
|
-
async function finishLogin(req, res) {
|
|
74
|
-
const up = await authFetch(req, `${authServerUrl}/webAuthn/login/finish`, {
|
|
75
|
-
method: "POST",
|
|
76
|
-
body: req.body,
|
|
77
|
-
});
|
|
78
|
-
const data = (await up.json());
|
|
79
|
-
if (!up.ok)
|
|
80
|
-
return res.status(up.status).json(data);
|
|
81
|
-
const verifiedAccessToken = await verifySignedAuthResponse(data.token, authServerUrl);
|
|
82
|
-
if (!verifiedAccessToken) {
|
|
83
|
-
throw new Error("Invalid signed response from Auth Server");
|
|
84
|
-
}
|
|
85
|
-
if (verifiedAccessToken.sub !== data.sub) {
|
|
86
|
-
throw new Error("Signature mismatch with data payload");
|
|
87
|
-
}
|
|
88
|
-
setSessionCookie(res, { sub: data.sub, roles: data.roles }, cookieDomain, data.ttl, accesscookieName);
|
|
89
|
-
setSessionCookie(res, { sub: data.sub, refreshToken: data.refreshToken }, req.hostname, data.refreshTtl, refreshCookieName);
|
|
90
|
-
res.status(200).json(data).end();
|
|
91
|
-
}
|
|
92
|
-
async function finishRegister(req, res) {
|
|
93
|
-
const up = await authFetch(req, `${authServerUrl}/webAuthn/register/finish`, {
|
|
94
|
-
method: "POST",
|
|
95
|
-
body: req.body,
|
|
96
|
-
});
|
|
97
|
-
const data = (await up.json());
|
|
98
|
-
if (!up.ok)
|
|
99
|
-
return res.status(up.status).json(data);
|
|
100
|
-
setSessionCookie(res, { sub: data.sub, roles: data.roles }, cookieDomain, data.ttl, accesscookieName);
|
|
101
|
-
res.status(204).end();
|
|
102
|
-
}
|
|
103
|
-
async function logout(req, res) {
|
|
104
|
-
await authFetch(req, `${authServerUrl}/logout`, {
|
|
105
|
-
method: "GET",
|
|
106
|
-
});
|
|
107
|
-
clearAllCookies(res, cookieDomain, accesscookieName, registrationCookieName, refreshCookieName);
|
|
108
|
-
res.status(204).end();
|
|
109
|
-
}
|
|
110
|
-
async function me(req, res) {
|
|
111
|
-
const up = await authFetch(req, `${authServerUrl}/users/me`, {
|
|
112
|
-
method: "GET",
|
|
113
|
-
});
|
|
114
|
-
const data = (await up.json());
|
|
115
|
-
clearSessionCookie(res, cookieDomain, preAuthCookieName);
|
|
116
|
-
if (!data.user)
|
|
117
|
-
return res.status(401).json({ error: "unauthenticated" });
|
|
118
|
-
res.json({ user: data.user });
|
|
119
|
-
}
|
|
120
|
-
}
|
package/dist/index.d.ts
DELETED
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
import { createSeamlessAuthServer } from "./createServer";
|
|
2
|
-
export { requireAuth } from "./middleware/requireAuth";
|
|
3
|
-
export { requireRole } from "./middleware/requireRole";
|
|
4
|
-
export { getSeamlessUser } from "./internal/getSeamlessUser";
|
|
5
|
-
export type { SeamlessAuthServerOptions } from "./types";
|
|
6
|
-
export default createSeamlessAuthServer;
|
|
7
|
-
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.d.ts.map
DELETED
|
@@ -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" | "PATCH" | "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,OAAO,GAAG,QAAQ,CAAC;IACrD,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,qBAuDxE"}
|
|
@@ -1,41 +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
|
-
console.debug("[SeamlessAuth] Performing authentication fetch to Auth server");
|
|
8
|
-
// -------------------------------
|
|
9
|
-
// Issue short-lived machine token
|
|
10
|
-
// -------------------------------
|
|
11
|
-
const token = jwt.sign({
|
|
12
|
-
// Minimal, safe fields
|
|
13
|
-
iss: process.env.FRONTEND_URL,
|
|
14
|
-
aud: process.env.AUTH_SERVER_URL,
|
|
15
|
-
sub: req.cookiePayload?.sub,
|
|
16
|
-
roles: req.cookiePayload?.roles ?? [],
|
|
17
|
-
iat: Math.floor(Date.now() / 1000),
|
|
18
|
-
}, serviceKey, {
|
|
19
|
-
expiresIn: "60s", // Short-lived = safer
|
|
20
|
-
algorithm: "HS256", // HMAC-based
|
|
21
|
-
keyid: "dev-main", // For future rotation
|
|
22
|
-
});
|
|
23
|
-
const finalHeaders = {
|
|
24
|
-
...(method !== "GET" && { "Content-Type": "application/json" }),
|
|
25
|
-
...(cookies ? { Cookie: cookies.join("; ") } : {}),
|
|
26
|
-
Authorization: `Bearer ${serviceKey}`,
|
|
27
|
-
...headers,
|
|
28
|
-
};
|
|
29
|
-
let finalUrl = url;
|
|
30
|
-
console.debug("[SeamlessAuth] URL ...", finalUrl);
|
|
31
|
-
if (method === "GET" && body && typeof body === "object") {
|
|
32
|
-
const qs = new URLSearchParams(body).toString();
|
|
33
|
-
finalUrl += url.includes("?") ? `&${qs}` : `?${qs}`;
|
|
34
|
-
}
|
|
35
|
-
const res = await fetch(finalUrl, {
|
|
36
|
-
method,
|
|
37
|
-
headers: finalHeaders,
|
|
38
|
-
...(method !== "GET" && body ? { body: JSON.stringify(body) } : {}),
|
|
39
|
-
});
|
|
40
|
-
return res;
|
|
41
|
-
}
|
|
@@ -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"}
|
package/dist/internal/cookie.js
DELETED
|
@@ -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,11 +0,0 @@
|
|
|
1
|
-
import { CookieRequest } from "../middleware/ensureCookies.js";
|
|
2
|
-
/**
|
|
3
|
-
* Retrieves the Seamless Auth user information by calling the auth server's introspection endpoint.
|
|
4
|
-
* Requires the sa_session (or custom) cookie to be present on the request.
|
|
5
|
-
*
|
|
6
|
-
* @param req Express request object
|
|
7
|
-
* @param authServerUrl Base URL of the client's auth server
|
|
8
|
-
* @returns The user data object if valid, or null if invalid/unauthenticated
|
|
9
|
-
*/
|
|
10
|
-
export declare function getSeamlessUser<T = any>(req: CookieRequest, authServerUrl: string, cookieName?: string): Promise<T | null>;
|
|
11
|
-
//# 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;;;;;;;GAOG;AACH,wBAAsB,eAAe,CAAC,CAAC,GAAG,GAAG,EAC3C,GAAG,EAAE,aAAa,EAClB,aAAa,EAAE,MAAM,EACrB,UAAU,GAAE,MAA+B,GAC1C,OAAO,CAAC,CAAC,GAAG,IAAI,CAAC,CAwBnB"}
|
|
@@ -1,32 +0,0 @@
|
|
|
1
|
-
import { authFetch } from "./authFetch.js";
|
|
2
|
-
import { verifyCookieJwt } from "./verifyCookieJwt.js";
|
|
3
|
-
/**
|
|
4
|
-
* Retrieves the Seamless Auth user information by calling the auth server's introspection endpoint.
|
|
5
|
-
* Requires the sa_session (or custom) cookie to be present on the request.
|
|
6
|
-
*
|
|
7
|
-
* @param req Express request object
|
|
8
|
-
* @param authServerUrl Base URL of the client's auth server
|
|
9
|
-
* @returns The user data object if valid, or null if invalid/unauthenticated
|
|
10
|
-
*/
|
|
11
|
-
export async function getSeamlessUser(req, authServerUrl, cookieName = "seamless-auth-access") {
|
|
12
|
-
try {
|
|
13
|
-
const payload = verifyCookieJwt(req.cookies[cookieName]);
|
|
14
|
-
if (!payload) {
|
|
15
|
-
throw new Error("Missing cookie");
|
|
16
|
-
}
|
|
17
|
-
req.cookiePayload = payload;
|
|
18
|
-
const response = await authFetch(req, `${authServerUrl}/users/me`, {
|
|
19
|
-
method: "GET",
|
|
20
|
-
});
|
|
21
|
-
if (!response.ok) {
|
|
22
|
-
console.warn(`[SeamlessAuth] Auth server responded ${response.status}`);
|
|
23
|
-
return null;
|
|
24
|
-
}
|
|
25
|
-
const data = (await response.json());
|
|
26
|
-
return data.user;
|
|
27
|
-
}
|
|
28
|
-
catch (err) {
|
|
29
|
-
console.error("[SeamlessAuth] getSeamlessUser failed:", err);
|
|
30
|
-
return null;
|
|
31
|
-
}
|
|
32
|
-
}
|
|
@@ -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 +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,wDA2FpB"}
|
|
@@ -1,84 +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
|
-
//
|
|
36
|
-
// --- NEW REFRESH-AWARE LOGIC ---
|
|
37
|
-
//
|
|
38
|
-
// If required cookie is missing BUT refresh cookie exists,
|
|
39
|
-
// allow request to proceed. requireAuth() will perform refresh.
|
|
40
|
-
//
|
|
41
|
-
if (required && !cookieValue) {
|
|
42
|
-
if (refreshCookieValue) {
|
|
43
|
-
console.log("[SeamlessAuth] Access token expired — attempting refresh");
|
|
44
|
-
const refreshed = await refreshAccessToken(req, AUTH_SERVER_URL, refreshCookieValue);
|
|
45
|
-
if (!refreshed?.token) {
|
|
46
|
-
clearAllCookies(res, cookieDomain, name, opts.registrationCookieName, opts.refreshCookieName);
|
|
47
|
-
res.status(401).json({ error: "Refresh failed" });
|
|
48
|
-
return;
|
|
49
|
-
}
|
|
50
|
-
// Update cookie with new access token
|
|
51
|
-
setSessionCookie(res, {
|
|
52
|
-
sub: refreshed.sub,
|
|
53
|
-
token: refreshed.token,
|
|
54
|
-
roles: refreshed.roles,
|
|
55
|
-
}, cookieDomain, refreshed.ttl, name);
|
|
56
|
-
setSessionCookie(res, { sub: refreshed.sub, refreshToken: refreshed.refreshToken }, cookieDomain, refreshed.refreshTtl, opts.refreshCookieName);
|
|
57
|
-
// Let requireAuth() attempt refresh
|
|
58
|
-
req.cookiePayload = {
|
|
59
|
-
sub: refreshed.sub,
|
|
60
|
-
roles: refreshed.roles,
|
|
61
|
-
};
|
|
62
|
-
return next();
|
|
63
|
-
}
|
|
64
|
-
// No required cookie AND no refresh cookie → hard fail
|
|
65
|
-
return res.status(400).json({
|
|
66
|
-
error: `Missing required cookie "${name}" for route ${req.path}`,
|
|
67
|
-
hint: "Did you forget to call /auth/login/start first?",
|
|
68
|
-
});
|
|
69
|
-
}
|
|
70
|
-
//
|
|
71
|
-
// If cookie exists, verify it normally
|
|
72
|
-
//
|
|
73
|
-
if (cookieValue) {
|
|
74
|
-
const payload = verifyCookieJwt(cookieValue);
|
|
75
|
-
if (!payload) {
|
|
76
|
-
return res
|
|
77
|
-
.status(401)
|
|
78
|
-
.json({ error: `Invalid or expired ${name} cookie` });
|
|
79
|
-
}
|
|
80
|
-
req.cookiePayload = payload;
|
|
81
|
-
}
|
|
82
|
-
next();
|
|
83
|
-
};
|
|
84
|
-
}
|
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
import { Request, Response, NextFunction } from "express";
|
|
2
|
-
/**
|
|
3
|
-
* Express middleware that verifies a Seamless Auth access cookie.
|
|
4
|
-
* - Reads and verifies signed cookie JWT
|
|
5
|
-
* - Attaches decoded payload to req.user
|
|
6
|
-
* - Returns 401 if missing/invalid/expired
|
|
7
|
-
*/
|
|
8
|
-
export declare function requireAuth(cookieName?: string, refreshCookieName?: string, cookieDomain?: string): (req: Request, res: Response, next: NextFunction) => Promise<void>;
|
|
9
|
-
//# sourceMappingURL=requireAuth.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
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;;;;;GAKG;AACH,wBAAgB,WAAW,CACzB,UAAU,SAAyB,EACnC,iBAAiB,SAA0B,EAC3C,YAAY,SAAM,IAahB,KAAK,OAAO,EACZ,KAAK,QAAQ,EACb,MAAM,YAAY,KACjB,OAAO,CAAC,IAAI,CAAC,CA+EjB"}
|
|
@@ -1,74 +0,0 @@
|
|
|
1
|
-
import jwt from "jsonwebtoken";
|
|
2
|
-
import { refreshAccessToken } from "../internal/refreshAccessToken.js";
|
|
3
|
-
import { setSessionCookie } from "../internal/cookie.js";
|
|
4
|
-
/**
|
|
5
|
-
* Express middleware that verifies a Seamless Auth access cookie.
|
|
6
|
-
* - Reads and verifies signed cookie JWT
|
|
7
|
-
* - Attaches decoded payload to req.user
|
|
8
|
-
* - Returns 401 if missing/invalid/expired
|
|
9
|
-
*/
|
|
10
|
-
export function requireAuth(cookieName = "seamless-auth-access", refreshCookieName = "seamless-auth-refresh", cookieDomain = "/") {
|
|
11
|
-
const COOKIE_SECRET = process.env.SEAMLESS_COOKIE_SIGNING_KEY;
|
|
12
|
-
if (!COOKIE_SECRET) {
|
|
13
|
-
console.warn("[SeamlessAuth] SEAMLESS_COOKIE_SIGNING_KEY missing — requireAuth will always fail.");
|
|
14
|
-
throw new Error("Missing required env SEAMLESS_COOKIE_SIGNING_KEY");
|
|
15
|
-
}
|
|
16
|
-
const AUTH_SERVER_URL = process.env.AUTH_SERVER;
|
|
17
|
-
return async (req, res, next) => {
|
|
18
|
-
try {
|
|
19
|
-
if (!COOKIE_SECRET) {
|
|
20
|
-
throw new Error("Missing required SEAMLESS_COOKIE_SIGNING_KEY env");
|
|
21
|
-
}
|
|
22
|
-
const token = req.cookies?.[cookieName];
|
|
23
|
-
if (!token) {
|
|
24
|
-
res.status(401).json({ error: "Missing access cookie" });
|
|
25
|
-
return;
|
|
26
|
-
}
|
|
27
|
-
try {
|
|
28
|
-
const payload = jwt.verify(token, COOKIE_SECRET, {
|
|
29
|
-
algorithms: ["HS256"],
|
|
30
|
-
});
|
|
31
|
-
req.user = payload;
|
|
32
|
-
return next();
|
|
33
|
-
}
|
|
34
|
-
catch (err) {
|
|
35
|
-
// expired or invalid token
|
|
36
|
-
if (err.name !== "TokenExpiredError") {
|
|
37
|
-
console.warn("[SeamlessAuth] Invalid token:", err.message);
|
|
38
|
-
res.status(401).json({ error: "Invalid token" });
|
|
39
|
-
return;
|
|
40
|
-
}
|
|
41
|
-
// Try refresh
|
|
42
|
-
const refreshToken = req.cookies?.[refreshCookieName];
|
|
43
|
-
if (!refreshToken) {
|
|
44
|
-
res.status(401).json({ error: "Session expired; re-login required" });
|
|
45
|
-
return;
|
|
46
|
-
}
|
|
47
|
-
console.log("[SeamlessAuth] Access token expired — attempting refresh");
|
|
48
|
-
const refreshed = await refreshAccessToken(req, AUTH_SERVER_URL, refreshToken);
|
|
49
|
-
if (!refreshed?.token) {
|
|
50
|
-
res.status(401).json({ error: "Refresh failed" });
|
|
51
|
-
return;
|
|
52
|
-
}
|
|
53
|
-
// Update cookie with new access token
|
|
54
|
-
setSessionCookie(res, {
|
|
55
|
-
sub: refreshed.sub,
|
|
56
|
-
token: refreshed.token,
|
|
57
|
-
roles: refreshed.roles,
|
|
58
|
-
}, cookieDomain, refreshed.ttl, cookieName);
|
|
59
|
-
setSessionCookie(res, { sub: refreshed.sub, refreshToken: refreshed.refreshToken }, req.hostname, refreshed.refreshTtl, refreshCookieName);
|
|
60
|
-
// Decode new token so downstream has user
|
|
61
|
-
const payload = jwt.verify(refreshed.token, COOKIE_SECRET, {
|
|
62
|
-
algorithms: ["HS256"],
|
|
63
|
-
});
|
|
64
|
-
req.user = payload;
|
|
65
|
-
next();
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
|
-
catch (err) {
|
|
69
|
-
console.error("[SeamlessAuth] requireAuth error:", err.message);
|
|
70
|
-
res.status(401).json({ error: "Invalid or expired access cookie" });
|
|
71
|
-
return;
|
|
72
|
-
}
|
|
73
|
-
};
|
|
74
|
-
}
|
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
import { RequestHandler } from "express";
|
|
2
|
-
/**
|
|
3
|
-
* Express middleware to enforce a required role from Seamless Auth cookie JWT.
|
|
4
|
-
*
|
|
5
|
-
* @param role Role name to require (e.g. 'admin')
|
|
6
|
-
* @param cookieName Cookie name containing JWT (default: 'sa_session')
|
|
7
|
-
*/
|
|
8
|
-
export declare function requireRole(role: string, cookieName?: string): RequestHandler;
|
|
9
|
-
//# sourceMappingURL=requireRole.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"requireRole.d.ts","sourceRoot":"","sources":["../../src/middleware/requireRole.ts"],"names":[],"mappings":"AAAA,OAAO,EAAmC,cAAc,EAAE,MAAM,SAAS,CAAC;AAG1E;;;;;GAKG;AACH,wBAAgB,WAAW,CACzB,IAAI,EAAE,MAAM,EACZ,UAAU,SAAoB,GAC7B,cAAc,CAiChB"}
|
|
@@ -1,37 +0,0 @@
|
|
|
1
|
-
import jwt from "jsonwebtoken";
|
|
2
|
-
/**
|
|
3
|
-
* Express middleware to enforce a required role from Seamless Auth cookie JWT.
|
|
4
|
-
*
|
|
5
|
-
* @param role Role name to require (e.g. 'admin')
|
|
6
|
-
* @param cookieName Cookie name containing JWT (default: 'sa_session')
|
|
7
|
-
*/
|
|
8
|
-
export function requireRole(role, cookieName = "seamless-access") {
|
|
9
|
-
return (req, res, next) => {
|
|
10
|
-
try {
|
|
11
|
-
const COOKIE_SECRET = process.env.SEAMLESS_COOKIE_SIGNING_KEY;
|
|
12
|
-
if (!COOKIE_SECRET) {
|
|
13
|
-
console.warn("[SeamlessAuth] SEAMLESS_COOKIE_SIGNING_KEY missing — requireRole will always fail.");
|
|
14
|
-
throw new Error("Missing required env SEAMLESS_COOKIE_SIGNING_KEY");
|
|
15
|
-
}
|
|
16
|
-
const token = req.cookies?.[cookieName];
|
|
17
|
-
if (!token) {
|
|
18
|
-
res.status(401).json({ error: "Missing access cookie" });
|
|
19
|
-
return;
|
|
20
|
-
}
|
|
21
|
-
// Verify JWT signature
|
|
22
|
-
const payload = jwt.verify(token, COOKIE_SECRET, {
|
|
23
|
-
algorithms: ["HS256"],
|
|
24
|
-
});
|
|
25
|
-
// Check role membership
|
|
26
|
-
if (!payload.roles?.includes(role)) {
|
|
27
|
-
res.status(403).json({ error: `Forbidden: ${role} role required` });
|
|
28
|
-
return;
|
|
29
|
-
}
|
|
30
|
-
next();
|
|
31
|
-
}
|
|
32
|
-
catch (err) {
|
|
33
|
-
console.error(`[RequireRole] requireRole(${role}) failed:`, err.message);
|
|
34
|
-
res.status(401).json({ error: "Invalid or expired access cookie" });
|
|
35
|
-
}
|
|
36
|
-
};
|
|
37
|
-
}
|