@hachej/boring-core 0.1.48 → 0.1.49

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,11 +1,15 @@
1
1
  import {
2
2
  TopBarSlotProvider
3
3
  } from "./chunk-HYNKZSTF.js";
4
+ import {
5
+ canUseProtectedApi,
6
+ isRuntimeEmailVerificationEnabled
7
+ } from "./chunk-I4PGL4ZD.js";
4
8
  import {
5
9
  ConfigFetchError,
6
10
  ERROR_CODES,
7
11
  HttpError
8
- } from "./chunk-LIBHVT7V.js";
12
+ } from "./chunk-QZGYKLXB.js";
9
13
 
10
14
  // src/front/AppErrorBoundary.tsx
11
15
  import { Component } from "react";
@@ -660,11 +664,16 @@ function WorkspaceAuthProvider({
660
664
  const queryClient = useQueryClient();
661
665
  const routeWorkspaceId = id?.trim() ? id : workspaceIdFromPath(location.pathname, workspaceRoute, workspaceIdParam);
662
666
  const session = useSession();
663
- const isAuthenticated = Boolean(session.data?.user);
667
+ const config = useOptionalConfig();
668
+ const user = session.data?.user ?? null;
669
+ const canQueryProtectedApi = canUseProtectedApi(
670
+ user,
671
+ isRuntimeEmailVerificationEnabled(config)
672
+ );
664
673
  const workspacesQuery = useQuery2({
665
674
  queryKey: WORKSPACES_QUERY_KEY,
666
675
  queryFn: fetchWorkspaces,
667
- enabled: isAuthenticated
676
+ enabled: canQueryProtectedApi
668
677
  });
669
678
  const defaultWorkspace = routeWorkspaceId === null ? workspacesQuery.data?.find((workspace2) => workspace2.isDefault) ?? workspacesQuery.data?.[0] ?? null : null;
670
679
  const resolvedId = routeWorkspaceId ?? defaultWorkspace?.id ?? null;
@@ -677,13 +686,13 @@ function WorkspaceAuthProvider({
677
686
  }
678
687
  return fetchWorkspace(resolvedId);
679
688
  },
680
- enabled: isAuthenticated && resolvedId !== null
689
+ enabled: canQueryProtectedApi && resolvedId !== null
681
690
  });
682
691
  const detail = detailQuery.data ?? cachedDetail ?? null;
683
692
  const workspace = detailQuery.isError ? null : detail?.workspace ?? null;
684
693
  const role = detailQuery.isError ? null : detail?.role ?? null;
685
694
  const routeStatus = (() => {
686
- if (!isAuthenticated) return { status: "idle", workspaceId: routeWorkspaceId };
695
+ if (!canQueryProtectedApi) return { status: "idle", workspaceId: routeWorkspaceId };
687
696
  if (routeWorkspaceId === null) return { status: "idle", workspaceId: null };
688
697
  if (detailQuery.isError) return routeStatusFromError(routeWorkspaceId, detailQuery.error);
689
698
  if (detailQuery.isPending && !detail) return { status: "loading", workspaceId: routeWorkspaceId };
@@ -709,11 +718,16 @@ var UserContext = createContext5(null);
709
718
  var STALE_MS = 6e4;
710
719
  function UserIdentityProvider({ children }) {
711
720
  const { data: session } = useSession();
721
+ const config = useOptionalConfig();
722
+ const canFetchIdentity = canUseProtectedApi(
723
+ session?.user,
724
+ isRuntimeEmailVerificationEnabled(config)
725
+ );
712
726
  const [identity, setIdentity] = useState6(null);
713
727
  const fetchedForRef = useRef3(null);
714
728
  const lastFetchRef = useRef3(0);
715
729
  useEffect7(() => {
716
- if (!session?.user) {
730
+ if (!session?.user || !canFetchIdentity) {
717
731
  setIdentity(null);
718
732
  fetchedForRef.current = null;
719
733
  return;
@@ -736,7 +750,7 @@ function UserIdentityProvider({ children }) {
736
750
  return () => {
737
751
  cancelled = true;
738
752
  };
739
- }, [session?.user?.id]);
753
+ }, [canFetchIdentity, session?.user?.id]);
740
754
  return /* @__PURE__ */ jsx6(UserContext.Provider, { value: identity, children });
741
755
  }
742
756
  function useUser() {
@@ -892,6 +906,7 @@ function SignInPage() {
892
906
  // src/front/auth/SignUpPage.tsx
893
907
  import { useState as useState9 } from "react";
894
908
  import { useForm as useForm2 } from "react-hook-form";
909
+ import { useNavigate } from "react-router-dom";
895
910
  import { zodResolver as zodResolver2 } from "@hookform/resolvers/zod";
896
911
  import { z as z2 } from "zod";
897
912
  import { Button as Button4, Card as Card2, CardContent as CardContent2, CardDescription as CardDescription2, CardFooter as CardFooter2, CardHeader as CardHeader2, CardTitle as CardTitle2, Input as Input2, Label as Label2 } from "@hachej/boring-ui-kit";
@@ -909,6 +924,7 @@ function readGoogleAuthError2() {
909
924
  }
910
925
  function SignUpPage() {
911
926
  const signUp = useSignUp();
927
+ const navigate = useNavigate();
912
928
  const config = useOptionalConfig();
913
929
  const [serverError, setServerError] = useState9(null);
914
930
  const [oauthError, setOauthError] = useState9(() => readGoogleAuthError2());
@@ -935,6 +951,8 @@ function SignUpPage() {
935
951
  );
936
952
  if (result.error) {
937
953
  setServerError(result.error.message ?? "Sign up failed");
954
+ } else if (isRuntimeEmailVerificationEnabled(config)) {
955
+ navigate(routes.verifyEmail, { replace: true });
938
956
  } else {
939
957
  setSuccess(true);
940
958
  }
@@ -1332,11 +1350,12 @@ function VerifyEmailPage() {
1332
1350
  /* @__PURE__ */ jsx12(CardFooter5, { children: /* @__PURE__ */ jsx12("a", { href: routes.signin, className: "text-sm text-muted-foreground hover:underline", children: "Back to sign in" }) })
1333
1351
  ] }) });
1334
1352
  }
1353
+ const isWaitingForEmailClick = status === "no-token" && Boolean(sessionEmail);
1335
1354
  return /* @__PURE__ */ jsx12("div", { className: "flex min-h-screen items-center justify-center p-4", children: /* @__PURE__ */ jsxs5(Card5, { className: "w-full max-w-sm", children: [
1336
1355
  inviteWarning && /* @__PURE__ */ jsx12("div", { role: "status", className: "px-6 pt-4", children: /* @__PURE__ */ jsx12("div", { className: "rounded-md bg-muted px-3 py-2 text-sm text-muted-foreground", children: inviteWarning }) }),
1337
1356
  /* @__PURE__ */ jsxs5(CardHeader5, { children: [
1338
- /* @__PURE__ */ jsx12(CardTitle5, { children: "Invalid verification link" }),
1339
- /* @__PURE__ */ jsx12(CardDescription5, { children: status === "no-token" ? "No verification token found. Check the link in your email." : "This verification link is invalid. Request a new one below." })
1357
+ /* @__PURE__ */ jsx12(CardTitle5, { children: isWaitingForEmailClick ? "Check your email" : "Invalid verification link" }),
1358
+ /* @__PURE__ */ jsx12(CardDescription5, { children: isWaitingForEmailClick ? "We sent a verification link to your email address. Please check your inbox to continue." : status === "no-token" ? "No verification token found. Check the link in your email." : "This verification link is invalid. Request a new one below." })
1340
1359
  ] }),
1341
1360
  /* @__PURE__ */ jsx12(CardContent5, { children: resendSection }),
1342
1361
  /* @__PURE__ */ jsx12(CardFooter5, { children: /* @__PURE__ */ jsx12("a", { href: routes.signin, className: "text-sm text-muted-foreground hover:underline", children: "Back to sign in" }) })
@@ -1399,6 +1418,9 @@ function formatMemberSince(value) {
1399
1418
  });
1400
1419
  }
1401
1420
  function SettingsTopBar() {
1421
+ const config = useOptionalConfig();
1422
+ const appTitle = config?.appName ?? "Boring UI";
1423
+ const appInitial = (appTitle.trim().charAt(0) || "B").toUpperCase();
1402
1424
  return /* @__PURE__ */ jsx13(
1403
1425
  "header",
1404
1426
  {
@@ -1410,10 +1432,10 @@ function SettingsTopBar() {
1410
1432
  {
1411
1433
  "aria-hidden": "true",
1412
1434
  className: "flex h-7 w-7 shrink-0 items-center justify-center rounded-md bg-foreground text-[12px] font-semibold text-background",
1413
- children: "S"
1435
+ children: appInitial
1414
1436
  }
1415
1437
  ),
1416
- /* @__PURE__ */ jsx13("span", { className: "truncate text-[13px] font-medium tracking-tight text-foreground", children: "Sovereign Workspace" }),
1438
+ /* @__PURE__ */ jsx13("span", { className: "truncate text-[13px] font-medium tracking-tight text-foreground", children: appTitle }),
1417
1439
  /* @__PURE__ */ jsx13("span", { "aria-hidden": "true", className: "text-muted-foreground/30", children: "/" }),
1418
1440
  /* @__PURE__ */ jsx13("span", { className: "truncate text-[13px] text-muted-foreground", children: "Account settings" })
1419
1441
  ] })
@@ -1740,7 +1762,7 @@ function UserSettingsPage({ topBar, extraSections = [] } = {}) {
1740
1762
 
1741
1763
  // src/front/auth/InviteAcceptPage.tsx
1742
1764
  import { useCallback as useCallback5, useState as useState14 } from "react";
1743
- import { useParams as useParams2, useNavigate } from "react-router-dom";
1765
+ import { useParams as useParams2, useNavigate as useNavigate2 } from "react-router-dom";
1744
1766
  import { useQuery as useQuery3, useMutation, useQueryClient as useQueryClient2 } from "@tanstack/react-query";
1745
1767
  import {
1746
1768
  Button as Button9,
@@ -1757,7 +1779,7 @@ import { jsx as jsx14, jsxs as jsxs7 } from "react/jsx-runtime";
1757
1779
  function InviteAcceptPage() {
1758
1780
  const { token } = useParams2();
1759
1781
  const session = useSession();
1760
- const navigate = useNavigate();
1782
+ const navigate = useNavigate2();
1761
1783
  const queryClient = useQueryClient2();
1762
1784
  const [acceptError, setAcceptError] = useState14(null);
1763
1785
  const isSignedIn = Boolean(session.data);
@@ -1891,6 +1913,15 @@ import { matchPath as matchPath2 } from "react-router-dom";
1891
1913
  import { Fragment as Fragment3, jsx as jsx15 } from "react/jsx-runtime";
1892
1914
  var DEFAULT_GRACE_MS = 3e4;
1893
1915
  var UNSAFE_REDIRECT_RE = /[\0\r\n<>"'`]/;
1916
+ var UNVERIFIED_ALLOWED_PATHS = /* @__PURE__ */ new Set([
1917
+ routes.signup,
1918
+ routes.forgotPassword,
1919
+ routes.resetPassword,
1920
+ routes.verifyEmail,
1921
+ routes.authError,
1922
+ routes.callbackGithub,
1923
+ routes.callbackGoogle
1924
+ ]);
1894
1925
  function normalizePath(pathname) {
1895
1926
  if (!pathname) return "/";
1896
1927
  return pathname.startsWith("/") ? pathname : `/${pathname}`;
@@ -1921,6 +1952,9 @@ function isPublicPath(pathname, publicPaths) {
1921
1952
  return normalizedPath === candidate || normalizedPath.startsWith(`${candidate}/`);
1922
1953
  });
1923
1954
  }
1955
+ function shouldBlockUnverifiedUser(pathname, hasSession, requireEmailVerification, isEmailVerified) {
1956
+ return hasSession && requireEmailVerification && !isEmailVerified && !UNVERIFIED_ALLOWED_PATHS.has(normalizePath(pathname));
1957
+ }
1924
1958
  function readSafeRedirect(search) {
1925
1959
  const redirect = new URLSearchParams(normalizeSearch(search)).get("redirect");
1926
1960
  if (!redirect) return null;
@@ -1950,7 +1984,8 @@ function AuthGate({
1950
1984
  graceMs = DEFAULT_GRACE_MS,
1951
1985
  location,
1952
1986
  navigate,
1953
- now
1987
+ now,
1988
+ requireEmailVerification = false
1954
1989
  }) {
1955
1990
  const session = useSession();
1956
1991
  const nullSinceRef = useRef5(null);
@@ -1962,6 +1997,7 @@ function AuthGate({
1962
1997
  () => publicPaths.map(normalizePublicPath),
1963
1998
  [publicPaths]
1964
1999
  );
2000
+ const isEmailVerified = session.data?.user?.emailVerified ?? false;
1965
2001
  useEffect9(() => {
1966
2002
  return () => {
1967
2003
  if (!redirectTimerRef.current) return;
@@ -1978,6 +2014,10 @@ function AuthGate({
1978
2014
  const currentPath = buildCurrentPath(currentLocation);
1979
2015
  if (session.data) {
1980
2016
  nullSinceRef.current = null;
2017
+ if (shouldBlockUnverifiedUser(pathname2, true, requireEmailVerification, isEmailVerified)) {
2018
+ goTo(routes.verifyEmail, { replace: true });
2019
+ return;
2020
+ }
1981
2021
  if (pathname2 === routes.signin) {
1982
2022
  const destination = readSafeRedirect(currentLocation.search) ?? "/";
1983
2023
  if (destination !== currentPath) {
@@ -2008,15 +2048,16 @@ function AuthGate({
2008
2048
  goTo(`${routes.signin}?redirect=${encodeURIComponent(currentPath)}`, { replace: true });
2009
2049
  }, remainingMs);
2010
2050
  redirectTimerRef.current.unref?.();
2011
- }, [currentLocation, goTo, graceMs, normalizedPublicPaths, readNow, session.data, session.isPending]);
2051
+ }, [currentLocation, goTo, graceMs, isEmailVerified, normalizedPublicPaths, readNow, requireEmailVerification, session.data, session.isPending]);
2012
2052
  const pathname = normalizePath(currentLocation.pathname);
2053
+ if (shouldBlockUnverifiedUser(pathname, Boolean(session.data), requireEmailVerification, isEmailVerified)) return null;
2013
2054
  if (session.isPending && !isPublicPath(pathname, normalizedPublicPaths)) return null;
2014
2055
  return /* @__PURE__ */ jsx15(Fragment3, { children });
2015
2056
  }
2016
2057
 
2017
2058
  // src/front/CoreFront.tsx
2018
2059
  import { Suspense, useCallback as useCallback9, useMemo as useMemo6 } from "react";
2019
- import { BrowserRouter, Routes, Route, useLocation as useLocation2, useNavigate as useNavigate5 } from "react-router-dom";
2060
+ import { BrowserRouter, Routes, Route, useLocation as useLocation2, useNavigate as useNavigate6 } from "react-router-dom";
2020
2061
  import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
2021
2062
  import { Helmet, HelmetProvider } from "react-helmet-async";
2022
2063
 
@@ -2040,7 +2081,7 @@ import {
2040
2081
  Settings,
2041
2082
  Sun
2042
2083
  } from "lucide-react";
2043
- import { useNavigate as useNavigate2 } from "react-router-dom";
2084
+ import { useNavigate as useNavigate3 } from "react-router-dom";
2044
2085
  import { jsx as jsx16, jsxs as jsxs8 } from "react/jsx-runtime";
2045
2086
  var THEME_ORDER = ["light", "dark", "system"];
2046
2087
  function labelForTheme(preference) {
@@ -2062,7 +2103,7 @@ function initialsFor2(name, email) {
2062
2103
  function UserMenu() {
2063
2104
  const identity = useUser();
2064
2105
  const signOut = useSignOut();
2065
- const navigate = useNavigate2();
2106
+ const navigate = useNavigate3();
2066
2107
  const { preference, setTheme } = useTheme();
2067
2108
  const [isSigningOut, setIsSigningOut] = useState15(false);
2068
2109
  const user = identity?.user;
@@ -2192,7 +2233,7 @@ import {
2192
2233
  useToast
2193
2234
  } from "@hachej/boring-ui-kit";
2194
2235
  import { ChevronsUpDown, LayoutGrid, Plus, Settings as Settings2 } from "lucide-react";
2195
- import { useNavigate as useNavigate3 } from "react-router-dom";
2236
+ import { useNavigate as useNavigate4 } from "react-router-dom";
2196
2237
  import { z as z6 } from "zod";
2197
2238
  import { Fragment as Fragment4, jsx as jsx17, jsxs as jsxs9 } from "react/jsx-runtime";
2198
2239
  var workspaceNameSchema = z6.object({
@@ -2238,10 +2279,12 @@ function OpenInNewTabIcon({ className }) {
2238
2279
  ] });
2239
2280
  }
2240
2281
  function WorkspaceSwitcher({
2241
- appTitle = "Sovereign Workspace",
2282
+ appTitle,
2242
2283
  workspacePathPrefix = "/workspace"
2243
2284
  }) {
2244
- const navigate = useNavigate3();
2285
+ const config = useOptionalConfig();
2286
+ const resolvedAppTitle = appTitle ?? config?.appName ?? "Boring UI";
2287
+ const navigate = useNavigate4();
2245
2288
  const queryClient = useQueryClient3();
2246
2289
  const { toast } = useToastCompat();
2247
2290
  const currentWorkspace = useCurrentWorkspace();
@@ -2328,7 +2371,7 @@ function WorkspaceSwitcher({
2328
2371
  {
2329
2372
  "aria-hidden": "true",
2330
2373
  className: "flex h-7 w-7 shrink-0 items-center justify-center rounded-md bg-foreground text-[12px] font-semibold text-background",
2331
- children: appTitle.charAt(0).toUpperCase()
2374
+ children: resolvedAppTitle.charAt(0).toUpperCase()
2332
2375
  }
2333
2376
  ),
2334
2377
  /* @__PURE__ */ jsx17("span", { className: "text-[13px] font-medium text-foreground", children: "Create your first workspace" })
@@ -2348,11 +2391,11 @@ function WorkspaceSwitcher({
2348
2391
  {
2349
2392
  "aria-hidden": "true",
2350
2393
  className: "flex h-7 w-7 shrink-0 items-center justify-center rounded-md bg-foreground text-[12px] font-semibold text-background",
2351
- children: appTitle.charAt(0).toUpperCase()
2394
+ children: resolvedAppTitle.charAt(0).toUpperCase()
2352
2395
  }
2353
2396
  ),
2354
2397
  /* @__PURE__ */ jsxs9("span", { className: "flex min-w-0 items-center gap-1.5", children: [
2355
- /* @__PURE__ */ jsx17("span", { className: "truncate text-[13px] font-medium text-foreground", children: appTitle }),
2398
+ /* @__PURE__ */ jsx17("span", { className: "truncate text-[13px] font-medium text-foreground", children: resolvedAppTitle }),
2356
2399
  /* @__PURE__ */ jsx17("span", { "aria-hidden": "true", className: "text-muted-foreground/30", children: "/" }),
2357
2400
  /* @__PURE__ */ jsx17("span", { className: "truncate text-[13px] font-normal text-muted-foreground", children: switcherLabel })
2358
2401
  ] }),
@@ -2985,7 +3028,7 @@ function MembersPage() {
2985
3028
  // src/front/workspace/WorkspaceSettingsPage.tsx
2986
3029
  import { useCallback as useCallback8, useEffect as useEffect10, useState as useState19 } from "react";
2987
3030
  import { useQuery as useQuery6, useMutation as useMutation4, useQueryClient as useQueryClient6 } from "@tanstack/react-query";
2988
- import { useNavigate as useNavigate4 } from "react-router-dom";
3031
+ import { useNavigate as useNavigate5 } from "react-router-dom";
2989
3032
  import {
2990
3033
  AlertDialog as AlertDialog3,
2991
3034
  AlertDialogCancel as AlertDialogCancel3,
@@ -3020,7 +3063,10 @@ var STATE_TONES = {
3020
3063
  error: "danger"
3021
3064
  };
3022
3065
  function SettingsTopBar2({ workspaceId, workspaceName }) {
3023
- const navigate = useNavigate4();
3066
+ const config = useOptionalConfig();
3067
+ const appTitle = config?.appName ?? "Boring UI";
3068
+ const appInitial = (appTitle.trim().charAt(0) || "B").toUpperCase();
3069
+ const navigate = useNavigate5();
3024
3070
  const workspaceHref = workspaceId ? `/workspace/${encodeURIComponent(workspaceId)}` : "/";
3025
3071
  return /* @__PURE__ */ jsx21(
3026
3072
  "header",
@@ -3038,10 +3084,10 @@ function SettingsTopBar2({ workspaceId, workspaceName }) {
3038
3084
  title: "Back to workspace",
3039
3085
  onClick: () => navigate(workspaceHref),
3040
3086
  className: "shrink-0 bg-foreground text-[12px] font-semibold text-background hover:bg-foreground/90",
3041
- children: "S"
3087
+ children: appInitial
3042
3088
  }
3043
3089
  ),
3044
- /* @__PURE__ */ jsx21("span", { className: "truncate text-[13px] font-medium tracking-tight text-foreground", children: "Sovereign Workspace" }),
3090
+ /* @__PURE__ */ jsx21("span", { className: "truncate text-[13px] font-medium tracking-tight text-foreground", children: appTitle }),
3045
3091
  /* @__PURE__ */ jsx21("span", { "aria-hidden": "true", className: "text-muted-foreground/30", children: "/" }),
3046
3092
  /* @__PURE__ */ jsx21("span", { className: "truncate text-[13px] text-muted-foreground", children: workspaceName }),
3047
3093
  /* @__PURE__ */ jsx21("span", { "aria-hidden": "true", className: "text-muted-foreground/30", children: "/" }),
@@ -3091,7 +3137,7 @@ function WorkspaceSettingsPage({ topBar } = {}) {
3091
3137
  const workspace = useCurrentWorkspace();
3092
3138
  const role = useWorkspaceRole();
3093
3139
  const queryClient = useQueryClient6();
3094
- const navigate = useNavigate4();
3140
+ const navigate = useNavigate5();
3095
3141
  const workspaceId = workspace?.id ?? "";
3096
3142
  const [nameValue, setNameValue] = useState19(null);
3097
3143
  const [nameError, setNameError] = useState19(null);
@@ -3533,8 +3579,9 @@ function createDefaultQueryClient() {
3533
3579
  });
3534
3580
  }
3535
3581
  function RouterAuthGate({ children, publicPaths }) {
3582
+ const config = useConfig();
3536
3583
  const location = useLocation2();
3537
- const navigate = useNavigate5();
3584
+ const navigate = useNavigate6();
3538
3585
  const authLocation = useMemo6(
3539
3586
  () => ({ pathname: location.pathname, search: location.search, hash: location.hash }),
3540
3587
  [location.hash, location.pathname, location.search]
@@ -3551,6 +3598,7 @@ function RouterAuthGate({ children, publicPaths }) {
3551
3598
  location: authLocation,
3552
3599
  navigate: navigateWithinRouter,
3553
3600
  publicPaths,
3601
+ requireEmailVerification: isRuntimeEmailVerificationEnabled(config),
3554
3602
  children
3555
3603
  }
3556
3604
  );
@@ -3605,7 +3653,7 @@ function CoreFront({ children, authPages, cspNonce, workspaceRoute, workspaceIdP
3605
3653
 
3606
3654
  // src/front/commands/CoreCommandContributions.tsx
3607
3655
  import { useMemo as useMemo7, useState as useState20 } from "react";
3608
- import { useNavigate as useNavigate6 } from "react-router-dom";
3656
+ import { useNavigate as useNavigate7 } from "react-router-dom";
3609
3657
 
3610
3658
  // src/front/workspace/commands.ts
3611
3659
  function getWorkspaceCommands(workspaceId, navigate) {
@@ -3643,7 +3691,7 @@ function toPaletteCommand(command) {
3643
3691
  };
3644
3692
  }
3645
3693
  function useCoreCommands() {
3646
- const navigate = useNavigate6();
3694
+ const navigate = useNavigate7();
3647
3695
  const signOut = useSignOut();
3648
3696
  const workspace = useCurrentWorkspace();
3649
3697
  const [isSigningOut, setIsSigningOut] = useState20(false);
@@ -5,6 +5,7 @@ var ERROR_CODES = {
5
5
  FORBIDDEN: "forbidden",
6
6
  WEAK_PASSWORD: "weak_password",
7
7
  EMAIL_IN_USE: "email_in_use",
8
+ EMAIL_NOT_VERIFIED: "email_not_verified",
8
9
  // Workspace membership
9
10
  NOT_MEMBER: "not_member",
10
11
  LAST_OWNER: "last_owner",
@@ -12,16 +12,19 @@ import {
12
12
  workspaceRuntimes,
13
13
  workspaceSettings,
14
14
  workspaces
15
- } from "./chunk-FZC3VL5D.js";
15
+ } from "./chunk-BVZ2YT3M.js";
16
16
  import {
17
17
  noopTelemetry,
18
18
  safeCapture
19
19
  } from "./chunk-AQBXNPMD.js";
20
+ import {
21
+ isCoreEmailVerificationEnabled
22
+ } from "./chunk-I4PGL4ZD.js";
20
23
  import {
21
24
  ConfigValidationError,
22
25
  ERROR_CODES,
23
26
  HttpError
24
- } from "./chunk-LIBHVT7V.js";
27
+ } from "./chunk-QZGYKLXB.js";
25
28
 
26
29
  // src/server/config/schema.ts
27
30
  import { z } from "zod";
@@ -290,7 +293,8 @@ function buildRuntimeConfigPayload(config) {
290
293
  githubOauth: config.features.githubOauth,
291
294
  googleOauth: isGoogleOauthUsable(config),
292
295
  invitesEnabled: config.features.invitesEnabled,
293
- sendWelcomeEmail: config.features.sendWelcomeEmail
296
+ sendWelcomeEmail: config.features.sendWelcomeEmail,
297
+ emailVerification: isCoreEmailVerificationEnabled(config)
294
298
  }
295
299
  };
296
300
  }
@@ -419,7 +423,7 @@ function registerCapabilities(app) {
419
423
  contributors.set(name, fn);
420
424
  }
421
425
  );
422
- const hasMail = !!app.config.auth.mail;
426
+ const emailVerificationEnabled = isCoreEmailVerificationEnabled(app.config);
423
427
  app.registerCapabilitiesContributor("core", () => {
424
428
  const googleOauth = isGoogleOauthUsable(app.config);
425
429
  const core = {
@@ -428,15 +432,15 @@ function registerCapabilities(app) {
428
432
  invitesEnabled: app.config.features.invitesEnabled,
429
433
  githubOauth: app.config.features.githubOauth,
430
434
  googleOauth,
431
- emailFlows: hasMail
435
+ emailFlows: emailVerificationEnabled
432
436
  },
433
437
  auth: {
434
438
  emailPassword: true,
435
439
  github: false,
436
440
  google: googleOauth,
437
- emailVerification: hasMail,
438
- passwordReset: hasMail,
439
- magicLink: hasMail
441
+ emailVerification: emailVerificationEnabled,
442
+ passwordReset: emailVerificationEnabled,
443
+ magicLink: emailVerificationEnabled
440
444
  }
441
445
  };
442
446
  return { core };
@@ -1747,7 +1751,8 @@ function buildMailTransport(config) {
1747
1751
  function createAuth(config, db, opts) {
1748
1752
  const transport = buildMailTransport(config);
1749
1753
  const telemetry = opts?.telemetry ?? noopTelemetry;
1750
- const emailVerificationConfig = transport ? {
1754
+ const emailVerificationEnabled = isCoreEmailVerificationEnabled(config);
1755
+ const emailVerificationConfig = emailVerificationEnabled && transport ? {
1751
1756
  sendOnSignUp: true,
1752
1757
  sendVerificationEmail: async (data) => {
1753
1758
  const email = await renderVerifyEmail({
@@ -1930,7 +1935,8 @@ var authHookPlugin = async (app, opts) => {
1930
1935
  request.user = {
1931
1936
  id: result.user.id,
1932
1937
  email: result.user.email,
1933
- name: result.user.name ?? null
1938
+ name: result.user.name ?? null,
1939
+ emailVerified: Boolean(result.user.emailVerified)
1934
1940
  };
1935
1941
  }
1936
1942
  } catch {
@@ -1938,7 +1944,8 @@ var authHookPlugin = async (app, opts) => {
1938
1944
  }
1939
1945
  }
1940
1946
  const path = request.url.split("?")[0];
1941
- if (path.startsWith("/api/v1/") && !publicPatterns.some((re) => re.test(path)) && !request.user) {
1947
+ const isProtectedApi = path.startsWith("/api/v1/") && !publicPatterns.some((re) => re.test(path));
1948
+ if (isProtectedApi && !request.user) {
1942
1949
  throw new HttpError({
1943
1950
  status: 401,
1944
1951
  code: ERROR_CODES.UNAUTHORIZED,
@@ -1946,6 +1953,14 @@ var authHookPlugin = async (app, opts) => {
1946
1953
  requestId: request.id
1947
1954
  });
1948
1955
  }
1956
+ if (isProtectedApi && isCoreEmailVerificationEnabled(app.config) && request.user?.emailVerified !== true) {
1957
+ throw new HttpError({
1958
+ status: 403,
1959
+ code: ERROR_CODES.EMAIL_NOT_VERIFIED,
1960
+ message: "Email verification required",
1961
+ requestId: request.id
1962
+ });
1963
+ }
1949
1964
  });
1950
1965
  };
1951
1966
  var authHook = fp2(authHookPlugin, { name: "auth-hook" });
@@ -1,4 +1,4 @@
1
- import { C as CoreConfig, W as Workspace, E as ERROR_CODES, M as MemberRole, a as WorkspaceMember, U as User, b as WorkspaceInvite, c as WorkspaceRuntime, d as WorkspaceRuntimeResourceSelector, e as WorkspaceRuntimeResource, f as WorkspaceRuntimeResourceInput, g as CapabilitiesResponse } from './types-CWtJ4kgd.js';
1
+ import { C as CoreConfig, c as Workspace, E as ERROR_CODES, M as MemberRole, d as WorkspaceMember, U as User, e as WorkspaceInvite, f as WorkspaceRuntime, W as WorkspaceRuntimeResourceSelector, a as WorkspaceRuntimeResource, b as WorkspaceRuntimeResourceInput, g as CapabilitiesResponse } from './types-Bb7I-83I.js';
2
2
  import { drizzle } from 'drizzle-orm/postgres-js';
3
3
  import postgres from 'postgres';
4
4
 
@@ -131,6 +131,7 @@ declare module 'fastify' {
131
131
  id: string;
132
132
  email: string;
133
133
  name: string | null;
134
+ emailVerified: boolean;
134
135
  } | null;
135
136
  cspNonce?: string;
136
137
  }
@@ -142,4 +143,4 @@ declare function createDatabase(config: CoreConfig): {
142
143
  sql: postgres.Sql;
143
144
  };
144
145
 
145
- export { type AuthProvider as A, type CreateCoreAppOptions as C, type Database as D, type ProvisionContext as P, type UserStore as U, type WorkspaceStore as W, type WorkspaceProvisioner as a, type CapabilitiesContributor as b, createDatabase as c, type ProvisionResult as d };
146
+ export { type AuthProvider as A, type CreateCoreAppOptions as C, type Database as D, type ProvisionContext as P, type UserStore as U, type WorkspaceStore as W, type WorkspaceProvisioner as a, type CapabilitiesContributor as b, type ProvisionResult as c, createDatabase as d };
@@ -1,7 +1,7 @@
1
1
  import * as react_jsx_runtime from 'react/jsx-runtime';
2
2
  import * as react from 'react';
3
3
  import { Component, ReactNode, ErrorInfo } from 'react';
4
- import { R as RuntimeConfig, g as CapabilitiesResponse, a as WorkspaceMember, U as User, W as Workspace, M as MemberRole, S as SessionState } from '../types-CWtJ4kgd.js';
4
+ import { R as RuntimeConfig, g as CapabilitiesResponse, d as WorkspaceMember, U as User, c as Workspace, M as MemberRole, S as SessionState } from '../types-Bb7I-83I.js';
5
5
  import * as _tanstack_react_query from '@tanstack/react-query';
6
6
  import * as better_auth_react from 'better-auth/react';
7
7
  import { createAuthClient } from 'better-auth/react';
@@ -461,8 +461,9 @@ interface AuthGateProps {
461
461
  replace?: boolean;
462
462
  }) => void;
463
463
  now?: () => number;
464
+ requireEmailVerification?: boolean;
464
465
  }
465
- declare function AuthGate({ children, publicPaths, graceMs, location, navigate, now, }: AuthGateProps): react_jsx_runtime.JSX.Element | null;
466
+ declare function AuthGate({ children, publicPaths, graceMs, location, navigate, now, requireEmailVerification, }: AuthGateProps): react_jsx_runtime.JSX.Element | null;
466
467
 
467
468
  interface CoreCommand {
468
469
  id: string;
@@ -58,12 +58,13 @@ import {
58
58
  useWorkspaceMembers,
59
59
  useWorkspaceRole,
60
60
  useWorkspaceRouteStatus
61
- } from "../chunk-VYXEXOCO.js";
61
+ } from "../chunk-P4RF2D7H.js";
62
62
  import {
63
63
  TopBarSlotProvider,
64
64
  useTopBarSlot
65
65
  } from "../chunk-HYNKZSTF.js";
66
- import "../chunk-LIBHVT7V.js";
66
+ import "../chunk-I4PGL4ZD.js";
67
+ import "../chunk-QZGYKLXB.js";
67
68
  import "../chunk-MLKGABMK.js";
68
69
  export {
69
70
  AppErrorBoundary,
@@ -1,7 +1,7 @@
1
- import { U as UserStore, W as WorkspaceStore } from '../../connection-C5SiqoNc.js';
2
- export { D as Database, c as createDatabase } from '../../connection-C5SiqoNc.js';
3
- export { F as FinishReservationInput, G as GrantOnceInput, I as InsufficientCreditError, M as MeteringBalance, P as PostgresMeteringStore, R as RecordUsageInput, a as RecordUsageResult, b as ReservationFinalStatus, c as ReserveInput, d as ReserveResult, e as RunMigrationsOptions, r as runMigrations } from '../../PostgresMeteringStore-DhOVVtau.js';
4
- import { U as User, W as Workspace, E as ERROR_CODES, M as MemberRole, a as WorkspaceMember, b as WorkspaceInvite, c as WorkspaceRuntime, d as WorkspaceRuntimeResourceSelector, e as WorkspaceRuntimeResource, f as WorkspaceRuntimeResourceInput } from '../../types-CWtJ4kgd.js';
1
+ import { U as UserStore, W as WorkspaceStore } from '../../connection-B1iC6B-w.js';
2
+ export { D as Database, d as createDatabase } from '../../connection-B1iC6B-w.js';
3
+ export { F as FinishReservationInput, G as GrantOnceInput, I as InsufficientCreditError, M as MeteringBalance, P as PostgresMeteringStore, R as RecordUsageInput, a as RecordUsageResult, b as ReservationFinalStatus, c as ReserveInput, d as ReserveResult, e as RunMigrationsOptions, r as runMigrations } from '../../PostgresMeteringStore-qjKGmVFr.js';
4
+ import { U as User, c as Workspace, E as ERROR_CODES, M as MemberRole, d as WorkspaceMember, e as WorkspaceInvite, f as WorkspaceRuntime, W as WorkspaceRuntimeResourceSelector, a as WorkspaceRuntimeResource, b as WorkspaceRuntimeResourceInput } from '../../types-Bb7I-83I.js';
5
5
  import { PostgresJsDatabase } from 'drizzle-orm/postgres-js';
6
6
  import 'postgres';
7
7
 
@@ -7,8 +7,8 @@ import {
7
7
  PostgresWorkspaceStore,
8
8
  createDatabase,
9
9
  runMigrations
10
- } from "../../chunk-FZC3VL5D.js";
11
- import "../../chunk-LIBHVT7V.js";
10
+ } from "../../chunk-BVZ2YT3M.js";
11
+ import "../../chunk-QZGYKLXB.js";
12
12
  import "../../chunk-MLKGABMK.js";
13
13
  export {
14
14
  InsufficientCreditError,