diff --git a/api/server/routes/admin/auth.js b/api/server/routes/admin/auth.js index 6c56e04938..b8a69f8a67 100644 --- a/api/server/routes/admin/auth.js +++ b/api/server/routes/admin/auth.js @@ -71,6 +71,19 @@ function resolveRequestOrigin(req) { } } +async function isEmailAllowedForUser(user) { + if (!user?.email) return false; + try { + const appConfig = user.tenantId + ? await resolveAppConfigForUser(getAppConfig, user) + : await getAppConfig({ role: user.role ?? '' }); + return isEmailDomainAllowed(user.email, appConfig?.registration?.allowedDomains); + } catch (err) { + logger.warn(`[admin/oauth/refresh] domain allowlist check failed, denying: ${err?.message}`); + return false; + } +} + function buildAdminRefreshClosures(sessionExpiry) { return { canAccessAdmin: async (user) => { @@ -88,6 +101,7 @@ function buildAdminRefreshClosures(sessionExpiry) { return false; } }, + isEmailAllowed: isEmailAllowedForUser, mintToken: async (user) => ({ token: await generateToken(user, sessionExpiry), expiresAt: Date.now() + sessionExpiry, @@ -100,20 +114,6 @@ function buildGoogleAdminRefreshDeps(sessionExpiry) { findUsers, getUserById, ...buildAdminRefreshClosures(sessionExpiry), - isEmailAllowed: async (user) => { - if (!user?.email) return false; - try { - const appConfig = user.tenantId - ? await resolveAppConfigForUser(getAppConfig, user) - : await getAppConfig({ baseOnly: true }); - return isEmailDomainAllowed(user.email, appConfig?.registration?.allowedDomains); - } catch (err) { - logger.warn( - `[admin/oauth/refresh] domain allowlist check failed, denying: ${err?.message}`, - ); - return false; - } - }, }; } @@ -564,6 +564,7 @@ router.post('/oauth/exchange', middleware.loginLimiter, async (req, res) => { router.post( '/oauth/refresh', middleware.loginLimiter, + middleware.checkBan, preAuthTenantMiddleware, async (req, res) => { try { diff --git a/packages/api/src/auth/refresh.ts b/packages/api/src/auth/refresh.ts index 2ae5ca0f0a..ba35c15f4c 100644 --- a/packages/api/src/auth/refresh.ts +++ b/packages/api/src/auth/refresh.ts @@ -65,6 +65,13 @@ export interface AdminRefreshDeps { * bearers should always inject this. */ canAccessAdmin?: (user: IUser) => Promise; + /** + * Re-runs the deployment's `registration.allowedDomains` check against the + * resolved user's email. Returns true to allow refresh, false to reject. + * Mirrors the domain check the initial OAuth callback enforces so a domain + * removed from the allowlist after issuance can't refresh. + */ + isEmailAllowed?: (user: IUser) => Promise; /** * Optional post-success hook for forks that need to do additional work * with the refreshed tokenset and resolved user (e.g. update a server-side @@ -275,6 +282,14 @@ export async function applyAdminRefresh( throw new AdminRefreshError('USER_NOT_FOUND', 401, 'No user found for the refreshed identity'); } + if (deps.isEmailAllowed && !(await deps.isEmailAllowed(user))) { + throw new AdminRefreshError( + 'FORBIDDEN', + 403, + 'User email domain is not on the deployment allowlist', + ); + } + if (deps.canAccessAdmin && !(await deps.canAccessAdmin(user))) { throw new AdminRefreshError('FORBIDDEN', 403, 'User does not have admin access'); }