From 0ab0cc563edc7d106376c4ee0835931b8a01f82d Mon Sep 17 00:00:00 2001 From: Ritul Date: Tue, 31 Mar 2026 16:25:19 +0530 Subject: [PATCH] Redirecting to fp/install and fp/auth on missing token --- Dockerfile | 1 + client/src/utils/fyndSalesChannels.js | 47 +++++++++++- server/index.js | 3 + server/routes/platformPublic.js | 102 ++++++++++++++++++++++++++ 4 files changed, 152 insertions(+), 1 deletion(-) create mode 100644 server/routes/platformPublic.js diff --git a/Dockerfile b/Dockerfile index ef786a4..2cd01ce 100644 --- a/Dockerfile +++ b/Dockerfile @@ -18,6 +18,7 @@ COPY server/index.js ./ COPY server/fdk.js ./ COPY server/postgresFdkStorage.js ./ COPY server/config ./config +# Includes protected and public platform routes such as routes/platformPublic.js. COPY server/routes ./routes COPY server/services ./services COPY --from=client-build /client/dist ./public diff --git a/client/src/utils/fyndSalesChannels.js b/client/src/utils/fyndSalesChannels.js index 969de15..a904c5d 100644 --- a/client/src/utils/fyndSalesChannels.js +++ b/client/src/utils/fyndSalesChannels.js @@ -4,6 +4,8 @@ import { getRuntimeApplicationId, getRuntimeCompanyId } from './runtimeContext'; const FDK_BOOTSTRAP_ATTEMPT_PREFIX = 'sms_fdk_bootstrap_attempt'; const FDK_BOOTSTRAP_TTL_MS = 2 * 60 * 1000; +const FDK_BOOTSTRAP_REDIRECT_CODE = 'FDK_AUTH_BOOTSTRAP_REDIRECT'; +const FDK_SESSION_UNAVAILABLE_CODE = 'FDK_SESSION_UNAVAILABLE'; function getBootstrapStorageKey(companyId) { return `${FDK_BOOTSTRAP_ATTEMPT_PREFIX}:${companyId}`; @@ -63,14 +65,57 @@ function buildFdkInstallUrl(companyId) { function createBootstrapRedirectError() { const error = new Error('Redirecting to FDK auth bootstrap'); - error.code = 'FDK_AUTH_BOOTSTRAP_REDIRECT'; + error.code = FDK_BOOTSTRAP_REDIRECT_CODE; return error; } +function createSessionUnavailableError(message = 'FDK session is unavailable') { + const error = new Error(message); + error.code = FDK_SESSION_UNAVAILABLE_CODE; + return error; +} + +function shouldAttemptBootstrap(sessionStatus = {}) { + return Boolean(sessionStatus?.configured) && Boolean(sessionStatus?.needsBootstrap); +} + +async function getSessionStatus() { + const response = await apiClient.get('/api/platform/session-status'); + return response?.data || {}; +} + +async function ensureFdkPlatformSession(companyId) { + if (!companyId) { + throw createSessionUnavailableError('Missing company id for FDK session bootstrap'); + } + + const sessionStatus = await getSessionStatus(); + if (sessionStatus?.authenticated) { + clearBootstrapAttempt(companyId); + return sessionStatus; + } + + if (shouldAttemptBootstrap(sessionStatus) && shouldBootstrapSession(companyId)) { + const installUrl = buildFdkInstallUrl(companyId); + if (installUrl && typeof window !== 'undefined') { + writeBootstrapAttempt(companyId); + window.location.replace(installUrl); + throw createBootstrapRedirectError(); + } + } + + throw createSessionUnavailableError( + sessionStatus?.reason + ? `FDK session unavailable: ${sessionStatus.reason}` + : 'FDK session is unavailable' + ); +} + export async function fetchActiveSalesChannels() { const companyId = getRuntimeCompanyId(); try { + await ensureFdkPlatformSession(companyId); const response = await apiClient.get('/api/platform/sales-channels'); clearBootstrapAttempt(companyId); return normalizeChannelsPayload( diff --git a/server/index.js b/server/index.js index 62882bf..576b9a8 100644 --- a/server/index.js +++ b/server/index.js @@ -7,6 +7,7 @@ const path = require('path'); const businessesRoutes = require('./routes/businesses'); const platformRoutes = require('./routes/platform'); +const platformPublicRoutes = require('./routes/platformPublic'); const { fdkExtension, isFdkConfigured } = require('./fdk'); const app = express(); @@ -29,9 +30,11 @@ app.get('/api/health', (req, res) => res.json({ })); if (fdkExtension) { + app.use('/api/platform', platformPublicRoutes); app.use(fdkExtension.fdkHandler); app.use('/api/platform', fdkExtension.platformApiRoutes, platformRoutes); } else { + app.use('/api/platform', platformPublicRoutes); app.use('/api/platform', platformRoutes); } diff --git a/server/routes/platformPublic.js b/server/routes/platformPublic.js new file mode 100644 index 0000000..16dc7aa --- /dev/null +++ b/server/routes/platformPublic.js @@ -0,0 +1,102 @@ +const express = require('express'); +const { SESSION_COOKIE_NAME } = require('@gofynd/fdk-extension-javascript/express/constants'); + +const { fdkExtension, isFdkConfigured } = require('../fdk'); + +const router = express.Router(); + +function normalizeText(value) { + return typeof value === 'string' ? value.trim() : ''; +} + +function getCompanyId(req) { + return normalizeText( + req.get('x-company-id') + || req.query.company_id + || req.query.companyId + || '' + ); +} + +function getSessionCookieName(companyId) { + return `${SESSION_COOKIE_NAME}_${companyId}`; +} + +function clearSessionCookie(res, companyId) { + res.clearCookie(getSessionCookieName(companyId), { + path: '/', + httpOnly: true, + secure: true, + sameSite: 'None', + partitioned: true, + }); +} + +router.get('/session-status', async (req, res) => { + const companyId = getCompanyId(req); + + if (!isFdkConfigured || !fdkExtension) { + return res.json({ + configured: false, + authenticated: false, + companyId, + needsBootstrap: false, + reason: 'fdk_not_configured', + }); + } + + if (!companyId) { + return res.status(400).json({ + configured: true, + authenticated: false, + companyId: '', + needsBootstrap: false, + reason: 'missing_company_id', + }); + } + + const sessionCookieName = getSessionCookieName(companyId); + const sessionId = normalizeText(req.signedCookies?.[sessionCookieName] || ''); + + if (!sessionId) { + return res.json({ + configured: true, + authenticated: false, + companyId, + needsBootstrap: true, + reason: 'missing_session_cookie', + }); + } + + try { + const session = await fdkExtension.storage.get(sessionId); + const authenticated = Boolean( + session + && normalizeText(String(session.company_id || '')) === companyId + && normalizeText(session.access_token || '') + ); + + if (!authenticated) { + clearSessionCookie(res, companyId); + } + + return res.json({ + configured: true, + authenticated, + companyId, + needsBootstrap: !authenticated, + reason: authenticated ? 'ok' : 'missing_or_invalid_session', + }); + } catch (error) { + return res.status(503).json({ + configured: true, + authenticated: false, + companyId, + needsBootstrap: false, + reason: 'session_status_error', + error: error.message || 'Failed to inspect FDK session', + }); + } +}); + +module.exports = router;