@seamless-auth/express 0.0.4-beta.0 → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +12 -9
- package/dist/index.d.ts +40 -36
- package/dist/index.js +137 -75
- package/package.json +6 -6
package/README.md
CHANGED
|
@@ -110,14 +110,17 @@ This keeps trust boundaries clean and auditable.
|
|
|
110
110
|
|
|
111
111
|
## Environment Variables
|
|
112
112
|
|
|
113
|
-
| Variable | Description | Example
|
|
114
|
-
| -------------------- | ----------------------------------------- |
|
|
115
|
-
| `AUTH_SERVER_URL` | Base URL of your Seamless Auth Server | `https://auth.client.com`
|
|
116
|
-
| `COOKIE_SIGNING_KEY` | Secret for signing API session cookies | `local-dev-secret`
|
|
117
|
-
| `API_SERVICE_TOKEN` | API → Auth Server service secret | `shared-m2m-value`
|
|
118
|
-
| `APP_ORIGIN` | Your site URL (or localhost in demo mode) | `https://myapp.com`
|
|
119
|
-
| `
|
|
120
|
-
| `
|
|
113
|
+
| Variable | Description | Example |
|
|
114
|
+
| -------------------- | ----------------------------------------- | ------------------------- |
|
|
115
|
+
| `AUTH_SERVER_URL` | Base URL of your Seamless Auth Server | `https://auth.client.com` |
|
|
116
|
+
| `COOKIE_SIGNING_KEY` | Secret for signing API session cookies | `local-dev-secret` |
|
|
117
|
+
| `API_SERVICE_TOKEN` | API → Auth Server service secret | `shared-m2m-value` |
|
|
118
|
+
| `APP_ORIGIN` | Your site URL (or localhost in demo mode) | `https://myapp.com` |
|
|
119
|
+
| `DB_HOST` | Database Host | `localhost` |
|
|
120
|
+
| `DB_PORT` | Database Port | `5432` |
|
|
121
|
+
| `DB_USER` | Database user | `myuser` |
|
|
122
|
+
| `DB_PASSWORD` | Database password | `mypassword` |
|
|
123
|
+
| `DB_NAME` | Name of your database | `seamless` |
|
|
121
124
|
|
|
122
125
|
---
|
|
123
126
|
|
|
@@ -219,7 +222,7 @@ Returned shape (example):
|
|
|
219
222
|
AUTH_SERVER_URL=http://localhost:5312
|
|
220
223
|
SEAMLESS_SERVICE_TOKEN=generated-secret
|
|
221
224
|
COOKIE_SIGNING_KEY=local-dev-key
|
|
222
|
-
FRONTEND_URL=
|
|
225
|
+
FRONTEND_URL=http://localhost:5001
|
|
223
226
|
```
|
|
224
227
|
|
|
225
228
|
---
|
package/dist/index.d.ts
CHANGED
|
@@ -1,15 +1,27 @@
|
|
|
1
1
|
import { Router, Request, Response, NextFunction, RequestHandler } from 'express';
|
|
2
|
-
import { JwtPayload } from 'jsonwebtoken';
|
|
3
2
|
|
|
4
|
-
|
|
3
|
+
type SeamlessAuthServerOptions = {
|
|
5
4
|
authServerUrl: string;
|
|
5
|
+
cookieSecret: string;
|
|
6
|
+
serviceSecret: string;
|
|
7
|
+
issuer: string;
|
|
8
|
+
audience: string;
|
|
9
|
+
jwksKid?: string;
|
|
6
10
|
cookieDomain?: string;
|
|
7
11
|
accessCookieName?: string;
|
|
8
12
|
registrationCookieName?: string;
|
|
9
13
|
refreshCookieName?: string;
|
|
10
14
|
preAuthCookieName?: string;
|
|
15
|
+
};
|
|
16
|
+
interface SeamlessAuthUser {
|
|
17
|
+
id: string;
|
|
18
|
+
sub: string;
|
|
19
|
+
roles: string[];
|
|
20
|
+
email: string;
|
|
21
|
+
phone: string;
|
|
22
|
+
iat?: number;
|
|
23
|
+
exp?: number;
|
|
11
24
|
}
|
|
12
|
-
|
|
13
25
|
/**
|
|
14
26
|
* Creates an Express Router that proxies all authentication traffic to a Seamless Auth server.
|
|
15
27
|
*
|
|
@@ -38,6 +50,9 @@ interface SeamlessAuthServerOptions {
|
|
|
38
50
|
* app.use("/auth", createSeamlessAuthServer({
|
|
39
51
|
* authServerUrl: "https://identifier.seamlessauth.com",
|
|
40
52
|
* cookieDomain: "mycompany.com",
|
|
53
|
+
* cookieSecret: "someLongRandomValue"
|
|
54
|
+
* serviceSecret: "someLongRandomValueToo"
|
|
55
|
+
* jwksKid: "dev-main"
|
|
41
56
|
* accessCookieName: "sa_access",
|
|
42
57
|
* registrationCookieName: "sa_registration",
|
|
43
58
|
* refreshCookieName: "sa_refresh",
|
|
@@ -46,6 +61,9 @@ interface SeamlessAuthServerOptions {
|
|
|
46
61
|
*
|
|
47
62
|
* @param opts - Configuration options for the Seamless Auth proxy:
|
|
48
63
|
* - `authServerUrl` — Base URL of your Seamless Auth instance (required)
|
|
64
|
+
* - `cookieSecret` — The value to encode your cookies secrets with (required)
|
|
65
|
+
* - `serviceSecret` - An machine to machine shared secret that matches your auth servers (required)
|
|
66
|
+
* - `jwksKid` - The active jwks KID
|
|
49
67
|
* - `cookieDomain` — Domain attribute applied to all auth cookies
|
|
50
68
|
* - `accessCookieName` — Name of the session access cookie
|
|
51
69
|
* - `registrationCookieName` — Name of the ephemeral registration cookie
|
|
@@ -56,6 +74,10 @@ interface SeamlessAuthServerOptions {
|
|
|
56
74
|
*/
|
|
57
75
|
declare function createSeamlessAuthServer(opts: SeamlessAuthServerOptions): Router;
|
|
58
76
|
|
|
77
|
+
interface RequireAuthOptions {
|
|
78
|
+
cookieName?: string;
|
|
79
|
+
cookieSecret: string;
|
|
80
|
+
}
|
|
59
81
|
/**
|
|
60
82
|
* Express middleware that enforces authentication using Seamless Auth cookies.
|
|
61
83
|
*
|
|
@@ -106,9 +128,6 @@ declare function createSeamlessAuthServer(opts: SeamlessAuthServerOptions): Rout
|
|
|
106
128
|
* @returns An Express middleware function that enforces Seamless Auth
|
|
107
129
|
* authentication on incoming requests.
|
|
108
130
|
*/
|
|
109
|
-
interface AuthenticatedRequest extends Request {
|
|
110
|
-
user?: JwtPayload;
|
|
111
|
-
}
|
|
112
131
|
interface RequireAuthOptions {
|
|
113
132
|
cookieName?: string;
|
|
114
133
|
cookieSecret: string;
|
|
@@ -121,32 +140,22 @@ interface RequireAuthOptions {
|
|
|
121
140
|
* - This middleware does NOT attempt token refresh.
|
|
122
141
|
* - Refresh is handled upstream by ensureCookies().
|
|
123
142
|
*/
|
|
124
|
-
declare function requireAuth(opts: RequireAuthOptions): (req:
|
|
143
|
+
declare function requireAuth(opts: RequireAuthOptions): (req: Request, res: Response, next: NextFunction) => void;
|
|
125
144
|
|
|
126
145
|
/**
|
|
127
|
-
* Express middleware that enforces role-based authorization for Seamless Auth
|
|
146
|
+
* Express middleware that enforces role-based authorization for Seamless Auth.
|
|
128
147
|
*
|
|
129
|
-
* This
|
|
130
|
-
*
|
|
131
|
-
*
|
|
132
|
-
* of several, when an array is provided).
|
|
148
|
+
* This middleware assumes `requireAuth()` has already:
|
|
149
|
+
* - authenticated the request
|
|
150
|
+
* - populated `req.user` with the authenticated session payload
|
|
133
151
|
*
|
|
134
|
-
*
|
|
135
|
-
*
|
|
152
|
+
* `requireRole` performs **authorization only**. It does not inspect cookies,
|
|
153
|
+
* verify tokens, or read environment variables.
|
|
136
154
|
*
|
|
137
|
-
*
|
|
138
|
-
*
|
|
139
|
-
* - Ensures the authenticated user includes the specified role(s)
|
|
140
|
-
* - Blocks unauthorized access with a standardized JSON 403 response
|
|
155
|
+
* If any of the required roles are present on the user, access is granted.
|
|
156
|
+
* Otherwise, a 403 Forbidden response is returned.
|
|
141
157
|
*
|
|
142
|
-
* ###
|
|
143
|
-
* - **requiredRole** — A role (string) or list of roles the user must have.
|
|
144
|
-
* If an array is provided, *any* matching role grants access.
|
|
145
|
-
* - **cookieName** — Optional name of the access cookie to inspect.
|
|
146
|
-
* Defaults to `"seamless-access"`, but typically not needed because
|
|
147
|
-
* `requireAuth` is expected to run first.
|
|
148
|
-
*
|
|
149
|
-
* ### Example
|
|
158
|
+
* * ### Example
|
|
150
159
|
* ```ts
|
|
151
160
|
* // Require a single role
|
|
152
161
|
* app.get("/admin/users",
|
|
@@ -163,13 +172,10 @@ declare function requireAuth(opts: RequireAuthOptions): (req: AuthenticatedReque
|
|
|
163
172
|
* requireRole(["admin", "supervisor"]),
|
|
164
173
|
* updateSettingsHandler
|
|
165
174
|
* );
|
|
166
|
-
* ```
|
|
167
175
|
*
|
|
168
|
-
* @param
|
|
169
|
-
* @param cookieName - Optional access cookie name (defaults to `seamless-access`).
|
|
170
|
-
* @returns An Express middleware function enforcing role-based access control.
|
|
176
|
+
* @param requiredRoles - A role or list of roles required to access the route
|
|
171
177
|
*/
|
|
172
|
-
declare function requireRole(
|
|
178
|
+
declare function requireRole(requiredRoles: string | string[]): RequestHandler;
|
|
173
179
|
|
|
174
180
|
interface EnsureCookiesMiddlewareOptions {
|
|
175
181
|
authServerUrl: string;
|
|
@@ -184,10 +190,8 @@ interface EnsureCookiesMiddlewareOptions {
|
|
|
184
190
|
audience: string;
|
|
185
191
|
keyId: string;
|
|
186
192
|
}
|
|
187
|
-
declare function createEnsureCookiesMiddleware(opts: EnsureCookiesMiddlewareOptions): (req: Request
|
|
188
|
-
cookiePayload?: any;
|
|
189
|
-
}, res: Response, next: NextFunction) => Promise<void>;
|
|
193
|
+
declare function createEnsureCookiesMiddleware(opts: EnsureCookiesMiddlewareOptions): (req: Request, res: Response, next: NextFunction) => Promise<void>;
|
|
190
194
|
|
|
191
|
-
declare function getSeamlessUser
|
|
195
|
+
declare function getSeamlessUser(req: Request, opts: SeamlessAuthServerOptions): Promise<any>;
|
|
192
196
|
|
|
193
|
-
export { type SeamlessAuthServerOptions, createEnsureCookiesMiddleware, createSeamlessAuthServer as default, getSeamlessUser, requireAuth, requireRole };
|
|
197
|
+
export { type SeamlessAuthServerOptions, type SeamlessAuthUser, createEnsureCookiesMiddleware, createSeamlessAuthServer as default, getSeamlessUser, requireAuth, requireRole };
|
package/dist/index.js
CHANGED
|
@@ -98,7 +98,7 @@ function applyResult(res, req, result, opts, cookieSigner) {
|
|
|
98
98
|
import { loginHandler } from "@seamless-auth/core/handlers/login";
|
|
99
99
|
async function login(req, res, opts) {
|
|
100
100
|
const cookieSigner = {
|
|
101
|
-
secret:
|
|
101
|
+
secret: opts.cookieSecret,
|
|
102
102
|
secure: process.env.NODE_ENV === "production",
|
|
103
103
|
sameSite: process.env.NODE_ENV === "production" ? "none" : "lax"
|
|
104
104
|
};
|
|
@@ -138,16 +138,16 @@ import { finishLoginHandler } from "@seamless-auth/core/handlers/finishLogin";
|
|
|
138
138
|
|
|
139
139
|
// src/internal/buildAuthorization.ts
|
|
140
140
|
import { createServiceToken } from "@seamless-auth/core";
|
|
141
|
-
function buildServiceAuthorization(req) {
|
|
142
|
-
if (!req.cookiePayload?.sub) {
|
|
141
|
+
function buildServiceAuthorization(req, opts) {
|
|
142
|
+
if (!req.cookiePayload?.sub && !req.user.sub) {
|
|
143
143
|
return void 0;
|
|
144
144
|
}
|
|
145
145
|
const token = createServiceToken({
|
|
146
|
-
subject: req.cookiePayload.sub,
|
|
147
|
-
issuer:
|
|
148
|
-
audience:
|
|
149
|
-
serviceSecret:
|
|
150
|
-
keyId: "dev-main"
|
|
146
|
+
subject: req.cookiePayload?.sub || req.user.sub,
|
|
147
|
+
issuer: opts.issuer,
|
|
148
|
+
audience: opts.audience,
|
|
149
|
+
serviceSecret: opts.serviceSecret,
|
|
150
|
+
keyId: opts.jwksKid || "dev-main"
|
|
151
151
|
});
|
|
152
152
|
return `Bearer ${token}`;
|
|
153
153
|
}
|
|
@@ -155,11 +155,11 @@ function buildServiceAuthorization(req) {
|
|
|
155
155
|
// src/handlers/finishLogin.ts
|
|
156
156
|
async function finishLogin(req, res, opts) {
|
|
157
157
|
const cookieSigner = {
|
|
158
|
-
secret:
|
|
158
|
+
secret: opts.cookieSecret,
|
|
159
159
|
secure: process.env.NODE_ENV === "production",
|
|
160
160
|
sameSite: process.env.NODE_ENV === "production" ? "none" : "lax"
|
|
161
161
|
};
|
|
162
|
-
const authorization = buildServiceAuthorization(req);
|
|
162
|
+
const authorization = buildServiceAuthorization(req, opts);
|
|
163
163
|
const result = await finishLoginHandler(
|
|
164
164
|
{ body: req.body, authorization },
|
|
165
165
|
{
|
|
@@ -196,7 +196,7 @@ async function finishLogin(req, res, opts) {
|
|
|
196
196
|
import { registerHandler } from "@seamless-auth/core/handlers/register";
|
|
197
197
|
async function register(req, res, opts) {
|
|
198
198
|
const cookieSigner = {
|
|
199
|
-
secret:
|
|
199
|
+
secret: opts.cookieSecret,
|
|
200
200
|
secure: process.env.NODE_ENV === "production",
|
|
201
201
|
sameSite: process.env.NODE_ENV === "production" ? "none" : "lax"
|
|
202
202
|
};
|
|
@@ -235,11 +235,11 @@ async function register(req, res, opts) {
|
|
|
235
235
|
import { finishRegisterHandler } from "@seamless-auth/core/handlers/finishRegister";
|
|
236
236
|
async function finishRegister(req, res, opts) {
|
|
237
237
|
const cookieSigner = {
|
|
238
|
-
secret:
|
|
238
|
+
secret: opts.cookieSecret,
|
|
239
239
|
secure: process.env.NODE_ENV === "production",
|
|
240
240
|
sameSite: process.env.NODE_ENV === "production" ? "none" : "lax"
|
|
241
241
|
};
|
|
242
|
-
const authorization = buildServiceAuthorization(req);
|
|
242
|
+
const authorization = buildServiceAuthorization(req, opts);
|
|
243
243
|
const result = await finishRegisterHandler(
|
|
244
244
|
{ body: req.body, authorization },
|
|
245
245
|
{
|
|
@@ -269,13 +269,13 @@ async function finishRegister(req, res, opts) {
|
|
|
269
269
|
if (result.error) {
|
|
270
270
|
return res.status(result.status).json(result.error);
|
|
271
271
|
}
|
|
272
|
-
res.status(result.status).
|
|
272
|
+
res.status(result.status).json({ message: "success" });
|
|
273
273
|
}
|
|
274
274
|
|
|
275
275
|
// src/handlers/me.ts
|
|
276
276
|
import { meHandler } from "@seamless-auth/core/handlers/me";
|
|
277
277
|
async function me(req, res, opts) {
|
|
278
|
-
const authorization = buildServiceAuthorization(req);
|
|
278
|
+
const authorization = buildServiceAuthorization(req, opts);
|
|
279
279
|
const result = await meHandler({
|
|
280
280
|
authServerUrl: opts.authServerUrl,
|
|
281
281
|
preAuthCookieName: opts.preAuthCookieName,
|
|
@@ -305,10 +305,50 @@ async function logout(req, res, opts) {
|
|
|
305
305
|
res.status(result.status).end();
|
|
306
306
|
}
|
|
307
307
|
|
|
308
|
+
// src/handlers/pollMagicLinkConfirmation.ts
|
|
309
|
+
import { pollMagicLinkConfirmationHandler } from "@seamless-auth/core/handlers/pollMagicLinkConfirmationHandler";
|
|
310
|
+
async function pollMagicLinkConfirmation(req, res, opts) {
|
|
311
|
+
const cookieSigner = {
|
|
312
|
+
secret: opts.cookieSecret,
|
|
313
|
+
secure: process.env.NODE_ENV === "production",
|
|
314
|
+
sameSite: process.env.NODE_ENV === "production" ? "none" : "lax"
|
|
315
|
+
};
|
|
316
|
+
const authorization = buildServiceAuthorization(req, opts);
|
|
317
|
+
const result = await pollMagicLinkConfirmationHandler(
|
|
318
|
+
{ authorization },
|
|
319
|
+
{
|
|
320
|
+
authServerUrl: opts.authServerUrl,
|
|
321
|
+
cookieDomain: opts.cookieDomain,
|
|
322
|
+
accessCookieName: opts.accessCookieName,
|
|
323
|
+
refreshCookieName: opts.refreshCookieName
|
|
324
|
+
}
|
|
325
|
+
);
|
|
326
|
+
if (!cookieSigner.secret) {
|
|
327
|
+
throw new Error("Missing COOKIE_SIGNING_KEY");
|
|
328
|
+
}
|
|
329
|
+
if (result.setCookies) {
|
|
330
|
+
for (const c of result.setCookies) {
|
|
331
|
+
setSessionCookie(
|
|
332
|
+
res,
|
|
333
|
+
{
|
|
334
|
+
name: c.name,
|
|
335
|
+
payload: c.value,
|
|
336
|
+
domain: c.domain,
|
|
337
|
+
ttlSeconds: c.ttl
|
|
338
|
+
},
|
|
339
|
+
cookieSigner
|
|
340
|
+
);
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
if (result.error) {
|
|
344
|
+
return res.status(result.status).json(result.error);
|
|
345
|
+
}
|
|
346
|
+
res.status(result.status).json(result.body).end();
|
|
347
|
+
}
|
|
348
|
+
|
|
308
349
|
// src/createServer.ts
|
|
309
350
|
import {
|
|
310
|
-
authFetch
|
|
311
|
-
createServiceToken as createServiceToken2
|
|
351
|
+
authFetch
|
|
312
352
|
} from "@seamless-auth/core";
|
|
313
353
|
function createSeamlessAuthServer(opts) {
|
|
314
354
|
const r = express.Router();
|
|
@@ -316,6 +356,11 @@ function createSeamlessAuthServer(opts) {
|
|
|
316
356
|
r.use(cookieParser());
|
|
317
357
|
const resolvedOpts = {
|
|
318
358
|
authServerUrl: opts.authServerUrl,
|
|
359
|
+
issuer: opts.issuer,
|
|
360
|
+
audience: opts.audience,
|
|
361
|
+
cookieSecret: opts.cookieSecret,
|
|
362
|
+
serviceSecret: opts.serviceSecret,
|
|
363
|
+
jwksKid: opts.jwksKid ?? "dev-main",
|
|
319
364
|
cookieDomain: opts.cookieDomain ?? "",
|
|
320
365
|
accessCookieName: opts.accessCookieName ?? "seamless-access",
|
|
321
366
|
registrationCookieName: opts.registrationCookieName ?? "seamless-ephemeral",
|
|
@@ -339,14 +384,11 @@ function createSeamlessAuthServer(opts) {
|
|
|
339
384
|
res.status(401).json({ error: "registeration session required" });
|
|
340
385
|
return;
|
|
341
386
|
}
|
|
342
|
-
const authorization =
|
|
387
|
+
const authorization = buildServiceAuthorization(req, resolvedOpts);
|
|
388
|
+
const options = method == "GET" ? { method, authorization } : { method, authorization, body: req.body };
|
|
343
389
|
const upstream = await authFetch(
|
|
344
390
|
`${resolvedOpts.authServerUrl}/${path}`,
|
|
345
|
-
|
|
346
|
-
method,
|
|
347
|
-
body: req.body,
|
|
348
|
-
authorization
|
|
349
|
-
}
|
|
391
|
+
options
|
|
350
392
|
);
|
|
351
393
|
const data = await upstream.json();
|
|
352
394
|
res.status(upstream.status).json(data);
|
|
@@ -359,26 +401,13 @@ function createSeamlessAuthServer(opts) {
|
|
|
359
401
|
registrationCookieName: resolvedOpts.registrationCookieName,
|
|
360
402
|
refreshCookieName: resolvedOpts.refreshCookieName,
|
|
361
403
|
preAuthCookieName: resolvedOpts.preAuthCookieName,
|
|
362
|
-
cookieSecret:
|
|
363
|
-
serviceSecret:
|
|
364
|
-
issuer:
|
|
365
|
-
audience:
|
|
366
|
-
keyId:
|
|
404
|
+
cookieSecret: resolvedOpts.cookieSecret,
|
|
405
|
+
serviceSecret: resolvedOpts.serviceSecret,
|
|
406
|
+
issuer: resolvedOpts.issuer,
|
|
407
|
+
audience: resolvedOpts.authServerUrl,
|
|
408
|
+
keyId: resolvedOpts.jwksKid
|
|
367
409
|
})
|
|
368
410
|
);
|
|
369
|
-
function buildServiceAuthorization2(req) {
|
|
370
|
-
if (!req.cookiePayload?.sub) {
|
|
371
|
-
return void 0;
|
|
372
|
-
}
|
|
373
|
-
const token = createServiceToken2({
|
|
374
|
-
subject: req.cookiePayload.sub,
|
|
375
|
-
issuer: process.env.APP_ORIGIN,
|
|
376
|
-
audience: process.env.AUTH_SERVER_URL,
|
|
377
|
-
serviceSecret: process.env.API_SERVICE_TOKEN,
|
|
378
|
-
keyId: "dev-main"
|
|
379
|
-
});
|
|
380
|
-
return `Bearer ${token}`;
|
|
381
|
-
}
|
|
382
411
|
r.post(
|
|
383
412
|
"/webAuthn/login/start",
|
|
384
413
|
proxyWithIdentity("webAuthn/login/start", "preAuth")
|
|
@@ -419,6 +448,19 @@ function createSeamlessAuthServer(opts) {
|
|
|
419
448
|
"/users/credentials",
|
|
420
449
|
proxyWithIdentity("users/credentials", "access")
|
|
421
450
|
);
|
|
451
|
+
r.get("/magic-link", proxyWithIdentity("magic-link", "preAuth", "GET"));
|
|
452
|
+
r.get("/magic-link/verify/:token", async (req, res) => {
|
|
453
|
+
const upstream = await authFetch(
|
|
454
|
+
`${resolvedOpts.authServerUrl}/magic-link/verify/${req.params.token}`,
|
|
455
|
+
{ method: "GET" }
|
|
456
|
+
);
|
|
457
|
+
const data = await upstream.json();
|
|
458
|
+
res.status(upstream.status).json(data);
|
|
459
|
+
});
|
|
460
|
+
r.get(
|
|
461
|
+
"/magic-link/check",
|
|
462
|
+
(req, res) => pollMagicLinkConfirmation(req, res, resolvedOpts)
|
|
463
|
+
);
|
|
422
464
|
return r;
|
|
423
465
|
}
|
|
424
466
|
|
|
@@ -432,58 +474,78 @@ function requireAuth(opts) {
|
|
|
432
474
|
return function(req, res, next) {
|
|
433
475
|
const token = req.cookies?.[cookieName];
|
|
434
476
|
if (!token) {
|
|
435
|
-
|
|
477
|
+
console.error(
|
|
478
|
+
"[SEAMLESS-AUTH-EXPRESS] - (requireAuth) - Missing expected cookie. Ensure you are using `cookieParser` in your express server",
|
|
479
|
+
cookieName
|
|
480
|
+
);
|
|
481
|
+
res.status(401).json({
|
|
482
|
+
error: "Failed to find authentication token required"
|
|
483
|
+
});
|
|
436
484
|
return;
|
|
437
485
|
}
|
|
438
486
|
const payload = verifyCookieJwt(token, cookieSecret);
|
|
439
|
-
if (!payload) {
|
|
440
|
-
res.status(401).json({
|
|
487
|
+
if (!payload || !payload.sub) {
|
|
488
|
+
res.status(401).json({
|
|
489
|
+
error: "Invalid or expired session"
|
|
490
|
+
});
|
|
441
491
|
return;
|
|
442
492
|
}
|
|
443
|
-
|
|
493
|
+
const user = {
|
|
494
|
+
id: payload.sub,
|
|
495
|
+
sub: payload.sub,
|
|
496
|
+
// TODO: Silly to store the same value twice. Search every where its used and phase this out.
|
|
497
|
+
roles: Array.isArray(payload.roles) ? payload.roles : [],
|
|
498
|
+
email: payload.email,
|
|
499
|
+
phone: payload.phone,
|
|
500
|
+
iat: payload.iat,
|
|
501
|
+
exp: payload.exp
|
|
502
|
+
};
|
|
503
|
+
req.user = user;
|
|
444
504
|
next();
|
|
445
505
|
};
|
|
446
506
|
}
|
|
447
507
|
|
|
448
508
|
// src/middleware/requireRole.ts
|
|
449
|
-
|
|
450
|
-
|
|
509
|
+
function requireRole(requiredRoles) {
|
|
510
|
+
const roles = Array.isArray(requiredRoles) ? requiredRoles : [requiredRoles];
|
|
451
511
|
return (req, res, next) => {
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
"[SeamlessAuth] COOKIE_SIGNING_KEY missing \u2014 requireRole will always fail."
|
|
457
|
-
);
|
|
458
|
-
throw new Error("Missing required env COOKIE_SIGNING_KEY");
|
|
459
|
-
}
|
|
460
|
-
const token = req.cookies?.[cookieName];
|
|
461
|
-
if (!token) {
|
|
462
|
-
res.status(401).json({ error: "Missing access cookie" });
|
|
463
|
-
return;
|
|
464
|
-
}
|
|
465
|
-
const payload = jwt2.verify(token, COOKIE_SECRET, {
|
|
466
|
-
algorithms: ["HS256"]
|
|
512
|
+
const user = req.user;
|
|
513
|
+
if (!user) {
|
|
514
|
+
res.status(401).json({
|
|
515
|
+
error: "Authentication required"
|
|
467
516
|
});
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
res.status(401).json({ error: "Invalid or expired access cookie" });
|
|
517
|
+
return;
|
|
518
|
+
}
|
|
519
|
+
if (!Array.isArray(user.roles)) {
|
|
520
|
+
res.status(403).json({
|
|
521
|
+
error: "User has no roles assigned"
|
|
522
|
+
});
|
|
523
|
+
return;
|
|
476
524
|
}
|
|
525
|
+
const hasRole = roles.some((role) => user.roles.includes(role));
|
|
526
|
+
if (!hasRole) {
|
|
527
|
+
res.status(403).json({
|
|
528
|
+
error: "Insufficient role",
|
|
529
|
+
required: roles,
|
|
530
|
+
actual: user.roles
|
|
531
|
+
});
|
|
532
|
+
return;
|
|
533
|
+
}
|
|
534
|
+
next();
|
|
477
535
|
};
|
|
478
536
|
}
|
|
479
537
|
|
|
480
538
|
// src/getSeamlessUser.ts
|
|
481
|
-
import {
|
|
482
|
-
|
|
539
|
+
import {
|
|
540
|
+
getSeamlessUser as getSeamlessUserCore
|
|
541
|
+
} from "@seamless-auth/core";
|
|
542
|
+
async function getSeamlessUser(req, opts) {
|
|
543
|
+
const authorization = buildServiceAuthorization(req, opts);
|
|
483
544
|
return getSeamlessUserCore(req.cookies ?? {}, {
|
|
484
|
-
authServerUrl,
|
|
485
|
-
cookieSecret:
|
|
486
|
-
cookieName
|
|
545
|
+
authServerUrl: opts.authServerUrl,
|
|
546
|
+
cookieSecret: opts.cookieSecret,
|
|
547
|
+
cookieName: opts.accessCookieName ?? "seamless-access",
|
|
548
|
+
authorization
|
|
487
549
|
});
|
|
488
550
|
}
|
|
489
551
|
|
package/package.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@seamless-auth/express",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.1.0",
|
|
4
4
|
"description": "Express adapter for Seamless Auth passwordless authentication",
|
|
5
5
|
"license": "AGPL-3.0-only",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"main": "dist/index.js",
|
|
8
8
|
"types": "./dist/index.d.ts",
|
|
9
|
-
"author": "Fells Code LLC",
|
|
9
|
+
"author": "Fells Code, LLC",
|
|
10
10
|
"files": [
|
|
11
11
|
"dist",
|
|
12
12
|
"LICENSE",
|
|
@@ -33,11 +33,11 @@
|
|
|
33
33
|
"url": "https://github.com/fells-code/seamless-auth-server/tree/main/packages/express"
|
|
34
34
|
},
|
|
35
35
|
"peerDependencies": {
|
|
36
|
-
"express": ">=4.
|
|
37
|
-
"
|
|
36
|
+
"@types/express": ">=4.17.0",
|
|
37
|
+
"express": ">=4.18.0"
|
|
38
38
|
},
|
|
39
39
|
"dependencies": {
|
|
40
|
-
"@seamless-auth/core": "^0.
|
|
40
|
+
"@seamless-auth/core": "^0.2.0",
|
|
41
41
|
"cookie-parser": "^1.4.6",
|
|
42
42
|
"jsonwebtoken": "^9.0.3"
|
|
43
43
|
},
|
|
@@ -46,8 +46,8 @@
|
|
|
46
46
|
"@types/jest": "^29.5.14",
|
|
47
47
|
"@types/jsonwebtoken": "^9.0.10",
|
|
48
48
|
"jest": "^29.7.0",
|
|
49
|
-
"ts-node": "^10.9.2",
|
|
50
49
|
"supertest": "^7.2.2",
|
|
50
|
+
"ts-node": "^10.9.2",
|
|
51
51
|
"tsup": "^8.5.1",
|
|
52
52
|
"typescript": "^5.5.0"
|
|
53
53
|
},
|