Files
probo/apps/console/src/routes.tsx
Émile Ré 5d0882778f Gate password sign-in on email verification
Unverified password identities were able to open sessions after
signing out. Reject sign-in with EMAIL_NOT_VERIFIED and add a
resend-confirmation flow so users can complete verification.

Signed-off-by: Émile Ré <emile@probo.com>
2026-07-28 15:11:05 +02:00

369 lines
12 KiB
TypeScript

// Copyright (c) 2025-2026 Probo Inc <hello@probo.com>.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
import { Role } from "@probo/helpers";
import { lazy } from "@probo/react-lazy";
import { type AppRoute, routeFromAppRoute } from "@probo/routes";
import { CenteredLayout } from "@probo/ui";
import { use } from "react";
import {
createBrowserRouter,
Navigate,
redirect,
} from "react-router";
import { OrganizationErrorBoundary } from "./components/OrganizationErrorBoundary";
import { PageError } from "./components/PageError";
import { RootErrorBoundary } from "./components/RootErrorBoundary";
import { PageSkeleton } from "./components/skeletons/PageSkeleton";
import { ViewerLayoutLoading } from "./pages/iam/memberships/ViewerLayoutLoading";
import { peopleRoutes } from "./pages/iam/organizations/people/routes";
import { compliancePageRoutes } from "./pages/organizations/compliance-page/routes";
import { cookieBannerRoutes } from "./pages/organizations/cookie-banners/routes";
import { deviceRoutes } from "./pages/organizations/devices/routes";
import { riskRoutes } from "./pages/organizations/risks/routes";
import { thirdPartyRoutes } from "./pages/organizations/third-parties/routes";
import { CurrentUser } from "./providers/CurrentUser";
import { accessReviewRoutes } from "./routes/accessReviewRoutes";
import { assetRoutes } from "./routes/assetRoutes";
import { auditRoutes } from "./routes/auditRoutes";
import { contextRoutes } from "./routes/contextRoutes";
import { dataRoutes } from "./routes/dataRoutes";
import { documentsRoutes } from "./routes/documentsRoutes";
import { findingRoutes } from "./routes/findingRoutes";
import { frameworkRoutes } from "./routes/frameworkRoutes";
import { measureRoutes } from "./routes/measureRoutes";
import { obligationRoutes } from "./routes/obligationRoutes";
import { processingActivityRoutes } from "./routes/processingActivityRoutes";
import { rightsRequestRoutes } from "./routes/rightsRequestRoutes";
import { statementsOfApplicabilityRoutes } from "./routes/statementsOfApplicabilityRoutes";
import { taskRoutes } from "./routes/taskRoutes";
const routes = [
{
path: "/auth",
Component: lazy(() => import("./pages/iam/auth/AuthLayout")),
children: [
{
path: "login",
Component: lazy(
() => import("./pages/iam/auth/sign-in/SignInPageLoader"),
),
},
{
path: "password-login",
Component: lazy(
() => import("./pages/iam/auth/sign-in/PasswordSignInPage"),
),
},
{
path: "sso-login",
Component: lazy(() => import("./pages/iam/auth/sign-in/SSOSignInPage")),
},
{
path: "register",
Component: lazy(() => import("./pages/iam/auth/SignUpPage")),
},
{
path: "verify-email",
Component: lazy(() => import("./pages/iam/auth/VerifyEmailPage")),
},
{
path: "resend-verification-email",
Component: lazy(
() => import("./pages/iam/auth/ResendVerificationEmailPage"),
),
},
{
path: "activate-account",
Component: lazy(
() => import("./pages/iam/auth/ActivateAccountPage"),
),
},
{
path: "create-password",
Component: lazy(
() => import("./pages/iam/auth/CreatePasswordPage"),
),
},
{
path: "forgot-password",
Component: lazy(() => import("./pages/iam/auth/ForgotPasswordPage")),
},
{
path: "reset-password",
Component: lazy(() => import("./pages/iam/auth/ResetPasswordPage")),
},
{
path: "device",
ErrorBoundary: RootErrorBoundary,
Component: lazy(
() => import("./pages/iam/auth/DeviceActivationPageLoader"),
),
},
{
path: "consent",
ErrorBoundary: RootErrorBoundary,
Component: lazy(
() => import("./pages/iam/auth/ConsentPageLoader"),
),
},
{
path: "error",
Component: lazy(() => import("./pages/iam/auth/AuthErrorPage")),
},
],
},
{
path: "/",
ErrorBoundary: RootErrorBoundary,
children: [
{
Component: lazy(() => import("./pages/iam/memberships/ViewerLayoutLoader")),
Fallback: ViewerLayoutLoading,
children: [
{
index: true,
Component: lazy(
() => import("./pages/iam/memberships/MembershipsPageLoader"),
),
},
{
path: "me/api-keys",
Component: lazy(
() => import("./pages/iam/apiKeys/APIKeysPageLoader"),
),
},
{
path: "me/oauth-tokens",
Component: lazy(
() => import("./pages/iam/oauthTokens/OAuthTokensPageLoader"),
),
},
{
path: "me/oauth-tokens/new",
Component: lazy(
() => import("./pages/iam/oauthTokens/NewOAuthTokenPageLoader"),
),
},
{
path: "enroll",
Component: lazy(
() => import("./pages/iam/enroll/EnrollDevicePageLoader"),
),
},
{
Component: CenteredLayout,
children: [
{
path: "organizations/new",
Component: lazy(
() => import("./pages/iam/organizations/NewOrganizationPage"),
),
},
],
},
],
},
],
},
{
path: "/organizations/:organizationId",
children: [
{
path: "assume",
Component: lazy(() => import("./pages/iam/organizations/AssumePageLoader")),
},
{
path: "employee",
ErrorBoundary: OrganizationErrorBoundary,
Component: lazy(
() => import("./pages/organizations/employee/EmployeeLayoutLoader"),
),
children: [
{
index: true,
loader: ({ params: { organizationId } }) => {
// eslint-disable-next-line
throw redirect(`/organizations/${organizationId}/employee/signatures`);
},
Component: () => null,
},
{
Component: lazy(
() => import("./pages/organizations/employee/EmployeeTabsLayout"),
),
children: [
{
path: "signatures",
Component: lazy(
() =>
import("./pages/organizations/employee/EmployeeDocumentsPageLoader"),
),
},
{
path: "approvals",
Component: lazy(
() =>
import("./pages/organizations/employee/EmployeeApprovalsPageLoader"),
),
},
{
path: "devices",
Component: lazy(
() =>
import("./pages/organizations/employee/EmployeeDevicesPageLoader"),
),
},
],
},
{
path: ":documentId",
loader: ({ params: { organizationId, documentId } }) => {
// eslint-disable-next-line
throw redirect(`/organizations/${organizationId}/employee/signatures/${documentId}`);
},
Component: () => null,
},
{
path: "signatures/:documentId",
Component: lazy(
() =>
import("./pages/organizations/employee/EmployeeDocumentSignaturePageLoader"),
),
},
{
path: "approvals/:documentId",
Component: lazy(
() =>
import("./pages/organizations/documents/approve/DocumentApprovePageLoader"),
),
},
],
},
{
Component: lazy(
() => import("./pages/iam/organizations/ViewerMembershipLayoutLoader"),
),
ErrorBoundary: OrganizationErrorBoundary,
children: [
{
path: "",
Component: () => {
const { role } = use(CurrentUser);
switch (role) {
case Role.EMPLOYEE:
return <Navigate to="employee" />;
case Role.AUDITOR:
return <Navigate to="measures" />;
default:
return <Navigate to="tasks" />;
}
},
},
{
path: "settings",
Fallback: PageSkeleton,
Component: lazy(
() => import("./pages/iam/organizations/settings/SettingsLayout"),
),
children: [
{
index: true,
loader: () => {
// eslint-disable-next-line
throw redirect("general");
},
},
{
path: "general",
Component: lazy(
() =>
import("./pages/iam/organizations/settings/GeneralSettingsPageLoader"),
),
},
{
path: "saml-sso",
Component: lazy(
() =>
import("./pages/iam/organizations/settings/SAMLSettingsPageLoader"),
),
},
{
path: "scim",
Component: lazy(
() =>
import("./pages/iam/organizations/settings/SCIMSettingsPageLoader"),
),
},
{
path: "webhooks",
Component: lazy(
() =>
import("./pages/iam/organizations/settings/WebhooksSettingsPageLoader"),
),
},
{
path: "audit-log",
Component: lazy(
() =>
import("./pages/iam/organizations/settings/AuditLogSettingsPageLoader"),
),
},
],
},
...peopleRoutes,
...riskRoutes,
...measureRoutes,
...documentsRoutes,
...thirdPartyRoutes,
...deviceRoutes,
...frameworkRoutes,
...taskRoutes,
...assetRoutes,
...dataRoutes,
...auditRoutes,
...contextRoutes,
...findingRoutes,
...obligationRoutes,
...rightsRequestRoutes,
...processingActivityRoutes,
...statementsOfApplicabilityRoutes,
...accessReviewRoutes,
...compliancePageRoutes,
...cookieBannerRoutes,
{
path: "*",
Component: PageError,
},
],
},
],
},
// Fallback URL to the NotFound Page
{
path: "*",
Component: PageError,
},
] satisfies AppRoute[];
export const router = createBrowserRouter(routes.map(routeFromAppRoute));