create-nara 1.0.7 → 1.0.8
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.
Potentially problematic release.
This version of create-nara might be problematic. Click here for more details.
package/package.json
CHANGED
|
@@ -4,6 +4,16 @@ import bcrypt from 'bcrypt';
|
|
|
4
4
|
import jwt from 'jsonwebtoken';
|
|
5
5
|
|
|
6
6
|
const JWT_SECRET = process.env.JWT_SECRET || 'your-secret-key';
|
|
7
|
+
const JWT_EXPIRES_IN = process.env.JWT_EXPIRES_IN || '7d';
|
|
8
|
+
|
|
9
|
+
// Cookie options for auth token
|
|
10
|
+
const COOKIE_OPTIONS = {
|
|
11
|
+
httpOnly: true,
|
|
12
|
+
secure: process.env.NODE_ENV === 'production',
|
|
13
|
+
sameSite: 'lax' as const,
|
|
14
|
+
maxAge: 7 * 24 * 60 * 60 * 1000, // 7 days in milliseconds
|
|
15
|
+
path: '/',
|
|
16
|
+
};
|
|
7
17
|
|
|
8
18
|
export class AuthController extends BaseController {
|
|
9
19
|
async login(req: NaraRequest, res: NaraResponse) {
|
|
@@ -14,15 +24,25 @@ export class AuthController extends BaseController {
|
|
|
14
24
|
}
|
|
15
25
|
|
|
16
26
|
// TODO: Replace with your actual user lookup
|
|
17
|
-
// const user = await
|
|
27
|
+
// const user = await UserModel.findByEmail(email);
|
|
18
28
|
// if (!user || !await bcrypt.compare(password, user.password)) {
|
|
19
|
-
//
|
|
29
|
+
// throw new ValidationError({ email: ['Invalid credentials'] });
|
|
20
30
|
// }
|
|
21
31
|
|
|
22
|
-
// Example: Generate JWT token
|
|
23
|
-
const token = jwt.sign(
|
|
32
|
+
// Example: Generate JWT token with user info
|
|
33
|
+
const token = jwt.sign(
|
|
34
|
+
{ userId: 1, email, name: 'Demo User' },
|
|
35
|
+
JWT_SECRET,
|
|
36
|
+
{ expiresIn: JWT_EXPIRES_IN }
|
|
37
|
+
);
|
|
38
|
+
|
|
39
|
+
// Set auth cookie for web routes
|
|
40
|
+
res.cookie('auth_token', token, COOKIE_OPTIONS);
|
|
24
41
|
|
|
25
|
-
return jsonSuccess(res, {
|
|
42
|
+
return jsonSuccess(res, {
|
|
43
|
+
user: { id: 1, email, name: 'Demo User' },
|
|
44
|
+
redirect: '/dashboard'
|
|
45
|
+
}, 'Login successful');
|
|
26
46
|
}
|
|
27
47
|
|
|
28
48
|
async register(req: NaraRequest, res: NaraResponse) {
|
|
@@ -40,15 +60,25 @@ export class AuthController extends BaseController {
|
|
|
40
60
|
const hashedPassword = await bcrypt.hash(password, 10);
|
|
41
61
|
|
|
42
62
|
// TODO: Replace with your actual user creation
|
|
43
|
-
// const user = await
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
63
|
+
// const user = await UserModel.create({ name, email, password: hashedPassword });
|
|
64
|
+
|
|
65
|
+
// Generate JWT token
|
|
66
|
+
const token = jwt.sign(
|
|
67
|
+
{ userId: 1, email, name },
|
|
68
|
+
JWT_SECRET,
|
|
69
|
+
{ expiresIn: JWT_EXPIRES_IN }
|
|
70
|
+
);
|
|
71
|
+
|
|
72
|
+
// Set auth cookie for web routes
|
|
73
|
+
res.cookie('auth_token', token, COOKIE_OPTIONS);
|
|
74
|
+
|
|
75
|
+
return jsonSuccess(res, {
|
|
76
|
+
user: { id: 1, email, name },
|
|
77
|
+
redirect: '/dashboard'
|
|
78
|
+
}, 'Registration successful');
|
|
48
79
|
}
|
|
49
80
|
|
|
50
81
|
async me(req: NaraRequest, res: NaraResponse) {
|
|
51
|
-
// TODO: Get user from JWT token in auth middleware
|
|
52
82
|
const user = req.user;
|
|
53
83
|
|
|
54
84
|
if (!user) {
|
|
@@ -59,7 +89,9 @@ export class AuthController extends BaseController {
|
|
|
59
89
|
}
|
|
60
90
|
|
|
61
91
|
async logout(req: NaraRequest, res: NaraResponse) {
|
|
62
|
-
//
|
|
63
|
-
|
|
92
|
+
// Clear auth cookie
|
|
93
|
+
res.cookie('auth_token', '', { ...COOKIE_OPTIONS, maxAge: 0 });
|
|
94
|
+
|
|
95
|
+
return jsonSuccess(res, { redirect: '/login' }, 'Logged out successfully');
|
|
64
96
|
}
|
|
65
97
|
}
|
|
@@ -3,6 +3,9 @@ import jwt from 'jsonwebtoken';
|
|
|
3
3
|
|
|
4
4
|
const JWT_SECRET = process.env.JWT_SECRET || 'your-secret-key';
|
|
5
5
|
|
|
6
|
+
/**
|
|
7
|
+
* Auth middleware for API routes (Bearer token)
|
|
8
|
+
*/
|
|
6
9
|
export function authMiddleware(req: NaraRequest, res: NaraResponse, next: () => void) {
|
|
7
10
|
const authHeader = req.headers.authorization;
|
|
8
11
|
|
|
@@ -21,3 +24,62 @@ export function authMiddleware(req: NaraRequest, res: NaraResponse, next: () =>
|
|
|
21
24
|
res.status(401).json({ error: 'Invalid token' });
|
|
22
25
|
}
|
|
23
26
|
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Auth middleware for web/Inertia routes (cookie-based)
|
|
30
|
+
* Redirects to login if not authenticated
|
|
31
|
+
*/
|
|
32
|
+
export function webAuthMiddleware(req: NaraRequest, res: NaraResponse, next: () => void) {
|
|
33
|
+
// Check for auth token in cookie
|
|
34
|
+
const token = req.cookies?.auth_token;
|
|
35
|
+
|
|
36
|
+
if (!token) {
|
|
37
|
+
// For Inertia requests, redirect to login
|
|
38
|
+
if (req.headers['x-inertia']) {
|
|
39
|
+
res.status(409).setHeader('X-Inertia-Location', '/login').send('');
|
|
40
|
+
} else {
|
|
41
|
+
res.redirect('/login');
|
|
42
|
+
}
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
try {
|
|
47
|
+
const decoded = jwt.verify(token, JWT_SECRET) as { userId: number; email: string; name: string };
|
|
48
|
+
req.user = { id: decoded.userId, email: decoded.email, name: decoded.name };
|
|
49
|
+
next();
|
|
50
|
+
} catch (error) {
|
|
51
|
+
// Clear invalid token
|
|
52
|
+
res.cookie('auth_token', '', { maxAge: 0 });
|
|
53
|
+
if (req.headers['x-inertia']) {
|
|
54
|
+
res.status(409).setHeader('X-Inertia-Location', '/login').send('');
|
|
55
|
+
} else {
|
|
56
|
+
res.redirect('/login');
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Guest middleware - only allow unauthenticated users
|
|
63
|
+
* Redirects to dashboard if already logged in
|
|
64
|
+
*/
|
|
65
|
+
export function guestMiddleware(req: NaraRequest, res: NaraResponse, next: () => void) {
|
|
66
|
+
const token = req.cookies?.auth_token;
|
|
67
|
+
|
|
68
|
+
if (token) {
|
|
69
|
+
try {
|
|
70
|
+
jwt.verify(token, JWT_SECRET);
|
|
71
|
+
// User is authenticated, redirect to dashboard
|
|
72
|
+
if (req.headers['x-inertia']) {
|
|
73
|
+
res.status(409).setHeader('X-Inertia-Location', '/dashboard').send('');
|
|
74
|
+
} else {
|
|
75
|
+
res.redirect('/dashboard');
|
|
76
|
+
}
|
|
77
|
+
return;
|
|
78
|
+
} catch {
|
|
79
|
+
// Invalid token, clear it and continue
|
|
80
|
+
res.cookie('auth_token', '', { maxAge: 0 });
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
next();
|
|
85
|
+
}
|
|
@@ -1,29 +1,37 @@
|
|
|
1
1
|
import type { NaraApp } from '@nara-web/core';
|
|
2
|
+
import { webAuthMiddleware, guestMiddleware } from '../app/middlewares/auth.js';
|
|
2
3
|
|
|
3
4
|
export function registerRoutes(app: NaraApp) {
|
|
5
|
+
// Public routes
|
|
4
6
|
app.get('/', (req, res) => {
|
|
5
7
|
res.inertia?.('landing', {
|
|
6
8
|
title: 'Welcome to NARA'
|
|
7
9
|
});
|
|
8
10
|
});
|
|
9
11
|
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
});
|
|
13
|
-
|
|
14
|
-
app.get('/login', (req, res) => {
|
|
12
|
+
// Guest only routes (redirect to dashboard if logged in)
|
|
13
|
+
app.get('/login', guestMiddleware as any, (req, res) => {
|
|
15
14
|
res.inertia?.('auth/login');
|
|
16
15
|
});
|
|
17
16
|
|
|
18
|
-
app.get('/register', (req, res) => {
|
|
17
|
+
app.get('/register', guestMiddleware as any, (req, res) => {
|
|
19
18
|
res.inertia?.('auth/register');
|
|
20
19
|
});
|
|
21
20
|
|
|
22
|
-
app.get('/
|
|
21
|
+
app.get('/forgot-password', guestMiddleware as any, (req, res) => {
|
|
22
|
+
res.inertia?.('auth/forgot-password');
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
// Protected routes (redirect to login if not authenticated)
|
|
26
|
+
app.get('/dashboard', webAuthMiddleware as any, (req, res) => {
|
|
27
|
+
res.inertia?.('dashboard');
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
app.get('/users', webAuthMiddleware as any, (req, res) => {
|
|
23
31
|
res.inertia?.('users');
|
|
24
32
|
});
|
|
25
33
|
|
|
26
|
-
app.get('/profile', (req, res) => {
|
|
34
|
+
app.get('/profile', webAuthMiddleware as any, (req, res) => {
|
|
27
35
|
res.inertia?.('profile');
|
|
28
36
|
});
|
|
29
37
|
}
|