Skip to main content

Where the code lives

Every file below is in jodapp-web. The tables are grouped the way the pages are, so a reader who has just finished one page can open the files it described.

What JodApp Web knows about a session describes these.

FileWhat it holds
app/auth/auth-session-cookies.jsauthSessionCookies: the user session cookie name, jodapp_session_id, and hasSessionCookie(request), the one question the middleware asks before it calls Rails
app/auth/auth-admin-session-cookies.jsThe admin twin, with teamjod_session_id
app/api/ky-client.jscreateApiClient, and the two clients it builds once per process: apiClient for the user API and teamApiClient for the Team API. The beforeRequest hook that adds X-CSRF-Token in the browser, credentials: 'include', the conversion of every failure into an ApiError, and the choice of host: the public host in the browser, the private host on Node
app/api/identities-user-sessions-api.jsidentitiesUserSessionsApi: create for login, current for the read, destroy for logout, destroyAll for sign out everywhere
app/api/team-identities-admin-sessions-api.jsteamIdentitiesAdminSessionsApi, the admin twin
app/errors/api-error.jsApiError, with status and code from every Rails error, and status: 0 for a network failure

Reading the session​

Reading the session describes these.

FileWhat it holds
app/auth/auth-user-session-middleware.jsauthUserSession: the middleware that stores getIdentitiesUserSessionsCurrent() in router context and sets Cache-Control: private, no-store, and the context key the root loader and the guard read
app/auth/auth-admin-session-middleware.jsauthAdminSession, the admin twin, with getTeamIdentitiesAdminSessionsCurrent()
app/roots/public-root.jsxThe root route for jodapp.com. Exports the session middleware, the root loader that returns { identitiesUserSession }, the shouldRevalidate that reloads after an action fails with 401 or 403, and the error boundary
app/roots/team-root.jsxThe root route for teamjod.app, returning { identitiesAdminSession }
app/hooks/use-identities-user-session.jsuseIdentitiesUserSession(), which reads the root's route data and nothing else
app/hooks/use-identities-admin-session.jsuseIdentitiesAdminSession(), the admin twin

Changing a session​

Changing a session describes these.

FileWhat it holds
app/routes/login-page.jsxThe user login page and its clientAction, which switches on error.code and redirects with replace()
app/routes/employers/employers-login-page.jsxThe employer login page, the same shape with its own default target and guest paths
app/routes/team/team-login-page.jsxThe admin login page, the same shape
app/routes/logout.jsThe /logout resource route: a clientAction that calls DELETE /identities/user_sessions/current and returns replace('/')
app/routes/logout-everywhere.jsThe /logout/everywhere resource route, calling DELETE /identities/user_sessions
app/routes/team/team-logout.js, app/routes/team/team-logout-everywhere.jsThe admin twins, under /team/logout
app/routes/identities-user/identities-user-edit-page.jsxThe account page. Its clientAction changes the name or email, which the session carries
app/routes/talent/profile/talent-setup-page.jsxTalent setup. Its final step moves talent_access.state to ready
app/routes/org/org-create-account-page.jsxEmployer account creation. Its clientAction moves employer_access.state to active

Policy routes, the guard, and the entry rules​

Policy routes, the policy loader and entry rules describe these.

FileWhat it holds
app/routes.js, app/public.routes.js, app/talent.routes.js, app/org.routes.js, app/team.routes.jsThe route tree. Every policy route is mounted here, around exactly the routes it guards
app/routes/policies/*.jsxThe ten policy routes. Each exports middleware, a loader that returns null, and a component that renders an Outlet
app/auth/auth-user-guard.jsauthUserGuard.protect(rule): turns one entry rule into the middleware a policy exports. Reads url, throws replace()
app/auth/auth-admin-guard.jsauthAdminGuard.protect(rule), the admin twin
app/auth/auth-entry-rules.jsauthEntryRules: the eight user-side rules, userGuest to employerRequired, and USER_GUEST_PATHS
app/auth/auth-admin-entry-rules.jsauthAdminEntryRules: adminGuest, adminRequired, and ADMIN_GUEST_PATHS
app/auth/auth-safe-redirect.jsauthSafeRedirect: the redirect_to check. The only auth file both identity systems share
app/domains/talent-profile/talent-profile-constant.js, app/domains/org-membership/org-membership-constants.jsThe access state words the rules compare against: TALENT_PROFILE_ACCESS_STATE and ORG_MEMBERSHIP_ACCESS_STATE
app/routes/employers/employers-access-unavailable-page.jsxThe holding page for company_disabled, revoked and any unknown employer state. Reads the WhatsApp link from DEFAULT_COUNTRY.social in app/utils/constants.js

The visual layouts, and the server rendering flag​

Rails is the boundary describes what these must not do.

FileWhat it holds
app/layouts/careers/careers-public-layout.jsx, app/layouts/org/org-public-layout.jsx, app/layouts/org-dashboard/org-dashboard-layout.jsx, app/layouts/team/team-layout.jsxThe visual layouts. They draw the navbar, the sidebar and the page frame. None exports middleware or an action, and none reads the session to decide access
react-router.config.jsssr: true. Every rule about Node in these pages depends on it

The deploy variables​

VariableRead byValue
VITE_API_JODAPP_URLThe browser, written into the bundle at build timehttps://api.jodapp.com in production, https://api.jodapp.dev in QA
VITE_API_TEAMJOD_URLThe browser, the same wayhttps://api.teamjod.app in production, https://api.jodapp.dev in QA
API_INTERNAL_JODAPP_URLNode only, at runtimehttp://api.internal.jodapp.com in production, http://api.internal.jodapp.dev in QA
API_INTERNAL_TEAMJOD_URLNode only, at runtimehttp://api.internal.teamjod.app in production, http://api.internal.jodapp.dev in QA

The two API_INTERNAL_* variables have no VITE_ prefix on purpose, so Vite never writes them into the browser bundle. When one is unset, Node uses the public host, so a missing variable can never break a deploy.

Tests​

FileWhat it covers
app/auth/auth-session-cookies.spec.js, auth-admin-session-cookies.spec.jsThe cookie name matches exactly, and a user cookie never looks like an admin cookie
app/auth/auth-user-session-middleware.spec.js, auth-admin-session-middleware.spec.jsNo cookie means no call. Two callers in one request share one call. 401 returns null, and 403, 5xx and a network failure stay errors. Every response with a cookie carries Cache-Control: private, no-store
app/auth/auth-entry-rules.spec.js, auth-admin-entry-rules.spec.jsEvery row of every table on the entry rules page, including the unknown-state rows
app/auth/auth-safe-redirect.spec.jsOutside URLs, protocol-relative paths, and the guest-loop targets are refused
app/auth/auth-user-guard.spec.js, auth-admin-guard.spec.jsThe guard reads url, passes the session to the rule, and throws replace() on a path
app/api/ky-client.spec.jsThe CSRF header is added on writes in the browser only, and never as the string undefined. A refused request is returned once, with no retry
app/routes/team/team-login-page.spec.js, team-logout.spec.jsThe clientAction shapes: form messages as 400, everything else thrown, replace() on success
app/routes/employers/employers-access-unavailable-page.spec.jsxOne text per state, the WhatsApp link, and no employer data request

Two navigations to test for every protected area, because they run different code paths:

NavigationWhat it proves
A document requestNode applies the policy on every matched middleware
A click that enters the areaThe policy loader forces the .data request, and the policy runs on Node