@ciscode/authentication-kit 1.0.43 → 1.1.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/README.md +57 -73
- package/package.json +68 -43
- package/.github/workflows/ci .yml +0 -36
- package/.github/workflows/publish.yml +0 -37
- package/CODE_OF_CONDUCT +0 -38
- package/CONTRIBUTING.md +0 -40
- package/SECURITY +0 -31
- package/azure-pipelines.yml +0 -100
- package/src/config/db.config.js +0 -21
- package/src/config/passport.config.js +0 -280
- package/src/controllers/auth.controller.js +0 -566
- package/src/controllers/passwordReset.controller.js +0 -127
- package/src/controllers/permission.controller.js +0 -81
- package/src/controllers/roles.controller.js +0 -108
- package/src/controllers/user.controller.js +0 -283
- package/src/index.js +0 -32
- package/src/middleware/auth.middleware.js +0 -16
- package/src/middleware/authenticate.js +0 -25
- package/src/middleware/rbac.middleware.js +0 -24
- package/src/middleware/tenant.middleware.js +0 -16
- package/src/models/client.model.js +0 -39
- package/src/models/permission.model.js +0 -9
- package/src/models/role.model.js +0 -14
- package/src/models/tenant.model.js +0 -9
- package/src/models/user.model.js +0 -51
- package/src/routes/admin.routes.js +0 -8
- package/src/routes/auth.routes.js +0 -77
- package/src/routes/passwordReset.routes.js +0 -8
- package/src/routes/permission.routes.js +0 -17
- package/src/routes/roles.routes.js +0 -11
- package/src/routes/user.routes.js +0 -22
- package/src/utils/helper.js +0 -26
|
@@ -1,81 +0,0 @@
|
|
|
1
|
-
const Permission = require('../models/permission.model');
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Create a new permission.
|
|
5
|
-
* Expects request body to include at least "name" (and optionally "description").
|
|
6
|
-
*/
|
|
7
|
-
const createPermission = async (req, res) => {
|
|
8
|
-
try {
|
|
9
|
-
const { name, description } = req.body;
|
|
10
|
-
if (!name) {
|
|
11
|
-
return res.status(400).json({ message: 'Permission name is required.' });
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
const newPermission = new Permission({ name, description });
|
|
15
|
-
await newPermission.save();
|
|
16
|
-
return res.status(201).json(newPermission);
|
|
17
|
-
} catch (error) {
|
|
18
|
-
console.error('Error creating permission:', error);
|
|
19
|
-
return res.status(500).json({ message: 'Server error', error: error.message });
|
|
20
|
-
}
|
|
21
|
-
};
|
|
22
|
-
|
|
23
|
-
/**
|
|
24
|
-
* Retrieve a list of permissions.
|
|
25
|
-
* Supports pagination via query parameters (page & limit).
|
|
26
|
-
*/
|
|
27
|
-
const getPermissions = async (req, res) => {
|
|
28
|
-
try {
|
|
29
|
-
const { page, limit } = req.query;
|
|
30
|
-
const permissions = await Permission.paginate({}, {
|
|
31
|
-
page: parseInt(page) || 1,
|
|
32
|
-
limit: parseInt(limit) || 10
|
|
33
|
-
});
|
|
34
|
-
return res.status(200).json(permissions);
|
|
35
|
-
} catch (error) {
|
|
36
|
-
console.error('Error retrieving permissions:', error);
|
|
37
|
-
return res.status(500).json({ message: 'Server error', error: error.message });
|
|
38
|
-
}
|
|
39
|
-
};
|
|
40
|
-
|
|
41
|
-
/**
|
|
42
|
-
* Update a permission by its ID.
|
|
43
|
-
* Expects new values in the request body.
|
|
44
|
-
*/
|
|
45
|
-
const updatePermission = async (req, res) => {
|
|
46
|
-
try {
|
|
47
|
-
const { id } = req.params;
|
|
48
|
-
const updatedPermission = await Permission.findByIdAndUpdate(id, req.body, { new: true });
|
|
49
|
-
if (!updatedPermission) {
|
|
50
|
-
return res.status(404).json({ message: 'Permission not found.' });
|
|
51
|
-
}
|
|
52
|
-
return res.status(200).json(updatedPermission);
|
|
53
|
-
} catch (error) {
|
|
54
|
-
console.error('Error updating permission:', error);
|
|
55
|
-
return res.status(500).json({ message: 'Server error', error: error.message });
|
|
56
|
-
}
|
|
57
|
-
};
|
|
58
|
-
|
|
59
|
-
/**
|
|
60
|
-
* Delete a permission by its ID.
|
|
61
|
-
*/
|
|
62
|
-
const deletePermission = async (req, res) => {
|
|
63
|
-
try {
|
|
64
|
-
const { id } = req.params;
|
|
65
|
-
const deletedPermission = await Permission.findByIdAndDelete(id);
|
|
66
|
-
if (!deletedPermission) {
|
|
67
|
-
return res.status(404).json({ message: 'Permission not found.' });
|
|
68
|
-
}
|
|
69
|
-
return res.status(200).json({ message: 'Permission deleted successfully.' });
|
|
70
|
-
} catch (error) {
|
|
71
|
-
console.error('Error deleting permission:', error);
|
|
72
|
-
return res.status(500).json({ message: 'Server error', error: error.message });
|
|
73
|
-
}
|
|
74
|
-
};
|
|
75
|
-
|
|
76
|
-
module.exports = {
|
|
77
|
-
createPermission,
|
|
78
|
-
getPermissions,
|
|
79
|
-
updatePermission,
|
|
80
|
-
deletePermission
|
|
81
|
-
};
|
|
@@ -1,108 +0,0 @@
|
|
|
1
|
-
const Role = require('../models/role.model');
|
|
2
|
-
|
|
3
|
-
// Create a new role (accessible by superadmin only)
|
|
4
|
-
const createRole = async (req, res) => {
|
|
5
|
-
try {
|
|
6
|
-
const { tenantId, name, description, permissions } = req.body;
|
|
7
|
-
if (!tenantId || !name) {
|
|
8
|
-
return res.status(400).json({ message: 'tenantId and role name are required.' });
|
|
9
|
-
}
|
|
10
|
-
const newRole = new Role({ tenantId, name, description, permissions });
|
|
11
|
-
await newRole.save();
|
|
12
|
-
return res.status(201).json(newRole);
|
|
13
|
-
} catch (error) {
|
|
14
|
-
console.error('Error creating role:', error);
|
|
15
|
-
return res.status(500).json({ message: 'Server error', error: error.message });
|
|
16
|
-
}
|
|
17
|
-
};
|
|
18
|
-
|
|
19
|
-
// Get all roles for a specific tenant
|
|
20
|
-
const getRoles = async (req, res) => {
|
|
21
|
-
try {
|
|
22
|
-
const { tenantId } = req.params;
|
|
23
|
-
if (!tenantId) {
|
|
24
|
-
return res.status(400).json({ message: 'tenantId is required in the URL.' });
|
|
25
|
-
}
|
|
26
|
-
const roles = await Role.paginate({ tenantId }, { page: 1, limit: 100 });
|
|
27
|
-
return res.status(200).json(roles);
|
|
28
|
-
} catch (error) {
|
|
29
|
-
console.error('Error retrieving roles:', error);
|
|
30
|
-
return res.status(500).json({ message: 'Server error', error: error.message });
|
|
31
|
-
}
|
|
32
|
-
};
|
|
33
|
-
|
|
34
|
-
const updateRole = async (req, res) => {
|
|
35
|
-
try {
|
|
36
|
-
const updatedRole = await Role.findByIdAndUpdate(req.params.id, req.body, { new: true });
|
|
37
|
-
if (!updatedRole) {
|
|
38
|
-
return res.status(404).json({ message: 'Role not found.' });
|
|
39
|
-
}
|
|
40
|
-
return res.status(200).json(updatedRole);
|
|
41
|
-
} catch (error) {
|
|
42
|
-
console.error('Error updating role:', error);
|
|
43
|
-
return res.status(500).json({ message: 'Server error', error: error.message });
|
|
44
|
-
}
|
|
45
|
-
};
|
|
46
|
-
|
|
47
|
-
const deleteRole = async (req, res) => {
|
|
48
|
-
try {
|
|
49
|
-
const deletedRole = await Role.findByIdAndDelete(req.params.id);
|
|
50
|
-
if (!deletedRole) {
|
|
51
|
-
return res.status(404).json({ message: 'Role not found.' });
|
|
52
|
-
}
|
|
53
|
-
return res.status(200).json({ message: 'Role deleted successfully.' });
|
|
54
|
-
} catch (error) {
|
|
55
|
-
console.error('Error deleting role:', error);
|
|
56
|
-
return res.status(500).json({ message: 'Server error', error: error.message });
|
|
57
|
-
}
|
|
58
|
-
};
|
|
59
|
-
|
|
60
|
-
/**
|
|
61
|
-
* suspendUser
|
|
62
|
-
*
|
|
63
|
-
* This controller sets a user's status to "suspended". It expects:
|
|
64
|
-
* - The authenticated user's info in req.user.
|
|
65
|
-
* - The target user's id in req.params.id.
|
|
66
|
-
*
|
|
67
|
-
* Only a superadmin (a user whose roles include "superadmin") is authorized
|
|
68
|
-
* to perform this action.
|
|
69
|
-
*/
|
|
70
|
-
const suspendUser = async (req, res) => {
|
|
71
|
-
try {
|
|
72
|
-
// Ensure the authenticated user is provided.
|
|
73
|
-
if (!req.user || !req.user.roles) {
|
|
74
|
-
return res.status(403).json({ message: 'Access denied. Superadmin privileges required.' });
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
// Check if the current user has the superadmin role.
|
|
78
|
-
// (Adjust this check if your roles are stored as ObjectIds or have different structure.)
|
|
79
|
-
if (!req.user.roles.includes("superadmin")) {
|
|
80
|
-
return res.status(403).json({ message: 'Access denied. Superadmin privileges required.' });
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
// Extract the target user's ID from the URL.
|
|
84
|
-
const { id } = req.params;
|
|
85
|
-
if (!id) {
|
|
86
|
-
return res.status(400).json({ message: 'User ID is required in the URL.' });
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
// Update the target user's status to "suspended"
|
|
90
|
-
const updatedUser = await User.findByIdAndUpdate(id, { status: 'suspended' }, { new: true });
|
|
91
|
-
if (!updatedUser) {
|
|
92
|
-
return res.status(404).json({ message: 'User not found.' });
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
return res.status(200).json({ message: 'User suspended successfully.', user: updatedUser });
|
|
96
|
-
} catch (error) {
|
|
97
|
-
console.error('Error suspending user:', error);
|
|
98
|
-
return res.status(500).json({ message: 'Server error', error: error.message });
|
|
99
|
-
}
|
|
100
|
-
};
|
|
101
|
-
|
|
102
|
-
module.exports = {
|
|
103
|
-
createRole,
|
|
104
|
-
getRoles,
|
|
105
|
-
updateRole,
|
|
106
|
-
deleteRole,
|
|
107
|
-
suspendUser
|
|
108
|
-
};
|
|
@@ -1,283 +0,0 @@
|
|
|
1
|
-
const User = require('../models/user.model');
|
|
2
|
-
const nodemailer = require('nodemailer');
|
|
3
|
-
const bcrypt = require('bcryptjs');
|
|
4
|
-
const crypto = require('crypto');
|
|
5
|
-
|
|
6
|
-
/**
|
|
7
|
-
* Create a new user.
|
|
8
|
-
* For local login, a password is required. If provided, it will be hashed.
|
|
9
|
-
*/
|
|
10
|
-
const createUser = async (req, res) => {
|
|
11
|
-
try {
|
|
12
|
-
const { email, password, name, tenantId, microsoftId, roles } = req.body;
|
|
13
|
-
|
|
14
|
-
// Validate required fields.
|
|
15
|
-
if (!email) {
|
|
16
|
-
return res.status(400).json({ message: 'Email is required.' });
|
|
17
|
-
}
|
|
18
|
-
if (!tenantId) {
|
|
19
|
-
return res.status(400).json({ message: 'TenantId is required.' });
|
|
20
|
-
}
|
|
21
|
-
// For local login, password is required.
|
|
22
|
-
if (!microsoftId && !password) {
|
|
23
|
-
return res.status(400).json({ message: 'Password is required for local login.' });
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
// Check if a user with this email already exists for this tenant.
|
|
27
|
-
const existingUser = await User.findOne({ email, tenantId });
|
|
28
|
-
if (existingUser) {
|
|
29
|
-
return res.status(400).json({ message: 'User with this email already exists.' });
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
// Hash the provided password.
|
|
33
|
-
const salt = await bcrypt.genSalt(10);
|
|
34
|
-
const hashedPassword = password ? await bcrypt.hash(password, salt) : undefined;
|
|
35
|
-
|
|
36
|
-
// Set user status to "pending" (waiting for email confirmation)
|
|
37
|
-
const status = 'pending';
|
|
38
|
-
|
|
39
|
-
// Read the expiration time for the confirmation token from environment variables (in hours)
|
|
40
|
-
// Default is 24 hours if not specified.
|
|
41
|
-
const tokenExpiryHours = parseFloat(process.env.EMAIL_TOKEN_EXPIRATION_HOURS) || 24;
|
|
42
|
-
const tokenExpiration = Date.now() + tokenExpiryHours * 60 * 60 * 1000;
|
|
43
|
-
|
|
44
|
-
// Generate a secure confirmation token.
|
|
45
|
-
const confirmationToken = crypto.randomBytes(20).toString('hex');
|
|
46
|
-
|
|
47
|
-
// Create and save the new user.
|
|
48
|
-
const newUser = new User({
|
|
49
|
-
email,
|
|
50
|
-
password: hashedPassword,
|
|
51
|
-
name,
|
|
52
|
-
tenantId,
|
|
53
|
-
microsoftId,
|
|
54
|
-
roles,
|
|
55
|
-
status,
|
|
56
|
-
resetPasswordToken: confirmationToken,
|
|
57
|
-
resetPasswordExpires: tokenExpiration
|
|
58
|
-
});
|
|
59
|
-
|
|
60
|
-
await newUser.save();
|
|
61
|
-
|
|
62
|
-
// Set up nodemailer transporter using SMTP settings from environment variables.
|
|
63
|
-
const transporter = nodemailer.createTransport({
|
|
64
|
-
host: process.env.SMTP_HOST,
|
|
65
|
-
port: parseInt(process.env.SMTP_PORT),
|
|
66
|
-
secure: process.env.SMTP_SECURE === 'true',
|
|
67
|
-
auth: {
|
|
68
|
-
user: process.env.SMTP_USER,
|
|
69
|
-
pass: process.env.SMTP_PASS
|
|
70
|
-
}
|
|
71
|
-
});
|
|
72
|
-
|
|
73
|
-
// Construct the email confirmation URL.
|
|
74
|
-
const confirmationUrl = `${process.env.FRONTEND_URL}/confirm-email?token=${confirmationToken}&email=${encodeURIComponent(email)}`;
|
|
75
|
-
|
|
76
|
-
const mailOptions = {
|
|
77
|
-
from: process.env.FROM_EMAIL,
|
|
78
|
-
to: email,
|
|
79
|
-
subject: 'Confirm Your Email Address',
|
|
80
|
-
text: `Hello,
|
|
81
|
-
|
|
82
|
-
Thank you for registering. Please confirm your account by clicking the link below:
|
|
83
|
-
|
|
84
|
-
${confirmationUrl}
|
|
85
|
-
|
|
86
|
-
This link will expire in ${tokenExpiryHours} hour(s).
|
|
87
|
-
|
|
88
|
-
If you did not initiate this registration, please ignore this email.
|
|
89
|
-
|
|
90
|
-
Thank you.`
|
|
91
|
-
};
|
|
92
|
-
|
|
93
|
-
// Send the confirmation email.
|
|
94
|
-
await transporter.sendMail(mailOptions);
|
|
95
|
-
|
|
96
|
-
return res.status(201).json({ message: 'User created and confirmation email sent successfully.', user: newUser });
|
|
97
|
-
} catch (error) {
|
|
98
|
-
console.error('Error creating user:', error);
|
|
99
|
-
return res.status(500).json({ message: 'Server error', error: error.message });
|
|
100
|
-
}
|
|
101
|
-
};
|
|
102
|
-
|
|
103
|
-
/**
|
|
104
|
-
* Update an existing user.
|
|
105
|
-
* If a new password is provided in the request, it will be hashed before updating.
|
|
106
|
-
*/
|
|
107
|
-
const updateUser = async (req, res) => {
|
|
108
|
-
try {
|
|
109
|
-
const userId = req.params.id;
|
|
110
|
-
|
|
111
|
-
// If password is provided, hash it before updating.
|
|
112
|
-
if (req.body.password) {
|
|
113
|
-
const salt = await bcrypt.genSalt(10);
|
|
114
|
-
req.body.password = await bcrypt.hash(req.body.password, salt);
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
const updatedUser = await User.findByIdAndUpdate(userId, req.body, { new: true });
|
|
118
|
-
if (!updatedUser) {
|
|
119
|
-
return res.status(404).json({ message: 'User not found.' });
|
|
120
|
-
}
|
|
121
|
-
return res.status(200).json(updatedUser);
|
|
122
|
-
} catch (error) {
|
|
123
|
-
console.error('Error updating user:', error);
|
|
124
|
-
return res.status(500).json({ message: 'Server error', error: error.message });
|
|
125
|
-
}
|
|
126
|
-
};
|
|
127
|
-
|
|
128
|
-
/**
|
|
129
|
-
* Delete a user by their ID.
|
|
130
|
-
*/
|
|
131
|
-
const deleteUser = async (req, res) => {
|
|
132
|
-
try {
|
|
133
|
-
const userId = req.params.id;
|
|
134
|
-
const deletedUser = await User.findByIdAndDelete(userId);
|
|
135
|
-
if (!deletedUser) {
|
|
136
|
-
return res.status(404).json({ message: 'User not found.' });
|
|
137
|
-
}
|
|
138
|
-
return res.status(200).json({ message: 'User deleted successfully.' });
|
|
139
|
-
} catch (error) {
|
|
140
|
-
console.error('Error deleting user:', error);
|
|
141
|
-
return res.status(500).json({ message: 'Server error', error: error.message });
|
|
142
|
-
}
|
|
143
|
-
};
|
|
144
|
-
|
|
145
|
-
/**
|
|
146
|
-
* Create a new user invitation.
|
|
147
|
-
*
|
|
148
|
-
* The endpoint expects at least:
|
|
149
|
-
* - email: user's email address
|
|
150
|
-
* - tenantId: the tenant this user belongs to
|
|
151
|
-
* Optionally, it may include:
|
|
152
|
-
* - name: user's name
|
|
153
|
-
*
|
|
154
|
-
* It creates a new user with no password, generates an invitation token (which we reuse
|
|
155
|
-
* for password reset), and sends an email with a link where the user can set their password.
|
|
156
|
-
*/
|
|
157
|
-
const createUserInvitation = async (req, res) => {
|
|
158
|
-
try {
|
|
159
|
-
const { email, tenantId, name } = req.body;
|
|
160
|
-
|
|
161
|
-
// Validate required fields.
|
|
162
|
-
if (!email) {
|
|
163
|
-
return res.status(400).json({ message: 'Email is required.' });
|
|
164
|
-
}
|
|
165
|
-
if (!tenantId) {
|
|
166
|
-
return res.status(400).json({ message: 'TenantId is required.' });
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
// Check if the user already exists for this tenant.
|
|
170
|
-
const existingUser = await User.findOne({ email, tenantId });
|
|
171
|
-
if (existingUser) {
|
|
172
|
-
return res.status(400).json({ message: 'User with this email already exists.' });
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
// Generate a secure invitation token (similar to a password reset token)
|
|
176
|
-
const token = crypto.randomBytes(20).toString('hex');
|
|
177
|
-
// Set the token expiration (e.g., 24 hours from now)
|
|
178
|
-
const tokenExpiration = Date.now() + 24 * 60 * 60 * 1000; // 24 hours in milliseconds
|
|
179
|
-
|
|
180
|
-
// Create a new user with the invitation token and without a password.
|
|
181
|
-
// (You can use different fields like invitationToken if preferred; here we re-use resetPasswordToken)
|
|
182
|
-
const newUser = new User({
|
|
183
|
-
email,
|
|
184
|
-
tenantId,
|
|
185
|
-
name,
|
|
186
|
-
// No password yet; user will set it later
|
|
187
|
-
resetPasswordToken: token,
|
|
188
|
-
resetPasswordExpires: tokenExpiration
|
|
189
|
-
});
|
|
190
|
-
await newUser.save();
|
|
191
|
-
|
|
192
|
-
// Setup nodemailer transporter using your SMTP settings from environment variables.
|
|
193
|
-
const transporter = nodemailer.createTransport({
|
|
194
|
-
host: process.env.SMTP_HOST,
|
|
195
|
-
port: parseInt(process.env.SMTP_PORT),
|
|
196
|
-
secure: process.env.SMTP_SECURE === 'true', // true for 465, false for other ports
|
|
197
|
-
auth: {
|
|
198
|
-
user: process.env.SMTP_USER,
|
|
199
|
-
pass: process.env.SMTP_PASS
|
|
200
|
-
}
|
|
201
|
-
});
|
|
202
|
-
|
|
203
|
-
// Construct the invitation URL.
|
|
204
|
-
// The frontend should have a route like /set-password to handle completing registration.
|
|
205
|
-
const invitationUrl = `${process.env.FRONTEND_URL}/set-password?token=${token}&email=${encodeURIComponent(email)}`;
|
|
206
|
-
|
|
207
|
-
// Define the email options.
|
|
208
|
-
const mailOptions = {
|
|
209
|
-
from: process.env.FROM_EMAIL,
|
|
210
|
-
to: email,
|
|
211
|
-
subject: "You're invited: Set up your password",
|
|
212
|
-
text: `Hello,
|
|
213
|
-
|
|
214
|
-
You have been invited to join our platform. Please click on the link below to set your password and complete your registration:
|
|
215
|
-
|
|
216
|
-
${invitationUrl}
|
|
217
|
-
|
|
218
|
-
This link will expire in 24 hours. If you did not request this or believe it to be in error, please ignore this email.
|
|
219
|
-
|
|
220
|
-
Thank you!`
|
|
221
|
-
};
|
|
222
|
-
|
|
223
|
-
// Send the invitation email.
|
|
224
|
-
await transporter.sendMail(mailOptions);
|
|
225
|
-
return res.status(201).json({ message: 'Invitation sent successfully. Please check your email.' });
|
|
226
|
-
} catch (error) {
|
|
227
|
-
console.error('Error in createUserInvitation:', error);
|
|
228
|
-
return res.status(500).json({ message: 'Server error', error: error.message });
|
|
229
|
-
}
|
|
230
|
-
};
|
|
231
|
-
|
|
232
|
-
/**
|
|
233
|
-
* GET /api/users
|
|
234
|
-
* Query params:
|
|
235
|
-
* page, limit, tenantId, email
|
|
236
|
-
*/
|
|
237
|
-
const getAllUsers = async (req, res) => {
|
|
238
|
-
try {
|
|
239
|
-
/* ---------- filters ---------- */
|
|
240
|
-
const filter = {};
|
|
241
|
-
if (req.query.tenantId) filter.tenantId = req.query.tenantId;
|
|
242
|
-
if (req.query.email) filter.email = req.query.email;
|
|
243
|
-
|
|
244
|
-
/* ---------- pagination ---------- */
|
|
245
|
-
const page = Math.max(parseInt(req.query.page, 10) || 1, 1);
|
|
246
|
-
const limit = Math.min(parseInt(req.query.limit, 10) || 20, 100);
|
|
247
|
-
const skip = (page - 1) * limit;
|
|
248
|
-
|
|
249
|
-
/* ---------- query ---------- */
|
|
250
|
-
const [totalItems, users] = await Promise.all([
|
|
251
|
-
User.countDocuments(filter),
|
|
252
|
-
User.find(filter)
|
|
253
|
-
.populate({ path: 'roles', select: '-__v' })
|
|
254
|
-
.skip(skip)
|
|
255
|
-
.limit(limit)
|
|
256
|
-
.lean()
|
|
257
|
-
]);
|
|
258
|
-
|
|
259
|
-
return res.status(200).json({
|
|
260
|
-
data: users,
|
|
261
|
-
pagination: {
|
|
262
|
-
totalItems, // ← renamed
|
|
263
|
-
limit,
|
|
264
|
-
totalPages: Math.ceil(totalItems / limit) || 1,
|
|
265
|
-
currentPage: page,
|
|
266
|
-
hasNextPage: page * limit < totalItems,
|
|
267
|
-
hasPrevPage: page > 1
|
|
268
|
-
}
|
|
269
|
-
});
|
|
270
|
-
} catch (error) {
|
|
271
|
-
console.error('Error fetching users:', error);
|
|
272
|
-
return res.status(500).json({ message: 'Server error', error: error.message });
|
|
273
|
-
}
|
|
274
|
-
};
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
module.exports = {
|
|
278
|
-
createUser,
|
|
279
|
-
updateUser,
|
|
280
|
-
deleteUser,
|
|
281
|
-
createUserInvitation,
|
|
282
|
-
getAllUsers
|
|
283
|
-
};
|
package/src/index.js
DELETED
|
@@ -1,32 +0,0 @@
|
|
|
1
|
-
const express = require('express');
|
|
2
|
-
const connectDB = require('./config/db.config');
|
|
3
|
-
require('dotenv').config();
|
|
4
|
-
|
|
5
|
-
// Import routes
|
|
6
|
-
const authRoutes = require('./routes/auth.routes');
|
|
7
|
-
const passwordResetRoutes = require('./routes/passwordReset.routes');
|
|
8
|
-
const rolesRoutes = require('./routes/roles.routes');
|
|
9
|
-
const permissionsRoutes = require('./routes/permission.routes');
|
|
10
|
-
const adminRoutes = require('./routes/admin.routes');
|
|
11
|
-
const userRoutes = require('./routes/user.routes');
|
|
12
|
-
|
|
13
|
-
const app = express();
|
|
14
|
-
|
|
15
|
-
app.use(express.json());
|
|
16
|
-
connectDB();
|
|
17
|
-
|
|
18
|
-
// Auth endpoints
|
|
19
|
-
app.use('/api/auth', authRoutes);
|
|
20
|
-
app.use('/api/auth', passwordResetRoutes);
|
|
21
|
-
|
|
22
|
-
// User management endpoints
|
|
23
|
-
app.use('/api/users', userRoutes);
|
|
24
|
-
|
|
25
|
-
// Role & Permission endpoints
|
|
26
|
-
app.use('/api/auth/permissions', permissionsRoutes); // permissions under /api/auth/roles
|
|
27
|
-
app.use('/api/auth/roles', rolesRoutes); // roles under /api/auth/roles
|
|
28
|
-
|
|
29
|
-
// Admin‐only endpoints
|
|
30
|
-
app.use('/api/admin', adminRoutes);
|
|
31
|
-
|
|
32
|
-
module.exports = app;
|
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
const jwt = require("jsonwebtoken");
|
|
2
|
-
|
|
3
|
-
const authMiddleware = (req, res, next) => {
|
|
4
|
-
const token = req.headers.authorization?.split(" ")[1];
|
|
5
|
-
if (!token) return res.status(401).json({ error: "Unauthorized" });
|
|
6
|
-
|
|
7
|
-
try {
|
|
8
|
-
const decoded = jwt.verify(token, process.env.JWT_SECRET);
|
|
9
|
-
req.user = decoded; // Contains tenantId, userId, roleIds
|
|
10
|
-
next();
|
|
11
|
-
} catch (error) {
|
|
12
|
-
res.status(403).json({ error: "Invalid token" });
|
|
13
|
-
}
|
|
14
|
-
};
|
|
15
|
-
|
|
16
|
-
module.exports = authMiddleware;
|
|
@@ -1,25 +0,0 @@
|
|
|
1
|
-
// src/middleware/authenticate.js
|
|
2
|
-
|
|
3
|
-
const jwt = require('jsonwebtoken');
|
|
4
|
-
require('dotenv').config();
|
|
5
|
-
|
|
6
|
-
module.exports = (req, res, next) => {
|
|
7
|
-
const authHeader = req.headers.authorization;
|
|
8
|
-
if (!authHeader || !authHeader.startsWith('Bearer ')) {
|
|
9
|
-
return res.status(401).json({ message: 'Missing or invalid Authorization header.' });
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
const token = authHeader.split(' ')[1];
|
|
13
|
-
jwt.verify(token, process.env.JWT_SECRET, (err, decoded) => {
|
|
14
|
-
if (err) {
|
|
15
|
-
// TokenExpiredError comes here when the token is past its exp
|
|
16
|
-
if (err.name === 'TokenExpiredError') {
|
|
17
|
-
return res.status(401).json({ message: 'Access token expired.' });
|
|
18
|
-
}
|
|
19
|
-
return res.status(401).json({ message: 'Invalid access token.' });
|
|
20
|
-
}
|
|
21
|
-
// Attach the payload to req.user for downstream handlers
|
|
22
|
-
req.user = decoded;
|
|
23
|
-
next();
|
|
24
|
-
});
|
|
25
|
-
};
|
|
@@ -1,24 +0,0 @@
|
|
|
1
|
-
const Role = require("../models/role.model");
|
|
2
|
-
|
|
3
|
-
// Middleware to check if user has required permission
|
|
4
|
-
const hasPermission = (requiredPermission) => {
|
|
5
|
-
return async (req, res, next) => {
|
|
6
|
-
try {
|
|
7
|
-
const { tenantId, roleIds } = req.user;
|
|
8
|
-
|
|
9
|
-
// Fetch roles from DB
|
|
10
|
-
const roles = await Role.find({ _id: { $in: roleIds }, tenantId });
|
|
11
|
-
const permissions = roles.flatMap(role => role.permissions);
|
|
12
|
-
|
|
13
|
-
if (permissions.includes(requiredPermission)) {
|
|
14
|
-
return next();
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
res.status(403).json({ error: "Forbidden: Insufficient permissions" });
|
|
18
|
-
} catch (error) {
|
|
19
|
-
res.status(500).json({ error: "Authorization error" });
|
|
20
|
-
}
|
|
21
|
-
};
|
|
22
|
-
};
|
|
23
|
-
|
|
24
|
-
module.exports = { hasPermission };
|
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
const jwt = require("jsonwebtoken");
|
|
2
|
-
|
|
3
|
-
const tenantMiddleware = (req, res, next) => {
|
|
4
|
-
const token = req.headers.authorization?.split(" ")[1]; // Extract JWT
|
|
5
|
-
if (!token) return res.status(401).json({ error: "Unauthorized" });
|
|
6
|
-
|
|
7
|
-
try {
|
|
8
|
-
const decoded = jwt.verify(token, process.env.JWT_SECRET);
|
|
9
|
-
req.tenantId = decoded.tenantId; // Attach tenant ID to request
|
|
10
|
-
next();
|
|
11
|
-
} catch (error) {
|
|
12
|
-
res.status(403).json({ error: "Invalid token" });
|
|
13
|
-
}
|
|
14
|
-
};
|
|
15
|
-
|
|
16
|
-
module.exports = tenantMiddleware;
|
|
@@ -1,39 +0,0 @@
|
|
|
1
|
-
const mongoose = require('mongoose');
|
|
2
|
-
const mongoosePaginate = require('mongoose-paginate-v2');
|
|
3
|
-
|
|
4
|
-
const ClientSchema = new mongoose.Schema({
|
|
5
|
-
email: {
|
|
6
|
-
type: String,
|
|
7
|
-
required: true,
|
|
8
|
-
unique: true
|
|
9
|
-
},
|
|
10
|
-
// Hashed password; may be empty for social/OAuth clients
|
|
11
|
-
password: {
|
|
12
|
-
type: String,
|
|
13
|
-
required: function () {
|
|
14
|
-
return !this.microsoftId && !this.googleId && !this.facebookId;
|
|
15
|
-
}
|
|
16
|
-
},
|
|
17
|
-
name: { type: String },
|
|
18
|
-
|
|
19
|
-
// Social providers (optional)
|
|
20
|
-
microsoftId: { type: String, index: true },
|
|
21
|
-
googleId: { type: String, index: true },
|
|
22
|
-
facebookId: { type: String, index: true },
|
|
23
|
-
|
|
24
|
-
// Roles assigned to the client
|
|
25
|
-
roles: [{ type: mongoose.Schema.Types.ObjectId, ref: 'Role' }],
|
|
26
|
-
|
|
27
|
-
// Password reset
|
|
28
|
-
resetPasswordToken: { type: String },
|
|
29
|
-
resetPasswordExpires: { type: Date },
|
|
30
|
-
|
|
31
|
-
// For refresh flow (your controller already sets this)
|
|
32
|
-
refreshToken: { type: String },
|
|
33
|
-
|
|
34
|
-
createdAt: { type: Date, default: Date.now }
|
|
35
|
-
}, { timestamps: true });
|
|
36
|
-
|
|
37
|
-
ClientSchema.plugin(mongoosePaginate);
|
|
38
|
-
|
|
39
|
-
module.exports = mongoose.model('Client', ClientSchema);
|
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
const mongoose = require("mongoose");
|
|
2
|
-
|
|
3
|
-
const PermissionSchema = new mongoose.Schema({
|
|
4
|
-
name: { type: String, required: true, unique: true }, // e.g., "read:orders"
|
|
5
|
-
category: { type: String }, // e.g., "Orders"
|
|
6
|
-
description: { type: String }
|
|
7
|
-
});
|
|
8
|
-
|
|
9
|
-
module.exports = mongoose.model("Permission", PermissionSchema);
|
package/src/models/role.model.js
DELETED
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
const mongoose = require('mongoose');
|
|
2
|
-
const mongoosePaginate = require('mongoose-paginate-v2');
|
|
3
|
-
|
|
4
|
-
const RoleSchema = new mongoose.Schema({
|
|
5
|
-
tenantId: { type: String, required: true },
|
|
6
|
-
name: { type: String, required: true, unique: true },
|
|
7
|
-
description: { type: String },
|
|
8
|
-
// Permissions stored as strings (e.g., "create:invoice", "delete:user")
|
|
9
|
-
permissions: [{ type: String }]
|
|
10
|
-
}, { timestamps: true });
|
|
11
|
-
|
|
12
|
-
RoleSchema.plugin(mongoosePaginate);
|
|
13
|
-
|
|
14
|
-
module.exports = mongoose.model('Role', RoleSchema);
|