121 Zeilen
3.7 KiB
TypeScript
121 Zeilen
3.7 KiB
TypeScript
import { Router, Request, Response, NextFunction } from 'express'
|
|
import bcrypt from 'bcryptjs'
|
|
import jwt from 'jsonwebtoken'
|
|
import { body, validationResult } from 'express-validator'
|
|
import { db } from '../config/secureDatabase'
|
|
import { User, LoginRequest, LoginResponse } from '@skillmate/shared'
|
|
import { FieldEncryption } from '../services/encryption'
|
|
import { logger } from '../utils/logger'
|
|
|
|
const router = Router()
|
|
const JWT_SECRET = process.env.JWT_SECRET || 'your-secret-key-change-in-production'
|
|
|
|
router.post('/login',
|
|
[
|
|
body('username').optional().notEmpty().trim(),
|
|
body('email').optional().isEmail().normalizeEmail(),
|
|
body('password').notEmpty()
|
|
],
|
|
async (req: Request, res: Response, next: NextFunction) => {
|
|
try {
|
|
const errors = validationResult(req)
|
|
if (!errors.isEmpty()) {
|
|
return res.status(400).json({
|
|
success: false,
|
|
error: { message: 'Invalid input', details: errors.array() }
|
|
})
|
|
}
|
|
|
|
const { username, email, password } = req.body
|
|
|
|
// Determine login identifier (email takes precedence)
|
|
const loginIdentifier = email || username
|
|
if (!loginIdentifier) {
|
|
return res.status(400).json({
|
|
success: false,
|
|
error: { message: 'Either username or email is required' }
|
|
})
|
|
}
|
|
|
|
let userRow: any
|
|
|
|
// Try to find by email first (if looks like email), then by username
|
|
if (loginIdentifier.includes('@')) {
|
|
// Login with email
|
|
const emailHash = FieldEncryption.hash(loginIdentifier)
|
|
userRow = db.prepare(`
|
|
SELECT id, username, email, password, role, employee_id, last_login, is_active, created_at, updated_at
|
|
FROM users
|
|
WHERE email_hash = ? AND is_active = 1
|
|
`).get(emailHash) as any
|
|
} else {
|
|
// Login with username
|
|
userRow = db.prepare(`
|
|
SELECT id, username, email, password, role, employee_id, last_login, is_active, created_at, updated_at
|
|
FROM users
|
|
WHERE username = ? AND is_active = 1
|
|
`).get(loginIdentifier) as any
|
|
}
|
|
|
|
if (!userRow) {
|
|
return res.status(401).json({
|
|
success: false,
|
|
error: { message: 'Invalid credentials' }
|
|
})
|
|
}
|
|
|
|
// Check password
|
|
const isValidPassword = await bcrypt.compare(password, userRow.password)
|
|
if (!isValidPassword) {
|
|
return res.status(401).json({
|
|
success: false,
|
|
error: { message: 'Invalid credentials' }
|
|
})
|
|
}
|
|
|
|
// Update last login
|
|
const now = new Date().toISOString()
|
|
db.prepare('UPDATE users SET last_login = ? WHERE id = ?').run(now, userRow.id)
|
|
|
|
// Create user object without password (decrypt email)
|
|
const user: User = {
|
|
id: userRow.id,
|
|
username: userRow.username,
|
|
email: FieldEncryption.decrypt(userRow.email) || '',
|
|
role: userRow.role,
|
|
employeeId: userRow.employee_id,
|
|
lastLogin: new Date(now),
|
|
isActive: Boolean(userRow.is_active),
|
|
createdAt: new Date(userRow.created_at),
|
|
updatedAt: new Date(userRow.updated_at)
|
|
}
|
|
|
|
// Generate token
|
|
const token = jwt.sign(
|
|
{ user },
|
|
JWT_SECRET,
|
|
{ expiresIn: '24h' }
|
|
)
|
|
|
|
const response: LoginResponse = {
|
|
user,
|
|
token: {
|
|
accessToken: token,
|
|
expiresIn: 86400,
|
|
tokenType: 'Bearer'
|
|
}
|
|
}
|
|
|
|
logger.info(`User ${loginIdentifier} logged in successfully`)
|
|
res.json({ success: true, data: response })
|
|
} catch (error) {
|
|
next(error)
|
|
}
|
|
}
|
|
)
|
|
|
|
router.post('/logout', (req, res) => {
|
|
res.json({ success: true, message: 'Logged out successfully' })
|
|
})
|
|
|
|
export default router |