refactor(freight): move the Fayda callback to /fayda/callback

Namespaces the OAuth landing path in all three places it exists: the API's
ack controller, both web apps' routes, and the redirect_uri env values.
A bare /callback claimed a generic top-level path in every app for one
provider's redirect.

The API side needed care. The ack controller moves to @Controller
('fayda/callback'), and the global-prefix exclusion has to name that exact
path — setGlobalPrefix's exclude is an exact route match, not a subtree, so
excluding "fayda" would have left /fayda/callback served at
/api/fayda/callback and 404ing at the registered redirect_uri, while
reading as though it covered everything under /fayda. Naming the full path
also keeps /api/fayda/verification/* prefixed, which every client calls.

Also drops a stale comment on the portal's callback route describing the
popup that no longer exists, and records why the route is public: behind
RequireAuth the onboarding gate redirects to /portal before the code+state
exchange can run.

NOT verified at runtime — this changes route registration, so boot the API
and confirm GET /fayda/callback answers un-prefixed and
/api/fayda/verification/start still resolves before relying on it.

Deploying this requires registering the new redirect_uri with eSignet
first; FAYDA_WEB_REDIRECT_URI, FAYDA_PORTAL_REDIRECT_URI and any mobile
client must be updated in step or verification breaks with a redirect_uri
mismatch.
This commit is contained in:
Nathnael
2026-08-04 11:14:13 +00:00
parent b356433886
commit 3a69b961d4
20 changed files with 222 additions and 152 deletions

View File

@@ -292,7 +292,10 @@ const buildSidebarSections = (demoItems: SidebarItem[]): SidebarSection[] => [
label: "Locomotives",
href: "/dashboard/locomotives",
icon: <Train />,
permission: [FREIGHT_PERMS.locomotives.view, FREIGHT_PERMS.fleet.view],
permission: [
FREIGHT_PERMS.locomotives.view,
FREIGHT_PERMS.fleet.view,
],
},
{
label: "Train Builder",
@@ -818,7 +821,7 @@ const App = () => {
{UserManagementRoutes()}
{/* <Route path="/um/*" element={<UserManagementHostPage />} /> */}
<Route path="/health" element={<HealthCheck />} />
<Route path="/callback" element={<FaydaCallbackPage />} />
<Route path="/fayda/callback" element={<FaydaCallbackPage />} />
<Route path="/" element={<Navigate to="/dashboard/overview" replace />} />
<Route
path="/dashboard"
@@ -1085,7 +1088,10 @@ const App = () => {
<Route path="intercity" element={<IntercityPage />} />
<Route path="trucks-on-site" element={<TrucksOnSitePage />} />
<Route path="import-trucks" element={<ImportTrucksPage />} />
<Route path="edr-last-mile-returns" element={<EDRLastMileReturnsPage />} />
<Route
path="edr-last-mile-returns"
element={<EDRLastMileReturnsPage />}
/>
<Route path="container-returns" element={<ContainerReturnsPage />} />
<Route path="loaded-inventory" element={<LoadedInventoryPage />} />
<Route path="dispatch-queue" element={<DispatchQueuePage />} />
@@ -1173,7 +1179,9 @@ const App = () => {
<Route
path="routes"
element={
<RequirePermission permission={[FREIGHT_PERMS.routes.view, FREIGHT_PERMS.fleet.view]}>
<RequirePermission
permission={[FREIGHT_PERMS.routes.view, FREIGHT_PERMS.fleet.view]}
>
<RoutesPage />
</RequirePermission>
}
@@ -1181,7 +1189,12 @@ const App = () => {
<Route
path="locomotives"
element={
<RequirePermission permission={[FREIGHT_PERMS.locomotives.view, FREIGHT_PERMS.fleet.view]}>
<RequirePermission
permission={[
FREIGHT_PERMS.locomotives.view,
FREIGHT_PERMS.fleet.view,
]}
>
<FleetResourcePage />
</RequirePermission>
}
@@ -1189,7 +1202,9 @@ const App = () => {
<Route
path="trains"
element={
<RequirePermission permission={[FREIGHT_PERMS.trains.view, FREIGHT_PERMS.fleet.view]}>
<RequirePermission
permission={[FREIGHT_PERMS.trains.view, FREIGHT_PERMS.fleet.view]}
>
<FleetResourcePage />
</RequirePermission>
}
@@ -1197,7 +1212,9 @@ const App = () => {
<Route
path="trains/:id"
element={
<RequirePermission permission={[FREIGHT_PERMS.trains.view, FREIGHT_PERMS.fleet.view]}>
<RequirePermission
permission={[FREIGHT_PERMS.trains.view, FREIGHT_PERMS.fleet.view]}
>
<TrainDetailPage />
</RequirePermission>
}
@@ -1205,7 +1222,9 @@ const App = () => {
<Route
path="train-builder"
element={
<RequirePermission permission={[FREIGHT_PERMS.trains.view, FREIGHT_PERMS.fleet.view]}>
<RequirePermission
permission={[FREIGHT_PERMS.trains.view, FREIGHT_PERMS.fleet.view]}
>
<TrainBuilderListPage />
</RequirePermission>
}
@@ -1213,7 +1232,9 @@ const App = () => {
<Route
path="train-builder/:id"
element={
<RequirePermission permission={[FREIGHT_PERMS.trains.view, FREIGHT_PERMS.fleet.view]}>
<RequirePermission
permission={[FREIGHT_PERMS.trains.view, FREIGHT_PERMS.fleet.view]}
>
<TrainBuilderDetailPage />
</RequirePermission>
}
@@ -1221,7 +1242,9 @@ const App = () => {
<Route
path="wagons"
element={
<RequirePermission permission={[FREIGHT_PERMS.wagons.view, FREIGHT_PERMS.fleet.view]}>
<RequirePermission
permission={[FREIGHT_PERMS.wagons.view, FREIGHT_PERMS.fleet.view]}
>
<FleetResourcePage />
</RequirePermission>
}
@@ -1242,7 +1265,12 @@ const App = () => {
<Route
path="containers"
element={
<RequirePermission permission={[FREIGHT_PERMS.containers.view, FREIGHT_PERMS.fleet.view]}>
<RequirePermission
permission={[
FREIGHT_PERMS.containers.view,
FREIGHT_PERMS.fleet.view,
]}
>
<FleetResourcePage />
</RequirePermission>
}
@@ -1250,7 +1278,12 @@ const App = () => {
<Route
path="cargoes"
element={
<RequirePermission permission={[FREIGHT_PERMS.cargoes.view, FREIGHT_PERMS.fleet.view]}>
<RequirePermission
permission={[
FREIGHT_PERMS.cargoes.view,
FREIGHT_PERMS.fleet.view,
]}
>
<FleetResourcePage />
</RequirePermission>
}
@@ -1336,7 +1369,9 @@ const App = () => {
<Route
path="routes"
element={
<RequirePermission permission={[FREIGHT_PERMS.routes.view, FREIGHT_PERMS.fleet.view]}>
<RequirePermission
permission={[FREIGHT_PERMS.routes.view, FREIGHT_PERMS.fleet.view]}
>
<RoutesPage />
</RequirePermission>
}
@@ -1424,7 +1459,12 @@ const App = () => {
<Route
path="locomotives"
element={
<RequirePermission permission={[FREIGHT_PERMS.locomotives.view, FREIGHT_PERMS.fleet.view]}>
<RequirePermission
permission={[
FREIGHT_PERMS.locomotives.view,
FREIGHT_PERMS.fleet.view,
]}
>
<FleetResourcePage />
</RequirePermission>
}
@@ -1432,7 +1472,9 @@ const App = () => {
<Route
path="trains"
element={
<RequirePermission permission={[FREIGHT_PERMS.trains.view, FREIGHT_PERMS.fleet.view]}>
<RequirePermission
permission={[FREIGHT_PERMS.trains.view, FREIGHT_PERMS.fleet.view]}
>
<FleetResourcePage />
</RequirePermission>
}
@@ -1440,7 +1482,9 @@ const App = () => {
<Route
path="trains/:id"
element={
<RequirePermission permission={[FREIGHT_PERMS.trains.view, FREIGHT_PERMS.fleet.view]}>
<RequirePermission
permission={[FREIGHT_PERMS.trains.view, FREIGHT_PERMS.fleet.view]}
>
<TrainDetailPage />
</RequirePermission>
}
@@ -1448,7 +1492,9 @@ const App = () => {
<Route
path="train-builder"
element={
<RequirePermission permission={[FREIGHT_PERMS.trains.view, FREIGHT_PERMS.fleet.view]}>
<RequirePermission
permission={[FREIGHT_PERMS.trains.view, FREIGHT_PERMS.fleet.view]}
>
<TrainBuilderListPage />
</RequirePermission>
}
@@ -1456,7 +1502,9 @@ const App = () => {
<Route
path="train-builder/:id"
element={
<RequirePermission permission={[FREIGHT_PERMS.trains.view, FREIGHT_PERMS.fleet.view]}>
<RequirePermission
permission={[FREIGHT_PERMS.trains.view, FREIGHT_PERMS.fleet.view]}
>
<TrainBuilderDetailPage />
</RequirePermission>
}
@@ -1464,7 +1512,9 @@ const App = () => {
<Route
path="wagons"
element={
<RequirePermission permission={[FREIGHT_PERMS.wagons.view, FREIGHT_PERMS.fleet.view]}>
<RequirePermission
permission={[FREIGHT_PERMS.wagons.view, FREIGHT_PERMS.fleet.view]}
>
<FleetResourcePage />
</RequirePermission>
}
@@ -1485,7 +1535,12 @@ const App = () => {
<Route
path="containers"
element={
<RequirePermission permission={[FREIGHT_PERMS.containers.view, FREIGHT_PERMS.fleet.view]}>
<RequirePermission
permission={[
FREIGHT_PERMS.containers.view,
FREIGHT_PERMS.fleet.view,
]}
>
<FleetResourcePage />
</RequirePermission>
}
@@ -1493,7 +1548,12 @@ const App = () => {
<Route
path="cargoes"
element={
<RequirePermission permission={[FREIGHT_PERMS.cargoes.view, FREIGHT_PERMS.fleet.view]}>
<RequirePermission
permission={[
FREIGHT_PERMS.cargoes.view,
FREIGHT_PERMS.fleet.view,
]}
>
<FleetResourcePage />
</RequirePermission>
}

View File

@@ -47,7 +47,7 @@ export function isComplaintAuthContext(pathname = ""): boolean {
pathname.startsWith("/complaints") ||
pathname === "/complaint-form" ||
pathname === "/follow-complaint" ||
pathname === "/callback"
pathname === "/fayda/callback"
);
}

View File

@@ -28,7 +28,7 @@ let listener: Listener | null = null;
/** Current-page path patterns where the global modal must stay silent. */
const EXCLUDED_PATH_PATTERNS = [
/^\/auth/,
/^\/callback/,
/^\/fayda/,
/warehouse/i,
/first-mile/i,
/last-mile/i,

View File

@@ -148,7 +148,7 @@ const FleetFormDialog = ({
});
}, [open, fields]);
// Receive the ?code&state relayed by the /callback popup, exchange it for
// Receive the ?code&state relayed by the /fayda/callback popup, exchange it for
// the verified identity, and prefill the matching form fields.
useEffect(() => {
if (!open || !verifyWithFayda) return;

View File

@@ -5,7 +5,7 @@ import type { FaydaCallbackMessage } from "@/services/verifayda.service";
/**
* Landing page for the eSignet redirect_uri (FAYDA_WEB_REDIRECT_URI →
* http://localhost:5183/callback). Runs inside the verification popup:
* http://localhost:5183/fayda/callback). Runs inside the verification popup:
* relays ?code&state (or ?error) to the window that opened it via
* postMessage, then closes itself. The opener performs the /complete call
* so the single-use session is only consumed once, in one place.

View File

@@ -17,7 +17,7 @@ export interface FaydaCompleteResult {
userDataSaved?: boolean;
}
/** Message posted from the /callback popup back to the opener window. */
/** Message posted from the /fayda/callback popup back to the opener window. */
export interface FaydaCallbackMessage {
type: 'fayda-callback';
code?: string;

View File

@@ -5,7 +5,7 @@ import ExternalPortalCallback from "@/external-portal/components/Registration/Ex
/**
* Single FAYDA OIDC callback entry point.
* Fayda only allows whitelisted redirect URIs (e.g. /callback) — route
* Fayda only allows whitelisted redirect URIs (e.g. /fayda/callback) — route
* internally based on the `state` param sent during authorization.
*/
export default function FaydaCallbackDispatcher() {

View File

@@ -11,7 +11,7 @@ const PUBLIC_PATHS = [
"/set-password",
"/verify-otp",
"/verification_page",
"/callback",
"/fayda/callback",
"/complaints",
"/complaint-form",
"/follow-complaint",

View File

@@ -12,7 +12,7 @@ const DEFAULT_CODE_CHALLENGE = "E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM";
const DEFAULT_NONCE = "g4DEuje5Fx57Vb64dO4oqLHXGT8L8G7g";
const DEFAULT_STATE = "ptOO76SD";
/** OIDC state value that routes the shared /callback to the complaint flow (legacy sign-in). */
/** OIDC state value that routes the shared /fayda/callback to the complaint flow (legacy sign-in). */
export const COMPLAINT_FLOW_STATE = "complaint_flow";
/** Complaint flow OIDC states — distinguish sign-in vs sign-up endpoints. */
@@ -66,9 +66,7 @@ export function startExternalPortalFaydaAuth(): void {
export function generateFaydaAuthorizationUrl(
options: FaydaOidcOptions = {},
): string {
const redirectUri =
options.redirectUri ||
getDefaultFaydaRedirectUri();
const redirectUri = options.redirectUri || getDefaultFaydaRedirectUri();
const params = new URLSearchParams({
client_id: import.meta.env.VITE_CLIENT_ID || "",
@@ -98,7 +96,7 @@ export function generateFaydaAuthorizationUrl(
export function getDefaultFaydaRedirectUri(): string {
return (
import.meta.env.VITE_REDIRECT_URI ||
`${window.location.origin}/callback`
`${window.location.origin}/fayda/callback`
);
}
@@ -106,13 +104,12 @@ export function getDefaultFaydaRedirectUri(): string {
* Returns the redirect URI registered with FAYDA for the complaint flow.
*
* Must exactly match a URI whitelisted in the FAYDA OIDC client — we reuse
* the same /callback path as external-portal registration and distinguish
* the same /fayda/callback path as external-portal registration and distinguish
* flows via the `state` parameter (see COMPLAINT_FLOW_STATE).
*/
export function getComplaintFaydaRedirectUri(): string {
return (
import.meta.env.VITE_COMPLAINT_REDIRECT_URI ||
getDefaultFaydaRedirectUri()
import.meta.env.VITE_COMPLAINT_REDIRECT_URI || getDefaultFaydaRedirectUri()
);
}