diff --git a/libs/common/oapi/services/auth.js b/libs/common/oapi/services/auth.js index 5a5e337..4765be6 100644 --- a/libs/common/oapi/services/auth.js +++ b/libs/common/oapi/services/auth.js @@ -1,9 +1,10 @@ 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 (services) { +module.exports = function () { const castIntVars = ["deviceId", "userId"] function sessionVarsFromClaims(claims) { const session = { ...claims } @@ -26,7 +27,7 @@ module.exports = function (services) { } return async function auth(jwt, scopes) { - const hasMetaAuthToken = scopes.includes("meta.auth-token") + const hasMetaExpUser = scopes.includes("meta.exp-user") let jwtVerified = false try { @@ -41,32 +42,35 @@ module.exports = function (services) { } catch (err) { const logger = ctx.require("logger") - // Allow expired JWT only if meta.auth-token scope is present - if (hasMetaAuthToken && err.code === "ERR_JWT_EXPIRED") { + // 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.auth-token scope" + "Allowing expired JWT for meta.exp-user scope" ) - const req = reqCtx.get("req") - const authTokenJWT = req?.headers?.["x-auth-token"] - if (!authTokenJWT) { - return false - } - const authToken = - services.authTokenHandler.decodeAuthToken(authTokenJWT) - // Create a session that indicates auth token processing is needed - const session = { isAuthTokenRequest: true, authToken } - reqCtx.set("session", session) - return true + // Continue processing with expired JWT + } else { + logger.error({ error: err }, "jwVerify failed") + return false } - logger.error({ error: err }, "jwVerify failed") - return false } - // Regular user JWT processing 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 } diff --git a/services/api/src/api/v1/operations/geoloc/sync.post.js b/services/api/src/api/v1/operations/geoloc/sync.post.js index eeb1128..6e3e1e7 100644 --- a/services/api/src/api/v1/operations/geoloc/sync.post.js +++ b/services/api/src/api/v1/operations/geoloc/sync.post.js @@ -5,7 +5,7 @@ const { reqCtx } = require("@modjo/express/ctx") const tasks = require("~/tasks") -module.exports = function ({ services: { authTokenHandler } }) { +module.exports = function () { const { addTask } = ctx.require("amqp") const redis = ctx.require("redisHotGeodata") @@ -23,72 +23,41 @@ module.exports = function ({ services: { authTokenHandler } }) { longitude, }, } = location + // console.log("addOneGeolocSync", req.body) const session = reqCtx.get("session") - let userId - let deviceId - let userBearerJwt = null - // Check if this is an auth token request (set by auth.js) - if (session && session.isAuthTokenRequest) { - // This is an auth token request, process it - try { - logger.debug("Processing auth token for geoloc sync") + const { deviceId } = session - const { authToken } = session - const { - userId: newUserId, - deviceId: newDeviceId, - roles, - } = await authTokenHandler.getOrCreateUserSession( - authToken, - req.body.phoneModel, - req.body.deviceUuid - ) + // Check JWT expiration sequence to prevent replay attacks + if (session.exp) { + const deviceExpKey = `device:${deviceId}:last_exp` + const storedLastExp = await redis.get(deviceExpKey) - userId = newUserId - deviceId = newDeviceId - - // Generate new user JWT for token refresh - userBearerJwt = await authTokenHandler.generateUserJwt( - userId, - deviceId, - roles - ) - - logger.debug({ - action: "geoloc-sync-auth-token", - userId, - deviceId, - tokenRefreshed: true, - }) - } catch (error) { - logger.error({ error: error.message }, "Failed to process auth token") - if (httpError.isHttpError(error)) { - throw error - } - throw httpError(401, "Invalid auth token") + if (storedLastExp && session.exp < parseInt(storedLastExp, 10)) { + throw httpError(401, "not the latest jwt") + } + + // Store the new expiration date + if (session.exp !== storedLastExp) { + await redis.set(deviceExpKey, session.exp) } - } else if (session && session.userId && session.deviceId) { - // Regular user JWT session - userId = session.userId - deviceId = session.deviceId - logger.debug({ action: "geoloc-sync-user-jwt", userId, deviceId }) - } else { - // Invalid session - logger.error({ session }, "Invalid session") - throw httpError(401, "Invalid session") } - if (!userId || !deviceId) { - throw httpError(401, "Missing user or device information") - } + const { userId } = session + + logger.debug({ action: "geoloc-sync", userId, deviceId }) const coordinates = [longitude, latitude] await async.parallel([ async () => { + // const transaction = redis.multi() + // transaction.geoadd("device", longitude, latitude, deviceId) + // transaction.publish("deviceSet", deviceId) + // await transaction.exec() await redis.geoadd("device", longitude, latitude, deviceId) + await addTask(tasks.GEOCODE_MOVE, { deviceId, userId, coordinates }) }, async () => @@ -103,14 +72,7 @@ module.exports = function ({ services: { authTokenHandler } }) { }), ]) - const response = { ok: true } - - // Include userBearerJwt in response if token refresh occurred - if (userBearerJwt) { - response.userBearerJwt = userBearerJwt - } - - return response + return { ok: true } } return [addOneGeolocSync] diff --git a/services/api/src/api/v1/operations/geoloc/sync.post.spec.yaml b/services/api/src/api/v1/operations/geoloc/sync.post.spec.yaml index 51bf4fa..97154a6 100644 --- a/services/api/src/api/v1/operations/geoloc/sync.post.spec.yaml +++ b/services/api/src/api/v1/operations/geoloc/sync.post.spec.yaml @@ -1,13 +1,6 @@ # description: x-security: - - auth: ["user", "meta.auth-token"] -parameters: - - name: X-Auth-Token - in: header - required: false - schema: - type: string - description: Auth token for token refresh when user JWT is expired + - auth: ["user", "meta.exp-user"] requestBody: required: true content: @@ -108,7 +101,3 @@ responses: properties: ok: type: boolean - userBearerJwt: - type: string - description: New user JWT token when auth token refresh occurred - nullable: true