as-services/libs/common/oapi/services/auth.js
devthejo a13b07ec9d
All checks were successful
/ build (map[dockerfile:./services/watchers/Dockerfile name:watchers]) (push) Successful in 3m13s
/ build (map[dockerfile:./services/tasks/Dockerfile name:tasks]) (push) Successful in 3m21s
/ build (map[dockerfile:./services/hasura/Dockerfile name:hasura]) (push) Successful in 3m12s
/ build (map[dockerfile:./services/app/Dockerfile name:app]) (push) Successful in 3m2s
/ deploy (push) Successful in 21s
/ build (map[dockerfile:./services/api/Dockerfile name:api]) (push) Successful in 2m32s
/ build (map[dockerfile:./services/web/Dockerfile name:web]) (push) Successful in 3m0s
/ build (map[dockerfile:./services/files/Dockerfile name:files]) (push) Successful in 3m23s
chore: debug
2025-07-02 12:58:37 +02:00

116 lines
3.3 KiB
JavaScript

const { jwtVerify } = require("jose")
const jwtDecode = require("jwt-decode")
const getHasuraClaimsFromJWT = require("@modjo/hasura/utils/jwt/get-hasura-claims-from-jwt")
const { ctx } = require("@modjo/core")
const { reqCtx } = require("@modjo/express/ctx")
module.exports = function () {
const castIntVars = ["deviceId", "userId"]
function sessionVarsFromClaims(claims) {
const session = { ...claims }
for (const castIntVar of castIntVars) {
session[castIntVar] = parseInt(session[castIntVar], 10)
}
return session
}
const config = ctx.require("config.project")
const { claimsNamespace, JWKSet } = config
function isScopeAllowed(session, scopes) {
const { allowedRoles } = session
return scopes
.filter((scope) => !scope.startsWith("meta."))
.some((scope) => {
return allowedRoles.includes(scope)
})
}
return async function auth(jwt, scopes) {
const hasMetaExpUser = scopes.includes("meta.exp-user")
let jwtVerified = false
const logger = ctx.require("logger")
logger.debug({ scopes, hasMetaExpUser }, "Starting authentication")
try {
if (!jwt) {
logger.warn("No JWT provided for authentication")
return false
}
logger.debug("JWT provided, attempting verification")
jwtVerified = await jwtVerify(jwt, JWKSet)
if (!jwtVerified) {
logger.warn("JWT verification failed")
return false
}
logger.debug("JWT verification successful")
} catch (err) {
// Allow expired JWT only if meta.exp-user scope is present
if (hasMetaExpUser && err.code === "ERR_JWT_EXPIRED") {
logger.debug(
{ error: err },
"Allowing expired JWT for meta.exp-user scope"
)
// Continue processing with expired JWT
} else {
logger.error({ error: err }, "JWT verification failed")
return false
}
}
logger.debug("Extracting claims from JWT")
const claims = getHasuraClaimsFromJWT(jwt, claimsNamespace)
const session = sessionVarsFromClaims(claims)
logger.debug(
{ userId: session.userId, deviceId: session.deviceId },
"Session variables extracted from claims"
)
// Add exp claim to session if meta.exp-user scope is present
if (hasMetaExpUser) {
logger.debug("Adding exp claim for meta.exp-user scope")
try {
const payload = jwtDecode(jwt)
if (payload && payload.exp) {
session.exp = payload.exp
logger.debug({ exp: session.exp }, "Exp claim added to session")
} else {
logger.debug("No exp claim found in JWT payload")
}
} catch (err) {
logger.error({ error: err }, "Failed to decode JWT for exp claim")
}
}
logger.debug(
{ allowedRoles: session.allowedRoles, requestedScopes: scopes },
"Checking scope authorization"
)
if (!isScopeAllowed(session, scopes)) {
logger.warn(
{ allowedRoles: session.allowedRoles, requestedScopes: scopes },
"Scope authorization failed"
)
return false
}
logger.info("Authentication successful")
logger.debug(
{
userId: session.userId,
deviceId: session.deviceId,
allowedRoles: session.allowedRoles,
},
"Setting session context"
)
reqCtx.set("session", session)
return true
}
}