@sqrzro/server 2.0.0-bz.1 → 2.0.0-bz.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.
Files changed (51) hide show
  1. package/LICENSE +1 -1
  2. package/README.md +25 -1
  3. package/auth.d.ts +1 -0
  4. package/auth.js +1 -0
  5. package/cache.d.ts +1 -0
  6. package/cache.js +1 -0
  7. package/dist/auth.d.ts +100 -0
  8. package/dist/auth.js +891 -0
  9. package/dist/cache.d.ts +4 -0
  10. package/dist/cache.js +46 -0
  11. package/dist/forms.d.ts +46 -0
  12. package/dist/forms.js +327 -0
  13. package/dist/lists.d.ts +18 -0
  14. package/dist/lists.js +61 -0
  15. package/dist/mail.d.ts +12 -0
  16. package/dist/mail.js +97 -0
  17. package/dist/middleware.d.ts +5 -0
  18. package/dist/middleware.js +66 -0
  19. package/dist/schema.d.ts +288 -0
  20. package/dist/schema.js +77 -0
  21. package/dist/url.d.ts +28 -0
  22. package/dist/url.js +56 -0
  23. package/forms.d.ts +1 -0
  24. package/forms.js +1 -0
  25. package/lists.d.ts +1 -0
  26. package/lists.js +1 -0
  27. package/mail.d.ts +1 -0
  28. package/mail.js +1 -0
  29. package/middleware.d.ts +1 -0
  30. package/middleware.js +1 -0
  31. package/package.json +67 -44
  32. package/schema.d.ts +1 -0
  33. package/schema.js +1 -0
  34. package/url.d.ts +1 -0
  35. package/url.js +1 -0
  36. package/dist/AuthService.d.ts +0 -10
  37. package/dist/AuthService.js +0 -36
  38. package/dist/DataService.d.ts +0 -29
  39. package/dist/DataService.js +0 -64
  40. package/dist/LoginRequest.d.ts +0 -4
  41. package/dist/LoginRequest.js +0 -11
  42. package/dist/PasswordService.d.ts +0 -6
  43. package/dist/PasswordService.js +0 -63
  44. package/dist/RequestService.d.ts +0 -21
  45. package/dist/RequestService.js +0 -121
  46. package/dist/SessionService.d.ts +0 -5
  47. package/dist/SessionService.js +0 -56
  48. package/dist/index.d.ts +0 -7
  49. package/dist/index.js +0 -10
  50. package/dist/interfaces.d.ts +0 -11
  51. package/dist/interfaces.js +0 -2
package/dist/auth.js ADDED
@@ -0,0 +1,891 @@
1
+ var __create = Object.create;
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __getProtoOf = Object.getPrototypeOf;
6
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
7
+ var __export = (target, all) => {
8
+ for (var name in all)
9
+ __defProp(target, name, { get: all[name], enumerable: true });
10
+ };
11
+ var __copyProps = (to, from, except, desc) => {
12
+ if (from && typeof from === "object" || typeof from === "function") {
13
+ for (let key of __getOwnPropNames(from))
14
+ if (!__hasOwnProp.call(to, key) && key !== except)
15
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
16
+ }
17
+ return to;
18
+ };
19
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
20
+ // If the importer is in node compatibility mode or this is not an ESM
21
+ // file that has been converted to a CommonJS file using a Babel-
22
+ // compatible transform (i.e. "__esModule" has not been set), then set
23
+ // "default" to the CommonJS "module.exports" for node compatibility.
24
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
25
+ mod
26
+ ));
27
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
28
+
29
+ // src/auth/index.ts
30
+ var auth_exports = {};
31
+ __export(auth_exports, {
32
+ checkMFAEnabled: () => checkMFAEnabled,
33
+ checkPasswordComplexity: () => checkPasswordComplexity,
34
+ checkRouteAllowed: () => checkRouteAllowed,
35
+ checkSessionExists: () => checkSessionExists,
36
+ checkUserHasMFA: () => checkUserHasMFA,
37
+ createUserSession: () => createUserSession,
38
+ generateID: () => generateID,
39
+ generateMFA: () => generateMFA,
40
+ getAllowedRoles: () => getAllowedRoles,
41
+ getClientByID: () => getClientByID,
42
+ getPasswordComplexity: () => getPasswordComplexity,
43
+ getScopeByID: () => getScopeByID,
44
+ getScopes: () => getScopes,
45
+ getSessionID: () => getSessionID,
46
+ getSessionUser: () => getSessionUser,
47
+ handleClientAuth: () => handleClientAuth,
48
+ handleLoginForm: () => handleLoginForm,
49
+ handleLogout: () => handleLogout,
50
+ handleMFAForm: () => handleMFAForm,
51
+ handlePasswordForm: () => handlePasswordForm,
52
+ handlePasswordResetForm: () => handlePasswordResetForm,
53
+ handleSession: () => handleSession,
54
+ hashPassword: () => hashPassword,
55
+ invalidateSession: () => invalidateSession,
56
+ invalidateUserSessions: () => invalidateUserSessions,
57
+ lucia: () => lucia,
58
+ registerClient: () => registerClient,
59
+ registerUser: () => registerUser,
60
+ setScopes: () => setScopes,
61
+ verifyPassword: () => verifyPassword
62
+ });
63
+ module.exports = __toCommonJS(auth_exports);
64
+
65
+ // src/auth/AuthService.ts
66
+ var import_drizzle_orm2 = require("drizzle-orm");
67
+
68
+ // src/database/DatabaseService.ts
69
+ var import_postgres_js = require("drizzle-orm/postgres-js");
70
+ var import_postgres = __toESM(require("postgres"));
71
+ function createSingleton() {
72
+ if (!process.env.DATABASE_URL) {
73
+ throw new Error("DATABASE_URL is not defined");
74
+ }
75
+ return (0, import_postgres_js.drizzle)((0, import_postgres.default)(process.env.DATABASE_URL, { prepare: false }));
76
+ }
77
+ var db = globalThis.db ?? createSingleton();
78
+ if (!process.env.VERCEL_ENV) {
79
+ globalThis.db = db;
80
+ }
81
+
82
+ // src/database/schema.ts
83
+ var import_pg_core = require("drizzle-orm/pg-core");
84
+ var DEFAULT_ROLE = 10;
85
+ var mfaType = (0, import_pg_core.pgEnum)("mfaType", ["TOTP", "HARDWARE"]);
86
+ var scope = (0, import_pg_core.pgEnum)("scope", ["ANON", "MFA", "AUTHED"]);
87
+ var authSchema = (0, import_pg_core.pgSchema)("auth");
88
+ var authUserTable = authSchema.table("user_credentials", {
89
+ id: (0, import_pg_core.text)("id").primaryKey(),
90
+ email: (0, import_pg_core.text)("email").notNull().unique(),
91
+ password: (0, import_pg_core.text)("password"),
92
+ role: (0, import_pg_core.integer)("role").notNull().default(DEFAULT_ROLE)
93
+ });
94
+ var authSessionTable = authSchema.table("sessions", {
95
+ id: (0, import_pg_core.text)("id").primaryKey(),
96
+ userId: (0, import_pg_core.text)("userId").notNull().references(() => authUserTable.id),
97
+ scope: scope("scope").notNull().default("ANON"),
98
+ expiresAt: (0, import_pg_core.timestamp)("expiresAt").notNull()
99
+ });
100
+ var authResetTable = authSchema.table("resets", {
101
+ id: (0, import_pg_core.text)("id").primaryKey(),
102
+ userId: (0, import_pg_core.text)("userId").notNull().references(() => authUserTable.id),
103
+ expiresAt: (0, import_pg_core.timestamp)("expiresAt").notNull()
104
+ });
105
+ var authMFATable = authSchema.table("mfas", {
106
+ id: (0, import_pg_core.text)("id").primaryKey(),
107
+ name: (0, import_pg_core.text)("name").notNull(),
108
+ userId: (0, import_pg_core.text)("userId").notNull().references(() => authUserTable.id),
109
+ type: mfaType("type").notNull().default("TOTP"),
110
+ secret: (0, import_pg_core.text)("secret").notNull(),
111
+ verifiedAt: (0, import_pg_core.timestamp)("verifiedAt")
112
+ });
113
+ var authClientTable = authSchema.table("client_credentials", {
114
+ id: (0, import_pg_core.text)("id").primaryKey(),
115
+ alias: (0, import_pg_core.text)("alias").notNull().unique(),
116
+ secret: (0, import_pg_core.text)("secret").notNull().unique()
117
+ });
118
+
119
+ // src/forms/ValidationError.ts
120
+ var ValidationError = class extends Error {
121
+ constructor(messages2) {
122
+ super(JSON.stringify(messages2));
123
+ this.name = "ValidationError";
124
+ }
125
+ };
126
+ var ValidationError_default = ValidationError;
127
+
128
+ // src/forms/ValidationService.ts
129
+ var import_joi = __toESM(require("joi"));
130
+
131
+ // src/forms/lang.ts
132
+ var messages = {
133
+ "alternatives.all": "",
134
+ "alternatives.any": "",
135
+ "alternatives.match": "",
136
+ "alternatives.one": "",
137
+ "alternatives.types": "",
138
+ "any.custom": "",
139
+ "any.default": "",
140
+ "any.failover": "",
141
+ "any.invalid": "",
142
+ "any.only": "",
143
+ "any.ref": "",
144
+ "any.required": "{{#label}} is required",
145
+ "any.unknown": "",
146
+ "array.base": "",
147
+ "array.excludes": "",
148
+ "array.includesRequiredBoth": "",
149
+ "array.includesRequiredKnowns": "",
150
+ "array.includesRequiredUnknowns": "",
151
+ "array.includes": "",
152
+ "array.length": "",
153
+ "array.max": "",
154
+ "array.min": "",
155
+ "array.orderedLength": "",
156
+ "array.sort": "",
157
+ "array.sort.mismatching": "",
158
+ "array.sort.unsupported": "",
159
+ "array.sparse": "",
160
+ "array.unique": "",
161
+ "array.hasKnown": "",
162
+ "array.hasUnknown": "",
163
+ "binary.base": "",
164
+ "binary.length": "",
165
+ "binary.max": "",
166
+ "binary.min": "",
167
+ "boolean.base": "",
168
+ "date.base": "",
169
+ "date.format": "",
170
+ "date.greater": "",
171
+ "date.less": "",
172
+ "date.max": "",
173
+ "date.min": "",
174
+ "date.strict": "",
175
+ "function.arity": "",
176
+ "function.class": "",
177
+ "function.maxArity": "",
178
+ "function.minArity": "",
179
+ "number.base": "{{#label}} should be a number",
180
+ "number.greater": "",
181
+ "number.infinity": "",
182
+ "number.integer": "",
183
+ "number.less": "",
184
+ "number.max": "",
185
+ "number.min": "{{#label}} should be greater than or equal to {{#limit}}",
186
+ "number.multiple": "",
187
+ "number.negative": "",
188
+ "number.port": "",
189
+ "number.positive": "",
190
+ "number.precision": "",
191
+ "number.unsafe": "",
192
+ "object.unknown": "",
193
+ "object.and": "",
194
+ "object.assert": "",
195
+ "object.base": "",
196
+ "object.length": "",
197
+ "object.max": "",
198
+ "object.min": "",
199
+ "object.missing": "",
200
+ "object.nand": "",
201
+ "object.pattern.match": "",
202
+ "object.refType": "",
203
+ "object.regex": "",
204
+ "object.rename.multiple": "",
205
+ "object.rename.override": "",
206
+ "object.schema": "",
207
+ "object.instance": "",
208
+ "object.with": "",
209
+ "object.without": "",
210
+ "object.xor": "",
211
+ "object.oxor": "",
212
+ "string.alphanum": "",
213
+ "string.base64": "",
214
+ "string.base": "",
215
+ "string.creditCard": "",
216
+ "string.dataUri": "",
217
+ "string.domain": "",
218
+ "string.email": "",
219
+ "string.empty": "{{#label}} is required",
220
+ "string.guid": "",
221
+ "string.hexAlign": "",
222
+ "string.hex": "",
223
+ "string.hostname": "",
224
+ "string.ipVersion": "",
225
+ "string.ip": "",
226
+ "string.isoDate": "",
227
+ "string.isoDuration": "",
228
+ "string.length": "",
229
+ "string.lowercase": "",
230
+ "string.max": "",
231
+ "string.min": "",
232
+ "string.normalize": "",
233
+ "string.pattern.base": "",
234
+ "string.pattern.name": "",
235
+ "string.pattern.invert.base": "",
236
+ "string.pattern.invert.name": "",
237
+ "string.token": "",
238
+ "string.trim": "",
239
+ "string.uppercase": "",
240
+ "string.uri": "",
241
+ "string.uriCustomScheme": "",
242
+ "string.uriRelativeOnly": "",
243
+ "symbol.base": "",
244
+ "symbol.map": ""
245
+ };
246
+ var lang_default = messages;
247
+
248
+ // src/forms/ValidationService.ts
249
+ function getErrorMessages() {
250
+ return Object.entries(lang_default).reduce((acc, [key, value]) => {
251
+ if (!value) {
252
+ return acc;
253
+ }
254
+ return {
255
+ ...acc,
256
+ [key]: value
257
+ };
258
+ }, {});
259
+ }
260
+ function transformErrors(error) {
261
+ const messages2 = error.details.reduce(
262
+ (acc, cur) => ({
263
+ ...acc,
264
+ [cur.path.join(".")]: cur.message.replace(/"/gu, "")
265
+ }),
266
+ {}
267
+ );
268
+ return new ValidationError_default(messages2);
269
+ }
270
+ async function validateSchema(formData, validation) {
271
+ try {
272
+ const validated = await validation.validateAsync(formData, {
273
+ abortEarly: false,
274
+ messages: getErrorMessages()
275
+ });
276
+ return [validated, null];
277
+ } catch (err) {
278
+ if (err instanceof import_joi.default.ValidationError) {
279
+ return [null, transformErrors(err)];
280
+ }
281
+ if (err instanceof Error) {
282
+ return [null, err];
283
+ }
284
+ return [null, new Error("Unknown validation error occured")];
285
+ }
286
+ }
287
+ function createSchema(schema) {
288
+ return import_joi.default.object(schema);
289
+ }
290
+
291
+ // src/forms/FormService.ts
292
+ function serializeError(err) {
293
+ return {
294
+ cause: err.cause,
295
+ message: err.message,
296
+ name: err.name,
297
+ stack: err.stack
298
+ };
299
+ }
300
+ function hasFn(args) {
301
+ return Boolean(Object.prototype.hasOwnProperty.call(args, "fn"));
302
+ }
303
+ async function submitForm(args) {
304
+ let data = { ...args.formData };
305
+ if (args.request) {
306
+ const [validated, validationError] = await validateSchema(args.formData, args.request);
307
+ if (validationError !== null) {
308
+ if (validationError instanceof ValidationError_default) {
309
+ args.onValidationError?.(validationError);
310
+ }
311
+ return [null, serializeError(validationError)];
312
+ }
313
+ data = validated;
314
+ }
315
+ if (!hasFn(args)) {
316
+ try {
317
+ await args.onSuccess?.(data);
318
+ } catch (err) {
319
+ if (err instanceof Error) {
320
+ return [null, serializeError(err)];
321
+ }
322
+ return [
323
+ null,
324
+ serializeError(
325
+ new Error("The submitForm onSuccess function encountered an unknown error")
326
+ )
327
+ ];
328
+ }
329
+ return [data, null];
330
+ }
331
+ let model = null;
332
+ try {
333
+ model = await args.fn(data);
334
+ } catch (err) {
335
+ if (err instanceof ValidationError_default) {
336
+ args.onValidationError?.(err);
337
+ return [null, serializeError(err)];
338
+ }
339
+ if (err instanceof Error) {
340
+ return [null, serializeError(err)];
341
+ }
342
+ return [
343
+ null,
344
+ serializeError(
345
+ new Error("The function supplied to submitForm encountered an unknown error")
346
+ )
347
+ ];
348
+ }
349
+ if (!model) {
350
+ return [
351
+ null,
352
+ serializeError(
353
+ new Error("No model has been returned from the function supplied to submitForm")
354
+ )
355
+ ];
356
+ }
357
+ try {
358
+ await args.onSuccess?.(model);
359
+ } catch (err) {
360
+ if (err instanceof Error) {
361
+ return [null, serializeError(err)];
362
+ }
363
+ return [
364
+ null,
365
+ serializeError(
366
+ new Error("The submitForm onSuccess function encountered an unknown error")
367
+ )
368
+ ];
369
+ }
370
+ return [model, null];
371
+ }
372
+
373
+ // src/auth/MFAService.ts
374
+ var import_drizzle_orm = require("drizzle-orm");
375
+ var import_qrcode = __toESM(require("qrcode"));
376
+ var import_otplib = require("otplib");
377
+
378
+ // src/auth/MFARequest.ts
379
+ var import_joi2 = __toESM(require("joi"));
380
+ var MFARequest = createSchema({
381
+ token: import_joi2.default.string().pattern(/^[0-9]{6}$/u).required()
382
+ });
383
+ var MFARequest_default = MFARequest;
384
+
385
+ // src/auth/SessionService.ts
386
+ var import_adapter_drizzle = require("@lucia-auth/adapter-drizzle");
387
+ var import_lucia = require("lucia");
388
+ var import_headers2 = require("next/headers");
389
+ var import_server = require("next/server");
390
+ var import_path_to_regexp = require("path-to-regexp");
391
+
392
+ // src/cache/CacheService.ts
393
+ var import_redis = require("redis");
394
+ async function getClient() {
395
+ const client = (0, import_redis.createClient)();
396
+ await client.connect();
397
+ return client;
398
+ }
399
+ async function getFromCache(key) {
400
+ const client = await getClient();
401
+ return client.get(key);
402
+ }
403
+ async function setToCache(key, value) {
404
+ const client = await getClient();
405
+ await client.set(key, value);
406
+ }
407
+
408
+ // src/url/URLService.ts
409
+ var import_headers = require("next/headers");
410
+ function getOrigin() {
411
+ const origin = (0, import_headers.headers)().get("x-origin");
412
+ if (origin) {
413
+ return origin;
414
+ }
415
+ const proto = (0, import_headers.headers)().get("x-forwarded-proto");
416
+ const host = (0, import_headers.headers)().get("x-forwarded-host");
417
+ if (proto && host) {
418
+ return `${proto}://${host}`;
419
+ }
420
+ throw new Error("No origin could be determined");
421
+ }
422
+
423
+ // src/auth/SessionService.ts
424
+ var import_utility = require("@sqrzro/utility");
425
+ var DEFAULT_REDIRECT = "/auth/login";
426
+ var ID_LENGTH = 16;
427
+ var DEFAULT_SCOPES = {
428
+ ANON: {
429
+ allowedRoute: "/auth/(login|password)",
430
+ redirectOnUnauth: DEFAULT_REDIRECT
431
+ },
432
+ MFA: {
433
+ allowedRoute: "/auth/mfa",
434
+ redirectOnUnauth: "/auth/mfa"
435
+ },
436
+ AUTHED: {
437
+ allowedRoute: "*",
438
+ redirectOnAuth: "/"
439
+ }
440
+ };
441
+ var adapter = new import_adapter_drizzle.DrizzlePostgreSQLAdapter(db, authSessionTable, authUserTable);
442
+ var lucia = new import_lucia.Lucia(adapter, {
443
+ sessionCookie: {
444
+ attributes: {
445
+ secure: process.env.NODE_ENV === "production"
446
+ },
447
+ name: process.env.AUTH_COOKIE_NAME || "auth_session"
448
+ },
449
+ getSessionAttributes: (attributes) => ({
450
+ scope: attributes.scope
451
+ }),
452
+ getUserAttributes: (attributes) => ({
453
+ email: attributes.email,
454
+ role: attributes.role
455
+ })
456
+ });
457
+ function generateID(length = ID_LENGTH) {
458
+ return (0, import_lucia.generateId)(length);
459
+ }
460
+ async function invalidateSession(id) {
461
+ const cookie = lucia.createBlankSessionCookie();
462
+ (0, import_headers2.cookies)().set(cookie.name, cookie.value, cookie.attributes);
463
+ return lucia.invalidateSession(id);
464
+ }
465
+ async function invalidateUserSessions(id) {
466
+ return lucia.invalidateUserSessions(id);
467
+ }
468
+ async function createUserSession(id, scope2 = "ANON") {
469
+ const session = await lucia.createSession(id, { scope: scope2 });
470
+ const sessionCookie = lucia.createSessionCookie(session.id);
471
+ (0, import_headers2.cookies)().set(sessionCookie.name, sessionCookie.value, sessionCookie.attributes);
472
+ return true;
473
+ }
474
+ function getSessionID() {
475
+ return (0, import_headers2.cookies)().get(lucia.sessionCookieName)?.value ?? null;
476
+ }
477
+ function checkSessionExists() {
478
+ return Boolean(getSessionID());
479
+ }
480
+ async function getSessionUser() {
481
+ const sessionID = getSessionID();
482
+ if (!sessionID) {
483
+ return null;
484
+ }
485
+ const { user } = await lucia.validateSession(sessionID);
486
+ if (!user?.role || !getAllowedRoles().includes(user.role)) {
487
+ return null;
488
+ }
489
+ return (0, import_utility.getFromObject)(user, ["id", "email", "role"]);
490
+ }
491
+ function checkRouteAllowed(pathname, route) {
492
+ if (!route) {
493
+ return false;
494
+ }
495
+ if (route === "*") {
496
+ return true;
497
+ }
498
+ return (0, import_path_to_regexp.match)(route)(pathname) !== false;
499
+ }
500
+ async function getScopes() {
501
+ const scopes = await getFromCache(`${getOrigin()}:scopes`);
502
+ return scopes ? JSON.parse(scopes) : DEFAULT_SCOPES;
503
+ }
504
+ async function getScopeByID(id) {
505
+ const scopes = await getScopes();
506
+ return scopes[id];
507
+ }
508
+ async function setScopes(customScopes) {
509
+ const scopes = {
510
+ ANON: {
511
+ ...DEFAULT_SCOPES.ANON,
512
+ ...customScopes?.ANON
513
+ },
514
+ MFA: {
515
+ ...DEFAULT_SCOPES.MFA,
516
+ ...customScopes?.MFA
517
+ },
518
+ AUTHED: {
519
+ ...DEFAULT_SCOPES.AUTHED,
520
+ ...customScopes?.AUTHED
521
+ }
522
+ };
523
+ return setToCache(`${getOrigin()}:scopes`, JSON.stringify(scopes));
524
+ }
525
+ async function validateSessionFromID(sessionID, pathname) {
526
+ const scopes = await getScopes();
527
+ const { session, user } = await lucia.validateSession(sessionID);
528
+ const scope2 = scopes[session && getAllowedRoles().includes(user?.role) ? session.scope : "ANON"];
529
+ return checkRouteAllowed(pathname, scope2.allowedRoute) ? null : scope2.redirectOnUnauth || DEFAULT_REDIRECT;
530
+ }
531
+ async function handleSession(request, customScopes) {
532
+ const sessionID = request.nextUrl.searchParams.get("id") || "";
533
+ const pathname = request.nextUrl.searchParams.get("pathname") || "/";
534
+ await setScopes(customScopes);
535
+ return import_server.NextResponse.json({
536
+ redirect: await validateSessionFromID(sessionID, pathname)
537
+ });
538
+ }
539
+
540
+ // src/auth/MFAService.ts
541
+ function checkMFAEnabled() {
542
+ return process.env.AUTH_MFA_ENABLED !== "false";
543
+ }
544
+ async function generateMFA(name, email) {
545
+ if (!checkMFAEnabled() || !email) {
546
+ return null;
547
+ }
548
+ const [user] = await db.select().from(authUserTable).where((0, import_drizzle_orm.eq)(authUserTable.email, email)).limit(1);
549
+ if (!user) {
550
+ return null;
551
+ }
552
+ const secret = import_otplib.authenticator.generateSecret();
553
+ const otpauth = import_otplib.authenticator.keyuri(email, name, secret);
554
+ await db.delete(authMFATable).where((0, import_drizzle_orm.and)((0, import_drizzle_orm.eq)(authMFATable.userId, user.id), (0, import_drizzle_orm.isNull)(authMFATable.verifiedAt)));
555
+ await db.insert(authMFATable).values({
556
+ id: generateID(),
557
+ name: "Default",
558
+ secret,
559
+ userId: user.id
560
+ });
561
+ return new Promise((resolve, reject) => {
562
+ import_qrcode.default.toDataURL(
563
+ otpauth,
564
+ { rendererOpts: { quality: 1 }, margin: 0, scale: 2 },
565
+ (err, data) => {
566
+ if (err) {
567
+ reject(err);
568
+ }
569
+ resolve(data);
570
+ }
571
+ );
572
+ });
573
+ }
574
+ async function checkUserHasMFA(user) {
575
+ if (!checkMFAEnabled()) {
576
+ return false;
577
+ }
578
+ const [mfa] = await db.select().from(authMFATable).where((0, import_drizzle_orm.and)((0, import_drizzle_orm.eq)(authMFATable.userId, user.id), (0, import_drizzle_orm.isNotNull)(authMFATable.verifiedAt))).limit(1);
579
+ return Boolean(mfa);
580
+ }
581
+ async function markAsVerified(userID) {
582
+ if (!checkMFAEnabled()) {
583
+ return;
584
+ }
585
+ await db.update(authMFATable).set({ verifiedAt: /* @__PURE__ */ new Date() }).where((0, import_drizzle_orm.eq)(authMFATable.userId, userID));
586
+ }
587
+ async function validateUserToken(userID, token) {
588
+ if (!checkMFAEnabled()) {
589
+ return false;
590
+ }
591
+ const [mfa] = await db.select().from(authMFATable).where((0, import_drizzle_orm.eq)(authMFATable.userId, userID)).limit(1);
592
+ if (!mfa) {
593
+ return false;
594
+ }
595
+ return import_otplib.authenticator.check(token, mfa.secret);
596
+ }
597
+ async function handleMFA(formData) {
598
+ if (!checkMFAEnabled()) {
599
+ return false;
600
+ }
601
+ const user = await getSessionUser();
602
+ if (!user) {
603
+ return false;
604
+ }
605
+ const isValid = await validateUserToken(user.id, formData.token);
606
+ if (!isValid) {
607
+ return false;
608
+ }
609
+ await markAsVerified(user.id);
610
+ return createUserSession(user.id, "AUTHED");
611
+ }
612
+ async function handleMFAForm(formData) {
613
+ if (!checkMFAEnabled()) {
614
+ return [null, new Error("MFA is not enabled")];
615
+ }
616
+ const response = await submitForm({
617
+ fn: handleMFA,
618
+ formData,
619
+ request: MFARequest_default
620
+ });
621
+ return response;
622
+ }
623
+
624
+ // src/auth/PasswordService.ts
625
+ var import_bcryptjs = __toESM(require("bcryptjs"));
626
+ var PW_SALT_ROUNDS = 12;
627
+ var PASSWORD_RULES = {
628
+ min: 8,
629
+ upper: 1,
630
+ lower: 1,
631
+ number: 1,
632
+ symbol: 1
633
+ };
634
+ function checkPasswordMin(password, value) {
635
+ return password.length >= value;
636
+ }
637
+ function checkPasswordUpper(password, value) {
638
+ return password.replace(/[^A-Z]/gu, "").length >= value;
639
+ }
640
+ function checkPasswordLower(password, value) {
641
+ return password.replace(/[^a-z]/gu, "").length >= value;
642
+ }
643
+ function checkPasswordNumber(password, value) {
644
+ return password.replace(/[^0-9]/gu, "").length >= value;
645
+ }
646
+ function checkPasswordSymbol(password, value) {
647
+ return password.replace(/[^$]/gu, "").length >= value;
648
+ }
649
+ var PASSWORD_FUNCTIONS = {
650
+ min: checkPasswordMin,
651
+ upper: checkPasswordUpper,
652
+ lower: checkPasswordLower,
653
+ number: checkPasswordNumber,
654
+ symbol: checkPasswordSymbol
655
+ };
656
+ async function hashPassword(password) {
657
+ const hash = await import_bcryptjs.default.hash(password, PW_SALT_ROUNDS);
658
+ return hash;
659
+ }
660
+ async function verifyPassword(data, encrypted) {
661
+ if (!data || !encrypted) {
662
+ return false;
663
+ }
664
+ const verified = await import_bcryptjs.default.compare(data, encrypted);
665
+ return verified;
666
+ }
667
+ async function getPasswordComplexity(password, rules = PASSWORD_RULES) {
668
+ const entries = Object.entries(rules);
669
+ const validity = entries.reduce((acc, [rule, value]) => {
670
+ acc[rule] = PASSWORD_FUNCTIONS[rule](password, value);
671
+ return acc;
672
+ }, {});
673
+ return Promise.resolve(validity);
674
+ }
675
+ async function checkPasswordComplexity(password, rules = PASSWORD_RULES) {
676
+ const validity = await getPasswordComplexity(password, rules);
677
+ return Promise.resolve(Object.values(validity).every(Boolean));
678
+ }
679
+
680
+ // src/auth/LoginRequest.ts
681
+ var import_joi3 = __toESM(require("joi"));
682
+ var LoginRequest = createSchema({
683
+ email: import_joi3.default.string().email({ minDomainSegments: 2, tlds: false }).required(),
684
+ password: import_joi3.default.string().min(8).required()
685
+ });
686
+ var LoginRequest_default = LoginRequest;
687
+
688
+ // src/auth/PasswordRequest.ts
689
+ var import_joi4 = __toESM(require("joi"));
690
+ var PasswordRequest = createSchema({
691
+ email: import_joi4.default.string().max(60).email({ minDomainSegments: 2, tlds: false }).required().messages({
692
+ "any.required": "Please provide your email address, so we can send you a reset link",
693
+ "string.empty": "Please provide your email address, so we can send you a reset link",
694
+ "string.email": "Please make sure your email address is valid",
695
+ "string.max": "Please make sure your email address is valid"
696
+ })
697
+ });
698
+ var PasswordRequest_default = PasswordRequest;
699
+
700
+ // src/auth/PasswordResetRequest.ts
701
+ var import_joi5 = __toESM(require("joi"));
702
+ var PasswordResetRequest = createSchema({
703
+ token: import_joi5.default.string().pattern(/[a-z0-9]{40}/u).required(),
704
+ password: import_joi5.default.string().required().messages({
705
+ "any.required": "Please provide your new password",
706
+ "string.empty": "Please provide your new password"
707
+ })
708
+ });
709
+ var PasswordResetRequest_default = PasswordResetRequest;
710
+
711
+ // src/auth/AuthService.ts
712
+ var RESET_TOKEN_LENGTH = 40;
713
+ var RESET_TOKEN_EXPIRY = 36e5;
714
+ async function handleLogout() {
715
+ const id = getSessionID();
716
+ if (id) {
717
+ await invalidateSession(id);
718
+ }
719
+ }
720
+ function getAllowedRoles() {
721
+ const roles = process.env.AUTH_ALLOWED_ROLES;
722
+ if (!roles) {
723
+ throw new Error("AUTH_ALLOWED_ROLES is not defined. Authentication will not be possible.");
724
+ }
725
+ return roles.split(",").map((role) => Number(role)).filter((role) => !isNaN(role));
726
+ }
727
+ async function handleUserSession(userID) {
728
+ await createUserSession(userID, checkMFAEnabled() ? "MFA" : "AUTHED");
729
+ const scope2 = await getScopeByID("AUTHED");
730
+ return scope2?.redirectOnAuth || null;
731
+ }
732
+ async function getUserByEmail(email) {
733
+ const [user] = await db.select().from(authUserTable).where((0, import_drizzle_orm2.and)((0, import_drizzle_orm2.eq)(authUserTable.email, email), (0, import_drizzle_orm2.inArray)(authUserTable.role, getAllowedRoles()))).limit(1);
734
+ return user;
735
+ }
736
+ async function loginUser({ email, password }) {
737
+ const user = await getUserByEmail(email);
738
+ if (!user?.password || !await verifyPassword(password, user.password)) {
739
+ throw new ValidationError_default({ email: "", password: "" });
740
+ }
741
+ const session = await handleUserSession(user.id);
742
+ if (!session) {
743
+ throw new ValidationError_default({ email: "", password: "" });
744
+ }
745
+ return session;
746
+ }
747
+ async function handleLoginForm(formData) {
748
+ const response = await submitForm({
749
+ fn: loginUser,
750
+ formData,
751
+ request: LoginRequest_default
752
+ });
753
+ return response;
754
+ }
755
+ async function registerUser({
756
+ email,
757
+ password,
758
+ role
759
+ }) {
760
+ const hash = password ? await hashPassword(password) : null;
761
+ const [user] = await db.insert(authUserTable).values({ id: generateID(), email, password: hash, role }).returning();
762
+ return user;
763
+ }
764
+ async function createPasswordResetToken(email) {
765
+ const user = await getUserByEmail(email);
766
+ if (!user) {
767
+ return null;
768
+ }
769
+ await db.delete(authResetTable).where((0, import_drizzle_orm2.eq)(authResetTable.userId, user.id));
770
+ const id = generateID(RESET_TOKEN_LENGTH);
771
+ await db.insert(authResetTable).values({
772
+ id,
773
+ userId: user.id,
774
+ expiresAt: new Date((/* @__PURE__ */ new Date()).getTime() + RESET_TOKEN_EXPIRY)
775
+ });
776
+ return id;
777
+ }
778
+ async function handlePasswordForm(formData, mailFn) {
779
+ async function mutateFn(data) {
780
+ const token = await createPasswordResetToken(data.email);
781
+ if (!token) {
782
+ return true;
783
+ }
784
+ return mailFn(data.email, token);
785
+ }
786
+ const response = await submitForm({
787
+ fn: mutateFn,
788
+ formData,
789
+ request: PasswordRequest_default
790
+ });
791
+ return response;
792
+ }
793
+ async function validatePasswordResetToken(password, token) {
794
+ const [result] = await db.select().from(authResetTable).where((0, import_drizzle_orm2.eq)(authResetTable.id, token)).limit(1);
795
+ if (!result) {
796
+ return null;
797
+ }
798
+ await db.delete(authResetTable).where((0, import_drizzle_orm2.eq)(authResetTable.id, token));
799
+ if (!result || result.expiresAt < /* @__PURE__ */ new Date()) {
800
+ return null;
801
+ }
802
+ await invalidateUserSessions(result.userId);
803
+ await db.update(authUserTable).set({ password: await hashPassword(password) }).where(
804
+ (0, import_drizzle_orm2.and)((0, import_drizzle_orm2.eq)(authUserTable.id, result.userId), (0, import_drizzle_orm2.inArray)(authUserTable.role, getAllowedRoles()))
805
+ );
806
+ return result.userId;
807
+ }
808
+ async function handlePasswordResetForm(formData) {
809
+ async function mutateFn(data) {
810
+ const userID = await validatePasswordResetToken(data.password, data.token);
811
+ if (!userID) {
812
+ return null;
813
+ }
814
+ return handleUserSession(userID);
815
+ }
816
+ const response = await submitForm({
817
+ fn: mutateFn,
818
+ formData,
819
+ request: PasswordResetRequest_default
820
+ });
821
+ return response;
822
+ }
823
+
824
+ // src/auth/ClientService.ts
825
+ var import_drizzle_orm3 = require("drizzle-orm");
826
+ var ID_LENGTH2 = 16;
827
+ var SECRET_LENGTH = 64;
828
+ async function getClientByID(id) {
829
+ const [client] = await db.select().from(authClientTable).where((0, import_drizzle_orm3.eq)(authClientTable.id, id)).limit(1);
830
+ return client;
831
+ }
832
+ async function registerClient({
833
+ alias,
834
+ id,
835
+ secret
836
+ }) {
837
+ const [client] = await db.insert(authClientTable).values({
838
+ alias,
839
+ id: id || generateID(ID_LENGTH2),
840
+ secret: await hashPassword(secret || generateID(SECRET_LENGTH))
841
+ }).returning();
842
+ return client;
843
+ }
844
+ async function handleClientAuth(request) {
845
+ const { headers: headers2 } = request;
846
+ const header = headers2.get("authorization");
847
+ if (!header) {
848
+ return null;
849
+ }
850
+ const auth = Buffer.from(header.replace("Basic ", ""), "base64").toString("utf-8").replace(/:$/u, "");
851
+ const [id, ...secret] = auth.split("-");
852
+ const [client] = await db.select().from(authClientTable).where((0, import_drizzle_orm3.eq)(authClientTable.id, id)).limit(1);
853
+ if (!client) {
854
+ return null;
855
+ }
856
+ const isVerified = await verifyPassword(secret.join(""), client.secret);
857
+ return isVerified ? client : null;
858
+ }
859
+ // Annotate the CommonJS export names for ESM import in node:
860
+ 0 && (module.exports = {
861
+ checkMFAEnabled,
862
+ checkPasswordComplexity,
863
+ checkRouteAllowed,
864
+ checkSessionExists,
865
+ checkUserHasMFA,
866
+ createUserSession,
867
+ generateID,
868
+ generateMFA,
869
+ getAllowedRoles,
870
+ getClientByID,
871
+ getPasswordComplexity,
872
+ getScopeByID,
873
+ getScopes,
874
+ getSessionID,
875
+ getSessionUser,
876
+ handleClientAuth,
877
+ handleLoginForm,
878
+ handleLogout,
879
+ handleMFAForm,
880
+ handlePasswordForm,
881
+ handlePasswordResetForm,
882
+ handleSession,
883
+ hashPassword,
884
+ invalidateSession,
885
+ invalidateUserSessions,
886
+ lucia,
887
+ registerClient,
888
+ registerUser,
889
+ setScopes,
890
+ verifyPassword
891
+ });