fix(sync): allow last expired jwt
All checks were successful
/ build (map[dockerfile:./services/hasura/Dockerfile name:hasura]) (push) Successful in 3m2s
/ build (map[dockerfile:./services/tasks/Dockerfile name:tasks]) (push) Successful in 4m31s
/ build (map[dockerfile:./services/files/Dockerfile name:files]) (push) Successful in 4m36s
/ build (map[dockerfile:./services/web/Dockerfile name:web]) (push) Successful in 4m18s
/ build (map[dockerfile:./services/watchers/Dockerfile name:watchers]) (push) Successful in 4m13s
/ build (map[dockerfile:./services/app/Dockerfile name:app]) (push) Successful in 2m22s
/ build (map[dockerfile:./services/api/Dockerfile name:api]) (push) Successful in 4m52s
/ deploy (push) Successful in 23s
All checks were successful
/ build (map[dockerfile:./services/hasura/Dockerfile name:hasura]) (push) Successful in 3m2s
/ build (map[dockerfile:./services/tasks/Dockerfile name:tasks]) (push) Successful in 4m31s
/ build (map[dockerfile:./services/files/Dockerfile name:files]) (push) Successful in 4m36s
/ build (map[dockerfile:./services/web/Dockerfile name:web]) (push) Successful in 4m18s
/ build (map[dockerfile:./services/watchers/Dockerfile name:watchers]) (push) Successful in 4m13s
/ build (map[dockerfile:./services/app/Dockerfile name:app]) (push) Successful in 2m22s
/ build (map[dockerfile:./services/api/Dockerfile name:api]) (push) Successful in 4m52s
/ deploy (push) Successful in 23s
This commit is contained in:
parent
6025bfabff
commit
edee8d6bc4
3 changed files with 55 additions and 5 deletions
|
@ -1,4 +1,5 @@
|
|||
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")
|
||||
|
@ -18,23 +19,58 @@ module.exports = function () {
|
|||
|
||||
function isScopeAllowed(session, scopes) {
|
||||
const { allowedRoles } = session
|
||||
return scopes.some((scope) => allowedRoles.includes(scope))
|
||||
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
|
||||
|
||||
try {
|
||||
if (!jwt || !(await jwtVerify(jwt, JWKSet))) {
|
||||
if (!jwt) {
|
||||
return false
|
||||
}
|
||||
|
||||
jwtVerified = await jwtVerify(jwt, JWKSet)
|
||||
if (!jwtVerified) {
|
||||
return false
|
||||
}
|
||||
} catch (err) {
|
||||
const logger = ctx.require("logger")
|
||||
|
||||
// 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 }, "jwVerify failed")
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
const claims = getHasuraClaimsFromJWT(jwt, claimsNamespace)
|
||||
const session = sessionVarsFromClaims(claims)
|
||||
|
||||
// Add exp claim to session if meta.exp-user scope is present
|
||||
if (hasMetaExpUser) {
|
||||
try {
|
||||
const payload = jwtDecode(jwt)
|
||||
if (payload && payload.exp) {
|
||||
session.exp = payload.exp
|
||||
}
|
||||
} catch (err) {
|
||||
const logger = ctx.require("logger")
|
||||
logger.error({ error: err }, "Failed to decode JWT for exp claim")
|
||||
}
|
||||
}
|
||||
|
||||
if (!isScopeAllowed(session, scopes)) {
|
||||
return false
|
||||
}
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
const async = require("async")
|
||||
const httpError = require("http-errors")
|
||||
const { ctx } = require("@modjo/core")
|
||||
const { reqCtx } = require("@modjo/express/ctx")
|
||||
|
||||
|
@ -28,6 +29,19 @@ module.exports = function () {
|
|||
|
||||
const { deviceId } = session
|
||||
|
||||
// Check JWT expiration sequence to prevent replay attacks
|
||||
if (session.exp) {
|
||||
const deviceExpKey = `device:${deviceId}:last_exp`
|
||||
const storedLastExp = await redis.get(deviceExpKey)
|
||||
|
||||
if (storedLastExp && session.exp <= parseInt(storedLastExp, 10)) {
|
||||
throw httpError(401, "not the latest jwt")
|
||||
}
|
||||
|
||||
// Store the new expiration date
|
||||
await redis.set(deviceExpKey, session.exp, "EX", 30 * 24 * 60 * 60) // 30 days TTL
|
||||
}
|
||||
|
||||
const { userId } = session
|
||||
|
||||
logger.debug({ action: "geoloc-sync", userId, deviceId })
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
# description:
|
||||
x-security:
|
||||
- auth: ["user"]
|
||||
- auth: ["user", "meta.exp-user"]
|
||||
requestBody:
|
||||
required: true
|
||||
content:
|
||||
|
|
Loading…
Add table
Reference in a new issue