@dax-crafta/auth 1.1.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.
- package/package.json +16 -0
- package/src/index.js +205 -0
package/package.json
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@dax-crafta/auth",
|
|
3
|
+
"version": "1.1.0",
|
|
4
|
+
"description": "Authentication plugin for crafta framework",
|
|
5
|
+
"main": "src/index.js",
|
|
6
|
+
"private": false,
|
|
7
|
+
"dependencies": {
|
|
8
|
+
"bcryptjs": "^2.4.3",
|
|
9
|
+
"jsonwebtoken": "^9.0.2",
|
|
10
|
+
"mongoose": "^7.6.3",
|
|
11
|
+
"validator": "^13.11.0"
|
|
12
|
+
},
|
|
13
|
+
"peerDependencies": {
|
|
14
|
+
"crafta": "^1.1.4"
|
|
15
|
+
}
|
|
16
|
+
}
|
package/src/index.js
ADDED
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
const jwt = require('jsonwebtoken');
|
|
2
|
+
const bcrypt = require('bcryptjs');
|
|
3
|
+
const mongoose = require('mongoose');
|
|
4
|
+
const validator = require('validator');
|
|
5
|
+
|
|
6
|
+
// Default configuration
|
|
7
|
+
const defaultConfig = {
|
|
8
|
+
strategy: 'jwt',
|
|
9
|
+
fields: ['email', 'password'],
|
|
10
|
+
routes: {
|
|
11
|
+
register: '/register',
|
|
12
|
+
login: '/login',
|
|
13
|
+
verify: '/verify'
|
|
14
|
+
},
|
|
15
|
+
mongoUrl: process.env.MONGODB_URI || 'mongodb://localhost:27017/forge-auth',
|
|
16
|
+
sendVerificationEmail: null,
|
|
17
|
+
db: null
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
// Create dynamic Mongoose schema based on fields
|
|
21
|
+
function createUserSchema(fields) {
|
|
22
|
+
const schemaFields = fields.reduce((acc, field) => {
|
|
23
|
+
acc[field] = {
|
|
24
|
+
type: String,
|
|
25
|
+
required: true,
|
|
26
|
+
...(field === 'email' && {
|
|
27
|
+
validate: {
|
|
28
|
+
validator: validator.isEmail,
|
|
29
|
+
message: 'Invalid email format'
|
|
30
|
+
}
|
|
31
|
+
})
|
|
32
|
+
};
|
|
33
|
+
return acc;
|
|
34
|
+
}, {});
|
|
35
|
+
|
|
36
|
+
return new mongoose.Schema(schemaFields, { timestamps: true });
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function auth(config = {}) {
|
|
40
|
+
const finalConfig = { ...defaultConfig, ...config };
|
|
41
|
+
|
|
42
|
+
// Validate JWT secret
|
|
43
|
+
if (!finalConfig.env?.JWT_SECRET) {
|
|
44
|
+
throw new Error('JWT_SECRET is required in environment configuration');
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// Initialize MongoDB connection if custom DB handlers aren't provided
|
|
48
|
+
let User;
|
|
49
|
+
if (!finalConfig.db) {
|
|
50
|
+
mongoose.connect(finalConfig.mongoUrl, {
|
|
51
|
+
useNewUrlParser: true,
|
|
52
|
+
useUnifiedTopology: true
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
const userSchema = createUserSchema(finalConfig.fields);
|
|
56
|
+
User = mongoose.model('User', userSchema);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// Database operations
|
|
60
|
+
const dbOperations = finalConfig.db || {
|
|
61
|
+
saveUser: async (userData) => {
|
|
62
|
+
const user = new User(userData);
|
|
63
|
+
return await user.save();
|
|
64
|
+
},
|
|
65
|
+
getUserByEmail: async (email) => {
|
|
66
|
+
return await User.findOne({ email });
|
|
67
|
+
}
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
return function(app) {
|
|
71
|
+
// Register route
|
|
72
|
+
app.post(finalConfig.routes.register, async (req, res) => {
|
|
73
|
+
try {
|
|
74
|
+
// Validate required fields
|
|
75
|
+
const userData = {};
|
|
76
|
+
for (const field of finalConfig.fields) {
|
|
77
|
+
if (!req.body[field]) {
|
|
78
|
+
return res.status(400).json({
|
|
79
|
+
success: false,
|
|
80
|
+
error: `${field} is required`
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
userData[field] = req.body[field];
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// Hash password
|
|
87
|
+
if (userData.password) {
|
|
88
|
+
userData.password = await bcrypt.hash(userData.password, 10);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// Save user
|
|
92
|
+
const savedUser = await dbOperations.saveUser(userData);
|
|
93
|
+
|
|
94
|
+
// Generate JWT
|
|
95
|
+
const token = jwt.sign(
|
|
96
|
+
{ id: savedUser._id, email: savedUser.email },
|
|
97
|
+
finalConfig.env.JWT_SECRET,
|
|
98
|
+
{ expiresIn: '24h' }
|
|
99
|
+
);
|
|
100
|
+
|
|
101
|
+
// Send verification email if configured
|
|
102
|
+
if (finalConfig.sendVerificationEmail) {
|
|
103
|
+
try {
|
|
104
|
+
await finalConfig.sendVerificationEmail(savedUser);
|
|
105
|
+
} catch (emailError) {
|
|
106
|
+
console.error('Email verification failed:', emailError);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
res.status(201).json({
|
|
111
|
+
success: true,
|
|
112
|
+
token,
|
|
113
|
+
user: { ...savedUser.toJSON(), password: undefined }
|
|
114
|
+
});
|
|
115
|
+
} catch (error) {
|
|
116
|
+
if (error.code === 11000) {
|
|
117
|
+
return res.status(400).json({
|
|
118
|
+
success: false,
|
|
119
|
+
error: 'Email already exists'
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
res.status(400).json({
|
|
123
|
+
success: false,
|
|
124
|
+
error: error.message
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
// Login route
|
|
130
|
+
app.post(finalConfig.routes.login, async (req, res) => {
|
|
131
|
+
try {
|
|
132
|
+
const { email, password } = req.body;
|
|
133
|
+
|
|
134
|
+
if (!email || !password) {
|
|
135
|
+
return res.status(400).json({
|
|
136
|
+
success: false,
|
|
137
|
+
error: 'Email and password are required'
|
|
138
|
+
});
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
const user = await dbOperations.getUserByEmail(email);
|
|
142
|
+
|
|
143
|
+
if (!user) {
|
|
144
|
+
return res.status(404).json({
|
|
145
|
+
success: false,
|
|
146
|
+
error: 'User not found'
|
|
147
|
+
});
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
const isValid = await bcrypt.compare(password, user.password);
|
|
151
|
+
|
|
152
|
+
if (!isValid) {
|
|
153
|
+
return res.status(401).json({
|
|
154
|
+
success: false,
|
|
155
|
+
error: 'Invalid credentials'
|
|
156
|
+
});
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
const token = jwt.sign(
|
|
160
|
+
{ id: user._id, email: user.email },
|
|
161
|
+
finalConfig.env.JWT_SECRET,
|
|
162
|
+
{ expiresIn: '24h' }
|
|
163
|
+
);
|
|
164
|
+
|
|
165
|
+
res.json({
|
|
166
|
+
success: true,
|
|
167
|
+
token,
|
|
168
|
+
user: { ...user.toJSON(), password: undefined }
|
|
169
|
+
});
|
|
170
|
+
} catch (error) {
|
|
171
|
+
res.status(400).json({
|
|
172
|
+
success: false,
|
|
173
|
+
error: error.message
|
|
174
|
+
});
|
|
175
|
+
}
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
// Verify route
|
|
179
|
+
app.get(finalConfig.routes.verify, (req, res) => {
|
|
180
|
+
const token = req.headers.authorization?.split(' ')[1];
|
|
181
|
+
|
|
182
|
+
if (!token) {
|
|
183
|
+
return res.status(401).json({
|
|
184
|
+
success: false,
|
|
185
|
+
error: 'No token provided'
|
|
186
|
+
});
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
try {
|
|
190
|
+
const decoded = jwt.verify(token, finalConfig.env.JWT_SECRET);
|
|
191
|
+
res.json({
|
|
192
|
+
success: true,
|
|
193
|
+
user: decoded
|
|
194
|
+
});
|
|
195
|
+
} catch (error) {
|
|
196
|
+
res.status(401).json({
|
|
197
|
+
success: false,
|
|
198
|
+
error: 'Invalid token'
|
|
199
|
+
});
|
|
200
|
+
}
|
|
201
|
+
});
|
|
202
|
+
};
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
module.exports = { auth };
|