135 lines
3.7 KiB
TypeScript
135 lines
3.7 KiB
TypeScript
import express, { type Router, type Request, type Response } from 'express'
|
|
import jwt from 'jsonwebtoken'
|
|
|
|
import { UserModel } from '../../../models/user.model.ts'
|
|
|
|
import ldapAuth from './ldap.ts'
|
|
|
|
const router = express.Router()
|
|
|
|
const ACCESS_TOKEN_SECRET = process.env.ACCESS_TOKEN_SECRET!
|
|
const REFRESH_TOKEN_SECRET = process.env.REFRESH_TOKEN_SECRET!
|
|
|
|
function createAccessToken(user: any) {
|
|
return jwt.sign(
|
|
{ sub: user._id, role: user.role },
|
|
ACCESS_TOKEN_SECRET,
|
|
{ expiresIn: '15m' },
|
|
)
|
|
}
|
|
|
|
function createRefreshToken(user: any) {
|
|
return jwt.sign(
|
|
{ sub: user._id },
|
|
REFRESH_TOKEN_SECRET,
|
|
{ expiresIn: '7d' },
|
|
)
|
|
}
|
|
|
|
router.post('/login', async (req: Request, res: Response) => {
|
|
const { username, password, remember } = req.body
|
|
if (!username || !password) return res.status(400).json({ error: 'Missing credentials' })
|
|
|
|
try {
|
|
const ldapUser = await ldapAuth({ username, password })
|
|
|
|
if (!ldapUser.auth) return res.status(401).json({ error: 'Invalid credentials' })
|
|
|
|
let user = await UserModel.findOne({ username: ldapUser.user.cn })
|
|
if (!user) {
|
|
user = await UserModel.create({
|
|
username: ldapUser.user.cn,
|
|
email: ldapUser.user.dn,
|
|
refreshToken: '',
|
|
})
|
|
}
|
|
|
|
const accessToken = createAccessToken(user)
|
|
const refreshToken = createRefreshToken(user)
|
|
|
|
user.refreshToken = refreshToken
|
|
await user.save()
|
|
|
|
res.cookie('access_token', accessToken, {
|
|
httpOnly: true, sameSite: 'lax', secure: process.env.NODE_ENV !== 'dev', maxAge: 7 * 24 * 60 * 60 * 1000,
|
|
})
|
|
const refreshMaxAge = remember > 7 ? 365 * 24 * 60 * 60 * 1000 : 7 * 24 * 60 * 60 * 1000
|
|
|
|
res.cookie('refreshToken', refreshToken, {
|
|
httpOnly: true, sameSite: 'lax', secure: process.env.NODE_ENV !== 'dev', maxAge: refreshMaxAge,
|
|
})
|
|
|
|
res.json({
|
|
ok: true,
|
|
user: {
|
|
username: ldapUser.user.cn,
|
|
email: ldapUser.user.dn
|
|
},
|
|
})
|
|
|
|
} catch (err) {
|
|
console.error(err)
|
|
res.status(401).json({ error: 'Invalid credentials' })
|
|
}
|
|
})
|
|
|
|
router.post('/refresh', async (req: Request, res: Response) => {
|
|
const token = req.cookies.refreshToken
|
|
if (!token) return res.status(401).json({ error: 'No refresh token' })
|
|
|
|
try {
|
|
const payload = jwt.verify(token, REFRESH_TOKEN_SECRET) as any
|
|
const user = await UserModel.findById(payload.sub)
|
|
if (!user || user.refreshToken !== token)
|
|
return res.status(403).json({ error: 'Invalid refresh token' })
|
|
|
|
const newAccessToken = createAccessToken(user)
|
|
const newRefreshToken = createRefreshToken(user)
|
|
|
|
user.refreshToken = newRefreshToken
|
|
await user.save()
|
|
|
|
const existingRefreshCookie = req.cookies.refreshToken
|
|
const decodedOld = jwt.decode(existingRefreshCookie) as any
|
|
const remainingDays = (decodedOld.exp * 1000 - Date.now()) / (1000 * 60 * 60 * 24)
|
|
|
|
const refreshMaxAge = remainingDays > 7 ? 365 * 24 * 60 * 60 * 1000 : 7 * 24 * 60 * 60 * 1000
|
|
|
|
res.cookie('access_token', newAccessToken, {
|
|
httpOnly: true,
|
|
sameSite: 'lax',
|
|
secure: process.env.NODE_ENV !== 'dev',
|
|
maxAge: 15 * 60 * 1000,
|
|
})
|
|
res.cookie('refreshToken', newRefreshToken, {
|
|
httpOnly: true,
|
|
sameSite: 'lax',
|
|
secure: process.env.NODE_ENV !== 'dev',
|
|
maxAge: refreshMaxAge,
|
|
})
|
|
|
|
return res.json({ ok: true })
|
|
} catch (error) {
|
|
return res.status(401).json({ error: 'Invalid refresh token' })
|
|
}
|
|
})
|
|
|
|
router.post('/logout', async (req: Request, res: Response) => {
|
|
const token = req.cookies.refreshToken
|
|
if (token) {
|
|
try {
|
|
const payload = jwt.verify(token, REFRESH_TOKEN_SECRET)
|
|
const user = await UserModel.findById(payload.sub)
|
|
if (user) {
|
|
user.refreshToken = ''
|
|
await user.save()
|
|
}
|
|
} catch { }
|
|
}
|
|
res.clearCookie('access_token')
|
|
res.clearCookie('refreshToken')
|
|
res.json({ loggedOut: true })
|
|
})
|
|
|
|
export default router as Router
|