aaspai-authx 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs ADDED
@@ -0,0 +1,2266 @@
1
+ "use strict";
2
+ var __create = Object.create;
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __export = (target, all) => {
9
+ for (var name in all)
10
+ __defProp(target, name, { get: all[name], enumerable: true });
11
+ };
12
+ var __copyProps = (to, from, except, desc) => {
13
+ if (from && typeof from === "object" || typeof from === "function") {
14
+ for (let key of __getOwnPropNames(from))
15
+ if (!__hasOwnProp.call(to, key) && key !== except)
16
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
17
+ }
18
+ return to;
19
+ };
20
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
21
+ // If the importer is in node compatibility mode or this is not an ESM
22
+ // file that has been converted to a CommonJS file using a Babel-
23
+ // compatible transform (i.e. "__esModule" has not been set), then set
24
+ // "default" to the CommonJS "module.exports" for node compatibility.
25
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
26
+ mod
27
+ ));
28
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
29
+ var __decorateClass = (decorators, target, key, kind) => {
30
+ var result = kind > 1 ? void 0 : kind ? __getOwnPropDesc(target, key) : target;
31
+ for (var i = decorators.length - 1, decorator; i >= 0; i--)
32
+ if (decorator = decorators[i])
33
+ result = (kind ? decorator(target, key, result) : decorator(result)) || result;
34
+ if (kind && result) __defProp(target, key, result);
35
+ return result;
36
+ };
37
+
38
+ // src/index.ts
39
+ var src_exports = {};
40
+ __export(src_exports, {
41
+ AuthAdminService: () => AuthAdminService,
42
+ AuthXGuard: () => AuthXGuard,
43
+ AuthXProvider: () => AuthXProvider,
44
+ AuthXSessionDecorator: () => AuthXSessionDecorator,
45
+ AuthXStrategy: () => AuthXStrategy,
46
+ EmailService: () => EmailService,
47
+ HasPermission: () => HasPermission,
48
+ HasRole: () => HasRole,
49
+ PLATFORM_ROLES: () => PLATFORM_ROLES,
50
+ Permissions: () => Permissions,
51
+ ProjectsService: () => ProjectsService,
52
+ Roles: () => Roles,
53
+ SignedIn: () => SignedIn,
54
+ SignedOut: () => SignedOut,
55
+ UploadsService: () => UploadsService,
56
+ authorize: () => authorize,
57
+ authx: () => authx,
58
+ buildSession: () => buildSession,
59
+ createAuthXStrategy: () => createAuthXStrategy,
60
+ express: () => express_exports,
61
+ getPermissionsForRoles: () => getPermissionsForRoles,
62
+ hasAllPermissions: () => hasAllPermissions,
63
+ hasAllRoles: () => hasAllRoles,
64
+ hasAnyPermission: () => hasAnyPermission,
65
+ hasAnyRole: () => hasAnyRole,
66
+ hasPermission: () => hasPermission,
67
+ hasRole: () => hasRole,
68
+ nest: () => nest,
69
+ requireAuth: () => requireAuth,
70
+ requirePermission: () => requirePermission2,
71
+ requirePermissionLegacy: () => requirePermission,
72
+ requireRole: () => requireRole,
73
+ useAuthX: () => useAuthX,
74
+ useAuthXContext: () => useAuthXContext,
75
+ useHasPermission: () => useHasPermission,
76
+ useHasRole: () => useHasRole,
77
+ withAuthRoute: () => withAuthRoute
78
+ });
79
+ module.exports = __toCommonJS(src_exports);
80
+
81
+ // src/express/index.ts
82
+ var express_exports = {};
83
+ __export(express_exports, {
84
+ createAdminRouter: () => createAdminRouter,
85
+ createAuthRouter: () => createAuthRouter,
86
+ createDashboardRouter: () => createDashboardRouter,
87
+ createEmailRouter: () => createEmailRouter,
88
+ createProjectsRouter: () => createProjectsRouter
89
+ });
90
+
91
+ // src/express/auth.routes.ts
92
+ var import_bcryptjs = __toESM(require("bcryptjs"), 1);
93
+ var import_crypto = require("crypto");
94
+ var import_express = __toESM(require("express"), 1);
95
+ var import_jsonwebtoken4 = __toESM(require("jsonwebtoken"), 1);
96
+
97
+ // src/config/loadConfig.ts
98
+ function loadConfig() {
99
+ return {
100
+ orgDomain: process.env.ORG_DOMAIN,
101
+ orgId: process.env.ORG_ID,
102
+ email: {
103
+ host: process.env.EMAIL_HOST || "smtp.postmarkapp.com",
104
+ port: process.env.EMAIL_PORT ? Number(process.env.EMAIL_PORT) : 587,
105
+ secure: (process.env.EMAIL_SECURE || "false") === "true",
106
+ user: process.env.EMAIL_USER,
107
+ pass: process.env.EMAIL_PASSWORD,
108
+ from: process.env.EMAIL_FROM,
109
+ jwtSecret: process.env.EMAIL_JWT_SECRET
110
+ },
111
+ cookies: {
112
+ domain: process.env.COOKIE_DOMAIN,
113
+ secure: (process.env.COOKIE_SECURE || "true") === "true",
114
+ accessTtlMs: 24 * 60 * 60 * 1e3,
115
+ refreshTtlMs: 7 * 24 * 60 * 60 * 1e3
116
+ },
117
+ oidc: {
118
+ jwtSecret: process.env.JWT_SECRET
119
+ },
120
+ aws: {
121
+ bucket: process.env.AWS_S3_BUCKET,
122
+ region: process.env.AWS_REGION,
123
+ accessKeyId: process.env.AWS_ACCESS_KEY_ID,
124
+ secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY
125
+ }
126
+ };
127
+ }
128
+
129
+ // src/config/index.ts
130
+ var config = loadConfig();
131
+ function configureAuthX(overrides = {}) {
132
+ return deepMerge(config, overrides);
133
+ }
134
+ function deepMerge(target, source) {
135
+ if (!source) {
136
+ return target;
137
+ }
138
+ for (const key of Object.keys(source)) {
139
+ const value = source[key];
140
+ if (value === void 0) continue;
141
+ if (Array.isArray(value)) {
142
+ target[key] = [...value];
143
+ continue;
144
+ }
145
+ if (isPlainObject(value)) {
146
+ if (!isPlainObject(target[key])) {
147
+ target[key] = {};
148
+ }
149
+ deepMerge(target[key], value);
150
+ continue;
151
+ }
152
+ target[key] = value;
153
+ }
154
+ return target;
155
+ }
156
+ function isPlainObject(value) {
157
+ return typeof value === "object" && value !== null && !Array.isArray(value);
158
+ }
159
+
160
+ // src/core/roles.config.ts
161
+ var PLATFORM_ROLES = [
162
+ {
163
+ role: "platform_admin",
164
+ permissions: [
165
+ "projects.create",
166
+ "projects.read",
167
+ "projects.update",
168
+ "projects.delete",
169
+ "users.manage",
170
+ "api.manage"
171
+ ]
172
+ },
173
+ {
174
+ role: "platform_manager",
175
+ permissions: [
176
+ "projects.read",
177
+ "projects.update",
178
+ "users.read"
179
+ ]
180
+ },
181
+ {
182
+ role: "platform_user",
183
+ permissions: ["projects.read"]
184
+ }
185
+ ];
186
+ function getPermissionsForRoles(roles) {
187
+ if (!Array.isArray(roles) || roles.length === 0) {
188
+ return [];
189
+ }
190
+ const permissionSet = /* @__PURE__ */ new Set();
191
+ for (const roleName of roles) {
192
+ const roleConfig = PLATFORM_ROLES.find((r) => r.role === roleName);
193
+ if (roleConfig && Array.isArray(roleConfig.permissions)) {
194
+ for (const perm of roleConfig.permissions) {
195
+ permissionSet.add(perm);
196
+ }
197
+ }
198
+ }
199
+ return Array.from(permissionSet);
200
+ }
201
+
202
+ // src/core/session.ts
203
+ function buildSession(payload) {
204
+ const userId = payload?.sub || payload?.userId || payload?.id || "";
205
+ const email = payload?.email || payload?.email_address || "";
206
+ const roles = payload?.realm_access?.roles || payload?.roles || payload?.["cognito:groups"] || (Array.isArray(payload?.role) ? payload.role : []) || [];
207
+ const normalizedRoles = Array.isArray(roles) ? roles.map(String).filter(Boolean) : [];
208
+ const permissions = getPermissionsForRoles(normalizedRoles);
209
+ const session = {
210
+ userId,
211
+ email,
212
+ roles: normalizedRoles,
213
+ permissions
214
+ };
215
+ if (payload?.projectId) session.projectId = payload.projectId;
216
+ if (payload?.orgId) session.orgId = payload.orgId;
217
+ if (payload?.org_id) session.org_id = payload.org_id;
218
+ if (payload?.authType) session.authType = payload.authType;
219
+ Object.keys(payload || {}).forEach((key) => {
220
+ if (![
221
+ "sub",
222
+ "userId",
223
+ "id",
224
+ "email",
225
+ "email_address",
226
+ "realm_access",
227
+ "roles",
228
+ "cognito:groups",
229
+ "role",
230
+ "projectId",
231
+ "orgId",
232
+ "org_id",
233
+ "authType"
234
+ ].includes(key)) {
235
+ session[key] = payload[key];
236
+ }
237
+ });
238
+ return session;
239
+ }
240
+
241
+ // src/models/user.model.ts
242
+ var import_mongoose = __toESM(require("mongoose"), 1);
243
+ var import_uuid = require("uuid");
244
+ var MetadataSchema = new import_mongoose.default.Schema(
245
+ {
246
+ key: { type: String, required: true },
247
+ value: { type: import_mongoose.default.Schema.Types.Mixed, required: true }
248
+ },
249
+ { _id: false }
250
+ );
251
+ var OrgUserSchema = new import_mongoose.default.Schema(
252
+ {
253
+ id: { type: String, default: (0, import_uuid.v4)(), index: true },
254
+ email: { type: String, required: true, unique: true },
255
+ firstName: { type: String, required: true },
256
+ lastName: { type: String, required: true },
257
+ orgId: { type: String },
258
+ projectId: { type: String, required: true },
259
+ roles: { type: [String], default: [] },
260
+ emailVerified: { type: Boolean, default: false },
261
+ lastEmailSent: { type: [Date], default: [] },
262
+ lastPasswordReset: { type: Date },
263
+ metadata: { type: [MetadataSchema], default: [] },
264
+ passwordHash: { type: String }
265
+ },
266
+ { timestamps: true, collection: "users" }
267
+ );
268
+ var OrgUser = import_mongoose.default.model("OrgUser", OrgUserSchema);
269
+
270
+ // src/utils/extract.ts
271
+ var import_cookie = require("cookie");
272
+ function extractToken(req, opts) {
273
+ const headerNames = opts?.headerNames ?? ["authorization", "token"];
274
+ const cookieNames = opts?.cookieNames ?? ["access_token", "authorization"];
275
+ const queryNames = opts?.queryNames ?? ["access_token", "token"];
276
+ for (const h of headerNames) {
277
+ const raw = req.headers[h];
278
+ if (raw) {
279
+ const lower = raw.toLowerCase();
280
+ if (lower.startsWith("bearer ")) return raw.slice(7).trim();
281
+ if (!raw.includes(" ")) return raw.trim();
282
+ }
283
+ }
284
+ const ch = req.headers["cookie"];
285
+ if (typeof ch === "string") {
286
+ const parsed = (0, import_cookie.parse)(ch);
287
+ for (const c of cookieNames) if (parsed[c]) return parsed[c];
288
+ }
289
+ for (const q of queryNames) {
290
+ const v = req.query?.[q];
291
+ if (typeof v === "string" && v) return v;
292
+ }
293
+ return null;
294
+ }
295
+ function readProjectId(req) {
296
+ const ch = req.headers["cookie"];
297
+ if (typeof ch === "string") {
298
+ try {
299
+ const parsed = (0, import_cookie.parse)(ch);
300
+ return parsed["projectId"] || null;
301
+ } catch {
302
+ }
303
+ }
304
+ return null;
305
+ }
306
+
307
+ // src/utils/jwt.ts
308
+ var import_jsonwebtoken = __toESM(require("jsonwebtoken"), 1);
309
+ function verifyJwt(token) {
310
+ return new Promise((resolve, reject) => {
311
+ import_jsonwebtoken.default.verify(
312
+ token,
313
+ process.env.JWT_SECRET,
314
+ // This is your shared secret (string)
315
+ {
316
+ algorithms: ["HS256"],
317
+ // Only allow HS256
318
+ complete: false
319
+ // We only want payload
320
+ },
321
+ (err, decoded) => {
322
+ if (err) {
323
+ reject(err);
324
+ } else {
325
+ resolve(decoded);
326
+ }
327
+ }
328
+ );
329
+ });
330
+ }
331
+
332
+ // src/middlewares/auth.middleware.ts
333
+ function requireAuth() {
334
+ return async (req, res, next) => {
335
+ try {
336
+ const apiKey = req.headers["x-api-key"] || req.headers["x-apikey"];
337
+ const userId = req.headers["x-user-id"] || req.headers["x-userId"];
338
+ if (apiKey) {
339
+ if (apiKey !== process.env.SERVER_API_KEY) {
340
+ return res.status(401).json({ error: "Invalid API key" });
341
+ }
342
+ if (!userId) {
343
+ return res.status(401).json({ error: "User Id is Required" });
344
+ }
345
+ const user = await OrgUser.findOne({ id: userId }).lean();
346
+ if (!user) {
347
+ return res.status(401).json({ error: "User not found" });
348
+ }
349
+ const session2 = buildSession({
350
+ sub: user.id.toString(),
351
+ email: user.email,
352
+ roles: user.roles || []
353
+ });
354
+ session2.authType = "api-key";
355
+ session2.projectId = readProjectId(req) || user.projectId || void 0;
356
+ req.user = session2;
357
+ return next();
358
+ }
359
+ const token = extractToken(req);
360
+ if (!token) {
361
+ return res.status(401).json({ error: "Missing token" });
362
+ }
363
+ const claims = await verifyJwt(token);
364
+ const session = buildSession(claims);
365
+ const pid = readProjectId(req);
366
+ if (pid) session.projectId = pid;
367
+ req.user = session;
368
+ next();
369
+ } catch (e) {
370
+ res.status(401).json({ error: e?.message || "Unauthorized" });
371
+ }
372
+ };
373
+ }
374
+ function authorize(roles = []) {
375
+ return (req, res, next) => {
376
+ if (!roles || roles.length === 0) return next();
377
+ const user = req.user;
378
+ if (!user) {
379
+ return res.status(401).json({ error: "Unauthorized" });
380
+ }
381
+ console.log(user, "user");
382
+ const have = new Set((user.roles || []).map(String));
383
+ const ok = roles.some((r) => have.has(r));
384
+ if (!ok) {
385
+ return res.status(403).json({ error: `Requires one of roles: ${roles.join(", ")}` });
386
+ }
387
+ next();
388
+ };
389
+ }
390
+
391
+ // src/middlewares/validators.ts
392
+ function isEmail(v) {
393
+ return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(v);
394
+ }
395
+ function isPasswordStrong(v) {
396
+ return typeof v === "string" && v.length >= 6 && /[A-Z]/.test(v) && /[^a-zA-Z0-9]/.test(v);
397
+ }
398
+ function validateSignup(req, res, next) {
399
+ const { firstName, lastName, email, password, projectId, metadata } = req.body || {};
400
+ if (!firstName || !lastName)
401
+ return res.status(400).json({ error: "firstName,lastName required" });
402
+ if (!isEmail(email)) return res.status(400).json({ error: "invalid email" });
403
+ if (!isPasswordStrong(password))
404
+ return res.status(400).json({ error: "weak password" });
405
+ if (!projectId) return res.status(400).json({ error: "projectId required" });
406
+ if (!Array.isArray(metadata))
407
+ return res.status(400).json({ error: "metadata must be array" });
408
+ next();
409
+ }
410
+ function validateLogin(req, res, next) {
411
+ const { email, password } = req.body || {};
412
+ if (!isEmail(email)) return res.status(400).json({ error: "invalid email" });
413
+ if (typeof password !== "string")
414
+ return res.status(400).json({ error: "password required" });
415
+ next();
416
+ }
417
+ function validateResetPassword(req, res, next) {
418
+ const { token, newPassword } = req.body || {};
419
+ if (!token) return res.status(400).json({ error: "token required" });
420
+ if (!isPasswordStrong(newPassword))
421
+ return res.status(400).json({ error: "weak password" });
422
+ next();
423
+ }
424
+ function validateResendEmail(req, res, next) {
425
+ const { email } = req.body || {};
426
+ if (!isEmail(email)) return res.status(400).json({ error: "invalid email" });
427
+ next();
428
+ }
429
+ function validateSendInvite(req, res, next) {
430
+ const { email, role } = req.body || {};
431
+ if (!isEmail(email)) return res.status(400).json({ error: "invalid email" });
432
+ if (!["platform_user", "org_admin"].includes(role))
433
+ return res.status(400).json({ error: "invalid role" });
434
+ next();
435
+ }
436
+
437
+ // src/models/invite.model.ts
438
+ var import_mongoose2 = __toESM(require("mongoose"), 1);
439
+ var InviteSchema = new import_mongoose2.default.Schema(
440
+ {
441
+ id: { type: String, required: true, index: true },
442
+ email: { type: String, required: true },
443
+ role: {
444
+ type: String,
445
+ enum: ["platform_user", "org_admin"],
446
+ required: true
447
+ },
448
+ invitedBy: { type: String },
449
+ usedBy: { type: String },
450
+ isUsed: { type: Boolean, default: false },
451
+ usedAt: { type: Date },
452
+ expiresAt: { type: Date },
453
+ isExpired: { type: Boolean, default: false }
454
+ },
455
+ { timestamps: true, collection: "invites" }
456
+ );
457
+ var Invite = import_mongoose2.default.model("Invite", InviteSchema);
458
+
459
+ // src/services/auth-admin.service.ts
460
+ var import_bcrypt = __toESM(require("bcrypt"), 1);
461
+ var import_jsonwebtoken2 = __toESM(require("jsonwebtoken"), 1);
462
+
463
+ // src/models/client.model.ts
464
+ var import_mongoose3 = __toESM(require("mongoose"), 1);
465
+ var ClientSchema = new import_mongoose3.Schema(
466
+ {
467
+ clientId: {
468
+ type: String,
469
+ required: true,
470
+ unique: true,
471
+ index: true
472
+ },
473
+ redirectUris: {
474
+ type: [String],
475
+ default: []
476
+ },
477
+ publicClient: {
478
+ type: Boolean,
479
+ default: false
480
+ },
481
+ // Optional: if you want confidential clients
482
+ secret: {
483
+ type: String,
484
+ required: function() {
485
+ return !this.publicClient;
486
+ }
487
+ }
488
+ },
489
+ {
490
+ timestamps: true
491
+ }
492
+ );
493
+ var ClientModel = import_mongoose3.default.models.Client || import_mongoose3.default.model("Client", ClientSchema);
494
+
495
+ // src/models/rolePermission.model.ts
496
+ var import_mongoose4 = __toESM(require("mongoose"), 1);
497
+ var RolePermissionSchema = new import_mongoose4.Schema(
498
+ {
499
+ orgId: { type: String, default: null, index: true },
500
+ role: { type: String, required: true },
501
+ permissions: { type: [String], default: [] }
502
+ },
503
+ {
504
+ timestamps: true
505
+ }
506
+ );
507
+ RolePermissionSchema.index({ orgId: 1, role: 1 }, { unique: true });
508
+ var RolePermissionModel = import_mongoose4.default.model(
509
+ "RolePermission",
510
+ RolePermissionSchema,
511
+ "role_permissions"
512
+ );
513
+
514
+ // src/services/auth-admin.service.ts
515
+ var AuthAdminService = class {
516
+ token;
517
+ async getAdminToken() {
518
+ return this.ensureAdminToken();
519
+ }
520
+ // -------------------------------------------------------------------
521
+ // CLIENTS
522
+ // -------------------------------------------------------------------
523
+ async createClient(clientId, redirectUris = [], publicClient = false) {
524
+ const client = await ClientModel.create({
525
+ clientId,
526
+ redirectUris,
527
+ publicClient
528
+ });
529
+ return client;
530
+ }
531
+ async updateClient(id, patch) {
532
+ await ClientModel.findByIdAndUpdate(id, patch);
533
+ }
534
+ // -------------------------------------------------------------------
535
+ // USERS
536
+ // -------------------------------------------------------------------
537
+ async listUsersInRealm(_realm, filter) {
538
+ return OrgUser.find(filter || {});
539
+ }
540
+ async getUserById(userId) {
541
+ return OrgUser.findOne({ id: userId });
542
+ }
543
+ async isUserEmailVerified(userId) {
544
+ const user = await OrgUser.findOne({ id: userId });
545
+ return user?.emailVerified;
546
+ }
547
+ async createUserInRealm(payload) {
548
+ const hashedPassword = payload.credentials?.[0]?.value ? await import_bcrypt.default.hash(payload.credentials[0].value, 10) : void 0;
549
+ const user = await OrgUser.create({
550
+ username: payload.username,
551
+ email: payload.email,
552
+ firstName: payload.firstName,
553
+ lastName: payload.lastName,
554
+ projectId: payload.projectId,
555
+ emailVerified: payload.emailVerified || false,
556
+ passwordHash: hashedPassword,
557
+ enabled: true
558
+ });
559
+ return user;
560
+ }
561
+ async assignRealmRole(userId, roleName) {
562
+ const role = await RolePermissionModel.findOne({ name: roleName });
563
+ if (!role) throw new Error(`Role not found: ${roleName}`);
564
+ await OrgUser.findOneAndUpdate(
565
+ { id: userId },
566
+ {
567
+ $addToSet: { roles: role._id }
568
+ }
569
+ );
570
+ }
571
+ async updateUserEmailVerified(userId, emailVerified) {
572
+ await OrgUser.findOneAndUpdate({ id: userId }, { emailVerified });
573
+ }
574
+ async updateUserPassword(userId, newPassword) {
575
+ const hashed = await import_bcrypt.default.hash(newPassword, 10);
576
+ await OrgUser.findOneAndUpdate({ id: userId }, { password: hashed });
577
+ }
578
+ // -------------------------------------------------------------------
579
+ // ADMIN TOKEN (self-issued JWT)
580
+ // -------------------------------------------------------------------
581
+ async ensureAdminToken() {
582
+ const now = Math.floor(Date.now() / 1e3);
583
+ if (this.token && this.token.exp - 30 > now) {
584
+ return this.token.accessToken;
585
+ }
586
+ const payload = {
587
+ type: "admin",
588
+ system: true
589
+ };
590
+ const accessToken = import_jsonwebtoken2.default.sign(payload, process.env.JWT_SECRET, {
591
+ expiresIn: "1h"
592
+ });
593
+ this.token = {
594
+ accessToken,
595
+ exp: now + 3600
596
+ };
597
+ return this.token.accessToken;
598
+ }
599
+ };
600
+
601
+ // src/services/email.service.ts
602
+ var import_jsonwebtoken3 = __toESM(require("jsonwebtoken"), 1);
603
+ var import_nodemailer = __toESM(require("nodemailer"), 1);
604
+ var EmailService = class {
605
+ transporter;
606
+ MAX_EMAILS = 5;
607
+ WINDOW_MINUTES = 15;
608
+ BLOCK_HOURS = 1;
609
+ constructor() {
610
+ this.transporter = import_nodemailer.default.createTransport({
611
+ host: process.env.EMAIL_HOST || "smtp.postmarkapp.com",
612
+ port: process.env.EMAIL_PORT ? Number(process.env.EMAIL_PORT) : 587,
613
+ secure: (process.env.EMAIL_SECURE || "false") === "true",
614
+ auth: { user: process.env.EMAIL_USER, pass: process.env.EMAIL_PASSWORD }
615
+ });
616
+ }
617
+ sign(payload, ttlSec = 60 * 60 * 24) {
618
+ return import_jsonwebtoken3.default.sign(payload, process.env.EMAIL_JWT_SECRET, { expiresIn: ttlSec });
619
+ }
620
+ verify(token) {
621
+ return import_jsonwebtoken3.default.verify(token, process.env.EMAIL_JWT_SECRET);
622
+ }
623
+ async send(to, subject, html) {
624
+ await this.transporter.sendMail({
625
+ from: process.env.EMAIL_FROM,
626
+ to,
627
+ subject,
628
+ html
629
+ });
630
+ }
631
+ canSend(lastEmailSent) {
632
+ const now = Date.now();
633
+ const windowStart = now - this.WINDOW_MINUTES * 60 * 1e3;
634
+ const emailsInWindow = (lastEmailSent || []).map((d) => new Date(d)).filter((d) => d.getTime() >= windowStart);
635
+ if (emailsInWindow.length >= this.MAX_EMAILS)
636
+ return {
637
+ ok: false,
638
+ reason: "RATE_LIMIT",
639
+ waitMs: this.BLOCK_HOURS * 60 * 60 * 1e3
640
+ };
641
+ return { ok: true };
642
+ }
643
+ };
644
+
645
+ // src/utils/cookie.ts
646
+ function cookieOpts(isRefresh = false) {
647
+ const maxAge = isRefresh ? config.cookies.refreshTtlMs : config.cookies.accessTtlMs;
648
+ const secure = process.env.NODE_ENV === "production" ? process.env.COOKIE_SECURE ?? true : false;
649
+ return {
650
+ httpOnly: true,
651
+ secure,
652
+ sameSite: "none",
653
+ domain: process.env.COOKIE_DOMAIN,
654
+ maxAge
655
+ };
656
+ }
657
+ function clearOpts() {
658
+ const secure = process.env.NODE_ENV === "production" ? process.env.COOKIE_SECURE ?? true : false;
659
+ return {
660
+ domain: process.env.COOKIE_DOMAIN,
661
+ sameSite: "none",
662
+ secure
663
+ };
664
+ }
665
+
666
+ // src/express/auth.routes.ts
667
+ function createAuthRouter(options = {}) {
668
+ if (options.config) {
669
+ configureAuthX(options.config);
670
+ }
671
+ const r = (0, import_express.Router)();
672
+ const email = new EmailService();
673
+ const authAdmin = new AuthAdminService();
674
+ r.use(import_express.default.json());
675
+ r.use(import_express.default.urlencoded({ extended: true }));
676
+ r.get(
677
+ "/healthz",
678
+ (_req, res) => res.json({ status: "ok", server: "org-server" })
679
+ );
680
+ r.post("/login", validateLogin, async (req, res) => {
681
+ const { email: emailAddress, password } = req.body || {};
682
+ try {
683
+ const user = await OrgUser.findOne({ email: emailAddress }).select("+password").lean();
684
+ if (!user) {
685
+ return res.status(400).json({
686
+ error: "Invalid email or password",
687
+ code: "INVALID_CREDENTIALS"
688
+ });
689
+ }
690
+ if (!user.emailVerified) {
691
+ return res.status(400).json({
692
+ error: "Please verify your email before logging in.",
693
+ code: "EMAIL_NOT_VERIFIED"
694
+ });
695
+ }
696
+ const isPasswordValid = user.passwordHash ? await import_bcryptjs.default.compare(password, user.passwordHash) : false;
697
+ if (!isPasswordValid) {
698
+ return res.status(400).json({
699
+ error: "Invalid email or password",
700
+ code: "INVALID_CREDENTIALS"
701
+ });
702
+ }
703
+ const tokens = generateTokens(user);
704
+ setAuthCookies(res, tokens);
705
+ if (user.projectId) {
706
+ res.cookie(options.projectCookieName || "projectId", user.projectId, {
707
+ ...cookieOpts(false),
708
+ httpOnly: true
709
+ });
710
+ }
711
+ return res.json({
712
+ message: "Login successful",
713
+ user: toUserResponse(user)
714
+ });
715
+ } catch (err) {
716
+ console.error("Login error:", err);
717
+ return res.status(500).json({ error: "Internal server error" });
718
+ }
719
+ });
720
+ r.post("/signup", validateSignup, async (req, res) => {
721
+ const {
722
+ firstName,
723
+ lastName,
724
+ email: emailAddress,
725
+ password,
726
+ projectId,
727
+ metadata
728
+ } = req.body || {};
729
+ try {
730
+ const kcUser = await authAdmin.createUserInRealm({
731
+ username: emailAddress,
732
+ email: emailAddress,
733
+ firstName,
734
+ lastName,
735
+ projectId,
736
+ credentials: [{ type: "password", value: password, temporary: false }]
737
+ });
738
+ await authAdmin.assignRealmRole(kcUser.id, "platform_user");
739
+ const user = await OrgUser.findOneAndUpdate(
740
+ { email: kcUser.email },
741
+ {
742
+ id: kcUser.id,
743
+ email: kcUser.email,
744
+ firstName,
745
+ lastName,
746
+ projectId,
747
+ metadata,
748
+ roles: ["platform_user"],
749
+ emailVerified: false
750
+ },
751
+ { upsert: true, new: true, setDefaultsOnInsert: true }
752
+ );
753
+ const emailResult = await sendRateLimitedEmail({
754
+ emailService: email,
755
+ user,
756
+ subject: "Verify your email",
757
+ html: buildVerificationTemplate(
758
+ email.sign({ userId: kcUser.id, email: kcUser.email }),
759
+ options
760
+ )
761
+ });
762
+ if (emailResult.rateLimited) {
763
+ return res.status(429).json({
764
+ ok: false,
765
+ error: "Too many verification emails sent. Please try again later.",
766
+ waitMs: emailResult.waitMs
767
+ });
768
+ }
769
+ return res.json({
770
+ id: user.id,
771
+ email: user.email,
772
+ message: "Verification email sent. Please check your inbox."
773
+ });
774
+ } catch (err) {
775
+ return respondWithKeycloakError(res, err, "Signup failed");
776
+ }
777
+ });
778
+ r.get("/me", requireAuth(), (req, res) => {
779
+ return res.json(req.user || null);
780
+ });
781
+ r.post("/logout", async (_req, res) => {
782
+ res.clearCookie("access_token", clearOpts());
783
+ res.clearCookie("refresh_token", clearOpts());
784
+ res.json({ ok: true });
785
+ });
786
+ r.put("/:userId/metadata", requireAuth(), async (req, res) => {
787
+ const { userId } = req.params;
788
+ const { metadata } = req.body || {};
789
+ const user = await OrgUser.findOne({ id: userId });
790
+ if (!user)
791
+ return res.status(404).json({ ok: false, error: "User not found" });
792
+ const map = new Map(
793
+ (user.metadata || []).map((m) => [m.key, m.value])
794
+ );
795
+ for (const item of metadata || []) map.set(item.key, item.value);
796
+ user.metadata = Array.from(map.entries()).map(([key, value]) => ({
797
+ key,
798
+ value
799
+ }));
800
+ await user.save();
801
+ res.json({ ok: true, metadata: user.metadata });
802
+ });
803
+ r.get("/verify-email", async (req, res) => {
804
+ const token = String(req.query.token || "");
805
+ if (!token) {
806
+ return res.status(400).json({ error: "Verification token is required" });
807
+ }
808
+ try {
809
+ const payload = email.verify(token);
810
+ await authAdmin.updateUserEmailVerified(payload.userId, true);
811
+ await OrgUser.updateOne(
812
+ { id: payload.userId },
813
+ { $set: { emailVerified: true } }
814
+ );
815
+ res.json({ ok: true, message: "Email verified" });
816
+ } catch (err) {
817
+ res.status(400).json({ ok: false, error: err?.message || "Invalid token" });
818
+ }
819
+ });
820
+ r.post(
821
+ "/resend-verification-email",
822
+ validateResendEmail,
823
+ async (req, res) => {
824
+ const user = await OrgUser.findOne({ email: req.body.email });
825
+ if (!user)
826
+ return res.status(404).json({ ok: false, error: "User not found" });
827
+ const verified = await authAdmin.isUserEmailVerified(user.id);
828
+ if (verified) {
829
+ return res.status(400).json({ ok: false, error: "Email is already verified" });
830
+ }
831
+ const token = email.sign({
832
+ email: user.email,
833
+ userId: user.id
834
+ });
835
+ const resendResult = await sendRateLimitedEmail({
836
+ emailService: email,
837
+ user,
838
+ subject: "Verify your email",
839
+ html: buildVerificationTemplate(token, options)
840
+ });
841
+ if (resendResult.rateLimited) {
842
+ return res.status(429).json({
843
+ ok: false,
844
+ error: "Too many verification emails sent. Please try again later.",
845
+ waitMs: resendResult.waitMs
846
+ });
847
+ }
848
+ res.json({ ok: true });
849
+ }
850
+ );
851
+ r.post("/forgot-password", validateResendEmail, async (req, res) => {
852
+ const user = await OrgUser.findOne({ email: req.body.email });
853
+ if (!user)
854
+ return res.status(404).json({ ok: false, error: "User not found" });
855
+ const resetToken = email.sign(
856
+ {
857
+ userId: user.id,
858
+ email: user.email,
859
+ firstName: user.firstName,
860
+ lastName: user.lastName
861
+ },
862
+ 60 * 60
863
+ );
864
+ const resetResult = await sendRateLimitedEmail({
865
+ emailService: email,
866
+ user,
867
+ subject: "Reset password",
868
+ html: buildResetTemplate(resetToken, options)
869
+ });
870
+ if (resetResult.rateLimited) {
871
+ return res.status(429).json({
872
+ ok: false,
873
+ error: "Please wait before requesting another password reset email.",
874
+ waitMs: resetResult.waitMs
875
+ });
876
+ }
877
+ res.json({ ok: true, message: "Password reset email sent" });
878
+ });
879
+ r.post("/reset-password", validateResetPassword, async (req, res) => {
880
+ const { token, newPassword } = req.body || {};
881
+ try {
882
+ const payload = email.verify(token);
883
+ const user = await OrgUser.findOne({ keycloakId: payload.userId });
884
+ if (!user) {
885
+ return res.status(404).json({ ok: false, error: "User not found" });
886
+ }
887
+ if (user.lastPasswordReset && payload.iat * 1e3 < user.lastPasswordReset.getTime()) {
888
+ return res.status(400).json({
889
+ ok: false,
890
+ error: "This reset link has already been used. Please request a new one."
891
+ });
892
+ }
893
+ await authAdmin.updateUserPassword(payload.userId, newPassword);
894
+ user.lastPasswordReset = /* @__PURE__ */ new Date();
895
+ await user.save();
896
+ res.json({ ok: true, message: "Password updated successfully" });
897
+ } catch (err) {
898
+ res.status(400).json({ ok: false, error: err?.message || "Invalid or expired token" });
899
+ }
900
+ });
901
+ r.post(
902
+ "/send-invite",
903
+ requireAuth(),
904
+ validateSendInvite,
905
+ async (req, res) => {
906
+ const { email: emailAddress, role } = req.body || {};
907
+ const existingUser = await OrgUser.findOne({ email: emailAddress });
908
+ if (existingUser) {
909
+ return res.status(400).json({ ok: false, error: "User with this email already exists" });
910
+ }
911
+ const existingInvite = await Invite.findOne({
912
+ email: emailAddress,
913
+ isUsed: false,
914
+ isExpired: false
915
+ });
916
+ if (existingInvite) {
917
+ return res.status(400).json({
918
+ ok: false,
919
+ error: "An active invite already exists for this email"
920
+ });
921
+ }
922
+ const token = email.sign({
923
+ email: emailAddress,
924
+ role,
925
+ inviteId: (0, import_crypto.randomUUID)()
926
+ });
927
+ const invite = await Invite.create({
928
+ id: token,
929
+ email: emailAddress,
930
+ role,
931
+ invitedBy: req.user?.sub,
932
+ isUsed: false,
933
+ expiresAt: new Date(Date.now() + 7 * 24 * 60 * 60 * 1e3)
934
+ });
935
+ await email.send(
936
+ emailAddress,
937
+ "You are invited",
938
+ `<a href="${getFrontendBaseUrl(options)}/auth/accept-invite?token=${token}">Accept</a>`
939
+ );
940
+ res.json({
941
+ ok: true,
942
+ inviteId: invite.id,
943
+ email: invite.email,
944
+ role: invite.role,
945
+ expiresAt: invite.expiresAt
946
+ });
947
+ }
948
+ );
949
+ r.get("/accept-invite", async (req, res) => {
950
+ const inv = await Invite.findOne({ id: String(req.query.token) });
951
+ res.json({ ok: !!inv && !inv.isUsed && !inv.isExpired });
952
+ });
953
+ r.post("/accept-invite", async (req, res) => {
954
+ const { token, firstName, lastName, password, projectId } = req.body || {};
955
+ if (!token || !firstName || !lastName || !isPasswordStrong(password || "")) {
956
+ return res.status(400).json({ ok: false, error: "Invalid payload" });
957
+ }
958
+ const invite = await Invite.findOne({
959
+ id: token,
960
+ isUsed: false,
961
+ isExpired: false
962
+ });
963
+ if (!invite) {
964
+ return res.status(400).json({ ok: false, error: "Invitation not found or already used" });
965
+ }
966
+ if (invite.expiresAt && invite.expiresAt.getTime() < Date.now()) {
967
+ invite.isExpired = true;
968
+ await invite.save();
969
+ return res.status(400).json({ ok: false, error: "Invitation has expired" });
970
+ }
971
+ try {
972
+ const kcUser = await authAdmin.createUserInRealm({
973
+ username: invite.email,
974
+ email: invite.email,
975
+ firstName,
976
+ lastName,
977
+ projectId,
978
+ emailVerified: true,
979
+ credentials: [{ type: "password", value: password, temporary: false }]
980
+ });
981
+ await authAdmin.assignRealmRole(kcUser.id, invite.role);
982
+ await OrgUser.findOneAndUpdate(
983
+ { email: invite.email },
984
+ {
985
+ id: kcUser.id,
986
+ email: invite.email,
987
+ firstName,
988
+ lastName,
989
+ roles: [invite.role],
990
+ emailVerified: true
991
+ },
992
+ { upsert: true, new: true, setDefaultsOnInsert: true }
993
+ );
994
+ invite.isUsed = true;
995
+ invite.usedAt = /* @__PURE__ */ new Date();
996
+ invite.usedBy = kcUser.id;
997
+ await invite.save();
998
+ res.json({
999
+ ok: true,
1000
+ message: "Account created successfully.",
1001
+ email: invite.email
1002
+ });
1003
+ } catch (err) {
1004
+ res.status(400).json({
1005
+ ok: false,
1006
+ error: err?.response?.data?.error_description || err?.message || "Failed to create account"
1007
+ });
1008
+ }
1009
+ });
1010
+ r.get("/invites", requireAuth(), async (_req, res) => {
1011
+ const invites = await Invite.find().sort({ createdAt: -1 }).lean();
1012
+ res.json(invites);
1013
+ });
1014
+ r.delete("/invites/:inviteId", requireAuth(), async (req, res) => {
1015
+ await Invite.deleteOne({ id: req.params.inviteId });
1016
+ res.json({ ok: true });
1017
+ });
1018
+ r.get("/get-user-by-email", async (req, res) => {
1019
+ const user = await OrgUser.findOne({ email: req.query.email }).lean();
1020
+ res.json(user || null);
1021
+ });
1022
+ r.get("/google", async (_req, res) => {
1023
+ res.json({ url: "/auth/google/callback?code=demo" });
1024
+ });
1025
+ r.get("/google/callback", async (_req, res) => {
1026
+ res.cookie(
1027
+ "access_token",
1028
+ "ACCESS.TOKEN.PLACEHOLDER",
1029
+ cookieOpts(false)
1030
+ );
1031
+ res.redirect("/");
1032
+ });
1033
+ r.get("/get-users", async (req, res) => {
1034
+ const user = await OrgUser.find({ projectId: req.query.projectId }).lean();
1035
+ res.json(user || null);
1036
+ });
1037
+ return r;
1038
+ }
1039
+ function setAuthCookies(res, tokens) {
1040
+ if (tokens?.access_token) {
1041
+ res.cookie("access_token", tokens.access_token, {
1042
+ httpOnly: true,
1043
+ secure: false,
1044
+ sameSite: "lax",
1045
+ maxAge: 24 * 60 * 60 * 1e3,
1046
+ // 24 hours
1047
+ path: "/"
1048
+ });
1049
+ }
1050
+ if (tokens?.refresh_token) {
1051
+ res.cookie("refresh_token", tokens.refresh_token, {
1052
+ httpOnly: true,
1053
+ secure: false,
1054
+ sameSite: "lax",
1055
+ maxAge: 24 * 60 * 60 * 1e3,
1056
+ // 24 hours
1057
+ path: "/"
1058
+ });
1059
+ }
1060
+ }
1061
+ function toUserResponse(user) {
1062
+ if (!user) return null;
1063
+ return {
1064
+ sub: user.id || user.keycloakId,
1065
+ email: user.email,
1066
+ firstName: user.firstName,
1067
+ lastName: user.lastName,
1068
+ projectId: user.projectId,
1069
+ metadata: user.metadata,
1070
+ roles: user.roles
1071
+ };
1072
+ }
1073
+ function respondWithKeycloakError(res, err, fallback, status = 400) {
1074
+ const description = err?.response?.data?.error_description || err?.response?.data?.errorMessage || err?.message || fallback;
1075
+ return res.status(status).json({ ok: false, error: description });
1076
+ }
1077
+ function buildVerificationTemplate(token, options) {
1078
+ return `<a href="${getFrontendBaseUrl(options)}/auth/verify-email?token=${token}">Verify</a>`;
1079
+ }
1080
+ function buildResetTemplate(token, options) {
1081
+ return `<a href="${getFrontendBaseUrl(options)}/auth/reset-password?token=${token}">Reset</a>`;
1082
+ }
1083
+ function getFrontendBaseUrl(options) {
1084
+ if (options.frontendBaseUrl)
1085
+ return options.frontendBaseUrl.replace(/\/$/, "");
1086
+ const domain = process.env.ORG_DOMAIN?.replace(/\/$/, "");
1087
+ if (!domain) return "";
1088
+ return domain.startsWith("http") ? domain : `https://${domain}`;
1089
+ }
1090
+ async function sendRateLimitedEmail({
1091
+ emailService,
1092
+ user,
1093
+ subject,
1094
+ html
1095
+ }) {
1096
+ const can = emailService.canSend(user?.lastEmailSent || []);
1097
+ if (!can.ok) {
1098
+ return { rateLimited: true, waitMs: can.waitMs };
1099
+ }
1100
+ await emailService.send(user.email, subject, html);
1101
+ user.lastEmailSent = [...user.lastEmailSent || [], /* @__PURE__ */ new Date()];
1102
+ await user.save();
1103
+ return { rateLimited: false };
1104
+ }
1105
+ function generateTokens(user) {
1106
+ const accessToken = import_jsonwebtoken4.default.sign(
1107
+ {
1108
+ sub: user.id.toString(),
1109
+ email: user.email,
1110
+ roles: user.roles || [],
1111
+ type: "user"
1112
+ },
1113
+ process.env.JWT_SECRET,
1114
+ { expiresIn: "1h" }
1115
+ );
1116
+ const refreshToken = import_jsonwebtoken4.default.sign(
1117
+ { sub: user._id.toString() },
1118
+ process.env.JWT_SECRET,
1119
+ { expiresIn: "30d" }
1120
+ );
1121
+ return { access_token: accessToken, refresh_token: refreshToken };
1122
+ }
1123
+
1124
+ // src/express/dashboards.routes.ts
1125
+ var import_express2 = __toESM(require("express"), 1);
1126
+ function createDashboardRouter(options) {
1127
+ const r = (0, import_express2.Router)();
1128
+ const kc = new AuthAdminService();
1129
+ r.use(import_express2.default.json());
1130
+ r.post("/", requireAuth(), async (req, res, next) => {
1131
+ try {
1132
+ const { slug, isPublic, authFlow, orgDomain } = req.body || {};
1133
+ const redirectUris = [`https://${slug}.${orgDomain}/*`];
1134
+ const created = await kc.createClient(slug, redirectUris, !!isPublic);
1135
+ if (authFlow || isPublic != null) {
1136
+ await kc.updateClient(created.id, {
1137
+ authenticationFlowBindingOverrides: authFlow ? { browser: authFlow } : void 0,
1138
+ registrationAllowed: !!isPublic
1139
+ });
1140
+ }
1141
+ res.json({ clientId: created.clientId });
1142
+ } catch (e) {
1143
+ next(e);
1144
+ }
1145
+ });
1146
+ return r;
1147
+ }
1148
+
1149
+ // src/express/email.routes.ts
1150
+ var import_express3 = require("express");
1151
+ function createEmailRouter(options) {
1152
+ const r = (0, import_express3.Router)();
1153
+ r.get(
1154
+ "/verify",
1155
+ (req, res) => res.json({ ok: true, token: req.query.token })
1156
+ );
1157
+ return r;
1158
+ }
1159
+
1160
+ // src/express/projects.routes.ts
1161
+ var import_express4 = require("express");
1162
+
1163
+ // src/services/projects.service.ts
1164
+ var import_crypto2 = require("crypto");
1165
+
1166
+ // src/models/moduleConnection.model.ts
1167
+ var import_mongoose5 = __toESM(require("mongoose"), 1);
1168
+ var ModuleItemSchema = new import_mongoose5.default.Schema(
1169
+ { id: { type: String, required: true } },
1170
+ { _id: false }
1171
+ );
1172
+ var ModuleConnectionSchema = new import_mongoose5.default.Schema(
1173
+ {
1174
+ projectId: { type: String, required: true, index: true },
1175
+ modules: {
1176
+ data: { type: [ModuleItemSchema], default: [] },
1177
+ integration: { type: [ModuleItemSchema], default: [] },
1178
+ storage: { type: [ModuleItemSchema], default: [] }
1179
+ }
1180
+ },
1181
+ { timestamps: true, collection: "module_connection" }
1182
+ );
1183
+ var ModuleConnection = import_mongoose5.default.model(
1184
+ "ModuleConnection",
1185
+ ModuleConnectionSchema
1186
+ );
1187
+
1188
+ // src/models/project.model.ts
1189
+ var import_mongoose6 = __toESM(require("mongoose"), 1);
1190
+ var ProjectSchema = new import_mongoose6.default.Schema(
1191
+ {
1192
+ _id: { type: String, required: true },
1193
+ org_id: { type: String, required: true, index: true },
1194
+ name: { type: String, required: true },
1195
+ description: { type: String },
1196
+ secret: { type: String, required: true }
1197
+ },
1198
+ { timestamps: true, collection: "projects" }
1199
+ );
1200
+ var Project = import_mongoose6.default.model("Project", ProjectSchema);
1201
+
1202
+ // src/services/projects.service.ts
1203
+ var ProjectsService = class {
1204
+ async create(org_id, name, description) {
1205
+ const _id = (0, import_crypto2.randomUUID)();
1206
+ const secret = (0, import_crypto2.randomUUID)();
1207
+ const p = await Project.create({ _id, org_id, name, description, secret });
1208
+ await ModuleConnection.create({
1209
+ projectId: _id,
1210
+ modules: { data: [], integration: [], storage: [] }
1211
+ });
1212
+ return p.toObject();
1213
+ }
1214
+ async list(org_id) {
1215
+ return Project.find({ org_id }).lean();
1216
+ }
1217
+ async get(org_id, id) {
1218
+ return Project.findOne({ org_id, _id: id }).lean();
1219
+ }
1220
+ async update(org_id, id, patch) {
1221
+ return Project.findOneAndUpdate(
1222
+ { org_id, _id: id },
1223
+ { $set: patch },
1224
+ { new: true }
1225
+ ).lean();
1226
+ }
1227
+ async remove(org_id, id) {
1228
+ await Project.deleteOne({ org_id, _id: id });
1229
+ await ModuleConnection.deleteMany({ projectId: id });
1230
+ return { ok: true };
1231
+ }
1232
+ };
1233
+
1234
+ // src/express/projects.routes.ts
1235
+ function createProjectsRouter(options) {
1236
+ const r = (0, import_express4.Router)();
1237
+ const svc = new ProjectsService();
1238
+ r.post("/create", requireAuth(), async (req, res) => {
1239
+ const { org_id, name, description } = req.body || {};
1240
+ const p = await svc.create(org_id, name, description);
1241
+ res.json(p);
1242
+ });
1243
+ r.get("/:org_id", requireAuth(), async (req, res) => {
1244
+ res.json(await svc.list(req.params.org_id));
1245
+ });
1246
+ r.get("/:org_id/:id", requireAuth(), async (req, res) => {
1247
+ res.json(await svc.get(req.params.org_id, req.params.id));
1248
+ });
1249
+ r.put("/:org_id/:id", requireAuth(), async (req, res) => {
1250
+ res.json(
1251
+ await svc.update(req.params.org_id, req.params.id, req.body || {})
1252
+ );
1253
+ });
1254
+ r.delete("/:org_id/:id", requireAuth(), async (req, res) => {
1255
+ res.json(await svc.remove(req.params.org_id, req.params.id));
1256
+ });
1257
+ return r;
1258
+ }
1259
+
1260
+ // src/express/admin/admin.routes.ts
1261
+ var import_bcryptjs2 = __toESM(require("bcryptjs"), 1);
1262
+ var import_crypto3 = require("crypto");
1263
+ var import_express5 = __toESM(require("express"), 1);
1264
+
1265
+ // src/core/utils.ts
1266
+ function hasRole(session, role) {
1267
+ if (!session || !session.roles) return false;
1268
+ return session.roles.includes(role);
1269
+ }
1270
+ function hasAnyRole(session, roles) {
1271
+ if (!session || !session.roles || !Array.isArray(roles) || roles.length === 0) {
1272
+ return false;
1273
+ }
1274
+ return roles.some((role) => session.roles.includes(role));
1275
+ }
1276
+ function hasAllRoles(session, roles) {
1277
+ if (!session || !session.roles || !Array.isArray(roles) || roles.length === 0) {
1278
+ return false;
1279
+ }
1280
+ return roles.every((role) => session.roles.includes(role));
1281
+ }
1282
+ function hasPermission(session, permission) {
1283
+ if (!session || !session.permissions) return false;
1284
+ return session.permissions.includes(permission);
1285
+ }
1286
+ function hasAnyPermission(session, permissions) {
1287
+ if (!session || !session.permissions || !Array.isArray(permissions) || permissions.length === 0) {
1288
+ return false;
1289
+ }
1290
+ return permissions.some((perm) => session.permissions.includes(perm));
1291
+ }
1292
+ function hasAllPermissions(session, permissions) {
1293
+ if (!session || !session.permissions || !Array.isArray(permissions) || permissions.length === 0) {
1294
+ return false;
1295
+ }
1296
+ return permissions.every((perm) => session.permissions.includes(perm));
1297
+ }
1298
+
1299
+ // src/middlewares/requireRole.ts
1300
+ function requireRole(...roles) {
1301
+ return (req, res, next) => {
1302
+ const user = req.user;
1303
+ if (!user) {
1304
+ return res.status(401).json({ error: "Unauthorized" });
1305
+ }
1306
+ if (!roles || roles.length === 0) {
1307
+ return next();
1308
+ }
1309
+ if (!hasAnyRole(user, roles)) {
1310
+ return res.status(403).json({
1311
+ error: `Requires one of roles: ${roles.join(", ")}`,
1312
+ required: roles,
1313
+ userRoles: user.roles
1314
+ });
1315
+ }
1316
+ next();
1317
+ };
1318
+ }
1319
+
1320
+ // src/models/permissions.model.ts
1321
+ var import_mongoose7 = __toESM(require("mongoose"), 1);
1322
+ var PermissionsSchema = new import_mongoose7.Schema(
1323
+ {
1324
+ id: { type: String, required: true, index: true },
1325
+ orgId: { type: String, default: null, index: true },
1326
+ key: { type: String, required: true },
1327
+ type: { type: String, required: true },
1328
+ apiId: { type: String, required: false },
1329
+ description: { type: String },
1330
+ isInternal: { type: Boolean, default: false }
1331
+ },
1332
+ {
1333
+ timestamps: true
1334
+ }
1335
+ );
1336
+ PermissionsSchema.index({ orgId: 1, key: 1 }, { unique: true });
1337
+ var PermissionsModel = import_mongoose7.default.model(
1338
+ "Permissions",
1339
+ PermissionsSchema,
1340
+ "permissions"
1341
+ );
1342
+
1343
+ // src/express/admin/admin.routes.ts
1344
+ function resolveOrgId(req) {
1345
+ const user = req.user || {};
1346
+ const fromUser = user.orgId || user.org_id || null;
1347
+ const fromQuery = req.query.orgId || null;
1348
+ const fromBody = req.body && req.body.orgId || null;
1349
+ return fromQuery || fromBody || fromUser;
1350
+ }
1351
+ function resolveProjectId(req) {
1352
+ const user = req.user || {};
1353
+ const fromUser = user.projectId || null;
1354
+ const fromQuery = req.query.projectId || null;
1355
+ const fromBody = req.body && req.body.projectId || null;
1356
+ return fromQuery || fromBody || fromUser;
1357
+ }
1358
+ function createAdminRouter(_options = {}) {
1359
+ const r = (0, import_express5.Router)();
1360
+ r.use(import_express5.default.json());
1361
+ r.use(import_express5.default.urlencoded({ extended: true }));
1362
+ const adminGuards = [requireAuth(), requireRole("platform_admin")];
1363
+ r.post(
1364
+ "/users",
1365
+ ...adminGuards,
1366
+ async (req, res) => {
1367
+ const {
1368
+ firstName,
1369
+ lastName,
1370
+ email: emailAddress,
1371
+ password,
1372
+ emailVerified = false,
1373
+ roles = []
1374
+ } = req.body || {};
1375
+ const projectId = resolveProjectId(req);
1376
+ try {
1377
+ const hashedPassword = password ? await import_bcryptjs2.default.hash(password, 10) : void 0;
1378
+ const user = await OrgUser.create({
1379
+ id: (0, import_crypto3.randomUUID)(),
1380
+ email: emailAddress,
1381
+ orgId: process.env.ORG_ID,
1382
+ firstName,
1383
+ lastName,
1384
+ projectId,
1385
+ emailVerified,
1386
+ metadata: [],
1387
+ passwordHash: hashedPassword,
1388
+ roles
1389
+ });
1390
+ return res.json({
1391
+ id: user.id,
1392
+ email: user.email,
1393
+ message: "Verification email sent. Please check your inbox."
1394
+ });
1395
+ } catch (err) {
1396
+ console.error("Create user error:", err);
1397
+ return res.status(500).json({ error: "INTERNAL_ERROR" });
1398
+ }
1399
+ }
1400
+ );
1401
+ r.delete(
1402
+ "/users",
1403
+ ...adminGuards,
1404
+ async (req, res) => {
1405
+ try {
1406
+ const userId = req?.body?.id || req?.query?.id;
1407
+ if (!userId) {
1408
+ return res.status(400).json({
1409
+ error: "VALIDATION_ERROR",
1410
+ message: "UserId is required (send in body.id or ?id=...)"
1411
+ });
1412
+ }
1413
+ const deleted = await OrgUser.findOneAndDelete({ id: userId }).exec();
1414
+ if (!deleted) {
1415
+ return res.status(404).json({
1416
+ error: "NOT_FOUND",
1417
+ message: "User not found or already deleted"
1418
+ });
1419
+ }
1420
+ return res.status(200).json({
1421
+ ok: true,
1422
+ message: "User deleted successfully",
1423
+ deletedUser: {
1424
+ id: deleted.id,
1425
+ firstName: deleted.firstName,
1426
+ email: deleted.email,
1427
+ orgId: deleted.orgId
1428
+ }
1429
+ });
1430
+ } catch (err) {
1431
+ console.error("Delete user error:", err);
1432
+ return res.status(500).json({ error: "INTERNAL_ERROR" });
1433
+ }
1434
+ }
1435
+ );
1436
+ r.put(
1437
+ "/users/:id",
1438
+ ...adminGuards,
1439
+ async (req, res) => {
1440
+ const userId = req.params.id;
1441
+ const {
1442
+ firstName,
1443
+ lastName,
1444
+ email: emailAddress,
1445
+ password,
1446
+ emailVerified,
1447
+ roles
1448
+ } = req.body || {};
1449
+ try {
1450
+ const existingUser = await OrgUser.findOne({
1451
+ id: userId,
1452
+ orgId: process.env.ORG_ID
1453
+ });
1454
+ if (!existingUser) {
1455
+ return res.status(404).json({ error: "USER_NOT_FOUND" });
1456
+ }
1457
+ if (firstName !== void 0) existingUser.firstName = firstName;
1458
+ if (lastName !== void 0) existingUser.lastName = lastName;
1459
+ if (emailAddress !== void 0) existingUser.email = emailAddress;
1460
+ if (emailVerified !== void 0)
1461
+ existingUser.emailVerified = emailVerified;
1462
+ if (roles !== void 0) existingUser.roles = roles;
1463
+ if (password) {
1464
+ existingUser.passwordHash = await import_bcryptjs2.default.hash(password, 10);
1465
+ }
1466
+ await existingUser.save();
1467
+ return res.json({
1468
+ id: existingUser.id,
1469
+ email: existingUser.email,
1470
+ message: "User updated successfully."
1471
+ });
1472
+ } catch (err) {
1473
+ console.error("Update user error:", err);
1474
+ return res.status(500).json({ error: "INTERNAL_ERROR" });
1475
+ }
1476
+ }
1477
+ );
1478
+ r.get(
1479
+ "/permissions",
1480
+ ...adminGuards,
1481
+ async (req, res) => {
1482
+ try {
1483
+ const orgId = resolveOrgId(req);
1484
+ const filter = {};
1485
+ if (orgId !== null) {
1486
+ filter.orgId = orgId;
1487
+ } else {
1488
+ filter.orgId = null;
1489
+ }
1490
+ const items = await PermissionsModel.find(filter).lean().exec();
1491
+ return res.json(items);
1492
+ } catch (err) {
1493
+ return res.status(500).json({ error: "INTERNAL_ERROR" });
1494
+ }
1495
+ }
1496
+ );
1497
+ r.post(
1498
+ "/permissions",
1499
+ ...adminGuards,
1500
+ async (req, res) => {
1501
+ try {
1502
+ const orgId = resolveOrgId(req);
1503
+ const {
1504
+ key,
1505
+ type,
1506
+ apiId,
1507
+ description,
1508
+ isInternal = false
1509
+ } = req.body || {};
1510
+ if (!key || !type) {
1511
+ return res.status(400).json({
1512
+ error: "VALIDATION_ERROR",
1513
+ message: "permission key, and permission type are required"
1514
+ });
1515
+ }
1516
+ const id = (0, import_crypto3.randomUUID)();
1517
+ const permission = await PermissionsModel.create({
1518
+ id,
1519
+ orgId: orgId ?? null,
1520
+ key,
1521
+ type,
1522
+ apiId,
1523
+ description,
1524
+ isInternal: !!isInternal
1525
+ });
1526
+ await RolePermissionModel.findOneAndUpdate(
1527
+ { orgId: orgId ?? null, role: "platform_admin" },
1528
+ { $addToSet: { permissions: key } },
1529
+ { upsert: true, new: true }
1530
+ ).exec();
1531
+ return res.status(201).json(permission);
1532
+ } catch (err) {
1533
+ if (err && err.code === 11e3) {
1534
+ return res.status(409).json({
1535
+ error: "DUPLICATE_PERMISSION",
1536
+ message: "Permission key already exists for this org"
1537
+ });
1538
+ }
1539
+ return res.status(500).json({ error: "INTERNAL_ERROR" });
1540
+ }
1541
+ }
1542
+ );
1543
+ r.put(
1544
+ "/permissions/:id",
1545
+ ...adminGuards,
1546
+ async (req, res) => {
1547
+ try {
1548
+ const orgId = resolveOrgId(req);
1549
+ const permissionId = req.params.id;
1550
+ const { key, type, apiId, description, isInternal } = req.body || {};
1551
+ const existing = await PermissionsModel.findOne({
1552
+ id: permissionId,
1553
+ orgId: orgId ?? null
1554
+ });
1555
+ if (!existing) {
1556
+ return res.status(404).json({
1557
+ error: "NOT_FOUND",
1558
+ message: "Permission does not exist"
1559
+ });
1560
+ }
1561
+ const oldKey = existing.key;
1562
+ if (key !== void 0) existing.key = key;
1563
+ if (type !== void 0) existing.type = type;
1564
+ if (apiId !== void 0) existing.apiId = apiId;
1565
+ if (description !== void 0) existing.description = description;
1566
+ if (isInternal !== void 0) existing.isInternal = !!isInternal;
1567
+ await existing.save();
1568
+ if (oldKey !== key) {
1569
+ await RolePermissionModel.updateMany(
1570
+ {
1571
+ orgId: orgId ?? null,
1572
+ permissions: oldKey
1573
+ },
1574
+ {
1575
+ $pull: { permissions: oldKey }
1576
+ }
1577
+ );
1578
+ await RolePermissionModel.updateMany(
1579
+ {
1580
+ orgId: orgId ?? null
1581
+ },
1582
+ {
1583
+ $addToSet: { permissions: key }
1584
+ }
1585
+ );
1586
+ }
1587
+ return res.json(existing);
1588
+ } catch (err) {
1589
+ if (err && err.code === 11e3) {
1590
+ return res.status(409).json({
1591
+ error: "DUPLICATE_PERMISSION",
1592
+ message: "Permission key already exists for this org"
1593
+ });
1594
+ }
1595
+ console.error("Update permission error:", err);
1596
+ return res.status(500).json({ error: "INTERNAL_ERROR" });
1597
+ }
1598
+ }
1599
+ );
1600
+ r.delete(
1601
+ "/permissions",
1602
+ ...adminGuards,
1603
+ async (req, res) => {
1604
+ try {
1605
+ const permissionId = req?.body?.id || req?.query?.id;
1606
+ if (!permissionId) {
1607
+ return res.status(400).json({
1608
+ error: "VALIDATION_ERROR",
1609
+ message: "Permission id is required (send in body.id or ?id=...)"
1610
+ });
1611
+ }
1612
+ const existing = await PermissionsModel.findOne({ id: permissionId });
1613
+ if (!existing) {
1614
+ return res.status(404).json({
1615
+ error: "NOT_FOUND",
1616
+ message: "Permission not found or already deleted"
1617
+ });
1618
+ }
1619
+ const { key, orgId } = existing;
1620
+ await PermissionsModel.deleteOne({ id: permissionId });
1621
+ await RolePermissionModel.updateMany(
1622
+ { orgId: orgId ?? null },
1623
+ { $pull: { permissions: key } }
1624
+ );
1625
+ return res.status(200).json({
1626
+ ok: true,
1627
+ message: "Permission deleted successfully",
1628
+ deletedPermission: {
1629
+ id: existing.id,
1630
+ key: existing.key,
1631
+ type: existing.type,
1632
+ apiId: existing.apiId,
1633
+ description: existing.description,
1634
+ isInternal: existing.isInternal,
1635
+ orgId: existing.orgId
1636
+ }
1637
+ });
1638
+ } catch (err) {
1639
+ console.error("Delete permission error:", err);
1640
+ return res.status(500).json({ error: "INTERNAL_ERROR" });
1641
+ }
1642
+ }
1643
+ );
1644
+ r.get(
1645
+ "/roles",
1646
+ ...adminGuards,
1647
+ async (req, res) => {
1648
+ try {
1649
+ const orgId = resolveOrgId(req);
1650
+ const filter = {};
1651
+ if (orgId !== null) {
1652
+ filter.orgId = orgId;
1653
+ } else {
1654
+ filter.orgId = null;
1655
+ }
1656
+ const roles = await RolePermissionModel.find(filter).lean().exec();
1657
+ return res.json(roles);
1658
+ } catch (err) {
1659
+ return res.status(500).json({ error: "INTERNAL_ERROR" });
1660
+ }
1661
+ }
1662
+ );
1663
+ r.post(
1664
+ "/roles",
1665
+ ...adminGuards,
1666
+ async (req, res) => {
1667
+ try {
1668
+ const orgId = resolveOrgId(req);
1669
+ const { role, permissions } = req.body || {};
1670
+ if (!role || !Array.isArray(permissions)) {
1671
+ return res.status(400).json({
1672
+ error: "VALIDATION_ERROR",
1673
+ message: "role and permissions[] are required"
1674
+ });
1675
+ }
1676
+ const id = (0, import_crypto3.randomUUID)();
1677
+ const doc = await RolePermissionModel.findOneAndUpdate(
1678
+ { orgId: orgId ?? null, role },
1679
+ { $set: { permissions } },
1680
+ { upsert: true, new: true }
1681
+ ).exec();
1682
+ return res.status(200).json(doc);
1683
+ } catch (err) {
1684
+ return res.status(500).json({ error: "INTERNAL_ERROR" });
1685
+ }
1686
+ }
1687
+ );
1688
+ r.put(
1689
+ "/roles/:id",
1690
+ ...adminGuards,
1691
+ async (req, res) => {
1692
+ try {
1693
+ const orgId = resolveOrgId(req);
1694
+ const roleId = req.params.id;
1695
+ const { role: newRoleName, permissions } = req.body || {};
1696
+ if (!newRoleName || !Array.isArray(permissions)) {
1697
+ return res.status(400).json({
1698
+ error: "VALIDATION_ERROR",
1699
+ message: "role and permissions are required"
1700
+ });
1701
+ }
1702
+ const existing = await RolePermissionModel.findById(roleId);
1703
+ if (!existing) {
1704
+ return res.status(404).json({
1705
+ error: "ROLE_NOT_FOUND",
1706
+ message: "Role does not exist"
1707
+ });
1708
+ }
1709
+ const oldRoleName = existing.role;
1710
+ existing.role = newRoleName;
1711
+ existing.permissions = permissions;
1712
+ await existing.save();
1713
+ if (oldRoleName !== newRoleName) {
1714
+ await OrgUser.updateMany(
1715
+ {
1716
+ orgId: orgId ?? null,
1717
+ roles: oldRoleName
1718
+ },
1719
+ {
1720
+ $pull: { roles: oldRoleName }
1721
+ }
1722
+ );
1723
+ await OrgUser.updateMany(
1724
+ {
1725
+ orgId: orgId ?? null,
1726
+ roles: { $ne: newRoleName }
1727
+ // avoid duplicates
1728
+ },
1729
+ {
1730
+ $addToSet: { roles: newRoleName }
1731
+ }
1732
+ );
1733
+ }
1734
+ return res.status(200).json(existing);
1735
+ } catch (err) {
1736
+ console.error("Update role error:", err);
1737
+ return res.status(500).json({ error: "INTERNAL_ERROR" });
1738
+ }
1739
+ }
1740
+ );
1741
+ r.delete(
1742
+ "/roles",
1743
+ ...adminGuards,
1744
+ async (req, res) => {
1745
+ try {
1746
+ const roleId = req?.body?.id || req?.query?.id;
1747
+ if (!roleId) {
1748
+ return res.status(400).json({
1749
+ error: "VALIDATION_ERROR",
1750
+ message: "Role _id is required (send in body.id or ?id=...)"
1751
+ });
1752
+ }
1753
+ if (!/^[0-9a-fA-F]{24}$/.test(roleId)) {
1754
+ return res.status(400).json({
1755
+ error: "VALIDATION_ERROR",
1756
+ message: "Invalid role _id format"
1757
+ });
1758
+ }
1759
+ const deleted = await RolePermissionModel.findByIdAndDelete(roleId).exec();
1760
+ if (!deleted) {
1761
+ return res.status(404).json({
1762
+ error: "NOT_FOUND",
1763
+ message: "Role not found or already deleted"
1764
+ });
1765
+ }
1766
+ return res.status(200).json({
1767
+ ok: true,
1768
+ message: "Role deleted successfully",
1769
+ deletedRole: {
1770
+ _id: deleted._id,
1771
+ role: deleted.role,
1772
+ orgId: deleted.orgId
1773
+ }
1774
+ });
1775
+ } catch (err) {
1776
+ console.error("Delete role error:", err);
1777
+ return res.status(500).json({ error: "INTERNAL_ERROR" });
1778
+ }
1779
+ }
1780
+ );
1781
+ return r;
1782
+ }
1783
+
1784
+ // src/nest/index.ts
1785
+ var nest = {
1786
+ mountAuthRouter(app, options) {
1787
+ const httpAdapter = app.getHttpAdapter().getInstance();
1788
+ const authRouter = createAuthRouter(options.routerOptions || {});
1789
+ const authPath = options.authBasePath || "/auth";
1790
+ httpAdapter.use(authPath, authRouter);
1791
+ const adminRouter = createAdminRouter(options);
1792
+ const adminPath = options.adminBasePath || "/admin";
1793
+ httpAdapter.use(adminPath, adminRouter);
1794
+ const dashboardRouter = createDashboardRouter(options);
1795
+ const dashboardPath = options.dashboardBasePath || "/dashboards";
1796
+ httpAdapter.use(dashboardPath, dashboardRouter);
1797
+ const emailRouter = createEmailRouter(options);
1798
+ const emailPath = options.emailBasePath || "/email";
1799
+ httpAdapter.use(emailPath, emailRouter);
1800
+ const projectsRouter = createProjectsRouter(options);
1801
+ const projectsPath = options.projectsBasePath || "/projects";
1802
+ httpAdapter.use(projectsPath, projectsRouter);
1803
+ return {
1804
+ authRouter,
1805
+ dashboardRouter,
1806
+ emailRouter,
1807
+ projectsRouter,
1808
+ adminRouter
1809
+ };
1810
+ }
1811
+ };
1812
+
1813
+ // src/middlewares/permission.middleware.ts
1814
+ function requirePermission(permissionKey) {
1815
+ return async (req, res, next) => {
1816
+ try {
1817
+ console.log("INSIDE PERMISSION MIDDLEWARE", permissionKey);
1818
+ const user = req.user;
1819
+ if (!user) {
1820
+ return res.status(401).json({ error: "Unauthorized" });
1821
+ }
1822
+ const roles = Array.isArray(user.roles) ? user.roles : [];
1823
+ if (!roles.length) {
1824
+ return res.status(403).json({ error: "Forbidden", reason: "NO_ROLES" });
1825
+ }
1826
+ if (roles.includes("platform_admin")) {
1827
+ return next();
1828
+ }
1829
+ const orgId = user.orgId || user.org_id || user.projectId || null;
1830
+ const rolePermissions = await RolePermissionModel.find({
1831
+ orgId,
1832
+ role: { $in: roles }
1833
+ }).lean().exec();
1834
+ const allowed = /* @__PURE__ */ new Set();
1835
+ for (const rp of rolePermissions) {
1836
+ if (Array.isArray(rp.permissions)) {
1837
+ for (const p of rp.permissions) {
1838
+ allowed.add(p);
1839
+ }
1840
+ }
1841
+ }
1842
+ if (!allowed.has(permissionKey)) {
1843
+ return res.status(403).json({
1844
+ error: "Forbidden",
1845
+ reason: "MISSING_PERMISSION",
1846
+ permission: permissionKey
1847
+ });
1848
+ }
1849
+ return next();
1850
+ } catch (err) {
1851
+ return next(err);
1852
+ }
1853
+ };
1854
+ }
1855
+
1856
+ // src/middlewares/requirePermission.ts
1857
+ function requirePermission2(permission) {
1858
+ return (req, res, next) => {
1859
+ const user = req.user;
1860
+ if (!user) {
1861
+ return res.status(401).json({ error: "Unauthorized" });
1862
+ }
1863
+ if (!permission) {
1864
+ return next();
1865
+ }
1866
+ if (!hasPermission(user, permission)) {
1867
+ return res.status(403).json({
1868
+ error: "Forbidden",
1869
+ reason: "MISSING_PERMISSION",
1870
+ permission,
1871
+ userPermissions: user.permissions
1872
+ });
1873
+ }
1874
+ next();
1875
+ };
1876
+ }
1877
+
1878
+ // src/services/upload.service.ts
1879
+ var import_client_s3 = require("@aws-sdk/client-s3");
1880
+ var import_crypto4 = require("crypto");
1881
+ var UploadsService = class {
1882
+ s3;
1883
+ constructor() {
1884
+ this.s3 = new import_client_s3.S3Client({
1885
+ region: process.env.AWS_REGION,
1886
+ credentials: {
1887
+ accessKeyId: process.env.AWS_ACCESS_KEY_ID,
1888
+ secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY
1889
+ }
1890
+ });
1891
+ }
1892
+ async uploadPublicImage(buffer, mimetype, ext) {
1893
+ const key = `${(0, import_crypto4.randomUUID)()}.${ext}`;
1894
+ await this.s3.send(
1895
+ new import_client_s3.PutObjectCommand({
1896
+ Bucket: process.env.AWS_S3_BUCKET,
1897
+ Key: key,
1898
+ Body: buffer,
1899
+ ACL: "public-read",
1900
+ ContentType: mimetype
1901
+ })
1902
+ );
1903
+ return `https://${process.env.AWS_S3_BUCKET}.s3.${process.env.AWS_REGION}.amazonaws.com/${key}`;
1904
+ }
1905
+ };
1906
+
1907
+ // src/nest/authx.guard.ts
1908
+ var import_common3 = require("@nestjs/common");
1909
+ var import_core = require("@nestjs/core");
1910
+ var import_passport = require("@nestjs/passport");
1911
+
1912
+ // src/nest/decorators/permissions.decorator.ts
1913
+ var import_common = require("@nestjs/common");
1914
+ var PERMISSIONS_KEY = "permissions";
1915
+ var Permissions = (...permissions) => (0, import_common.SetMetadata)(PERMISSIONS_KEY, permissions);
1916
+
1917
+ // src/nest/decorators/roles.decorator.ts
1918
+ var import_common2 = require("@nestjs/common");
1919
+ var ROLES_KEY = "roles";
1920
+ var Roles = (...roles) => (0, import_common2.SetMetadata)(ROLES_KEY, roles);
1921
+
1922
+ // src/nest/authx.guard.ts
1923
+ var AuthXGuard = class extends (0, import_passport.AuthGuard)("authx") {
1924
+ reflector = new import_core.Reflector();
1925
+ constructor() {
1926
+ super();
1927
+ }
1928
+ /**
1929
+ * Override handleRequest to convert Passport errors to NestJS exceptions
1930
+ * This prevents the "Right-hand side of 'instanceof' is not an object" error
1931
+ */
1932
+ handleRequest(err, user, info, context) {
1933
+ if (err || !user) {
1934
+ const message = err?.message || info?.message || "Authentication required";
1935
+ throw new import_common3.UnauthorizedException(message);
1936
+ }
1937
+ return user;
1938
+ }
1939
+ async canActivate(context) {
1940
+ try {
1941
+ let authenticated;
1942
+ try {
1943
+ authenticated = await super.canActivate(context);
1944
+ } catch (passportError) {
1945
+ const message = passportError?.message || "Authentication required";
1946
+ throw new import_common3.UnauthorizedException(message);
1947
+ }
1948
+ if (!authenticated) {
1949
+ throw new import_common3.UnauthorizedException("Authentication required");
1950
+ }
1951
+ const request = context.switchToHttp().getRequest();
1952
+ const user = request.user;
1953
+ if (!user) {
1954
+ throw new import_common3.UnauthorizedException("Authentication required");
1955
+ }
1956
+ const requiredRoles = this.reflector.getAllAndOverride(
1957
+ ROLES_KEY,
1958
+ [context.getHandler(), context.getClass()]
1959
+ );
1960
+ const requiredPermissions = this.reflector.getAllAndOverride(
1961
+ PERMISSIONS_KEY,
1962
+ [context.getHandler(), context.getClass()]
1963
+ );
1964
+ if (requiredRoles && requiredRoles.length > 0) {
1965
+ if (!hasAnyRole(user, requiredRoles)) {
1966
+ throw new import_common3.ForbiddenException(
1967
+ `Requires one of roles: ${requiredRoles.join(", ")}`
1968
+ );
1969
+ }
1970
+ }
1971
+ if (requiredPermissions && requiredPermissions.length > 0) {
1972
+ if (!hasAnyPermission(user, requiredPermissions)) {
1973
+ throw new import_common3.ForbiddenException(
1974
+ `Requires one of permissions: ${requiredPermissions.join(", ")}`
1975
+ );
1976
+ }
1977
+ }
1978
+ return true;
1979
+ } catch (error) {
1980
+ if (error instanceof import_common3.UnauthorizedException || error instanceof import_common3.ForbiddenException) {
1981
+ throw error;
1982
+ }
1983
+ const errorMessage = error instanceof Error ? error.message : String(error);
1984
+ throw new import_common3.UnauthorizedException(errorMessage || "Authentication failed");
1985
+ }
1986
+ }
1987
+ };
1988
+ AuthXGuard = __decorateClass([
1989
+ (0, import_common3.Injectable)()
1990
+ ], AuthXGuard);
1991
+
1992
+ // src/nest/decorators/session.decorator.ts
1993
+ var import_common4 = require("@nestjs/common");
1994
+ var AuthXSessionDecorator = (0, import_common4.createParamDecorator)(
1995
+ (data, ctx) => {
1996
+ const request = ctx.switchToHttp().getRequest();
1997
+ return request.user || null;
1998
+ }
1999
+ );
2000
+
2001
+ // src/passport/authx.strategy.ts
2002
+ var import_passport2 = require("passport");
2003
+ var AuthXStrategy = class extends import_passport2.Strategy {
2004
+ name = "authx";
2005
+ authenticate(req) {
2006
+ try {
2007
+ console.log("AuthXStrategy.authenticate - starting");
2008
+ const token = extractToken(req);
2009
+ console.log("AuthXStrategy.authenticate - token extracted:", token ? "yes" : "no");
2010
+ if (!token) {
2011
+ console.log("AuthXStrategy.authenticate - no token, failing");
2012
+ return this.fail({ message: "Missing token" }, 401);
2013
+ }
2014
+ console.log("AuthXStrategy.authenticate - verifying JWT");
2015
+ verifyJwt(token).then((claims) => {
2016
+ console.log("AuthXStrategy.authenticate - JWT verified successfully");
2017
+ const session = buildSession(claims);
2018
+ req.user = session;
2019
+ return this.success(session);
2020
+ }).catch((error) => {
2021
+ console.log("AuthXStrategy.authenticate - JWT verification failed:", error?.message || error);
2022
+ return this.fail({ message: error?.message || "Unauthorized" }, 401);
2023
+ });
2024
+ } catch (error) {
2025
+ console.log("AuthXStrategy.authenticate - exception caught:", error?.message || error);
2026
+ return this.fail({ message: error?.message || "Unauthorized" }, 401);
2027
+ }
2028
+ }
2029
+ };
2030
+ function createAuthXStrategy() {
2031
+ return new AuthXStrategy();
2032
+ }
2033
+
2034
+ // src/next/server/authx.ts
2035
+ var import_headers = require("next/headers");
2036
+ async function authx() {
2037
+ try {
2038
+ const cookieStore = await (0, import_headers.cookies)();
2039
+ const token = cookieStore.get("access_token")?.value || cookieStore.get("authorization")?.value || cookieStore.get("auth_token")?.value || null;
2040
+ if (!token) {
2041
+ return { session: null };
2042
+ }
2043
+ const claims = await verifyJwt(token);
2044
+ const session = buildSession(claims);
2045
+ return { session };
2046
+ } catch (error) {
2047
+ return { session: null };
2048
+ }
2049
+ }
2050
+
2051
+ // src/next/server/withAuthRoute.ts
2052
+ var import_server = require("next/server");
2053
+ function withAuthRoute(permissionOrHandler, handler) {
2054
+ let permission;
2055
+ let routeHandler;
2056
+ if (typeof permissionOrHandler === "string") {
2057
+ permission = permissionOrHandler;
2058
+ routeHandler = handler;
2059
+ } else {
2060
+ permission = void 0;
2061
+ routeHandler = permissionOrHandler;
2062
+ }
2063
+ return async (req, context) => {
2064
+ const { session } = await authx();
2065
+ if (!session) {
2066
+ return new import_server.NextResponse(
2067
+ JSON.stringify({ error: "Unauthorized" }),
2068
+ { status: 401, headers: { "Content-Type": "application/json" } }
2069
+ );
2070
+ }
2071
+ if (permission && !hasPermission(session, permission)) {
2072
+ return new import_server.NextResponse(
2073
+ JSON.stringify({
2074
+ error: "Forbidden",
2075
+ reason: "MISSING_PERMISSION",
2076
+ permission
2077
+ }),
2078
+ { status: 403, headers: { "Content-Type": "application/json" } }
2079
+ );
2080
+ }
2081
+ return routeHandler(req, session, context);
2082
+ };
2083
+ }
2084
+
2085
+ // src/next/client/AuthXProvider.tsx
2086
+ var import_react = require("react");
2087
+ var import_jsx_runtime = require("react/jsx-runtime");
2088
+ var AuthXContext = (0, import_react.createContext)(void 0);
2089
+ function AuthXProvider({ children, apiUrl = "/auth/me" }) {
2090
+ const [session, setSession] = (0, import_react.useState)(null);
2091
+ const [isLoading, setIsLoading] = (0, import_react.useState)(true);
2092
+ const [error, setError] = (0, import_react.useState)(null);
2093
+ const fetchSession = async () => {
2094
+ try {
2095
+ setIsLoading(true);
2096
+ setError(null);
2097
+ const response = await fetch(apiUrl, {
2098
+ credentials: "include"
2099
+ // Include cookies
2100
+ });
2101
+ if (!response.ok) {
2102
+ if (response.status === 401) {
2103
+ setSession(null);
2104
+ return;
2105
+ }
2106
+ throw new Error(`Failed to fetch session: ${response.statusText}`);
2107
+ }
2108
+ const data = await response.json();
2109
+ setSession(data.user || data.session || data);
2110
+ } catch (err) {
2111
+ setError(err instanceof Error ? err : new Error("Unknown error"));
2112
+ setSession(null);
2113
+ } finally {
2114
+ setIsLoading(false);
2115
+ }
2116
+ };
2117
+ (0, import_react.useEffect)(() => {
2118
+ fetchSession();
2119
+ }, [apiUrl]);
2120
+ const value = {
2121
+ session,
2122
+ isLoading,
2123
+ error,
2124
+ refetch: fetchSession
2125
+ };
2126
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(AuthXContext.Provider, { value, children });
2127
+ }
2128
+ function useAuthXContext() {
2129
+ const context = (0, import_react.useContext)(AuthXContext);
2130
+ if (context === void 0) {
2131
+ throw new Error("useAuthX must be used within an AuthXProvider");
2132
+ }
2133
+ return context;
2134
+ }
2135
+
2136
+ // src/next/client/HasPermission.tsx
2137
+ var import_jsx_runtime2 = require("react/jsx-runtime");
2138
+ function HasPermission({
2139
+ permission,
2140
+ children,
2141
+ fallback = null
2142
+ }) {
2143
+ const { session, isLoading } = useAuthXContext();
2144
+ if (isLoading) {
2145
+ return null;
2146
+ }
2147
+ if (!session) {
2148
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_jsx_runtime2.Fragment, { children: fallback });
2149
+ }
2150
+ const hasPerm = hasPermission(session, permission);
2151
+ if (!hasPerm) {
2152
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_jsx_runtime2.Fragment, { children: fallback });
2153
+ }
2154
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_jsx_runtime2.Fragment, { children });
2155
+ }
2156
+
2157
+ // src/next/client/HasRole.tsx
2158
+ var import_jsx_runtime3 = require("react/jsx-runtime");
2159
+ function HasRole({ role, children, fallback = null }) {
2160
+ const { session, isLoading } = useAuthXContext();
2161
+ if (isLoading) {
2162
+ return null;
2163
+ }
2164
+ if (!session) {
2165
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_jsx_runtime3.Fragment, { children: fallback });
2166
+ }
2167
+ const roles = Array.isArray(role) ? role : [role];
2168
+ const hasRole2 = hasAnyRole(session, roles);
2169
+ if (!hasRole2) {
2170
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_jsx_runtime3.Fragment, { children: fallback });
2171
+ }
2172
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_jsx_runtime3.Fragment, { children });
2173
+ }
2174
+
2175
+ // src/next/client/SignedIn.tsx
2176
+ var import_jsx_runtime4 = require("react/jsx-runtime");
2177
+ function SignedIn({ children, fallback = null }) {
2178
+ const { session, isLoading } = useAuthXContext();
2179
+ if (isLoading) {
2180
+ return null;
2181
+ }
2182
+ if (!session) {
2183
+ return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_jsx_runtime4.Fragment, { children: fallback });
2184
+ }
2185
+ return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_jsx_runtime4.Fragment, { children });
2186
+ }
2187
+
2188
+ // src/next/client/SignedOut.tsx
2189
+ var import_jsx_runtime5 = require("react/jsx-runtime");
2190
+ function SignedOut({ children, fallback = null }) {
2191
+ const { session, isLoading } = useAuthXContext();
2192
+ if (isLoading) {
2193
+ return null;
2194
+ }
2195
+ if (session) {
2196
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_jsx_runtime5.Fragment, { children: fallback });
2197
+ }
2198
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_jsx_runtime5.Fragment, { children });
2199
+ }
2200
+
2201
+ // src/next/client/useAuthX.ts
2202
+ function useAuthX() {
2203
+ const { session, isLoading, error, refetch } = useAuthXContext();
2204
+ return {
2205
+ session,
2206
+ isLoading,
2207
+ error,
2208
+ refetch,
2209
+ isSignedIn: !!session,
2210
+ userId: session?.userId || null,
2211
+ email: session?.email || null,
2212
+ roles: session?.roles || [],
2213
+ permissions: session?.permissions || []
2214
+ };
2215
+ }
2216
+ function useHasRole(role) {
2217
+ const { session } = useAuthXContext();
2218
+ if (!session || !session.roles) return false;
2219
+ return session.roles.includes(role);
2220
+ }
2221
+ function useHasPermission(permission) {
2222
+ const { session } = useAuthXContext();
2223
+ if (!session || !session.permissions) return false;
2224
+ return session.permissions.includes(permission);
2225
+ }
2226
+ // Annotate the CommonJS export names for ESM import in node:
2227
+ 0 && (module.exports = {
2228
+ AuthAdminService,
2229
+ AuthXGuard,
2230
+ AuthXProvider,
2231
+ AuthXSessionDecorator,
2232
+ AuthXStrategy,
2233
+ EmailService,
2234
+ HasPermission,
2235
+ HasRole,
2236
+ PLATFORM_ROLES,
2237
+ Permissions,
2238
+ ProjectsService,
2239
+ Roles,
2240
+ SignedIn,
2241
+ SignedOut,
2242
+ UploadsService,
2243
+ authorize,
2244
+ authx,
2245
+ buildSession,
2246
+ createAuthXStrategy,
2247
+ express,
2248
+ getPermissionsForRoles,
2249
+ hasAllPermissions,
2250
+ hasAllRoles,
2251
+ hasAnyPermission,
2252
+ hasAnyRole,
2253
+ hasPermission,
2254
+ hasRole,
2255
+ nest,
2256
+ requireAuth,
2257
+ requirePermission,
2258
+ requirePermissionLegacy,
2259
+ requireRole,
2260
+ useAuthX,
2261
+ useAuthXContext,
2262
+ useHasPermission,
2263
+ useHasRole,
2264
+ withAuthRoute
2265
+ });
2266
+ //# sourceMappingURL=index.cjs.map