add login with sessian and cleanup
All checks were successful
Build and Push Docker Images / build (push) Successful in 2m34s
All checks were successful
Build and Push Docker Images / build (push) Successful in 2m34s
This commit is contained in:
117
server/src/api/v1/auth/index.ts
Normal file
117
server/src/api/v1/auth/index.ts
Normal file
@@ -0,0 +1,117 @@
|
||||
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 { email, username, password } = 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,
|
||||
refresh_token: '',
|
||||
})
|
||||
}
|
||||
|
||||
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: 15 * 60 * 1000,
|
||||
})
|
||||
res.cookie('refresh_token', refreshToken, {
|
||||
httpOnly: true, sameSite: 'lax', secure: process.env.NODE_ENV !== 'dev', maxAge: 7 * 24 * 3600 * 1000,
|
||||
})
|
||||
|
||||
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.refresh_token
|
||||
if (!token) return res.status(401).json({ error: 'No refresh token' })
|
||||
try {
|
||||
const payload = jwt.verify(token, REFRESH_TOKEN_SECRET)
|
||||
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()
|
||||
|
||||
res.cookie('access_token', newAccessToken, {
|
||||
httpOnly: true, sameSite: 'lax', secure: process.env.NODE_ENV !== 'dev', maxAge: 15 * 60 * 1000,
|
||||
})
|
||||
res.cookie('refresh_token', newRefreshToken, {
|
||||
httpOnly: true, sameSite: 'lax', secure: process.env.NODE_ENV !== 'dev', maxAge: 7 * 24 * 3600 * 1000,
|
||||
})
|
||||
res.json({ ok: true })
|
||||
} catch (error) {
|
||||
res.status(401).json({ error: 'Invalid refresh token' })
|
||||
}
|
||||
})
|
||||
|
||||
router.post('/logout', async (req: Request, res: Response) => {
|
||||
const token = req.cookies.refresh_token
|
||||
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('refresh_token')
|
||||
res.json({ loggedOut: true })
|
||||
})
|
||||
|
||||
export default router as Router
|
||||
31
server/src/api/v1/auth/ldap.ts
Normal file
31
server/src/api/v1/auth/ldap.ts
Normal file
@@ -0,0 +1,31 @@
|
||||
|
||||
import { Client } from 'ldapts'
|
||||
|
||||
const LDAP_URL = 'ldap://192.168.0.26:389';
|
||||
const BASE_DN = 'DC=ldap,DC=goauthentik,DC=io';
|
||||
|
||||
async function ldapAuth(userOptions: any) {
|
||||
const { username, password } = userOptions;
|
||||
if (!username || !password) return { auth: false }
|
||||
|
||||
const client = new Client({ url: LDAP_URL });
|
||||
|
||||
try {
|
||||
const userDN = `cn=${username},ou=users,${BASE_DN}`;
|
||||
await client.bind(userDN, password);
|
||||
|
||||
const { searchEntries } = await client.search(BASE_DN, {
|
||||
scope: 'sub',
|
||||
filter: `(cn=${username})`,
|
||||
attributes: ['cn', 'mail'],
|
||||
});
|
||||
|
||||
return { auth: true, user: searchEntries[0] }
|
||||
} catch (err) {
|
||||
return { auth: false }
|
||||
} finally {
|
||||
await client.unbind().catch(() => { })
|
||||
}
|
||||
}
|
||||
|
||||
export default ldapAuth
|
||||
@@ -1,12 +1,15 @@
|
||||
import express, { type Router, type Request, type Response } from 'express'
|
||||
import express, { type Router, type Response } from 'express'
|
||||
import { ApiKeyModel } from '../../../models/apikey.model.ts'
|
||||
import { verifyAccessToken, type AuthRequest } from '../../../middleware/auth.ts'
|
||||
|
||||
const router = express.Router()
|
||||
|
||||
router.use(verifyAccessToken)
|
||||
|
||||
router.get('/', async (_req: Request, res: Response) => {
|
||||
router.get('/', verifyAccessToken, async (req: AuthRequest, res: Response) => {
|
||||
try {
|
||||
const doc = await ApiKeyModel.findOne({})
|
||||
const userId = req.userId
|
||||
const doc = await ApiKeyModel.findOne({ userId: userId })
|
||||
|
||||
const apiKey = doc?.apiKey || ''
|
||||
|
||||
@@ -17,17 +20,19 @@ router.get('/', async (_req: Request, res: Response) => {
|
||||
}
|
||||
})
|
||||
|
||||
router.post('/', async (req: Request, res: Response) => {
|
||||
router.post('/', verifyAccessToken, async (req: AuthRequest, res: Response) => {
|
||||
try {
|
||||
const { apiKey } = req.body as { apiKey?: string }
|
||||
const { apiKey } = req.body
|
||||
if (!apiKey || !apiKey.trim()) {
|
||||
return res.status(400).json({ error: 'Invalid API key' })
|
||||
}
|
||||
console.log(req.body)
|
||||
const userId = req.userId
|
||||
|
||||
await ApiKeyModel.updateOne(
|
||||
{},
|
||||
{ $set: { apiKey: apiKey } },
|
||||
{ upsert: true }
|
||||
{ $set: { userId: userId, apiKey: apiKey, lastUsed: new Date() } },
|
||||
{ upsert: true },
|
||||
)
|
||||
|
||||
res.json({ success: true })
|
||||
@@ -37,9 +42,10 @@ router.post('/', async (req: Request, res: Response) => {
|
||||
}
|
||||
})
|
||||
|
||||
router.delete('/', async (_req: Request, res: Response) => {
|
||||
router.delete('/', verifyAccessToken, async (req: AuthRequest, res: Response) => {
|
||||
try {
|
||||
const result = await ApiKeyModel.deleteOne({})
|
||||
const userId = req.userId
|
||||
const result = await ApiKeyModel.deleteOne({ userId: userId })
|
||||
|
||||
if (result.deletedCount === 0) {
|
||||
console.log('No API key found to delete.')
|
||||
|
||||
@@ -7,7 +7,6 @@ const router = express.Router()
|
||||
router.get('/', async (_req: Request, res: Response) => {
|
||||
try {
|
||||
const doc = await KanjiModel.find()
|
||||
console.log(doc)
|
||||
res.json(doc)
|
||||
} catch (error) {
|
||||
console.error('Error fetching Kanji Subjects', error)
|
||||
|
||||
22
server/src/api/v1/user/index.ts
Normal file
22
server/src/api/v1/user/index.ts
Normal file
@@ -0,0 +1,22 @@
|
||||
import express, { type Router } from 'express'
|
||||
|
||||
import { UserModel } from '../../../models/user.model.ts'
|
||||
import { verifyAccessToken, type AuthRequest } from '../../../middleware/auth.ts'
|
||||
|
||||
const router = express.Router()
|
||||
|
||||
router.get('/info', verifyAccessToken, async (req: AuthRequest, res) => {
|
||||
try {
|
||||
if (!req.userId) return res.status(401).json({ ok: false, message: 'Unauthorized' })
|
||||
|
||||
const user = await UserModel.findById(req.userId).select('-refreshToken -__v -createdAt -updatedAt')
|
||||
if (!user) return res.status(404).json({ ok: false, message: 'User not found' })
|
||||
|
||||
return res.json({ ok: true, user })
|
||||
} catch (err) {
|
||||
console.error(err)
|
||||
return res.status(500).json({ ok: false, message: 'Server error' })
|
||||
}
|
||||
})
|
||||
|
||||
export default router as Router
|
||||
@@ -1,26 +1,22 @@
|
||||
import express, { Router } from 'express'
|
||||
import express, { type Router, type Response } from 'express'
|
||||
|
||||
import { ApiKeyModel } from '../../../models/apikey.model.ts'
|
||||
|
||||
import { syncWanikaniData } from '../../../services/wanikaniService.ts'
|
||||
import { verifyAccessToken, type AuthRequest } from '../../../middleware/auth.ts'
|
||||
|
||||
const router = express.Router()
|
||||
|
||||
interface ApiKeyDocument {
|
||||
apiKey?: string;
|
||||
}
|
||||
|
||||
router.get('/sync', async (req, res) => {
|
||||
router.get('/sync', verifyAccessToken, async (req: AuthRequest, res: Response) => {
|
||||
if (!req.userId) return res.status(401).json({ error: 'Unauthorized' })
|
||||
try {
|
||||
const apiKeyDoc = await ApiKeyModel.findOne() as ApiKeyDocument | null
|
||||
const apiKeyDoc = await ApiKeyModel.findOne({ userId: req.userId })
|
||||
|
||||
const apiKey = apiKeyDoc?.apiKey
|
||||
|
||||
console.log(apiKey, apiKeyDoc)
|
||||
if (!apiKey || apiKey.trim() === '') {
|
||||
return res.status(401).json({ error: 'API Key not configured. Please sync your key first.' })
|
||||
}
|
||||
await syncWanikaniData(apiKey)
|
||||
await syncWanikaniData(apiKey, req.userId)
|
||||
res.json({ success: true })
|
||||
} catch (err) {
|
||||
console.error(err)
|
||||
|
||||
Reference in New Issue
Block a user