From 8a9115ad2676cc4c842da84753381f94019ab42b Mon Sep 17 00:00:00 2001 From: Max Carlson <> Date: Sun, 15 Mar 2026 17:16:32 +0800 Subject: [PATCH] fix: sync WorkOS role to database on every login Previously, the user's role was only set in the database during initial signup (via invite code) or through WorkOS webhooks. If a user's role was changed in WorkOS and the webhook failed or wasn't configured, the database role would remain stale. This caused role-gated routes like /studio to be inaccessible despite the correct role in WorkOS. Now setWorkOSUserContext() compares the WorkOS organization membership role with the database role on each authenticated request and updates the database if they differ, making role sync self-healing. Co-Authored-By: Claude Opus 4.6 (1M context) --- src/utils/workos.util.ts | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/src/utils/workos.util.ts b/src/utils/workos.util.ts index 9480a048..e2752614 100644 --- a/src/utils/workos.util.ts +++ b/src/utils/workos.util.ts @@ -8,6 +8,8 @@ import httpStatus from "http-status"; import { AUTH_MESSAGES } from "constants/messages/auth.constant"; import type { SessionCookieData, User as WorkOSUser } from "@workos-inc/node"; import { APP_LOGGER } from "shared/logger"; +import { roleRepository, userRepository } from "database/repositories"; +import { RoleType } from "types/role.types"; const IS_DEVELOPMENT = env.NODE_ENV === "development"; @@ -128,6 +130,17 @@ export const setWorkOSUserContext = async ( const workOSRole = organizationMemberships.data[0]?.role.slug; const user = await syncWorkOSUser(workOSUser); + // Sync role from WorkOS if it differs from the database role + if (workOSRole && user.role?.name !== workOSRole) { + const matchedRole = await roleRepository.findOneBy({ + name: workOSRole as RoleType, + }); + if (matchedRole) { + await userRepository.update(user.id, { role: matchedRole }); + user.role = matchedRole; + } + } + res.locals.workosUser = workOSUser; res.locals.userRole = workOSRole; res.locals.user = user;