/**
## User Auth
This module handles user authentication and authorization.
@module /user/auth
*/
const jwt = require('jsonwebtoken')
const acl = require('./acl')
const fromACL = require('./fromACL')
const user_sessions = {}
/**
* Authenticates the user based on the provided token or authorization header.
* @function auth
* @async
* @param {Object} req - The request object.
* @param {string} [req.headers.authorization] - The authorization header.
* @param {string} [req.params.token] - The token provided as a query parameter.
* @param {Object} [req.cookies] - The cookies object.
* @param {string} [req.cookies[process.env.TITLE]] - The cookie containing the token.
* @param {Object} res - The response object.
* @returns {Promise<Object|Error>} A Promise that resolves with the user object or an Error if authentication fails.
*/
module.exports = async (req, res) => {
if (req.headers.authorization) {
return await fromACL(req)
}
// Get token from params or cookie.
const token = req.params.token || req.cookies && req.cookies[process.env.TITLE]
// Return if there is no token to decode
if (!token) return null
// Verify the token signature.
return jwt.verify(
token,
process.env.SECRET,
async (err, user) => {
// Return error if verification fails.
if (err) return err
// user sessions are enabled in the env.
if (process.env.USER_SESSION && user.session) {
// The session token is stored in the user_session object.
if (Object.hasOwn(user_sessions, user.email)) {
// The stored session doesn't match the token user session.
if (user_sessions[user.email] !== user.session) {
// Delete the user_session
delete user_sessions[user.email]
}
}
if (!Object.hasOwn(user_sessions, user.email)) {
// Get session from the ACL.
let rows = await acl(`
SELECT session
FROM acl_schema.acl_table
WHERE lower(email) = lower($1);`,
[user.email])
if (rows instanceof Error) return rows
// The session stored in the ACL doesn't match the token user session.
if (user.session !== rows[0].session) return new Error('Session ID mismatch')
// Store the user.session in the user_sessions object.
user_sessions[user.email] = user.session
}
}
// The token was provided as param.
if (req.params.token) {
// and is an api key.
if (user.api) {
// Retrieve the original api key for the user from ACL.
let rows = await acl(`
SELECT api, blocked
FROM acl_schema.acl_table
WHERE lower(email) = lower($1);`, [user.email])
if (rows instanceof Error) return rows
if (rows.blocked) return new Error('Account is blocked')
// API key do not expire and must therefore match the copy in the ACL to allow for retraction.
if (rows[0].api !== req.params.token) return new Error('API Key mismatch')
}
// Token access must not have admin rights.
delete user.admin
// Flag the user to be created from a token.
// It must not be possible created a new token from a token user.
user.from_token = true
// Check whether the token matches the params token.
if (req.cookies && req.cookies[process.env.TITLE] !== req.params.token) {
// Create and assign a new cookie from the token user.
const cookie = jwt.sign(user, process.env.SECRET)
res.setHeader('Set-Cookie', `${process.env.TITLE}=${cookie};HttpOnly;Max-Age=${user.exp && (user.exp - user.iat) || process.env.COOKIE_TTL};Path=${process.env.DIR || '/'};SameSite=Strict${!req.headers.host.includes('localhost') && ';Secure' || ''}`)
}
}
return user
})
}