2024-03-09 23:43:54 +00:00

237 lines
7.3 KiB
JavaScript

import jwt from "jsonwebtoken"
import { Session, RegenerationToken, User } from "../../db_models"
export default class Token {
static get strategy() {
return {
secret: process.env.JWT_SECRET,
expiresIn: process.env.JWT_EXPIRES_IN ?? "24h",
algorithm: process.env.JWT_ALGORITHM ?? "HS256",
}
}
static async createNewAuthToken(payload, options = {}) {
if (options.updateSession) {
const sessionData = await Session.findOne({ _id: options.updateSession })
payload.session_uuid = sessionData.session_uuid
} else {
payload.session_uuid = global.nanoid()
}
const { secret, expiresIn, algorithm } = Token.strategy
const token = jwt.sign(
{
session_uuid: payload.session_uuid,
username: payload.username,
user_id: payload.user_id,
signLocation: payload.signLocation,
},
secret,
{
expiresIn: expiresIn,
algorithm: algorithm
}
)
return token
}
static async validate(token) {
let result = {
expired: false,
valid: true,
error: null,
data: null,
}
if (typeof token === "undefined") {
result.valid = false
result.error = "Missing token"
return result
}
const { secret } = Token.strategy
await jwt.verify(token, secret, async (err, decoded) => {
if (err) {
result.valid = false
result.error = err.message
if (err.message === "jwt expired") {
result.expired = true
}
return
}
result.data = decoded
const sessions = await Session.find({ user_id: decoded.user_id })
const currentSession = sessions.find((session) => session.token === token)
if (!currentSession) {
result.valid = false
result.error = "Session token not found"
} else {
result.session = currentSession
result.valid = true
result.user = async () => await User.findOne({ _id: decoded.user_id })
}
})
return result
}
static async regenerate(expiredToken, refreshToken, aggregateData = {}) {
// search for a regeneration token with the expired token (Should exist only one)
const regenerationToken = await RegenerationToken.findOne({ refreshToken: refreshToken })
if (!regenerationToken) {
throw new Error("Cannot find regeneration token")
}
// check if the regeneration token is valid and not expired
let decodedRefreshToken = null
let decodedExpiredToken = null
try {
decodedRefreshToken = jwt.decode(refreshToken)
decodedExpiredToken = jwt.decode(expiredToken)
} catch (error) {
console.error(error)
// TODO: Storage this incident
}
if (!decodedRefreshToken) {
throw new Error("Cannot decode refresh token")
}
if (!decodedExpiredToken) {
throw new Error("Cannot decode expired token")
}
// is not needed to verify the expired token, because it suppossed to be expired
// verify refresh token
await jwt.verify(refreshToken, global.jwtStrategy.secretOrKey, async (err) => {
// check if is expired
if (err) {
if (err.message === "jwt expired") {
// check if server has enabled the enforcement of regeneration token expiration
if (global.jwtStrategy.enforceRegenerationTokenExpiration) {
// delete the regeneration token
await RegenerationToken.deleteOne({ refreshToken: refreshToken })
throw new Error("Regeneration token expired and cannot be regenerated due server has enabled enforcement security policy")
}
}
}
// check if the regeneration token is associated with the expired token
if (decodedRefreshToken.expiredToken !== expiredToken) {
throw new Error("Regeneration token is not associated with the expired token")
}
})
// find the session associated with the expired token
const session = await Session.findOne({ token: expiredToken })
if (!session) {
throw new Error("Cannot find session associated with the expired token")
}
// generate a new token
const newToken = await this.createNewAuthToken({
username: decodedExpiredToken.username,
session_uuid: session.session_uuid,
user_id: decodedExpiredToken.user_id,
ip_address: aggregateData.ip_address,
}, {
updateSession: session._id,
})
// delete the regeneration token
await RegenerationToken.deleteOne({ refreshToken: refreshToken })
return newToken
}
static async createAuth(payload, options = {}) {
const token = await this.createNewAuthToken(payload, options)
const session = {
token: token,
session_uuid: payload.session_uuid,
username: payload.username,
user_id: payload.user_id,
location: payload.signLocation,
ip_address: payload.ip_address,
client: payload.client,
date: new Date().getTime(),
}
if (options.updateSession) {
await Session.findByIdAndUpdate(options.updateSession, session)
} else {
let newSession = new Session(session)
await newSession.save()
}
return token
}
static async createRegenerative(expiredToken) {
// check if token is only expired, if is corrupted, reject
let decoded = null
try {
decoded = jwt.decode(expiredToken)
} catch (error) {
console.error(error)
}
if (!decoded) {
return false
}
// check if token exists on a session
const sessions = await Session.find({ user_id: decoded.user_id })
const currentSession = sessions.find((session) => session.token === expiredToken)
if (!currentSession) {
throw new Error("This token is not associated with any session")
}
// create a new refresh token and sign it with maximum expiration time of 1 day
const refreshToken = jwt.sign(
{
expiredToken
},
global.jwtStrategy.secretOrKey,
{
expiresIn: "1d"
}
)
// create a new regeneration token and save it
const regenerationToken = new RegenerationToken({
expiredToken,
refreshToken,
})
await regenerationToken.save()
// return the regeneration token
return regenerationToken
}
static async getRegenerationToken(expiredToken) {
const regenerationToken = await RegenerationToken.findOne({ expiredToken })
return regenerationToken
}
}