You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
201 lines
4.7 KiB
201 lines
4.7 KiB
import { Response } from 'express'; |
|
import { AuthRequest } from '../types/index.js'; |
|
import { db } from '../config/database.js'; |
|
import { createTokens, refreshAccessToken, revokeRefreshToken, verifyPassword } from '../services/auth.service.js'; |
|
import { env } from '../config/env.js'; |
|
import jwt from 'jsonwebtoken'; |
|
|
|
export async function login(req: AuthRequest, res: Response) { |
|
try { |
|
const { username, password } = req.body; |
|
|
|
// Find user |
|
const result = await db.execute({ |
|
sql: 'SELECT * FROM users WHERE username = ?', |
|
args: [username] |
|
}); |
|
|
|
if (!result.rows.length) { |
|
return res.status(401).json({ |
|
success: false, |
|
error: { |
|
code: 'INVALID_CREDENTIALS', |
|
message: 'Invalid username or password' |
|
} |
|
}); |
|
} |
|
|
|
const user = result.rows[0]; |
|
|
|
// Verify password |
|
const valid = await verifyPassword(password, user.password_hash as string); |
|
|
|
if (!valid) { |
|
return res.status(401).json({ |
|
success: false, |
|
error: { |
|
code: 'INVALID_CREDENTIALS', |
|
message: 'Invalid username or password' |
|
} |
|
}); |
|
} |
|
|
|
// Update last login |
|
await db.execute({ |
|
sql: 'UPDATE users SET last_login = ? WHERE id = ?', |
|
args: [new Date().toISOString(), user.id] |
|
}); |
|
|
|
// Create tokens |
|
const { accessToken, refreshToken } = await createTokens( |
|
user.id as number, |
|
user.username as string |
|
); |
|
|
|
// Set refresh token as httpOnly cookie |
|
res.cookie('refresh_token', refreshToken, { |
|
httpOnly: true, |
|
secure: env.nodeEnv === 'production', |
|
sameSite: 'strict', |
|
maxAge: 7 * 24 * 60 * 60 * 1000 // 7 days |
|
}); |
|
|
|
res.json({ |
|
success: true, |
|
data: { |
|
user: { |
|
id: user.id, |
|
username: user.username |
|
}, |
|
accessToken, |
|
refreshToken |
|
} |
|
}); |
|
} catch (error: any) { |
|
console.error('Login error:', error); |
|
res.status(500).json({ |
|
success: false, |
|
error: { |
|
code: 'LOGIN_ERROR', |
|
message: 'An error occurred during login' |
|
} |
|
}); |
|
} |
|
} |
|
|
|
export async function refresh(req: AuthRequest, res: Response) { |
|
try { |
|
const refreshToken = req.cookies.refresh_token || req.body.refreshToken; |
|
|
|
if (!refreshToken) { |
|
return res.status(401).json({ |
|
success: false, |
|
error: { |
|
code: 'NO_REFRESH_TOKEN', |
|
message: 'Refresh token not provided' |
|
} |
|
}); |
|
} |
|
|
|
const { accessToken } = await refreshAccessToken(refreshToken); |
|
|
|
res.json({ |
|
success: true, |
|
data: { accessToken } |
|
}); |
|
} catch (error: any) { |
|
res.status(401).json({ |
|
success: false, |
|
error: { |
|
code: 'REFRESH_ERROR', |
|
message: error.message || 'Failed to refresh token' |
|
} |
|
}); |
|
} |
|
} |
|
|
|
export async function logout(req: AuthRequest, res: Response) { |
|
try { |
|
const refreshToken = req.cookies.refresh_token; |
|
|
|
if (refreshToken) { |
|
try { |
|
const decoded = jwt.verify(refreshToken, env.jwtRefreshSecret) as { token: string }; |
|
await revokeRefreshToken(decoded.token); |
|
} catch (error) { |
|
// Token might be invalid, but we still clear the cookie |
|
console.error('Error revoking token:', error); |
|
} |
|
} |
|
|
|
// Clear cookie |
|
res.clearCookie('refresh_token', { |
|
httpOnly: true, |
|
secure: env.nodeEnv === 'production', |
|
sameSite: 'strict', |
|
path: '/' |
|
}); |
|
|
|
res.json({ success: true }); |
|
} catch (error: any) { |
|
console.error('Logout error:', error); |
|
res.status(500).json({ |
|
success: false, |
|
error: { |
|
code: 'LOGOUT_ERROR', |
|
message: 'Error during logout' |
|
} |
|
}); |
|
} |
|
} |
|
|
|
export async function getCurrentUser(req: AuthRequest, res: Response) { |
|
try { |
|
if (!req.userId) { |
|
return res.status(401).json({ |
|
success: false, |
|
error: { |
|
code: 'UNAUTHORIZED', |
|
message: 'User not authenticated' |
|
} |
|
}); |
|
} |
|
|
|
const result = await db.execute({ |
|
sql: 'SELECT id, username, last_login FROM users WHERE id = ?', |
|
args: [req.userId] |
|
}); |
|
|
|
if (!result.rows.length) { |
|
return res.status(404).json({ |
|
success: false, |
|
error: { |
|
code: 'USER_NOT_FOUND', |
|
message: 'User not found' |
|
} |
|
}); |
|
} |
|
|
|
const user = result.rows[0]; |
|
|
|
res.json({ |
|
success: true, |
|
data: { |
|
id: user.id, |
|
username: user.username, |
|
lastLogin: user.last_login |
|
} |
|
}); |
|
} catch (error: any) { |
|
console.error('Get user error:', error); |
|
res.status(500).json({ |
|
success: false, |
|
error: { |
|
code: 'GET_USER_ERROR', |
|
message: 'Error fetching user data' |
|
} |
|
}); |
|
} |
|
} |
|
|
|
|
|
|