SSO token caching and retrieval in CachePool (#4931)

* feat: Implement SSO token caching and retrieval in CachePool

This implementation improves the authentication process by securely caching SSO tokens and managing user sessions.

* Removed commented code

* feat: add deleteSSOTokenCache in ssoSuccess

---------

Co-authored-by: Ong Chung Yau <33013947+chungyau97@users.noreply.github.com>
Co-authored-by: chungyau97 <chungyau97@gmail.com>
This commit is contained in:
Vinod Kiran
2025-07-25 00:44:46 +05:30
committed by GitHub
parent 00342bde88
commit d272683a98
6 changed files with 96 additions and 16 deletions
+41
View File
@@ -9,6 +9,7 @@ export class CachePool {
activeLLMCache: IActiveCache = {} activeLLMCache: IActiveCache = {}
activeEmbeddingCache: IActiveCache = {} activeEmbeddingCache: IActiveCache = {}
activeMCPCache: { [key: string]: any } = {} activeMCPCache: { [key: string]: any } = {}
ssoTokenCache: { [key: string]: any } = {}
constructor() { constructor() {
if (process.env.MODE === MODE.QUEUE) { if (process.env.MODE === MODE.QUEUE) {
@@ -42,6 +43,46 @@ export class CachePool {
} }
} }
/**
* Add to the sso token cache pool
* @param {string} ssoToken
* @param {any} value
*/
async addSSOTokenCache(ssoToken: string, value: any) {
if (process.env.MODE === MODE.QUEUE) {
if (this.redisClient) {
const serializedValue = JSON.stringify(value)
await this.redisClient.set(`ssoTokenCache:${ssoToken}`, serializedValue, 'EX', 120)
}
} else {
this.ssoTokenCache[ssoToken] = value
}
}
async getSSOTokenCache(ssoToken: string): Promise<any | undefined> {
if (process.env.MODE === MODE.QUEUE) {
if (this.redisClient) {
const serializedValue = await this.redisClient.get(`ssoTokenCache:${ssoToken}`)
if (serializedValue) {
return JSON.parse(serializedValue)
}
}
} else {
return this.ssoTokenCache[ssoToken]
}
return undefined
}
async deleteSSOTokenCache(ssoToken: string) {
if (process.env.MODE === MODE.QUEUE) {
if (this.redisClient) {
await this.redisClient.del(`ssoTokenCache:${ssoToken}`)
}
} else {
delete this.ssoTokenCache[ssoToken]
}
}
/** /**
* Add to the llm cache pool * Add to the llm cache pool
* @param {string} chatflowid * @param {string} chatflowid
@@ -10,6 +10,19 @@ const getAllPermissions = async (req: Request, res: Response, next: NextFunction
} }
} }
export default { const ssoSuccess = async (req: Request, res: Response, next: NextFunction) => {
getAllPermissions try {
const appServer = getRunningExpressApp()
const ssoToken = req.query.token as string
const user = await appServer.cachePool.getSSOTokenCache(ssoToken)
if (!user) return res.status(401).json({ message: 'Invalid or expired SSO token' })
await appServer.cachePool.deleteSSOTokenCache(ssoToken)
return res.json(user)
} catch (error) {
next(error)
}
}
export default {
getAllPermissions,
ssoSuccess
} }
@@ -22,6 +22,7 @@ import { WorkspaceUserService } from '../../services/workspace-user.service'
import { decryptToken, encryptToken, generateSafeCopy } from '../../utils/tempTokenUtils' import { decryptToken, encryptToken, generateSafeCopy } from '../../utils/tempTokenUtils'
import { getAuthStrategy } from './AuthStrategy' import { getAuthStrategy } from './AuthStrategy'
import { initializeDBClientAndStore, initializeRedisClientAndStore } from './SessionPersistance' import { initializeDBClientAndStore, initializeRedisClientAndStore } from './SessionPersistance'
import { v4 as uuidv4 } from 'uuid'
const localStrategy = require('passport-local').Strategy const localStrategy = require('passport-local').Strategy
@@ -298,8 +299,14 @@ export const setTokenOrCookies = (
returnUser.isSSO = !isSSO ? false : isSSO returnUser.isSSO = !isSSO ? false : isSSO
if (redirect) { if (redirect) {
// Send user data as part of the redirect URL (using query parameters) // 1. Generate a random token
const dashboardUrl = `/sso-success?user=${encodeURIComponent(JSON.stringify(returnUser))}` const ssoToken = uuidv4()
// 2. Store returnUser in your session store, keyed by ssoToken, with a short expiry
storeSSOUserPayload(ssoToken, returnUser)
// 3. Redirect with token only
const dashboardUrl = `/sso-success?token=${ssoToken}`
// Return the token as a cookie in our response. // Return the token as a cookie in our response.
let resWithCookies = res let resWithCookies = res
.cookie('token', token, { .cookie('token', token, {
@@ -408,3 +415,8 @@ export const verifyToken = (req: Request, res: Response, next: NextFunction) =>
next() next()
})(req, res, next) })(req, res, next)
} }
const storeSSOUserPayload = (ssoToken: string, returnUser: any) => {
const app = getRunningExpressApp()
app.cachePool.addSSOTokenCache(ssoToken, returnUser)
}
@@ -5,4 +5,6 @@ const router = express.Router()
// RBAC // RBAC
router.get(['/', '/permissions'], authController.getAllPermissions) router.get(['/', '/permissions'], authController.getAllPermissions)
router.get(['/sso-success'], authController.ssoSuccess)
export default router export default router
+3 -1
View File
@@ -6,9 +6,11 @@ const login = (body) => client.post(`/auth/login`, body)
// permissions // permissions
const getAllPermissions = () => client.get(`/auth/permissions`) const getAllPermissions = () => client.get(`/auth/permissions`)
const ssoSuccess = (token) => client.get(`/auth/sso-success?token=${token}`)
export default { export default {
resolveLogin, resolveLogin,
login, login,
getAllPermissions getAllPermissions,
ssoSuccess
} }
+21 -11
View File
@@ -2,26 +2,36 @@ import { useEffect } from 'react'
import { useLocation, useNavigate } from 'react-router-dom' import { useLocation, useNavigate } from 'react-router-dom'
import { store } from '@/store' import { store } from '@/store'
import { loginSuccess } from '@/store/reducers/authSlice' import { loginSuccess } from '@/store/reducers/authSlice'
import authApi from '@/api/auth'
const SSOSuccess = () => { const SSOSuccess = () => {
const location = useLocation() const location = useLocation()
const navigate = useNavigate() const navigate = useNavigate()
useEffect(() => { useEffect(() => {
// Parse the "user" query parameter from the URL const run = async () => {
const queryParams = new URLSearchParams(location.search) const queryParams = new URLSearchParams(location.search)
const userData = queryParams.get('user') const token = queryParams.get('token')
if (userData) { if (token) {
// Decode the user data and save it to the state try {
try { const user = await authApi.ssoSuccess(token)
const parsedUser = JSON.parse(decodeURIComponent(userData)) if (user) {
store.dispatch(loginSuccess(parsedUser)) if (user.status === 200) {
navigate('/chatflows') store.dispatch(loginSuccess(user.data))
} catch (error) { navigate('/chatflows')
console.error('Failed to parse user data:', error) } else {
navigate('/login')
}
} else {
navigate('/login')
}
} catch (error) {
navigate('/login')
}
} }
} }
run()
// eslint-disable-next-line react-hooks/exhaustive-deps // eslint-disable-next-line react-hooks/exhaustive-deps
}, [location.search]) }, [location.search])