mirror of
https://github.com/yu-i-i/overleaf-cep.git
synced 2026-05-23 17:19:37 +02:00
Merge pull request #29966 from overleaf/acf-migration2-security-middleware
(2) Create Express authentication middleware (replaces swagger-security) GitOrigin-RevId: 0a15ec7ca601430a1ae00c7f81da456948650a0c
This commit is contained in:
committed by
Copybot
parent
83db1e858f
commit
c1dc70fc92
144
services/history-v1/api/middleware/security.js
Normal file
144
services/history-v1/api/middleware/security.js
Normal file
@@ -0,0 +1,144 @@
|
||||
'use strict'
|
||||
|
||||
const basicAuth = require('basic-auth')
|
||||
const config = require('config')
|
||||
const HTTPStatus = require('http-status')
|
||||
const jwt = require('jsonwebtoken')
|
||||
const tsscmp = require('tsscmp')
|
||||
const { validateReq } = require('@overleaf/validation-tools')
|
||||
const schemas = require('../schema')
|
||||
|
||||
function hasValidBasicAuthCredentials(req) {
|
||||
const credentials = basicAuth(req)
|
||||
if (!credentials) return false
|
||||
|
||||
// No security in the name, so just use straight comparison.
|
||||
if (credentials.name !== 'staging') return false
|
||||
|
||||
const password = config.get('basicHttpAuth.password')
|
||||
if (password && tsscmp(credentials.pass, password)) return true
|
||||
|
||||
// Support an old password so we can change the password without downtime.
|
||||
if (config.has('basicHttpAuth.oldPassword')) {
|
||||
const oldPassword = config.get('basicHttpAuth.oldPassword')
|
||||
if (oldPassword && tsscmp(credentials.pass, oldPassword)) return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
function setupSSL(app) {
|
||||
const httpsOnly = config.get('httpsOnly') === 'true'
|
||||
if (!httpsOnly) {
|
||||
return
|
||||
}
|
||||
app.enable('trust proxy')
|
||||
app.use(function (req, res, next) {
|
||||
if (req.protocol === 'https') {
|
||||
next()
|
||||
return
|
||||
}
|
||||
if (req.method === 'GET' || req.method === 'HEAD') {
|
||||
res.redirect('https://' + req.headers.host + req.url)
|
||||
} else {
|
||||
res
|
||||
.status(HTTPStatus.FORBIDDEN)
|
||||
.send('Please use HTTPS when submitting data to this server.')
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
exports.setupSSL = setupSSL
|
||||
|
||||
function handleJWTAuth(req, res, next) {
|
||||
if (hasValidBasicAuthCredentials(req)) {
|
||||
return next()
|
||||
}
|
||||
|
||||
let token
|
||||
if (req.query.token) {
|
||||
token = req.query.token
|
||||
} else if (
|
||||
req.headers.authorization &&
|
||||
req.headers.authorization.split(' ')[0] === 'Bearer'
|
||||
) {
|
||||
token = req.headers.authorization.split(' ')[1]
|
||||
}
|
||||
|
||||
if (!token) {
|
||||
const err = new Error('jwt missing')
|
||||
err.statusCode = HTTPStatus.UNAUTHORIZED
|
||||
err.headers = { 'WWW-Authenticate': 'Bearer' }
|
||||
return next(err)
|
||||
}
|
||||
|
||||
let decoded
|
||||
try {
|
||||
decoded = decodeJWT(token)
|
||||
} catch (error) {
|
||||
if (
|
||||
error instanceof jwt.JsonWebTokenError ||
|
||||
error instanceof jwt.TokenExpiredError
|
||||
) {
|
||||
const err = new Error(error.message)
|
||||
err.statusCode = HTTPStatus.UNAUTHORIZED
|
||||
err.headers = { 'WWW-Authenticate': 'Bearer error="invalid_token"' }
|
||||
return next(err)
|
||||
}
|
||||
throw error
|
||||
}
|
||||
|
||||
const { params } = validateReq(req, schemas.projectId)
|
||||
if (decoded.project_id.toString() !== params.project_id.toString()) {
|
||||
const err = new Error('Wrong project_id')
|
||||
err.statusCode = HTTPStatus.FORBIDDEN
|
||||
return next(err)
|
||||
}
|
||||
|
||||
req.jwt = decoded
|
||||
next()
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify and decode the given JSON Web Token
|
||||
*/
|
||||
function decodeJWT(token) {
|
||||
const key = config.get('jwtAuth.key')
|
||||
const algorithm = config.get('jwtAuth.algorithm')
|
||||
try {
|
||||
return jwt.verify(token, key, { algorithms: [algorithm] })
|
||||
} catch (err) {
|
||||
// Support an old key so we can change the key without downtime.
|
||||
if (config.has('jwtAuth.oldKey')) {
|
||||
const oldKey = config.get('jwtAuth.oldKey')
|
||||
return jwt.verify(token, oldKey, { algorithms: [algorithm] })
|
||||
} else {
|
||||
throw err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function handleBasicAuth(req, res, next) {
|
||||
if (hasValidBasicAuthCredentials(req)) {
|
||||
return next()
|
||||
}
|
||||
const error = new Error()
|
||||
error.statusCode = HTTPStatus.UNAUTHORIZED
|
||||
error.headers = { 'WWW-Authenticate': 'Basic realm="Application"' }
|
||||
return next(error)
|
||||
}
|
||||
|
||||
function getAuthHandlers() {
|
||||
if (!config.has('jwtAuth.key') || !config.has('basicHttpAuth.password')) {
|
||||
throw new Error('missing authentication env vars')
|
||||
}
|
||||
|
||||
const handlers = {}
|
||||
handlers.jwt = handleJWTAuth
|
||||
handlers.basic = handleBasicAuth
|
||||
handlers.token = handleJWTAuth
|
||||
return handlers
|
||||
}
|
||||
|
||||
exports.hasValidBasicAuthCredentials = hasValidBasicAuthCredentials
|
||||
exports.getAuthHandlers = getAuthHandlers
|
||||
@@ -42,6 +42,13 @@ const changeSchema = z.object({
|
||||
})
|
||||
|
||||
const schemas = {
|
||||
projectId: z.object({
|
||||
params: z
|
||||
.object({
|
||||
project_id: z.string().optional(),
|
||||
})
|
||||
.optional(),
|
||||
}),
|
||||
initializeProject: z.object({
|
||||
body: z
|
||||
.object({
|
||||
|
||||
Reference in New Issue
Block a user