frontend-hamroun 1.2.15 → 1.2.17
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 +4 -0
- package/bin/cli.js +673 -0
- package/dist/component.d.ts +1 -1
- package/dist/context.d.ts +4 -3
- package/dist/index.client.d.ts +11 -0
- package/dist/index.d.ts +9 -89
- package/dist/index.js +396 -67
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +392 -0
- package/dist/index.mjs.map +1 -0
- package/dist/jsx-runtime/jsx-runtime.d.ts +0 -1
- package/dist/jsx-runtime.d.ts +1 -1
- package/dist/renderer.d.ts +0 -10
- package/dist/server-renderer.d.ts +0 -3
- package/dist/server-types.d.ts +42 -0
- package/package.json +69 -50
- package/templates/basic-app/index.html +6 -6
- package/templates/basic-app/package.json +15 -11
- package/templates/basic-app/postcss.config.js +0 -1
- package/templates/basic-app/src/main.tsx +1 -10
- package/templates/basic-app/tailwind.config.js +2 -23
- package/templates/basic-app/tsconfig.json +4 -17
- package/templates/basic-app/vite.config.ts +3 -54
- package/templates/fullstack-app/api/hello.ts +18 -0
- package/templates/fullstack-app/api/users/[id].ts +73 -0
- package/templates/fullstack-app/api/users/index.ts +32 -0
- package/templates/fullstack-app/package.json +31 -0
- package/templates/fullstack-app/server.ts +46 -0
- package/templates/fullstack-app/src/pages/index.tsx +59 -0
- package/templates/ssr-template/vite.config.ts +1 -11
- package/bin/cli.cjs +0 -16
- package/bin/cli.mjs +0 -237
- package/dist/backend/api-utils.d.ts +0 -38
- package/dist/backend/api-utils.js +0 -135
- package/dist/backend/auth.d.ts +0 -134
- package/dist/backend/auth.js +0 -387
- package/dist/backend/database.d.ts +0 -27
- package/dist/backend/database.js +0 -91
- package/dist/backend/model.d.ts +0 -43
- package/dist/backend/model.js +0 -178
- package/dist/backend/router.d.ts +0 -27
- package/dist/backend/router.js +0 -137
- package/dist/backend/server.d.ts +0 -19
- package/dist/backend/server.js +0 -268
- package/dist/backend/types.d.ts +0 -217
- package/dist/backend/types.js +0 -1
- package/dist/batch.js +0 -22
- package/dist/cli/index.d.ts +0 -2
- package/dist/cli/index.js +0 -215
- package/dist/component.js +0 -84
- package/dist/components/Counter.js +0 -2
- package/dist/context.js +0 -18
- package/dist/frontend-hamroun.es.js +0 -1378
- package/dist/frontend-hamroun.umd.js +0 -66
- package/dist/hooks.js +0 -164
- package/dist/jsx-runtime/index.d.ts +0 -11
- package/dist/jsx-runtime/index.js +0 -19
- package/dist/jsx-runtime/jsx-dev-runtime.js +0 -1
- package/dist/jsx-runtime/jsx-runtime.js +0 -95
- package/dist/jsx-runtime.js +0 -192
- package/dist/renderer.js +0 -51
- package/dist/server-renderer.js +0 -102
- package/dist/types.js +0 -1
- package/dist/vdom.js +0 -27
- package/scripts/build-cli.js +0 -1107
- package/scripts/generate.js +0 -134
- package/src/backend/api-utils.ts +0 -178
- package/src/backend/auth.ts +0 -544
- package/src/backend/database.ts +0 -104
- package/src/backend/model.ts +0 -198
- package/src/backend/router.ts +0 -176
- package/src/backend/server.ts +0 -330
- package/src/backend/types.ts +0 -257
- package/src/batch.ts +0 -24
- package/src/cli/index.js +0 -554
- package/src/cli/index.ts +0 -257
- package/src/component.ts +0 -98
- package/src/components/Counter.tsx +0 -4
- package/src/context.ts +0 -29
- package/src/hooks.ts +0 -211
- package/src/index.ts +0 -144
- package/src/jsx-runtime/index.ts +0 -27
- package/src/jsx-runtime/jsx-dev-runtime.ts +0 -0
- package/src/jsx-runtime/jsx-runtime.ts +0 -104
- package/src/jsx-runtime.ts +0 -226
- package/src/renderer.ts +0 -55
- package/src/server-renderer.ts +0 -114
- package/src/shims.d.ts +0 -20
- package/src/types/bcrypt.d.ts +0 -30
- package/src/types/jsonwebtoken.d.ts +0 -55
- package/src/types.d.ts +0 -26
- package/src/types.ts +0 -21
- package/src/vdom.ts +0 -34
- package/templates/basic/.eslintignore +0 -5
- package/templates/basic/.eslintrc.json +0 -25
- package/templates/basic/docs/rapport_pfe.aux +0 -27
- package/templates/basic/docs/rapport_pfe.log +0 -399
- package/templates/basic/docs/rapport_pfe.out +0 -10
- package/templates/basic/docs/rapport_pfe.pdf +0 -0
- package/templates/basic/docs/rapport_pfe.tex +0 -68
- package/templates/basic/docs/rapport_pfe.toc +0 -14
- package/templates/basic/index.html +0 -12
- package/templates/basic/jsconfig.json +0 -14
- package/templates/basic/package.json +0 -20
- package/templates/basic/postcss.config.js +0 -7
- package/templates/basic/src/App.js +0 -105
- package/templates/basic/src/App.tsx +0 -65
- package/templates/basic/src/api.ts +0 -58
- package/templates/basic/src/components/Counter.tsx +0 -26
- package/templates/basic/src/components/Header.tsx +0 -9
- package/templates/basic/src/components/TodoList.tsx +0 -90
- package/templates/basic/src/main.css +0 -3
- package/templates/basic/src/main.js +0 -11
- package/templates/basic/src/main.ts +0 -20
- package/templates/basic/src/main.tsx +0 -144
- package/templates/basic/src/server.ts +0 -99
- package/templates/basic/tailwind.config.js +0 -32
- package/templates/basic/tsconfig.json +0 -20
- package/templates/basic/tsconfig.node.json +0 -10
- package/templates/basic/vite.config.js +0 -18
- package/templates/basic/vite.config.ts +0 -86
- package/templates/basic-app/src/App.js +0 -105
- package/templates/basic-app/src/App.tsx +0 -143
- package/templates/basic-app/src/api.ts +0 -58
- package/templates/basic-app/src/components/Counter.tsx +0 -26
- package/templates/basic-app/src/components/Header.tsx +0 -9
- package/templates/basic-app/src/components/TodoList.tsx +0 -90
- package/templates/basic-app/src/main.js +0 -10
- package/templates/basic-app/src/main.ts +0 -21
- package/templates/basic-app/src/react/index.ts +0 -35
- package/templates/basic-app/src/react/jsx-dev-runtime.ts +0 -13
- package/templates/basic-app/src/react/jsx-runtime.ts +0 -12
- package/templates/basic-app/src/server.ts +0 -99
- package/templates/basic-app/src/shims.ts +0 -9
- package/templates/basic-app/tsconfig.node.json +0 -10
- package/templates/basic-app/vite.config.js +0 -22
- package/templates/full-stack/.env.example +0 -11
- package/templates/full-stack/README.md +0 -51
- package/templates/full-stack/index.html +0 -12
- package/templates/full-stack/jsconfig.json +0 -14
- package/templates/full-stack/package.json +0 -20
- package/templates/full-stack/src/App.js +0 -105
- package/templates/full-stack/src/client/App.tsx +0 -50
- package/templates/full-stack/src/client/components/Header.tsx +0 -42
- package/templates/full-stack/src/client/components/UserList.tsx +0 -29
- package/templates/full-stack/src/client/main.tsx +0 -5
- package/templates/full-stack/src/main.css +0 -3
- package/templates/full-stack/src/main.js +0 -11
- package/templates/full-stack/src/main.ts +0 -20
- package/templates/full-stack/src/server/index.ts +0 -99
- package/templates/full-stack/src/server/routes/auth.ts +0 -39
- package/templates/full-stack/src/server/routes/users.ts +0 -48
- package/templates/full-stack/src/shims.ts +0 -9
- package/templates/full-stack/tsconfig.json +0 -20
- package/templates/full-stack/tsconfig.node.json +0 -10
- package/templates/full-stack/tsconfig.server.json +0 -15
- package/templates/full-stack/vite.config.js +0 -18
- package/templates/full-stack/vite.config.ts +0 -85
package/src/backend/auth.ts
DELETED
@@ -1,544 +0,0 @@
|
|
1
|
-
import { Request, Response, NextFunction } from 'express';
|
2
|
-
const jwt =require('jsonwebtoken');
|
3
|
-
const bcrypt=require('bcrypt');
|
4
|
-
|
5
|
-
const crypto=require('crypto');
|
6
|
-
|
7
|
-
/**
|
8
|
-
* Authentication configuration options
|
9
|
-
*/
|
10
|
-
export interface AuthOptions {
|
11
|
-
/**
|
12
|
-
* Secret key for JWT signing
|
13
|
-
*/
|
14
|
-
jwtSecret: string;
|
15
|
-
|
16
|
-
/**
|
17
|
-
* Secret key for refresh token signing (defaults to jwtSecret if not provided)
|
18
|
-
*/
|
19
|
-
refreshSecret?: string;
|
20
|
-
|
21
|
-
/**
|
22
|
-
* JWT token expiration time (default: '15m')
|
23
|
-
*/
|
24
|
-
tokenExpiration?: string;
|
25
|
-
|
26
|
-
/**
|
27
|
-
* Refresh token expiration time (default: '7d')
|
28
|
-
*/
|
29
|
-
refreshExpiration?: string;
|
30
|
-
|
31
|
-
/**
|
32
|
-
* Number of bcrypt salt rounds (default: 10)
|
33
|
-
*/
|
34
|
-
saltRounds?: number;
|
35
|
-
|
36
|
-
/**
|
37
|
-
* Custom user finder function
|
38
|
-
*/
|
39
|
-
findUser?: (username: string) => Promise<any>;
|
40
|
-
|
41
|
-
/**
|
42
|
-
* Custom password verification (defaults to bcrypt compare)
|
43
|
-
*/
|
44
|
-
verifyPassword?: (password: string, hashedPassword: string) => Promise<boolean>;
|
45
|
-
|
46
|
-
/**
|
47
|
-
* Custom function to save refresh token
|
48
|
-
*/
|
49
|
-
saveRefreshToken?: (userId: string, token: string, expires: Date) => Promise<void>;
|
50
|
-
|
51
|
-
/**
|
52
|
-
* Custom function to verify refresh token
|
53
|
-
*/
|
54
|
-
verifyRefreshToken?: (userId: string, token: string) => Promise<boolean>;
|
55
|
-
|
56
|
-
/**
|
57
|
-
* Use secure cookies for tokens (default: process.env.NODE_ENV === 'production')
|
58
|
-
*/
|
59
|
-
secureCookies?: boolean;
|
60
|
-
|
61
|
-
/**
|
62
|
-
* Cookie domain
|
63
|
-
*/
|
64
|
-
cookieDomain?: string;
|
65
|
-
|
66
|
-
/**
|
67
|
-
* Use HTTP-only cookies (default: true)
|
68
|
-
*/
|
69
|
-
httpOnlyCookies?: boolean;
|
70
|
-
|
71
|
-
/**
|
72
|
-
* Implement rate limiting for login attempts (default: true)
|
73
|
-
*/
|
74
|
-
rateLimit?: boolean;
|
75
|
-
}
|
76
|
-
|
77
|
-
/**
|
78
|
-
* Token pair containing access and refresh tokens
|
79
|
-
*/
|
80
|
-
export interface TokenPair {
|
81
|
-
accessToken: string;
|
82
|
-
refreshToken: string;
|
83
|
-
expiresIn: number;
|
84
|
-
}
|
85
|
-
|
86
|
-
/**
|
87
|
-
* Authentication service
|
88
|
-
*/
|
89
|
-
export class Auth {
|
90
|
-
private options: AuthOptions;
|
91
|
-
private loginAttempts: Map<string, { count: number, resetTime: number }> = new Map();
|
92
|
-
|
93
|
-
constructor(options: AuthOptions) {
|
94
|
-
this.options = {
|
95
|
-
tokenExpiration: '15m',
|
96
|
-
refreshExpiration: '7d',
|
97
|
-
saltRounds: 10,
|
98
|
-
secureCookies: process.env.NODE_ENV === 'production',
|
99
|
-
httpOnlyCookies: true,
|
100
|
-
rateLimit: true,
|
101
|
-
...options,
|
102
|
-
refreshSecret: options.refreshSecret || options.jwtSecret
|
103
|
-
};
|
104
|
-
|
105
|
-
if (!options.jwtSecret) {
|
106
|
-
throw new Error('JWT secret is required for authentication');
|
107
|
-
}
|
108
|
-
|
109
|
-
// Set default password verification if not provided
|
110
|
-
if (!this.options.verifyPassword) {
|
111
|
-
this.options.verifyPassword = this.verifyPasswordWithBcrypt;
|
112
|
-
}
|
113
|
-
}
|
114
|
-
|
115
|
-
/**
|
116
|
-
* Hash a password using bcrypt
|
117
|
-
*/
|
118
|
-
async hashPassword(password: string): Promise<string> {
|
119
|
-
return await bcrypt.hash(password, this.options.saltRounds || 10);
|
120
|
-
}
|
121
|
-
|
122
|
-
/**
|
123
|
-
* Verify a password against a hash using bcrypt
|
124
|
-
*/
|
125
|
-
private async verifyPasswordWithBcrypt(password: string, hashedPassword: string): Promise<boolean> {
|
126
|
-
return await bcrypt.compare(password, hashedPassword);
|
127
|
-
}
|
128
|
-
|
129
|
-
/**
|
130
|
-
* Generate a cryptographically secure random token
|
131
|
-
*/
|
132
|
-
generateSecureToken(length = 32): string {
|
133
|
-
return crypto.randomBytes(length).toString('hex');
|
134
|
-
}
|
135
|
-
|
136
|
-
/**
|
137
|
-
* Generate token pair (access token + refresh token)
|
138
|
-
*/
|
139
|
-
generateTokenPair(payload: any): TokenPair {
|
140
|
-
// Calculate expiration times
|
141
|
-
const expiresIn = this.getExpirationSeconds(this.options.tokenExpiration || '15m');
|
142
|
-
|
143
|
-
// Generate the access token
|
144
|
-
const accessToken = jwt.sign(
|
145
|
-
{ ...payload, type: 'access' },
|
146
|
-
this.options.jwtSecret,
|
147
|
-
{ expiresIn: this.options.tokenExpiration }
|
148
|
-
);
|
149
|
-
|
150
|
-
// Generate the refresh token
|
151
|
-
const refreshToken = jwt.sign(
|
152
|
-
{ id: payload.id, type: 'refresh' },
|
153
|
-
this.options.refreshSecret!,
|
154
|
-
{ expiresIn: this.options.refreshExpiration }
|
155
|
-
);
|
156
|
-
|
157
|
-
return {
|
158
|
-
accessToken,
|
159
|
-
refreshToken,
|
160
|
-
expiresIn
|
161
|
-
};
|
162
|
-
}
|
163
|
-
|
164
|
-
/**
|
165
|
-
* Convert JWT expiration time to seconds
|
166
|
-
*/
|
167
|
-
private getExpirationSeconds(expiration: string): number {
|
168
|
-
const unit = expiration.charAt(expiration.length - 1);
|
169
|
-
const value = parseInt(expiration.slice(0, -1));
|
170
|
-
|
171
|
-
switch (unit) {
|
172
|
-
case 's': return value;
|
173
|
-
case 'm': return value * 60;
|
174
|
-
case 'h': return value * 60 * 60;
|
175
|
-
case 'd': return value * 60 * 60 * 24;
|
176
|
-
default: return 3600; // Default 1 hour
|
177
|
-
}
|
178
|
-
}
|
179
|
-
|
180
|
-
/**
|
181
|
-
* Verify a JWT token
|
182
|
-
*/
|
183
|
-
verifyToken(token: string, type: 'access' | 'refresh' = 'access'): any {
|
184
|
-
try {
|
185
|
-
const secret = type === 'access' ? this.options.jwtSecret : this.options.refreshSecret;
|
186
|
-
const decoded = jwt.verify(token, secret!);
|
187
|
-
|
188
|
-
// Verify token type matches expected type
|
189
|
-
if (typeof decoded === 'object' && decoded.type !== type) {
|
190
|
-
throw new Error('Invalid token type');
|
191
|
-
}
|
192
|
-
|
193
|
-
return decoded;
|
194
|
-
} catch (error) {
|
195
|
-
throw new Error('Invalid or expired token');
|
196
|
-
}
|
197
|
-
}
|
198
|
-
|
199
|
-
/**
|
200
|
-
* Set authentication cookies
|
201
|
-
*/
|
202
|
-
setAuthCookies(res: Response, tokens: TokenPair): void {
|
203
|
-
// Set access token cookie
|
204
|
-
res.cookie('accessToken', tokens.accessToken, {
|
205
|
-
httpOnly: this.options.httpOnlyCookies,
|
206
|
-
secure: this.options.secureCookies,
|
207
|
-
domain: this.options.cookieDomain,
|
208
|
-
sameSite: 'strict',
|
209
|
-
maxAge: tokens.expiresIn * 1000
|
210
|
-
});
|
211
|
-
|
212
|
-
// Set refresh token cookie with longer expiration
|
213
|
-
const refreshExpiresIn = this.getExpirationSeconds(this.options.refreshExpiration || '7d');
|
214
|
-
res.cookie('refreshToken', tokens.refreshToken, {
|
215
|
-
httpOnly: true, // Always HTTP only for refresh tokens
|
216
|
-
secure: this.options.secureCookies,
|
217
|
-
domain: this.options.cookieDomain,
|
218
|
-
sameSite: 'strict',
|
219
|
-
maxAge: refreshExpiresIn * 1000,
|
220
|
-
path: '/api/auth/refresh' // Restrict to refresh endpoint
|
221
|
-
});
|
222
|
-
}
|
223
|
-
|
224
|
-
/**
|
225
|
-
* Clear authentication cookies
|
226
|
-
*/
|
227
|
-
clearAuthCookies(res: Response): void {
|
228
|
-
res.clearCookie('accessToken');
|
229
|
-
res.clearCookie('refreshToken', { path: '/api/auth/refresh' });
|
230
|
-
}
|
231
|
-
|
232
|
-
/**
|
233
|
-
* Check and handle rate limiting
|
234
|
-
*/
|
235
|
-
private checkRateLimit(ip: string): boolean {
|
236
|
-
if (!this.options.rateLimit) {
|
237
|
-
return true;
|
238
|
-
}
|
239
|
-
|
240
|
-
const now = Date.now();
|
241
|
-
const attempt = this.loginAttempts.get(ip);
|
242
|
-
|
243
|
-
if (!attempt) {
|
244
|
-
this.loginAttempts.set(ip, { count: 1, resetTime: now + 3600000 });
|
245
|
-
return true;
|
246
|
-
}
|
247
|
-
|
248
|
-
// Reset if time expired
|
249
|
-
if (now > attempt.resetTime) {
|
250
|
-
this.loginAttempts.set(ip, { count: 1, resetTime: now + 3600000 });
|
251
|
-
return true;
|
252
|
-
}
|
253
|
-
|
254
|
-
// Check attempts
|
255
|
-
if (attempt.count >= 5) {
|
256
|
-
return false;
|
257
|
-
}
|
258
|
-
|
259
|
-
// Increment counter
|
260
|
-
attempt.count++;
|
261
|
-
this.loginAttempts.set(ip, attempt);
|
262
|
-
return true;
|
263
|
-
}
|
264
|
-
|
265
|
-
/**
|
266
|
-
* Login middleware to authenticate users
|
267
|
-
*/
|
268
|
-
login = async (req: Request, res: Response): Promise<void> => {
|
269
|
-
try {
|
270
|
-
const { username, password } = req.body;
|
271
|
-
const ip = req.ip || req.connection.remoteAddress || '';
|
272
|
-
|
273
|
-
// Check rate limiting
|
274
|
-
if (!this.checkRateLimit(ip)) {
|
275
|
-
res.status(429).json({
|
276
|
-
success: false,
|
277
|
-
message: 'Too many login attempts. Please try again later.'
|
278
|
-
});
|
279
|
-
return;
|
280
|
-
}
|
281
|
-
|
282
|
-
if (!username || !password) {
|
283
|
-
res.status(400).json({
|
284
|
-
success: false,
|
285
|
-
message: 'Username and password are required'
|
286
|
-
});
|
287
|
-
return;
|
288
|
-
}
|
289
|
-
|
290
|
-
// Use custom find user function if provided
|
291
|
-
if (!this.options.findUser) {
|
292
|
-
res.status(500).json({
|
293
|
-
success: false,
|
294
|
-
message: 'User finder function not configured'
|
295
|
-
});
|
296
|
-
return;
|
297
|
-
}
|
298
|
-
|
299
|
-
const user = await this.options.findUser(username);
|
300
|
-
|
301
|
-
if (!user) {
|
302
|
-
res.status(401).json({
|
303
|
-
success: false,
|
304
|
-
message: 'Invalid credentials'
|
305
|
-
});
|
306
|
-
return;
|
307
|
-
}
|
308
|
-
|
309
|
-
// Verify password
|
310
|
-
const isPasswordValid = await this.options.verifyPassword!(
|
311
|
-
password,
|
312
|
-
user.password
|
313
|
-
);
|
314
|
-
|
315
|
-
if (!isPasswordValid) {
|
316
|
-
res.status(401).json({
|
317
|
-
success: false,
|
318
|
-
message: 'Invalid credentials'
|
319
|
-
});
|
320
|
-
return;
|
321
|
-
}
|
322
|
-
|
323
|
-
// Create a sanitized user object (without password)
|
324
|
-
const userWithoutPassword = { ...user };
|
325
|
-
delete userWithoutPassword.password;
|
326
|
-
|
327
|
-
// Generate tokens
|
328
|
-
const tokenPair = this.generateTokenPair({
|
329
|
-
id: user.id || user._id,
|
330
|
-
username: user.username,
|
331
|
-
role: user.role || 'user'
|
332
|
-
});
|
333
|
-
|
334
|
-
// Save refresh token if the function is provided
|
335
|
-
if (this.options.saveRefreshToken) {
|
336
|
-
const refreshExpiry = new Date();
|
337
|
-
refreshExpiry.setSeconds(refreshExpiry.getSeconds() +
|
338
|
-
this.getExpirationSeconds(this.options.refreshExpiration || '7d'));
|
339
|
-
|
340
|
-
await this.options.saveRefreshToken(
|
341
|
-
user.id || user._id,
|
342
|
-
tokenPair.refreshToken,
|
343
|
-
refreshExpiry
|
344
|
-
);
|
345
|
-
}
|
346
|
-
|
347
|
-
// Set authentication cookies if using cookie-based auth
|
348
|
-
if (req.body.useCookies) {
|
349
|
-
this.setAuthCookies(res, tokenPair);
|
350
|
-
}
|
351
|
-
|
352
|
-
res.json({
|
353
|
-
success: true,
|
354
|
-
message: 'Authentication successful',
|
355
|
-
tokens: tokenPair,
|
356
|
-
user: userWithoutPassword
|
357
|
-
});
|
358
|
-
} catch (error) {
|
359
|
-
console.error('Authentication error:', error);
|
360
|
-
res.status(500).json({
|
361
|
-
success: false,
|
362
|
-
message: 'Authentication failed',
|
363
|
-
error: process.env.NODE_ENV !== 'production' ? (error as Error).message : undefined
|
364
|
-
});
|
365
|
-
}
|
366
|
-
};
|
367
|
-
|
368
|
-
/**
|
369
|
-
* Refresh token handler
|
370
|
-
*/
|
371
|
-
refreshToken = async (req: Request, res: Response): Promise<void> => {
|
372
|
-
try {
|
373
|
-
// Get refresh token from cookies or request body
|
374
|
-
const refreshToken = req.cookies?.refreshToken || req.body.refreshToken;
|
375
|
-
|
376
|
-
if (!refreshToken) {
|
377
|
-
res.status(401).json({
|
378
|
-
success: false,
|
379
|
-
message: 'Refresh token required'
|
380
|
-
});
|
381
|
-
return;
|
382
|
-
}
|
383
|
-
|
384
|
-
// Verify the refresh token
|
385
|
-
const decoded = this.verifyToken(refreshToken, 'refresh');
|
386
|
-
|
387
|
-
// Check if token is still valid in database if verification function provided
|
388
|
-
if (this.options.verifyRefreshToken) {
|
389
|
-
const isValid = await this.options.verifyRefreshToken(decoded.id, refreshToken);
|
390
|
-
if (!isValid) {
|
391
|
-
this.clearAuthCookies(res);
|
392
|
-
res.status(401).json({
|
393
|
-
success: false,
|
394
|
-
message: 'Invalid refresh token'
|
395
|
-
});
|
396
|
-
return;
|
397
|
-
}
|
398
|
-
}
|
399
|
-
|
400
|
-
// Generate new token pair
|
401
|
-
const tokenPair = this.generateTokenPair({
|
402
|
-
id: decoded.id,
|
403
|
-
// We need to fetch the user data again for complete payload
|
404
|
-
...(this.options.findUser ? await this.options.findUser(decoded.id) : {})
|
405
|
-
});
|
406
|
-
|
407
|
-
// Update refresh token in database if save function provided
|
408
|
-
if (this.options.saveRefreshToken) {
|
409
|
-
const refreshExpiry = new Date();
|
410
|
-
refreshExpiry.setSeconds(refreshExpiry.getSeconds() +
|
411
|
-
this.getExpirationSeconds(this.options.refreshExpiration || '7d'));
|
412
|
-
|
413
|
-
await this.options.saveRefreshToken(
|
414
|
-
decoded.id,
|
415
|
-
tokenPair.refreshToken,
|
416
|
-
refreshExpiry
|
417
|
-
);
|
418
|
-
}
|
419
|
-
|
420
|
-
// Set cookies if cookie-based auth is used
|
421
|
-
const useCookies = req.cookies?.accessToken || req.body.useCookies;
|
422
|
-
if (useCookies) {
|
423
|
-
this.setAuthCookies(res, tokenPair);
|
424
|
-
}
|
425
|
-
|
426
|
-
res.json({
|
427
|
-
success: true,
|
428
|
-
message: 'Token refreshed successfully',
|
429
|
-
tokens: tokenPair
|
430
|
-
});
|
431
|
-
} catch (error) {
|
432
|
-
this.clearAuthCookies(res);
|
433
|
-
res.status(401).json({
|
434
|
-
success: false,
|
435
|
-
message: 'Invalid or expired refresh token',
|
436
|
-
error: process.env.NODE_ENV !== 'production' ? (error as Error).message : undefined
|
437
|
-
});
|
438
|
-
}
|
439
|
-
};
|
440
|
-
|
441
|
-
/**
|
442
|
-
* Logout handler
|
443
|
-
*/
|
444
|
-
logout = async (req: Request, res: Response): Promise<void> => {
|
445
|
-
try {
|
446
|
-
// Clear cookies
|
447
|
-
this.clearAuthCookies(res);
|
448
|
-
|
449
|
-
// Invalidate refresh token if verification function provided
|
450
|
-
const refreshToken = req.cookies?.refreshToken || req.body.refreshToken;
|
451
|
-
if (refreshToken && this.options.saveRefreshToken) {
|
452
|
-
try {
|
453
|
-
const decoded = this.verifyToken(refreshToken, 'refresh');
|
454
|
-
|
455
|
-
// Check if invalidateToken function exists, otherwise we just remove it from storage
|
456
|
-
if (typeof this.options.saveRefreshToken === 'function') {
|
457
|
-
// We pass null as the token to indicate deletion/invalidation
|
458
|
-
await this.options.saveRefreshToken(decoded.id, '', new Date());
|
459
|
-
}
|
460
|
-
} catch (e) {
|
461
|
-
// Token already invalid, nothing to do
|
462
|
-
}
|
463
|
-
}
|
464
|
-
|
465
|
-
res.json({ success: true, message: 'Logged out successfully' });
|
466
|
-
} catch (error) {
|
467
|
-
console.error('Logout error:', error);
|
468
|
-
res.status(500).json({ success: false, message: 'Logout failed', error: (error as Error).message });
|
469
|
-
}
|
470
|
-
};
|
471
|
-
|
472
|
-
/**
|
473
|
-
* Middleware to verify user is authenticated
|
474
|
-
*/
|
475
|
-
authenticate = (req: Request, res: Response, next: NextFunction): void => {
|
476
|
-
try {
|
477
|
-
// Get token from Authorization header or cookies
|
478
|
-
let token = req.cookies?.accessToken;
|
479
|
-
|
480
|
-
if (!token) {
|
481
|
-
const authHeader = req.headers.authorization;
|
482
|
-
if (authHeader && authHeader.startsWith('Bearer ')) {
|
483
|
-
token = authHeader.split(' ')[1];
|
484
|
-
}
|
485
|
-
}
|
486
|
-
|
487
|
-
if (!token) {
|
488
|
-
res.status(401).json({
|
489
|
-
success: false,
|
490
|
-
message: 'Authentication required'
|
491
|
-
});
|
492
|
-
return;
|
493
|
-
}
|
494
|
-
|
495
|
-
const decoded = this.verifyToken(token, 'access');
|
496
|
-
|
497
|
-
// Add user info to request
|
498
|
-
(req as any).user = decoded;
|
499
|
-
|
500
|
-
next();
|
501
|
-
} catch (error) {
|
502
|
-
res.status(401).json({
|
503
|
-
success: false,
|
504
|
-
message: 'Invalid or expired token',
|
505
|
-
error: process.env.NODE_ENV !== 'production' ? (error as Error).message : undefined
|
506
|
-
});
|
507
|
-
}
|
508
|
-
};
|
509
|
-
|
510
|
-
/**
|
511
|
-
* Middleware to check if user has required role
|
512
|
-
*/
|
513
|
-
hasRole = (role: string | string[]) => {
|
514
|
-
return (req: Request, res: Response, next: NextFunction): void => {
|
515
|
-
const user = (req as any).user;
|
516
|
-
|
517
|
-
if (!user) {
|
518
|
-
res.status(401).json({
|
519
|
-
success: false,
|
520
|
-
message: 'Authentication required'
|
521
|
-
});
|
522
|
-
return;
|
523
|
-
}
|
524
|
-
|
525
|
-
const roles = Array.isArray(role) ? role : [role];
|
526
|
-
|
527
|
-
if (roles.includes(user.role)) {
|
528
|
-
next();
|
529
|
-
} else {
|
530
|
-
res.status(403).json({
|
531
|
-
success: false,
|
532
|
-
message: 'Insufficient permissions'
|
533
|
-
});
|
534
|
-
}
|
535
|
-
};
|
536
|
-
};
|
537
|
-
}
|
538
|
-
|
539
|
-
/**
|
540
|
-
* Create an authentication service
|
541
|
-
*/
|
542
|
-
export function createAuth(options: AuthOptions): Auth {
|
543
|
-
return new Auth(options);
|
544
|
-
}
|
package/src/backend/database.ts
DELETED
@@ -1,104 +0,0 @@
|
|
1
|
-
import mongoose from 'mongoose';
|
2
|
-
import { DatabaseOptions } from './types';
|
3
|
-
|
4
|
-
/**
|
5
|
-
* Database connector for MongoDB using Mongoose
|
6
|
-
*/
|
7
|
-
export class DatabaseConnector {
|
8
|
-
private options: DatabaseOptions;
|
9
|
-
private connection: mongoose.Connection | null = null;
|
10
|
-
private _connected = false; // Renamed from isConnected to _connected
|
11
|
-
|
12
|
-
constructor(options: DatabaseOptions) {
|
13
|
-
this.options = {
|
14
|
-
retryAttempts: 3,
|
15
|
-
retryDelay: 1000,
|
16
|
-
connectionTimeout: 10000,
|
17
|
-
autoIndex: true,
|
18
|
-
...options
|
19
|
-
};
|
20
|
-
}
|
21
|
-
|
22
|
-
/**
|
23
|
-
* Connect to the database
|
24
|
-
*/
|
25
|
-
async connect(): Promise<mongoose.Connection> {
|
26
|
-
try {
|
27
|
-
if (this._connected && this.connection) {
|
28
|
-
return this.connection;
|
29
|
-
}
|
30
|
-
|
31
|
-
// Set Mongoose options
|
32
|
-
mongoose.set('strictQuery', true);
|
33
|
-
|
34
|
-
// Connect with retry logic
|
35
|
-
let attempts = 0;
|
36
|
-
while (attempts < (this.options.retryAttempts || 3)) {
|
37
|
-
try {
|
38
|
-
await mongoose.connect(this.options.uri, {
|
39
|
-
dbName: this.options.name,
|
40
|
-
connectTimeoutMS: this.options.connectionTimeout,
|
41
|
-
autoIndex: this.options.autoIndex,
|
42
|
-
...this.options.options
|
43
|
-
});
|
44
|
-
break;
|
45
|
-
} catch (error) {
|
46
|
-
attempts++;
|
47
|
-
if (attempts >= (this.options.retryAttempts || 3)) {
|
48
|
-
throw error;
|
49
|
-
}
|
50
|
-
console.log(`Connection attempt ${attempts} failed. Retrying in ${this.options.retryDelay}ms...`);
|
51
|
-
await new Promise(resolve => setTimeout(resolve, this.options.retryDelay));
|
52
|
-
}
|
53
|
-
}
|
54
|
-
|
55
|
-
this.connection = mongoose.connection;
|
56
|
-
this._connected = true;
|
57
|
-
|
58
|
-
// Log successful connection
|
59
|
-
console.log(`Connected to MongoDB at ${this.options.uri}/${this.options.name}`);
|
60
|
-
|
61
|
-
// Handle connection events
|
62
|
-
this.connection.on('error', (err) => {
|
63
|
-
console.error('MongoDB connection error:', err);
|
64
|
-
this._connected = false;
|
65
|
-
});
|
66
|
-
|
67
|
-
this.connection.on('disconnected', () => {
|
68
|
-
console.log('MongoDB disconnected');
|
69
|
-
this._connected = false;
|
70
|
-
});
|
71
|
-
|
72
|
-
return this.connection;
|
73
|
-
} catch (error) {
|
74
|
-
console.error('Failed to connect to MongoDB:', error);
|
75
|
-
throw error;
|
76
|
-
}
|
77
|
-
}
|
78
|
-
|
79
|
-
/**
|
80
|
-
* Disconnect from the database
|
81
|
-
*/
|
82
|
-
async disconnect(): Promise<void> {
|
83
|
-
if (this.connection) {
|
84
|
-
await mongoose.disconnect();
|
85
|
-
this._connected = false;
|
86
|
-
this.connection = null;
|
87
|
-
console.log('Disconnected from MongoDB');
|
88
|
-
}
|
89
|
-
}
|
90
|
-
|
91
|
-
/**
|
92
|
-
* Check if connected to the database
|
93
|
-
*/
|
94
|
-
isConnected(): boolean {
|
95
|
-
return this._connected;
|
96
|
-
}
|
97
|
-
|
98
|
-
/**
|
99
|
-
* Get the mongoose connection
|
100
|
-
*/
|
101
|
-
getConnection(): mongoose.Connection | null {
|
102
|
-
return this.connection;
|
103
|
-
}
|
104
|
-
}
|