@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.
Files changed (2) hide show
  1. package/package.json +16 -0
  2. 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 };