@seamless-auth/express 0.0.2-beta.1 → 0.0.2-beta.11
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/LICENSE +79 -0
- package/LICENSE.md +26 -0
- package/README.md +102 -123
- package/dist/index.d.ts +190 -7
- package/dist/index.js +501 -5
- package/package.json +31 -14
- package/dist/createServer.d.ts +0 -48
- package/dist/createServer.d.ts.map +0 -1
- package/dist/createServer.js +0 -164
- 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 -37
- 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 -51
- package/dist/internal/getSeamlessUser.d.ts.map +0 -1
- package/dist/internal/getSeamlessUser.js +0 -72
- 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 -78
- package/dist/middleware/requireAuth.d.ts +0 -53
- package/dist/middleware/requireAuth.d.ts.map +0 -1
- package/dist/middleware/requireAuth.js +0 -118
- package/dist/middleware/requireRole.d.ts +0 -49
- package/dist/middleware/requireRole.d.ts.map +0 -1
- package/dist/middleware/requireRole.js +0 -77
- package/dist/types.d.ts +0 -9
- package/dist/types.d.ts.map +0 -1
- package/dist/types.js +0 -1
package/dist/index.js
CHANGED
|
@@ -1,5 +1,501 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
1
|
+
// src/createServer.ts
|
|
2
|
+
import express from "express";
|
|
3
|
+
import cookieParser from "cookie-parser";
|
|
4
|
+
|
|
5
|
+
// src/middleware/ensureCookies.ts
|
|
6
|
+
import { ensureCookies } from "@seamless-auth/core";
|
|
7
|
+
|
|
8
|
+
// src/internal/cookie.ts
|
|
9
|
+
import jwt from "jsonwebtoken";
|
|
10
|
+
function setSessionCookie(res, opts, signer) {
|
|
11
|
+
const token = jwt.sign(opts.payload, signer.secret, {
|
|
12
|
+
algorithm: "HS256",
|
|
13
|
+
expiresIn: `${opts.ttlSeconds}s`
|
|
14
|
+
});
|
|
15
|
+
res.cookie(opts.name, token, {
|
|
16
|
+
httpOnly: true,
|
|
17
|
+
secure: signer.secure,
|
|
18
|
+
sameSite: signer.sameSite,
|
|
19
|
+
path: "/",
|
|
20
|
+
domain: opts.domain,
|
|
21
|
+
maxAge: Number(opts.ttlSeconds) * 1e3
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
function clearSessionCookie(res, domain, name) {
|
|
25
|
+
res.clearCookie(name, { domain, path: "/" });
|
|
26
|
+
}
|
|
27
|
+
function clearAllCookies(res, domain, ...cookieNames) {
|
|
28
|
+
for (const name of cookieNames) {
|
|
29
|
+
res.clearCookie(name, { domain, path: "/" });
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// src/middleware/ensureCookies.ts
|
|
34
|
+
function createEnsureCookiesMiddleware(opts) {
|
|
35
|
+
if (!opts.cookieSecret) {
|
|
36
|
+
throw new Error("Missing cookieSecret");
|
|
37
|
+
}
|
|
38
|
+
if (!opts.serviceSecret) {
|
|
39
|
+
throw new Error("Missing serviceSecret");
|
|
40
|
+
}
|
|
41
|
+
const cookieSigner = {
|
|
42
|
+
secret: opts.cookieSecret,
|
|
43
|
+
secure: process.env.NODE_ENV === "production",
|
|
44
|
+
sameSite: process.env.NODE_ENV === "production" ? "none" : "lax"
|
|
45
|
+
};
|
|
46
|
+
return async function ensureCookiesMiddleware(req, res, next) {
|
|
47
|
+
const result = await ensureCookies(
|
|
48
|
+
{
|
|
49
|
+
path: req.path,
|
|
50
|
+
cookies: req.cookies ?? {}
|
|
51
|
+
},
|
|
52
|
+
{
|
|
53
|
+
authServerUrl: opts.authServerUrl,
|
|
54
|
+
cookieDomain: opts.cookieDomain,
|
|
55
|
+
accessCookieName: opts.accessCookieName,
|
|
56
|
+
registrationCookieName: opts.registrationCookieName,
|
|
57
|
+
refreshCookieName: opts.refreshCookieName,
|
|
58
|
+
preAuthCookieName: opts.preAuthCookieName,
|
|
59
|
+
cookieSecret: opts.cookieSecret,
|
|
60
|
+
serviceSecret: opts.serviceSecret,
|
|
61
|
+
issuer: opts.issuer,
|
|
62
|
+
audience: opts.audience,
|
|
63
|
+
keyId: opts.keyId
|
|
64
|
+
}
|
|
65
|
+
);
|
|
66
|
+
applyResult(res, req, result, opts, cookieSigner);
|
|
67
|
+
if (result.type === "error") return;
|
|
68
|
+
next();
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
function applyResult(res, req, result, opts, cookieSigner) {
|
|
72
|
+
if (result.clearCookies?.length) {
|
|
73
|
+
clearAllCookies(res, opts.cookieDomain, ...result.clearCookies);
|
|
74
|
+
}
|
|
75
|
+
if (result.setCookies) {
|
|
76
|
+
for (const c of result.setCookies) {
|
|
77
|
+
setSessionCookie(
|
|
78
|
+
res,
|
|
79
|
+
{
|
|
80
|
+
name: c.name,
|
|
81
|
+
payload: c.value,
|
|
82
|
+
domain: c.domain,
|
|
83
|
+
ttlSeconds: c.ttl
|
|
84
|
+
},
|
|
85
|
+
cookieSigner
|
|
86
|
+
);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
if (result.user) {
|
|
90
|
+
req.cookiePayload = result.user;
|
|
91
|
+
}
|
|
92
|
+
if (result.type === "error") {
|
|
93
|
+
res.status(result.status ?? 401).json({ error: result.error });
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// src/handlers/login.ts
|
|
98
|
+
import { loginHandler } from "@seamless-auth/core/handlers/login";
|
|
99
|
+
async function login(req, res, opts) {
|
|
100
|
+
const cookieSigner = {
|
|
101
|
+
secret: opts.cookieSecret,
|
|
102
|
+
secure: process.env.NODE_ENV === "production",
|
|
103
|
+
sameSite: process.env.NODE_ENV === "production" ? "none" : "lax"
|
|
104
|
+
};
|
|
105
|
+
const result = await loginHandler(
|
|
106
|
+
{ body: req.body },
|
|
107
|
+
{
|
|
108
|
+
authServerUrl: opts.authServerUrl,
|
|
109
|
+
cookieDomain: opts.cookieDomain,
|
|
110
|
+
preAuthCookieName: opts.preAuthCookieName
|
|
111
|
+
}
|
|
112
|
+
);
|
|
113
|
+
if (!cookieSigner.secret) {
|
|
114
|
+
throw new Error("Missing COOKIE_SIGNING_KEY");
|
|
115
|
+
}
|
|
116
|
+
if (result.setCookies) {
|
|
117
|
+
for (const c of result.setCookies) {
|
|
118
|
+
setSessionCookie(
|
|
119
|
+
res,
|
|
120
|
+
{
|
|
121
|
+
name: c.name,
|
|
122
|
+
payload: c.value,
|
|
123
|
+
domain: c.domain,
|
|
124
|
+
ttlSeconds: c.ttl
|
|
125
|
+
},
|
|
126
|
+
cookieSigner
|
|
127
|
+
);
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
if (result.error) {
|
|
131
|
+
return res.status(result.status).json(result.error);
|
|
132
|
+
}
|
|
133
|
+
res.status(result.status).end();
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// src/handlers/finishLogin.ts
|
|
137
|
+
import { finishLoginHandler } from "@seamless-auth/core/handlers/finishLogin";
|
|
138
|
+
|
|
139
|
+
// src/internal/buildAuthorization.ts
|
|
140
|
+
import { createServiceToken } from "@seamless-auth/core";
|
|
141
|
+
function buildServiceAuthorization(req) {
|
|
142
|
+
if (!req.cookiePayload?.sub && !req.user.sub) {
|
|
143
|
+
return void 0;
|
|
144
|
+
}
|
|
145
|
+
const token = createServiceToken({
|
|
146
|
+
subject: req.cookiePayload?.sub || req.user.sub,
|
|
147
|
+
issuer: process.env.APP_ORIGIN,
|
|
148
|
+
audience: process.env.AUTH_SERVER_URL,
|
|
149
|
+
serviceSecret: process.env.API_SERVICE_TOKEN,
|
|
150
|
+
keyId: "dev-main"
|
|
151
|
+
});
|
|
152
|
+
return `Bearer ${token}`;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// src/handlers/finishLogin.ts
|
|
156
|
+
async function finishLogin(req, res, opts) {
|
|
157
|
+
const cookieSigner = {
|
|
158
|
+
secret: opts.cookieSecret,
|
|
159
|
+
secure: process.env.NODE_ENV === "production",
|
|
160
|
+
sameSite: process.env.NODE_ENV === "production" ? "none" : "lax"
|
|
161
|
+
};
|
|
162
|
+
const authorization = buildServiceAuthorization(req);
|
|
163
|
+
const result = await finishLoginHandler(
|
|
164
|
+
{ body: req.body, authorization },
|
|
165
|
+
{
|
|
166
|
+
authServerUrl: opts.authServerUrl,
|
|
167
|
+
cookieDomain: opts.cookieDomain,
|
|
168
|
+
accessCookieName: opts.accessCookieName,
|
|
169
|
+
refreshCookieName: opts.refreshCookieName
|
|
170
|
+
}
|
|
171
|
+
);
|
|
172
|
+
if (!cookieSigner.secret) {
|
|
173
|
+
throw new Error("Missing COOKIE_SIGNING_KEY");
|
|
174
|
+
}
|
|
175
|
+
if (result.setCookies) {
|
|
176
|
+
for (const c of result.setCookies) {
|
|
177
|
+
setSessionCookie(
|
|
178
|
+
res,
|
|
179
|
+
{
|
|
180
|
+
name: c.name,
|
|
181
|
+
payload: c.value,
|
|
182
|
+
domain: c.domain,
|
|
183
|
+
ttlSeconds: c.ttl
|
|
184
|
+
},
|
|
185
|
+
cookieSigner
|
|
186
|
+
);
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
if (result.error) {
|
|
190
|
+
return res.status(result.status).json(result.error);
|
|
191
|
+
}
|
|
192
|
+
res.status(result.status).json(result.body).end();
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
// src/handlers/register.ts
|
|
196
|
+
import { registerHandler } from "@seamless-auth/core/handlers/register";
|
|
197
|
+
async function register(req, res, opts) {
|
|
198
|
+
const cookieSigner = {
|
|
199
|
+
secret: opts.cookieSecret,
|
|
200
|
+
secure: process.env.NODE_ENV === "production",
|
|
201
|
+
sameSite: process.env.NODE_ENV === "production" ? "none" : "lax"
|
|
202
|
+
};
|
|
203
|
+
const result = await registerHandler(
|
|
204
|
+
{ body: req.body },
|
|
205
|
+
{
|
|
206
|
+
authServerUrl: opts.authServerUrl,
|
|
207
|
+
cookieDomain: opts.cookieDomain,
|
|
208
|
+
registrationCookieName: opts.registrationCookieName
|
|
209
|
+
}
|
|
210
|
+
);
|
|
211
|
+
if (!cookieSigner.secret) {
|
|
212
|
+
throw new Error("Missing COOKIE_SIGNING_KEY");
|
|
213
|
+
}
|
|
214
|
+
if (result.setCookies) {
|
|
215
|
+
for (const c of result.setCookies) {
|
|
216
|
+
setSessionCookie(
|
|
217
|
+
res,
|
|
218
|
+
{
|
|
219
|
+
name: opts.registrationCookieName || "seamless-auth-registraion",
|
|
220
|
+
payload: c.value,
|
|
221
|
+
domain: c.domain,
|
|
222
|
+
ttlSeconds: c.ttl
|
|
223
|
+
},
|
|
224
|
+
cookieSigner
|
|
225
|
+
);
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
if (result.error) {
|
|
229
|
+
return res.status(result.status).json(result.error);
|
|
230
|
+
}
|
|
231
|
+
res.status(result.status).json(result.body).end();
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
// src/handlers/finishRegister.ts
|
|
235
|
+
import { finishRegisterHandler } from "@seamless-auth/core/handlers/finishRegister";
|
|
236
|
+
async function finishRegister(req, res, opts) {
|
|
237
|
+
const cookieSigner = {
|
|
238
|
+
secret: opts.cookieSecret,
|
|
239
|
+
secure: process.env.NODE_ENV === "production",
|
|
240
|
+
sameSite: process.env.NODE_ENV === "production" ? "none" : "lax"
|
|
241
|
+
};
|
|
242
|
+
const authorization = buildServiceAuthorization(req);
|
|
243
|
+
const result = await finishRegisterHandler(
|
|
244
|
+
{ body: req.body, authorization },
|
|
245
|
+
{
|
|
246
|
+
authServerUrl: opts.authServerUrl,
|
|
247
|
+
cookieDomain: opts.cookieDomain,
|
|
248
|
+
accessCookieName: opts.accessCookieName,
|
|
249
|
+
refreshCookieName: opts.refreshCookieName
|
|
250
|
+
}
|
|
251
|
+
);
|
|
252
|
+
if (!cookieSigner.secret) {
|
|
253
|
+
throw new Error("Missing COOKIE_SIGNING_KEY");
|
|
254
|
+
}
|
|
255
|
+
if (result.setCookies) {
|
|
256
|
+
for (const c of result.setCookies) {
|
|
257
|
+
setSessionCookie(
|
|
258
|
+
res,
|
|
259
|
+
{
|
|
260
|
+
name: c.name,
|
|
261
|
+
payload: c.value,
|
|
262
|
+
domain: c.domain,
|
|
263
|
+
ttlSeconds: c.ttl
|
|
264
|
+
},
|
|
265
|
+
cookieSigner
|
|
266
|
+
);
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
if (result.error) {
|
|
270
|
+
return res.status(result.status).json(result.error);
|
|
271
|
+
}
|
|
272
|
+
res.status(result.status).end();
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
// src/handlers/me.ts
|
|
276
|
+
import { meHandler } from "@seamless-auth/core/handlers/me";
|
|
277
|
+
async function me(req, res, opts) {
|
|
278
|
+
const authorization = buildServiceAuthorization(req);
|
|
279
|
+
const result = await meHandler({
|
|
280
|
+
authServerUrl: opts.authServerUrl,
|
|
281
|
+
preAuthCookieName: opts.preAuthCookieName,
|
|
282
|
+
authorization
|
|
283
|
+
});
|
|
284
|
+
if (result.clearCookies) {
|
|
285
|
+
for (const name of result.clearCookies) {
|
|
286
|
+
clearSessionCookie(res, opts.cookieDomain || "", name);
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
if (result.error) {
|
|
290
|
+
return res.status(result.status).json({ error: result.error });
|
|
291
|
+
}
|
|
292
|
+
res.status(result.status).json(result.body);
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
// src/handlers/logout.ts
|
|
296
|
+
import { logoutHandler } from "@seamless-auth/core/handlers/logout";
|
|
297
|
+
async function logout(req, res, opts) {
|
|
298
|
+
const result = await logoutHandler({
|
|
299
|
+
authServerUrl: opts.authServerUrl,
|
|
300
|
+
accessCookieName: opts.accessCookieName,
|
|
301
|
+
registrationCookieName: opts.registrationCookieName,
|
|
302
|
+
refreshCookieName: opts.refreshCookieName
|
|
303
|
+
});
|
|
304
|
+
clearAllCookies(res, opts.cookieDomain || "", ...result.clearCookies);
|
|
305
|
+
res.status(result.status).end();
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
// src/createServer.ts
|
|
309
|
+
import {
|
|
310
|
+
authFetch
|
|
311
|
+
} from "@seamless-auth/core";
|
|
312
|
+
function createSeamlessAuthServer(opts) {
|
|
313
|
+
const r = express.Router();
|
|
314
|
+
r.use(express.json());
|
|
315
|
+
r.use(cookieParser());
|
|
316
|
+
const resolvedOpts = {
|
|
317
|
+
authServerUrl: opts.authServerUrl,
|
|
318
|
+
cookieSecret: opts.cookieSecret,
|
|
319
|
+
serviceSecret: opts.serviceSecret,
|
|
320
|
+
jwksKid: opts.jwksKid ?? "dev-main",
|
|
321
|
+
cookieDomain: opts.cookieDomain ?? "",
|
|
322
|
+
accessCookieName: opts.accessCookieName ?? "seamless-access",
|
|
323
|
+
registrationCookieName: opts.registrationCookieName ?? "seamless-ephemeral",
|
|
324
|
+
refreshCookieName: opts.refreshCookieName ?? "seamless-refresh",
|
|
325
|
+
preAuthCookieName: opts.preAuthCookieName ?? "seamless-ephemeral"
|
|
326
|
+
};
|
|
327
|
+
const proxyWithIdentity = (path, identity, method = "POST") => async (req, res) => {
|
|
328
|
+
if (!req.cookiePayload?.sub) {
|
|
329
|
+
res.status(401).json({ error: "unauthenticated" });
|
|
330
|
+
return;
|
|
331
|
+
}
|
|
332
|
+
if (identity === "access" && !req.cookies[resolvedOpts.accessCookieName]) {
|
|
333
|
+
res.status(401).json({ error: "access session required" });
|
|
334
|
+
return;
|
|
335
|
+
}
|
|
336
|
+
if (identity === "preAuth" && !req.cookies[resolvedOpts.preAuthCookieName]) {
|
|
337
|
+
res.status(401).json({ error: "pre-auth session required" });
|
|
338
|
+
return;
|
|
339
|
+
}
|
|
340
|
+
if (identity === "register" && !req.cookies[resolvedOpts.registrationCookieName]) {
|
|
341
|
+
res.status(401).json({ error: "registeration session required" });
|
|
342
|
+
return;
|
|
343
|
+
}
|
|
344
|
+
const authorization = buildServiceAuthorization(req);
|
|
345
|
+
const upstream = await authFetch(
|
|
346
|
+
`${resolvedOpts.authServerUrl}/${path}`,
|
|
347
|
+
{
|
|
348
|
+
method,
|
|
349
|
+
body: req.body,
|
|
350
|
+
authorization
|
|
351
|
+
}
|
|
352
|
+
);
|
|
353
|
+
const data = await upstream.json();
|
|
354
|
+
res.status(upstream.status).json(data);
|
|
355
|
+
};
|
|
356
|
+
r.use(
|
|
357
|
+
createEnsureCookiesMiddleware({
|
|
358
|
+
authServerUrl: resolvedOpts.authServerUrl,
|
|
359
|
+
cookieDomain: resolvedOpts.cookieDomain,
|
|
360
|
+
accessCookieName: resolvedOpts.accessCookieName,
|
|
361
|
+
registrationCookieName: resolvedOpts.registrationCookieName,
|
|
362
|
+
refreshCookieName: resolvedOpts.refreshCookieName,
|
|
363
|
+
preAuthCookieName: resolvedOpts.preAuthCookieName,
|
|
364
|
+
cookieSecret: resolvedOpts.cookieSecret,
|
|
365
|
+
serviceSecret: resolvedOpts.serviceSecret,
|
|
366
|
+
issuer: process.env.APP_ORIGIN,
|
|
367
|
+
audience: resolvedOpts.authServerUrl,
|
|
368
|
+
keyId: resolvedOpts.jwksKid
|
|
369
|
+
})
|
|
370
|
+
);
|
|
371
|
+
r.post(
|
|
372
|
+
"/webAuthn/login/start",
|
|
373
|
+
proxyWithIdentity("webAuthn/login/start", "preAuth")
|
|
374
|
+
);
|
|
375
|
+
r.post(
|
|
376
|
+
"/webAuthn/login/finish",
|
|
377
|
+
(req, res) => finishLogin(req, res, resolvedOpts)
|
|
378
|
+
);
|
|
379
|
+
r.get(
|
|
380
|
+
"/webAuthn/register/start",
|
|
381
|
+
proxyWithIdentity("webAuthn/register/start", "preAuth", "GET")
|
|
382
|
+
);
|
|
383
|
+
r.post(
|
|
384
|
+
"/webAuthn/register/finish",
|
|
385
|
+
(req, res) => finishRegister(req, res, resolvedOpts)
|
|
386
|
+
);
|
|
387
|
+
r.post(
|
|
388
|
+
"/otp/verify-phone-otp",
|
|
389
|
+
proxyWithIdentity("otp/verify-phone-otp", "preAuth")
|
|
390
|
+
);
|
|
391
|
+
r.post(
|
|
392
|
+
"/otp/verify-email-otp",
|
|
393
|
+
proxyWithIdentity("otp/verify-email-otp", "preAuth")
|
|
394
|
+
);
|
|
395
|
+
r.post("/login", (req, res) => login(req, res, resolvedOpts));
|
|
396
|
+
r.post(
|
|
397
|
+
"/registration/register",
|
|
398
|
+
(req, res) => register(req, res, resolvedOpts)
|
|
399
|
+
);
|
|
400
|
+
r.get("/users/me", (req, res) => me(req, res, resolvedOpts));
|
|
401
|
+
r.get("/logout", (req, res) => logout(req, res, resolvedOpts));
|
|
402
|
+
r.post("/users/update", proxyWithIdentity("users/update", "access"));
|
|
403
|
+
r.post(
|
|
404
|
+
"/users/credentials",
|
|
405
|
+
proxyWithIdentity("users/credentials", "access")
|
|
406
|
+
);
|
|
407
|
+
r.delete(
|
|
408
|
+
"/users/credentials",
|
|
409
|
+
proxyWithIdentity("users/credentials", "access")
|
|
410
|
+
);
|
|
411
|
+
return r;
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
// src/middleware/requireAuth.ts
|
|
415
|
+
import { verifyCookieJwt } from "@seamless-auth/core";
|
|
416
|
+
function requireAuth(opts) {
|
|
417
|
+
const { cookieName = "seamless-access", cookieSecret } = opts;
|
|
418
|
+
if (!cookieSecret) {
|
|
419
|
+
throw new Error("requireAuth: missing cookieSecret");
|
|
420
|
+
}
|
|
421
|
+
return function(req, res, next) {
|
|
422
|
+
const token = req.cookies?.[cookieName];
|
|
423
|
+
if (!token) {
|
|
424
|
+
res.status(401).json({
|
|
425
|
+
error: "Authentication required"
|
|
426
|
+
});
|
|
427
|
+
return;
|
|
428
|
+
}
|
|
429
|
+
const payload = verifyCookieJwt(token, cookieSecret);
|
|
430
|
+
if (!payload || !payload.sub) {
|
|
431
|
+
res.status(401).json({
|
|
432
|
+
error: "Invalid or expired session"
|
|
433
|
+
});
|
|
434
|
+
return;
|
|
435
|
+
}
|
|
436
|
+
const user = {
|
|
437
|
+
sub: payload.sub,
|
|
438
|
+
roles: Array.isArray(payload.roles) ? payload.roles : [],
|
|
439
|
+
email: payload.email,
|
|
440
|
+
phone: payload.phone,
|
|
441
|
+
iat: payload.iat,
|
|
442
|
+
exp: payload.exp
|
|
443
|
+
};
|
|
444
|
+
req.user = user;
|
|
445
|
+
next();
|
|
446
|
+
};
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
// src/middleware/requireRole.ts
|
|
450
|
+
function requireRole(requiredRoles) {
|
|
451
|
+
const roles = Array.isArray(requiredRoles) ? requiredRoles : [requiredRoles];
|
|
452
|
+
return (req, res, next) => {
|
|
453
|
+
const user = req.user;
|
|
454
|
+
if (!user) {
|
|
455
|
+
res.status(401).json({
|
|
456
|
+
error: "Authentication required"
|
|
457
|
+
});
|
|
458
|
+
return;
|
|
459
|
+
}
|
|
460
|
+
if (!Array.isArray(user.roles)) {
|
|
461
|
+
res.status(403).json({
|
|
462
|
+
error: "User has no roles assigned"
|
|
463
|
+
});
|
|
464
|
+
return;
|
|
465
|
+
}
|
|
466
|
+
const hasRole = roles.some((role) => user.roles.includes(role));
|
|
467
|
+
if (!hasRole) {
|
|
468
|
+
res.status(403).json({
|
|
469
|
+
error: "Insufficient role",
|
|
470
|
+
required: roles,
|
|
471
|
+
actual: user.roles
|
|
472
|
+
});
|
|
473
|
+
return;
|
|
474
|
+
}
|
|
475
|
+
next();
|
|
476
|
+
};
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
// src/getSeamlessUser.ts
|
|
480
|
+
import {
|
|
481
|
+
getSeamlessUser as getSeamlessUserCore
|
|
482
|
+
} from "@seamless-auth/core";
|
|
483
|
+
async function getSeamlessUser(req, opts) {
|
|
484
|
+
const authorization = buildServiceAuthorization(req);
|
|
485
|
+
return getSeamlessUserCore(req.cookies ?? {}, {
|
|
486
|
+
authServerUrl: opts.authServerUrl,
|
|
487
|
+
cookieSecret: opts.cookieSecret,
|
|
488
|
+
cookieName: opts.cookieName ?? "seamless-access",
|
|
489
|
+
authorization
|
|
490
|
+
});
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
// src/index.ts
|
|
494
|
+
var index_default = createSeamlessAuthServer;
|
|
495
|
+
export {
|
|
496
|
+
createEnsureCookiesMiddleware,
|
|
497
|
+
index_default as default,
|
|
498
|
+
getSeamlessUser,
|
|
499
|
+
requireAuth,
|
|
500
|
+
requireRole
|
|
501
|
+
};
|
package/package.json
CHANGED
|
@@ -1,37 +1,54 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@seamless-auth/express",
|
|
3
|
-
"version": "0.0.2-beta.
|
|
3
|
+
"version": "0.0.2-beta.11",
|
|
4
4
|
"description": "Express adapter for Seamless Auth passwordless authentication",
|
|
5
|
-
"license": "
|
|
5
|
+
"license": "AGPL-3.0-only",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"main": "dist/index.js",
|
|
8
8
|
"types": "./dist/index.d.ts",
|
|
9
9
|
"author": "Fells Code LLC",
|
|
10
|
+
"files": [
|
|
11
|
+
"dist",
|
|
12
|
+
"LICENSE",
|
|
13
|
+
"LICENSE.md",
|
|
14
|
+
"README.md"
|
|
15
|
+
],
|
|
16
|
+
"exports": {
|
|
17
|
+
".": {
|
|
18
|
+
"types": "./dist/index.d.ts",
|
|
19
|
+
"import": "./dist/index.js"
|
|
20
|
+
}
|
|
21
|
+
},
|
|
22
|
+
"engines": {
|
|
23
|
+
"node": ">=18"
|
|
24
|
+
},
|
|
10
25
|
"scripts": {
|
|
11
|
-
"build": "
|
|
12
|
-
"dev": "tsc --watch"
|
|
26
|
+
"build": "tsup src/index.ts --format esm --out-dir dist --dts --splitting",
|
|
27
|
+
"dev": "tsc --watch",
|
|
28
|
+
"test": "npm run build && NODE_OPTIONS=--experimental-vm-modules jest",
|
|
29
|
+
"test:watch": "npm run build && NODE_OPTIONS=--experimental-vm-modules jest --watch"
|
|
13
30
|
},
|
|
14
31
|
"repository": {
|
|
15
32
|
"type": "git",
|
|
16
|
-
"url": "
|
|
33
|
+
"url": "https://github.com/fells-code/seamless-auth-server/tree/main/packages/express"
|
|
17
34
|
},
|
|
18
|
-
"files": [
|
|
19
|
-
"dist"
|
|
20
|
-
],
|
|
21
35
|
"peerDependencies": {
|
|
22
|
-
"
|
|
23
|
-
"express": ">=
|
|
36
|
+
"express": ">=4.18.0",
|
|
37
|
+
"@types/express": ">=4.17.0"
|
|
24
38
|
},
|
|
25
39
|
"dependencies": {
|
|
40
|
+
"@seamless-auth/core": "beta",
|
|
26
41
|
"cookie-parser": "^1.4.6",
|
|
27
|
-
"
|
|
28
|
-
"jose": "^6.1.1",
|
|
29
|
-
"jsonwebtoken": "^9.0.2",
|
|
30
|
-
"node-fetch": "^3.3.2"
|
|
42
|
+
"jsonwebtoken": "^9.0.3"
|
|
31
43
|
},
|
|
32
44
|
"devDependencies": {
|
|
33
45
|
"@types/cookie-parser": "^1.4.10",
|
|
46
|
+
"@types/jest": "^29.5.14",
|
|
34
47
|
"@types/jsonwebtoken": "^9.0.10",
|
|
48
|
+
"jest": "^29.7.0",
|
|
49
|
+
"ts-node": "^10.9.2",
|
|
50
|
+
"supertest": "^7.2.2",
|
|
51
|
+
"tsup": "^8.5.1",
|
|
35
52
|
"typescript": "^5.5.0"
|
|
36
53
|
},
|
|
37
54
|
"publishConfig": {
|
package/dist/createServer.d.ts
DELETED
|
@@ -1,48 +0,0 @@
|
|
|
1
|
-
import { Router } from "express";
|
|
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
|
-
*/
|
|
47
|
-
export declare function createSeamlessAuthServer(opts: SeamlessAuthServerOptions): Router;
|
|
48
|
-
//# sourceMappingURL=createServer.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
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"}
|