67 lines
1.8 KiB
TypeScript
67 lines
1.8 KiB
TypeScript
import crypto from 'crypto'
|
|
import { NextFunction, Request, Response } from 'express'
|
|
|
|
import { config } from '../config'
|
|
|
|
const isValidToken = (token: string, apiKey: string): boolean => {
|
|
if (token.length !== apiKey.length) {
|
|
return false
|
|
}
|
|
const tokenBuf = Buffer.from(token)
|
|
const keyBuf = Buffer.from(apiKey)
|
|
return crypto.timingSafeEqual(tokenBuf, keyBuf)
|
|
}
|
|
|
|
export const authMiddleware = async (req: Request, res: Response, next: NextFunction) => {
|
|
const auth = req.header('authorization') || ''
|
|
const xApiKey = req.header('x-api-key') || ''
|
|
|
|
// Fast rejection if neither credential header provided
|
|
if (!auth && !xApiKey) {
|
|
return res.status(401).json({ error: 'Unauthorized: missing credentials' })
|
|
}
|
|
|
|
const { apiKey } = await config.get()
|
|
|
|
if (!apiKey) {
|
|
return res.status(403).json({ error: 'Forbidden' })
|
|
}
|
|
|
|
// Check API key first (priority)
|
|
if (xApiKey) {
|
|
const trimmedApiKey = xApiKey.trim()
|
|
if (!trimmedApiKey) {
|
|
return res.status(401).json({ error: 'Unauthorized: empty x-api-key' })
|
|
}
|
|
|
|
if (isValidToken(trimmedApiKey, apiKey)) {
|
|
return next()
|
|
} else {
|
|
return res.status(403).json({ error: 'Forbidden' })
|
|
}
|
|
}
|
|
|
|
// Fallback to Bearer token
|
|
if (auth) {
|
|
const trimmed = auth.trim()
|
|
const bearerPrefix = /^Bearer\s+/i
|
|
|
|
if (!bearerPrefix.test(trimmed)) {
|
|
return res.status(401).json({ error: 'Unauthorized: invalid authorization format' })
|
|
}
|
|
|
|
const token = trimmed.replace(bearerPrefix, '').trim()
|
|
if (!token) {
|
|
return res.status(401).json({ error: 'Unauthorized: empty bearer token' })
|
|
}
|
|
|
|
if (isValidToken(token, apiKey)) {
|
|
return next()
|
|
} else {
|
|
return res.status(403).json({ error: 'Forbidden' })
|
|
}
|
|
}
|
|
|
|
return res.status(401).json({ error: 'Unauthorized: invalid credentials format' })
|
|
}
|