@xeplr/auth 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (51) hide show
  1. package/LICENSE +21 -0
  2. package/bin/migrate.js +95 -0
  3. package/bin/server.js +35 -0
  4. package/index.js +165 -0
  5. package/lib/accessMiddleware.js +79 -0
  6. package/lib/accessService.js +188 -0
  7. package/lib/adminRouter.js +387 -0
  8. package/lib/authHelper.js +63 -0
  9. package/lib/authMiddleware.js +31 -0
  10. package/lib/authRouter.js +192 -0
  11. package/lib/authService.js +194 -0
  12. package/lib/sessionService.js +210 -0
  13. package/migrations/0001_users.js +22 -0
  14. package/migrations/0002_menus.js +16 -0
  15. package/migrations/0003_apis.js +16 -0
  16. package/migrations/0004_uiPages.js +16 -0
  17. package/migrations/0005_uiElements.js +16 -0
  18. package/migrations/0006_roles.js +15 -0
  19. package/migrations/0007_apisRolesMapping.js +16 -0
  20. package/migrations/0008_uiPagesRolesMapping.js +16 -0
  21. package/migrations/0009_uiElementsRolesMapping.js +16 -0
  22. package/migrations/0010_menuRolesMapping.js +16 -0
  23. package/migrations/0011_userRolesMapping.js +16 -0
  24. package/migrations/0012_users_add_reset_token.js +13 -0
  25. package/migrations/0013_add_isPublic.js +27 -0
  26. package/migrations/0014_users_add_activation_token.js +13 -0
  27. package/migrations/0015_add_mt_columns.js +41 -0
  28. package/migrations/0016_tenants.js +23 -0
  29. package/migrations/0017_userTenantsMapping.js +20 -0
  30. package/models/Api.js +45 -0
  31. package/models/ApisRolesMapping.js +29 -0
  32. package/models/Menu.js +45 -0
  33. package/models/MenuRolesMapping.js +29 -0
  34. package/models/Role.js +89 -0
  35. package/models/Tenant.js +63 -0
  36. package/models/UiElement.js +44 -0
  37. package/models/UiElementsRolesMapping.js +29 -0
  38. package/models/UiPage.js +45 -0
  39. package/models/UiPagesRolesMapping.js +29 -0
  40. package/models/User.js +76 -0
  41. package/models/UserRolesMapping.js +29 -0
  42. package/models/UserTenantsMapping.js +30 -0
  43. package/models/index.js +29 -0
  44. package/package.json +31 -0
  45. package/seeds/001_roles.js +26 -0
  46. package/seeds/002_apis.js +70 -0
  47. package/seeds/003_pages.js +41 -0
  48. package/seeds/004_elements.js +46 -0
  49. package/seeds/005_menus.js +35 -0
  50. package/seeds/006_default_tenant.js +50 -0
  51. package/seeds/zzz_admin_access.js +49 -0
@@ -0,0 +1,387 @@
1
+ var express = require('express');
2
+ var { generateId } = require('./authHelper');
3
+ var { clearUserAccess, clearAccessRules } = require('./accessService');
4
+ var User = require('../models/User');
5
+ var Role = require('../models/Role');
6
+ var Api = require('../models/Api');
7
+ var UiPage = require('../models/UiPage');
8
+ var UiElement = require('../models/UiElement');
9
+ var Menu = require('../models/Menu');
10
+ var UserRolesMapping = require('../models/UserRolesMapping');
11
+ var ApisRolesMapping = require('../models/ApisRolesMapping');
12
+ var UiPagesRolesMapping = require('../models/UiPagesRolesMapping');
13
+ var UiElementsRolesMapping = require('../models/UiElementsRolesMapping');
14
+ var MenuRolesMapping = require('../models/MenuRolesMapping');
15
+ var Tenant = require('../models/Tenant');
16
+ var UserTenantsMapping = require('../models/UserTenantsMapping');
17
+
18
+ // Group field names per type
19
+ var GROUP_FIELDS = {
20
+ apis: 'apiGroup',
21
+ pages: 'uiPagesGroup',
22
+ elements: 'uiElementsGroup',
23
+ menus: 'menuGroup'
24
+ };
25
+
26
+ // Checks if a group value follows the module:action convention
27
+ function isModuleGroup(group) {
28
+ if (!group) return false;
29
+ var parts = group.split(':');
30
+ return parts.length === 2 && parts[0] && parts[1];
31
+ }
32
+
33
+ function createAdminRouter() {
34
+ var router = express.Router();
35
+
36
+ // ─── Users with their roles ───
37
+ router.get('/users', async function(req, res) {
38
+ try {
39
+ var users = await User.query()
40
+ .select('id', 'email', 'name', 'isActive', 'isActivated')
41
+ .withGraphFetched('[roles, tenants]');
42
+ res.json(users);
43
+ } catch (err) {
44
+ res.status(500).json({ error: 'Something went wrong' });
45
+ }
46
+ });
47
+
48
+ // ─── Roles ───
49
+ router.get('/roles', async function(req, res) {
50
+ try {
51
+ var roles = await Role.query().select('id', 'name');
52
+ res.json(roles);
53
+ } catch (err) {
54
+ res.status(500).json({ error: 'Something went wrong' });
55
+ }
56
+ });
57
+
58
+ // ─── Access items (apis, pages, elements, menus) with role mappings ───
59
+ router.get('/access-items', async function(req, res) {
60
+ try {
61
+ var [apis, pages, elements, menus] = await Promise.all([
62
+ Api.query().select('id', 'name', 'apiGroup', 'isPublic').withGraphFetched('roles'),
63
+ UiPage.query().select('id', 'name', 'uiPagesGroup', 'isPublic').withGraphFetched('roles'),
64
+ UiElement.query().select('id', 'name', 'uiElementsGroup').withGraphFetched('roles'),
65
+ Menu.query().select('id', 'name', 'menuGroup', 'isPublic').withGraphFetched('roles')
66
+ ]);
67
+ res.json({ apis, pages, elements, menus });
68
+ } catch (err) {
69
+ res.status(500).json({ error: 'Something went wrong' });
70
+ }
71
+ });
72
+
73
+ // ─── Toggle user-role mapping ───
74
+ router.post('/user-role', async function(req, res) {
75
+ try {
76
+ var { userId, roleId, assign } = req.body;
77
+
78
+ if (!userId || !roleId) {
79
+ return res.status(400).json({ error: 'userId and roleId are required' });
80
+ }
81
+
82
+ if (assign) {
83
+ var existing = await UserRolesMapping.query()
84
+ .findOne({ userId: userId, roleId: roleId });
85
+ if (!existing) {
86
+ await UserRolesMapping.query().insert({
87
+ id: generateId(),
88
+ userId: userId,
89
+ roleId: roleId,
90
+ recordCreatedBy: req.user ? req.user.id : null
91
+ });
92
+ }
93
+ } else {
94
+ await UserRolesMapping.query()
95
+ .delete()
96
+ .where({ userId: userId, roleId: roleId });
97
+ }
98
+
99
+ await clearUserAccess(userId);
100
+ res.json({ ok: true });
101
+ } catch (err) {
102
+ res.status(500).json({ error: 'Something went wrong' });
103
+ }
104
+ });
105
+
106
+ // ─── Toggle access-role mapping (single item) ───
107
+ router.post('/access-role', async function(req, res) {
108
+ try {
109
+ var { type, itemId, roleId, assign } = req.body;
110
+
111
+ if (!type || !itemId || !roleId) {
112
+ return res.status(400).json({ error: 'type, itemId, and roleId are required' });
113
+ }
114
+
115
+ var config = {
116
+ apis: { Model: ApisRolesMapping, fk: 'apiId' },
117
+ pages: { Model: UiPagesRolesMapping, fk: 'uiPageId' },
118
+ elements: { Model: UiElementsRolesMapping, fk: 'uiElementId' },
119
+ menus: { Model: MenuRolesMapping, fk: 'menuId' }
120
+ };
121
+
122
+ var mapping = config[type];
123
+ if (!mapping) {
124
+ return res.status(400).json({ error: 'Invalid type. Use: apis, pages, elements, menus' });
125
+ }
126
+
127
+ var where = { roleId: roleId };
128
+ where[mapping.fk] = itemId;
129
+
130
+ if (assign) {
131
+ var existing = await mapping.Model.query().findOne(where);
132
+ if (!existing) {
133
+ var row = {
134
+ id: generateId(),
135
+ roleId: roleId,
136
+ recordCreatedBy: req.user ? req.user.id : null
137
+ };
138
+ row[mapping.fk] = itemId;
139
+ await mapping.Model.query().insert(row);
140
+ }
141
+ } else {
142
+ await mapping.Model.query().delete().where(where);
143
+ }
144
+
145
+ await clearAccessRules();
146
+ res.json({ ok: true });
147
+ } catch (err) {
148
+ res.status(500).json({ error: 'Something went wrong' });
149
+ }
150
+ });
151
+
152
+ // ─── Toggle module:action for a role (bulk — toggles all items with that group across all types) ───
153
+ router.post('/module-role', async function(req, res) {
154
+ try {
155
+ var { module: moduleName, action, roleId, assign } = req.body;
156
+
157
+ if (!moduleName || !action || !roleId) {
158
+ return res.status(400).json({ error: 'module, action, and roleId are required' });
159
+ }
160
+
161
+ var groupValue = moduleName + ':' + action;
162
+ var createdBy = req.user ? req.user.id : null;
163
+
164
+ var types = [
165
+ { Model: Api, MappingModel: ApisRolesMapping, fk: 'apiId', groupField: 'apiGroup' },
166
+ { Model: UiPage, MappingModel: UiPagesRolesMapping, fk: 'uiPageId', groupField: 'uiPagesGroup' },
167
+ { Model: UiElement, MappingModel: UiElementsRolesMapping, fk: 'uiElementId', groupField: 'uiElementsGroup' },
168
+ { Model: Menu, MappingModel: MenuRolesMapping, fk: 'menuId', groupField: 'menuGroup' }
169
+ ];
170
+
171
+ var promises = types.map(async function(cfg) {
172
+ var where = {};
173
+ where[cfg.groupField] = groupValue;
174
+ var items = await cfg.Model.query().select('id').where(where);
175
+
176
+ for (var i = 0; i < items.length; i++) {
177
+ var itemWhere = { roleId: roleId };
178
+ itemWhere[cfg.fk] = items[i].id;
179
+
180
+ if (assign) {
181
+ var existing = await cfg.MappingModel.query().findOne(itemWhere);
182
+ if (!existing) {
183
+ var row = { id: generateId(), roleId: roleId, recordCreatedBy: createdBy };
184
+ row[cfg.fk] = items[i].id;
185
+ await cfg.MappingModel.query().insert(row);
186
+ }
187
+ } else {
188
+ await cfg.MappingModel.query().delete().where(itemWhere);
189
+ }
190
+ }
191
+ });
192
+
193
+ await Promise.all(promises);
194
+ await clearAccessRules();
195
+ res.json({ ok: true });
196
+ } catch (err) {
197
+ res.status(500).json({ error: 'Something went wrong' });
198
+ }
199
+ });
200
+
201
+ // ─── Master CRUD: Roles ───
202
+ router.get('/master/roles', async function(req, res) {
203
+ try {
204
+ var items = await Role.query().select('id', 'name');
205
+ res.json(items);
206
+ } catch (err) {
207
+ res.status(500).json({ error: 'Something went wrong' });
208
+ }
209
+ });
210
+
211
+ router.post('/master/roles', async function(req, res) {
212
+ try {
213
+ var { id, name } = req.body;
214
+ if (!name) return res.status(400).json({ error: 'name is required' });
215
+
216
+ if (id) {
217
+ await Role.query().findById(id).patch({ name: name });
218
+ res.json({ id: id });
219
+ } else {
220
+ var newId = generateId();
221
+ await Role.query().insert({ id: newId, name: name });
222
+ res.json({ id: newId });
223
+ }
224
+ } catch (err) {
225
+ res.status(500).json({ error: 'Something went wrong' });
226
+ }
227
+ });
228
+
229
+ router.post('/master/roles/delete', async function(req, res) {
230
+ try {
231
+ var { id } = req.body;
232
+ if (!id) return res.status(400).json({ error: 'id is required' });
233
+ await Role.query().deleteById(id);
234
+ await clearAccessRules();
235
+ res.json({ ok: true });
236
+ } catch (err) {
237
+ res.status(500).json({ error: 'Something went wrong' });
238
+ }
239
+ });
240
+
241
+ // ─── Master CRUD factory for APIs, Pages, Elements, Menus ───
242
+ var masterTypes = {
243
+ apis: { Model: Api, fields: ['name', 'apiGroup', 'isPublic'] },
244
+ pages: { Model: UiPage, fields: ['name', 'uiPagesGroup', 'isPublic'] },
245
+ elements: { Model: UiElement, fields: ['name', 'uiElementsGroup'] },
246
+ menus: { Model: Menu, fields: ['name', 'menuGroup', 'isPublic'] }
247
+ };
248
+
249
+ Object.keys(masterTypes).forEach(function(typeKey) {
250
+ var cfg = masterTypes[typeKey];
251
+
252
+ router.get('/master/' + typeKey, async function(req, res) {
253
+ try {
254
+ var selectFields = ['id'].concat(cfg.fields);
255
+ var items = await cfg.Model.query().select(selectFields);
256
+ res.json(items);
257
+ } catch (err) {
258
+ res.status(500).json({ error: 'Something went wrong' });
259
+ }
260
+ });
261
+
262
+ router.post('/master/' + typeKey, async function(req, res) {
263
+ try {
264
+ var id = req.body.id;
265
+ var data = {};
266
+ cfg.fields.forEach(function(f) {
267
+ if (req.body[f] !== undefined) data[f] = req.body[f];
268
+ });
269
+
270
+ if (!data.name) return res.status(400).json({ error: 'name is required' });
271
+
272
+ if (id) {
273
+ await cfg.Model.query().findById(id).patch(data);
274
+ res.json({ id: id });
275
+ } else {
276
+ var newId = generateId();
277
+ data.id = newId;
278
+ await cfg.Model.query().insert(data);
279
+ res.json({ id: newId });
280
+ }
281
+ } catch (err) {
282
+ res.status(500).json({ error: 'Something went wrong' });
283
+ }
284
+ });
285
+
286
+ router.post('/master/' + typeKey + '/delete', async function(req, res) {
287
+ try {
288
+ var id = req.body.id;
289
+ if (!id) return res.status(400).json({ error: 'id is required' });
290
+ await cfg.Model.query().deleteById(id);
291
+ await clearAccessRules();
292
+ res.json({ ok: true });
293
+ } catch (err) {
294
+ res.status(500).json({ error: 'Something went wrong' });
295
+ }
296
+ });
297
+ });
298
+
299
+ // ─── Tenants (Super Admin only) ───
300
+
301
+ function requireSuperAdmin(req, res, next) {
302
+ if (!req.user || !req.user.roles || req.user.roles.indexOf('Super Admin') === -1) {
303
+ return res.status(403).json({ error: 'Super Admin access required' });
304
+ }
305
+ next();
306
+ }
307
+
308
+ router.get('/tenants', requireSuperAdmin, async function(req, res) {
309
+ try {
310
+ var level = req.query.level ? parseInt(req.query.level) : null;
311
+ var query = Tenant.query().select('id', 'name', 'code', 'level', 'parentId', 'description');
312
+ if (level) query = query.where('level', level);
313
+ var tenants = await query;
314
+ res.json(tenants);
315
+ } catch (err) {
316
+ res.status(500).json({ error: 'Something went wrong' });
317
+ }
318
+ });
319
+
320
+ router.post('/tenants', requireSuperAdmin, async function(req, res) {
321
+ try {
322
+ var { id, name, code, level, parentId, description } = req.body;
323
+ if (!name) return res.status(400).json({ error: 'name is required' });
324
+
325
+ var data = { name: name };
326
+ if (code !== undefined) data.code = code;
327
+ if (level !== undefined) data.level = level;
328
+ if (parentId !== undefined) data.parentId = parentId;
329
+ if (description !== undefined) data.description = description;
330
+
331
+ if (id) {
332
+ await Tenant.query().findById(id).patch(data);
333
+ res.json({ id: id });
334
+ } else {
335
+ var newId = generateId();
336
+ data.id = newId;
337
+ await Tenant.query().insert(data);
338
+ res.json({ id: newId });
339
+ }
340
+ } catch (err) {
341
+ res.status(500).json({ error: 'Something went wrong' });
342
+ }
343
+ });
344
+
345
+ router.post('/tenants/delete', requireSuperAdmin, async function(req, res) {
346
+ try {
347
+ var { id } = req.body;
348
+ if (!id) return res.status(400).json({ error: 'id is required' });
349
+ await Tenant.query().findById(id).patch({ isActive: false });
350
+ res.json({ ok: true });
351
+ } catch (err) {
352
+ res.status(500).json({ error: 'Something went wrong' });
353
+ }
354
+ });
355
+
356
+ // Assign/unassign tenant to a user (via mapping table)
357
+ // Access controlled via Access Matrix (tenants:edit group), not hardcoded to Super Admin
358
+ router.post('/tenants/assign-user', async function(req, res) {
359
+ try {
360
+ var { userId, tenantId, assign } = req.body;
361
+ if (!userId || !tenantId) return res.status(400).json({ error: 'userId and tenantId are required' });
362
+
363
+ if (assign === false) {
364
+ await UserTenantsMapping.query().delete().where({ userId: userId, tenantId: tenantId });
365
+ } else {
366
+ var existing = await UserTenantsMapping.query().findOne({ userId: userId, tenantId: tenantId });
367
+ if (!existing) {
368
+ await UserTenantsMapping.query().insert({
369
+ id: generateId(),
370
+ userId: userId,
371
+ tenantId: tenantId,
372
+ recordCreatedBy: req.user ? req.user.id : null
373
+ });
374
+ }
375
+ }
376
+
377
+ await clearUserAccess(userId);
378
+ res.json({ ok: true });
379
+ } catch (err) {
380
+ res.status(500).json({ error: 'Something went wrong' });
381
+ }
382
+ });
383
+
384
+ return router;
385
+ }
386
+
387
+ module.exports = createAdminRouter;
@@ -0,0 +1,63 @@
1
+ const crypto = require('crypto');
2
+ const bcrypt = require('bcrypt');
3
+ const jwt = require('jsonwebtoken');
4
+
5
+ let _config = {};
6
+
7
+ /**
8
+ * Configure auth helper.
9
+ * @param {object} config
10
+ * @param {string} config.jwtSecret
11
+ * @param {string} [config.jwtExpiresIn='24h']
12
+ * @param {number} [config.saltRounds=10]
13
+ */
14
+ function configure(config) {
15
+ _config = { ..._config, ...config };
16
+ }
17
+
18
+ function getSecret() {
19
+ return _config.jwtSecret || process.env.JWT_SECRET || 'change_me_in_production';
20
+ }
21
+
22
+ function generateId() {
23
+ return crypto.randomBytes(12).toString('hex').substring(0, 25);
24
+ }
25
+
26
+ async function hashPassword(password) {
27
+ const rounds = _config.saltRounds || 10;
28
+ const salt = await bcrypt.genSalt(rounds);
29
+ const hash = await bcrypt.hash(password, salt);
30
+ return { hash, salt };
31
+ }
32
+
33
+ async function comparePassword(password, hash) {
34
+ return bcrypt.compare(password, hash);
35
+ }
36
+
37
+ function generateAccessToken(user) {
38
+ const payload = {
39
+ id: user.id,
40
+ email: user.email,
41
+ name: user.name
42
+ };
43
+ const expiresIn = _config.accessTokenExpiresIn || '15m';
44
+ return jwt.sign(payload, getSecret(), { expiresIn });
45
+ }
46
+
47
+ function generateRefreshToken() {
48
+ return crypto.randomBytes(40).toString('hex');
49
+ }
50
+
51
+ function verifyToken(token) {
52
+ return jwt.verify(token, getSecret());
53
+ }
54
+
55
+ module.exports = {
56
+ configure,
57
+ generateId,
58
+ hashPassword,
59
+ comparePassword,
60
+ generateAccessToken,
61
+ generateRefreshToken,
62
+ verifyToken
63
+ };
@@ -0,0 +1,31 @@
1
+ const { verifyToken } = require('./authHelper');
2
+ const { validateAccessToken } = require('./sessionService');
3
+
4
+ async function authMiddleware(req, res, next) {
5
+ const authHeader = req.headers.authorization;
6
+
7
+ if (!authHeader || !authHeader.startsWith('Bearer ')) {
8
+ return res.status(401).json({ error: 'No token provided' });
9
+ }
10
+
11
+ const token = authHeader.split(' ')[1];
12
+
13
+ // Step 1: Verify JWT signature + expiry
14
+ let decoded;
15
+ try {
16
+ decoded = verifyToken(token);
17
+ } catch (err) {
18
+ return res.status(401).json({ error: 'Invalid or expired token' });
19
+ }
20
+
21
+ // Step 2: Check Redis whitelist (token not revoked)
22
+ const session = await validateAccessToken(token);
23
+ if (!session) {
24
+ return res.status(401).json({ error: 'Session expired or revoked' });
25
+ }
26
+
27
+ req.user = decoded;
28
+ next();
29
+ }
30
+
31
+ module.exports = authMiddleware;
@@ -0,0 +1,192 @@
1
+ const express = require('express');
2
+ const { register, activate, login, forgotPassword, resetPassword } = require('./authService');
3
+ const { getUserAccess } = require('./accessService');
4
+ const { getSession, rotateSession, destroySession } = require('./sessionService');
5
+ const authMiddleware = require('./authMiddleware');
6
+ const User = require('../models/User');
7
+
8
+ function logError(req, err) {
9
+ if (req.log) req.log.error(err.message, { stack: err.stack });
10
+ }
11
+
12
+ function createAuthRouter(options = {}) {
13
+ const router = express.Router();
14
+ const resetBaseUrl = options.resetBaseUrl || null;
15
+
16
+ router.post('/register', async function(req, res) {
17
+ try {
18
+ const { email, phoneNumber, name, password } = req.body;
19
+
20
+ if (!email || !password) {
21
+ return res.status(400).json({ error: 'Email and password are required' });
22
+ }
23
+
24
+ const user = await register({ email, phoneNumber, name, password });
25
+ res.status(201).json({ message: 'Registration successful. Account pending activation.', user });
26
+ } catch (err) {
27
+ logError(req, err);
28
+ if (err.message === 'Email already registered') {
29
+ return res.status(409).json({ error: err.message });
30
+ }
31
+ res.status(500).json({ error: 'Something went wrong' });
32
+ }
33
+ });
34
+
35
+ router.get('/activate', async function(req, res) {
36
+ try {
37
+ var token = req.query.token;
38
+ if (!token) {
39
+ return res.status(400).json({ error: 'Activation token is required' });
40
+ }
41
+
42
+ var result = await activate(token);
43
+ res.json({ message: 'Account activated successfully', user: result });
44
+ } catch (err) {
45
+ logError(req, err);
46
+ res.status(400).json({ error: err.message });
47
+ }
48
+ });
49
+
50
+ router.post('/login', async function(req, res) {
51
+ try {
52
+ const { email, password } = req.body;
53
+
54
+ if (!email || !password) {
55
+ return res.status(400).json({ error: 'Email and password are required' });
56
+ }
57
+
58
+ const result = await login({ email, password });
59
+ res.json(result);
60
+ } catch (err) {
61
+ logError(req, err);
62
+ if (err.message === 'NOT_ACTIVATED') {
63
+ return res.status(403).json({ error: 'Please wait, someone will activate you.' });
64
+ }
65
+ if (err.message === 'Invalid email or password' || err.message === 'Account is deactivated') {
66
+ return res.status(401).json({ error: err.message });
67
+ }
68
+ res.status(500).json({ error: 'Something went wrong' });
69
+ }
70
+ });
71
+
72
+ router.post('/forgot-password', async function(req, res) {
73
+ try {
74
+ const { email } = req.body;
75
+
76
+ if (!email) {
77
+ return res.status(400).json({ error: 'Email is required' });
78
+ }
79
+
80
+ const baseUrl = resetBaseUrl || req.protocol + '://' + req.get('host');
81
+ await forgotPassword({ email }, baseUrl);
82
+ res.json({ message: 'If that email exists, a reset link has been sent.' });
83
+ } catch (err) {
84
+ logError(req, err);
85
+ res.status(500).json({ error: 'Something went wrong' });
86
+ }
87
+ });
88
+
89
+ router.post('/reset-password', async function(req, res) {
90
+ try {
91
+ const { token, newPassword } = req.body;
92
+
93
+ if (!token || !newPassword) {
94
+ return res.status(400).json({ error: 'Token and new password are required' });
95
+ }
96
+
97
+ await resetPassword({ token, newPassword });
98
+ res.json({ message: 'Password reset successful.' });
99
+ } catch (err) {
100
+ logError(req, err);
101
+ res.status(400).json({ error: err.message });
102
+ }
103
+ });
104
+
105
+ router.post('/refresh', async function(req, res) {
106
+ try {
107
+ const { refreshToken } = req.body;
108
+
109
+ if (!refreshToken) {
110
+ return res.status(400).json({ error: 'Refresh token is required' });
111
+ }
112
+
113
+ const session = await getSession(refreshToken);
114
+ if (!session || !session.userId) {
115
+ return res.status(401).json({ error: 'Invalid or expired refresh token' });
116
+ }
117
+
118
+ const user = await User.query().findById(session.userId);
119
+ if (!user || !user.isActive) {
120
+ return res.status(401).json({ error: 'Account not found or deactivated' });
121
+ }
122
+
123
+ const result = await rotateSession(refreshToken, user);
124
+ if (!result) {
125
+ return res.status(401).json({ error: 'Invalid or expired refresh token' });
126
+ }
127
+
128
+ const access = await getUserAccess(user.id);
129
+
130
+ res.json({
131
+ accessToken: result.accessToken,
132
+ refreshToken: result.refreshToken,
133
+ user: {
134
+ id: user.id,
135
+ email: user.email,
136
+ name: user.name,
137
+ isActivated: user.isActivated
138
+ },
139
+ access
140
+ });
141
+ } catch (err) {
142
+ logError(req, err);
143
+ res.status(500).json({ error: 'Something went wrong' });
144
+ }
145
+ });
146
+
147
+ router.post('/logout', async function(req, res) {
148
+ try {
149
+ const { refreshToken } = req.body;
150
+
151
+ if (refreshToken) {
152
+ await destroySession(refreshToken);
153
+ }
154
+
155
+ res.json({ message: 'Logged out successfully' });
156
+ } catch (err) {
157
+ logError(req, err);
158
+ res.status(500).json({ error: 'Something went wrong' });
159
+ }
160
+ });
161
+
162
+ router.get('/me', authMiddleware, async function(req, res) {
163
+ try {
164
+ const access = await getUserAccess(req.user.id);
165
+ res.json({
166
+ user: req.user,
167
+ access
168
+ });
169
+ } catch (err) {
170
+ logError(req, err);
171
+ res.status(500).json({ error: 'Something went wrong' });
172
+ }
173
+ });
174
+
175
+ // Get tenants for the current logged-in user
176
+ router.get('/my-tenants', authMiddleware, async function(req, res) {
177
+ try {
178
+ var user = await User.query().findById(req.user.id).withGraphFetched('tenants');
179
+ var tenants = (user && user.tenants) ? user.tenants : [];
180
+ res.json(tenants.map(function(t) {
181
+ return { id: t.id, name: t.name, code: t.code, description: t.description };
182
+ }));
183
+ } catch (err) {
184
+ logError(req, err);
185
+ res.status(500).json({ error: 'Something went wrong' });
186
+ }
187
+ });
188
+
189
+ return router;
190
+ }
191
+
192
+ module.exports = createAuthRouter;