diff --git a/internal/server/auth.go b/internal/server/auth.go
index 1e6ca52..c47e8ae 100644
--- a/internal/server/auth.go
+++ b/internal/server/auth.go
@@ -77,7 +77,7 @@ func (h *Handler) RegisterEndpoint(w http.ResponseWriter, r *http.Request) {
type loginRequest struct {
Email string `json:"email" validate:"required,email,max=254"`
- Password string `json:"password" validate:"min=8"`
+ Password string `json:"password" validate:"required"`
}
var dummyHash, _ = hashPassword("dummy-password-for-timing")
diff --git a/web/src/main.tsx b/web/src/main.tsx
index 59224e6..229be48 100644
--- a/web/src/main.tsx
+++ b/web/src/main.tsx
@@ -1,11 +1,28 @@
import { StrictMode } from "react";
import { createRoot } from "react-dom/client";
-import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
+import {
+ MutationCache,
+ QueryCache,
+ QueryClient,
+ QueryClientProvider,
+} from "@tanstack/react-query";
import { RouterProvider, createRouter } from "@tanstack/react-router";
import { routeTree } from "./routeTree.gen";
+import { meQuery } from "@/api/auth/me";
import "./index.css";
-const queryClient = new QueryClient();
+function handleUnauthorized(error: unknown) {
+ if ((error as { status?: number } | null)?.status !== 401) return;
+ if (!queryClient.getQueryData(meQuery.queryKey)) return;
+
+ queryClient.setQueryData(meQuery.queryKey, null);
+ router.invalidate();
+}
+
+const queryClient = new QueryClient({
+ queryCache: new QueryCache({ onError: handleUnauthorized }),
+ mutationCache: new MutationCache({ onError: handleUnauthorized }),
+});
const router = createRouter({ routeTree, context: { queryClient } });
declare module "@tanstack/react-router" {
diff --git a/web/src/routeTree.gen.ts b/web/src/routeTree.gen.ts
index 8db2531..23f6be5 100644
--- a/web/src/routeTree.gen.ts
+++ b/web/src/routeTree.gen.ts
@@ -10,10 +10,11 @@
import { Route as rootRouteImport } from './routes/__root'
import { Route as IndexRouteImport } from './routes/index'
+import { Route as GuestRouteImport } from './routes/_guest'
import { Route as ClientRouteRouteImport } from './routes/client/route'
-import { Route as authLoginRouteImport } from './routes/(auth)/login'
import { Route as authLogoutRouteImport } from './routes/(auth)/logout'
-import { Route as authRegisterRouteImport } from './routes/(auth)/register'
+import { Route as GuestLoginRouteImport } from './routes/_guest/login'
+import { Route as GuestRegisterRouteImport } from './routes/_guest/register'
import { Route as ClientIndexRouteImport } from './routes/client/index'
const IndexRoute = IndexRouteImport.update({
@@ -21,25 +22,29 @@ const IndexRoute = IndexRouteImport.update({
path: '/',
getParentRoute: () => rootRouteImport,
} as any)
+const GuestRoute = GuestRouteImport.update({
+ id: '/_guest',
+ getParentRoute: () => rootRouteImport,
+} as any)
const ClientRouteRoute = ClientRouteRouteImport.update({
id: '/client',
path: '/client',
getParentRoute: () => rootRouteImport,
} as any)
-const authLoginRoute = authLoginRouteImport.update({
- id: '/(auth)/login',
- path: '/login',
- getParentRoute: () => rootRouteImport,
-} as any)
const authLogoutRoute = authLogoutRouteImport.update({
id: '/(auth)/logout',
path: '/logout',
getParentRoute: () => rootRouteImport,
} as any)
-const authRegisterRoute = authRegisterRouteImport.update({
- id: '/(auth)/register',
+const GuestLoginRoute = GuestLoginRouteImport.update({
+ id: '/login',
+ path: '/login',
+ getParentRoute: () => GuestRoute,
+} as any)
+const GuestRegisterRoute = GuestRegisterRouteImport.update({
+ id: '/register',
path: '/register',
- getParentRoute: () => rootRouteImport,
+ getParentRoute: () => GuestRoute,
} as any)
const ClientIndexRoute = ClientIndexRouteImport.update({
id: '/',
@@ -50,48 +55,49 @@ const ClientIndexRoute = ClientIndexRouteImport.update({
export interface FileRoutesByFullPath {
'/': typeof IndexRoute
'/client': typeof ClientRouteRouteWithChildren
- '/login': typeof authLoginRoute
'/logout': typeof authLogoutRoute
- '/register': typeof authRegisterRoute
+ '/login': typeof GuestLoginRoute
+ '/register': typeof GuestRegisterRoute
'/client/': typeof ClientIndexRoute
}
export interface FileRoutesByTo {
'/': typeof IndexRoute
- '/login': typeof authLoginRoute
'/logout': typeof authLogoutRoute
- '/register': typeof authRegisterRoute
+ '/login': typeof GuestLoginRoute
+ '/register': typeof GuestRegisterRoute
'/client': typeof ClientIndexRoute
}
export interface FileRoutesById {
__root__: typeof rootRouteImport
'/': typeof IndexRoute
'/client': typeof ClientRouteRouteWithChildren
- '/(auth)/login': typeof authLoginRoute
+ '/_guest': typeof GuestRouteWithChildren
'/(auth)/logout': typeof authLogoutRoute
- '/(auth)/register': typeof authRegisterRoute
+ '/_guest/login': typeof GuestLoginRoute
+ '/_guest/register': typeof GuestRegisterRoute
'/client/': typeof ClientIndexRoute
}
export interface FileRouteTypes {
fileRoutesByFullPath: FileRoutesByFullPath
- fullPaths: '/' | '/client' | '/login' | '/logout' | '/register' | '/client/'
+ fullPaths: '/' | '/client' | '/logout' | '/login' | '/register' | '/client/'
fileRoutesByTo: FileRoutesByTo
- to: '/' | '/login' | '/logout' | '/register' | '/client'
+ to: '/' | '/logout' | '/login' | '/register' | '/client'
id:
| '__root__'
| '/'
| '/client'
- | '/(auth)/login'
+ | '/_guest'
| '/(auth)/logout'
- | '/(auth)/register'
+ | '/_guest/login'
+ | '/_guest/register'
| '/client/'
fileRoutesById: FileRoutesById
}
export interface RootRouteChildren {
IndexRoute: typeof IndexRoute
ClientRouteRoute: typeof ClientRouteRouteWithChildren
- authLoginRoute: typeof authLoginRoute
+ GuestRoute: typeof GuestRouteWithChildren
authLogoutRoute: typeof authLogoutRoute
- authRegisterRoute: typeof authRegisterRoute
}
declare module '@tanstack/react-router' {
@@ -103,6 +109,13 @@ declare module '@tanstack/react-router' {
preLoaderRoute: typeof IndexRouteImport
parentRoute: typeof rootRouteImport
}
+ '/_guest': {
+ id: '/_guest'
+ path: ''
+ fullPath: '/'
+ preLoaderRoute: typeof GuestRouteImport
+ parentRoute: typeof rootRouteImport
+ }
'/client': {
id: '/client'
path: '/client'
@@ -110,13 +123,6 @@ declare module '@tanstack/react-router' {
preLoaderRoute: typeof ClientRouteRouteImport
parentRoute: typeof rootRouteImport
}
- '/(auth)/login': {
- id: '/(auth)/login'
- path: '/login'
- fullPath: '/login'
- preLoaderRoute: typeof authLoginRouteImport
- parentRoute: typeof rootRouteImport
- }
'/(auth)/logout': {
id: '/(auth)/logout'
path: '/logout'
@@ -124,12 +130,19 @@ declare module '@tanstack/react-router' {
preLoaderRoute: typeof authLogoutRouteImport
parentRoute: typeof rootRouteImport
}
- '/(auth)/register': {
- id: '/(auth)/register'
+ '/_guest/login': {
+ id: '/_guest/login'
+ path: '/login'
+ fullPath: '/login'
+ preLoaderRoute: typeof GuestLoginRouteImport
+ parentRoute: typeof GuestRoute
+ }
+ '/_guest/register': {
+ id: '/_guest/register'
path: '/register'
fullPath: '/register'
- preLoaderRoute: typeof authRegisterRouteImport
- parentRoute: typeof rootRouteImport
+ preLoaderRoute: typeof GuestRegisterRouteImport
+ parentRoute: typeof GuestRoute
}
'/client/': {
id: '/client/'
@@ -153,12 +166,23 @@ const ClientRouteRouteWithChildren = ClientRouteRoute._addFileChildren(
ClientRouteRouteChildren,
)
+interface GuestRouteChildren {
+ GuestLoginRoute: typeof GuestLoginRoute
+ GuestRegisterRoute: typeof GuestRegisterRoute
+}
+
+const GuestRouteChildren: GuestRouteChildren = {
+ GuestLoginRoute: GuestLoginRoute,
+ GuestRegisterRoute: GuestRegisterRoute,
+}
+
+const GuestRouteWithChildren = GuestRoute._addFileChildren(GuestRouteChildren)
+
const rootRouteChildren: RootRouteChildren = {
IndexRoute: IndexRoute,
ClientRouteRoute: ClientRouteRouteWithChildren,
- authLoginRoute: authLoginRoute,
+ GuestRoute: GuestRouteWithChildren,
authLogoutRoute: authLogoutRoute,
- authRegisterRoute: authRegisterRoute,
}
export const routeTree = rootRouteImport
._addFileChildren(rootRouteChildren)
diff --git a/web/src/routes/_guest.tsx b/web/src/routes/_guest.tsx
new file mode 100644
index 0000000..d6ebe43
--- /dev/null
+++ b/web/src/routes/_guest.tsx
@@ -0,0 +1,13 @@
+import { Outlet, createFileRoute, redirect } from "@tanstack/react-router";
+
+import { meQuery } from "@/api/auth/me";
+
+export const Route = createFileRoute("/_guest")({
+ beforeLoad: async ({ context }) => {
+ const user = await context.queryClient.query(meQuery);
+ if (user) {
+ throw redirect({ to: "/client" });
+ }
+ },
+ component: () => ,
+});
diff --git a/web/src/routes/(auth)/login.tsx b/web/src/routes/_guest/login.tsx
similarity index 94%
rename from web/src/routes/(auth)/login.tsx
rename to web/src/routes/_guest/login.tsx
index 7a68504..bb24cbc 100644
--- a/web/src/routes/(auth)/login.tsx
+++ b/web/src/routes/_guest/login.tsx
@@ -11,7 +11,7 @@ import { AuthLayout } from "@/components/auth/auth-layout";
import { StatefulButton, type ButtonState } from "@/components/motion/button/stateful";
import { Input } from "@/components/motion/input";
-export const Route = createFileRoute("/(auth)/login")({
+export const Route = createFileRoute("/_guest/login")({
validateSearch: (search: Record): { redirect?: string } => ({
redirect: typeof search.redirect === "string" ? search.redirect : undefined,
}),
@@ -21,7 +21,7 @@ export const Route = createFileRoute("/(auth)/login")({
const loginSchema = z
.object({
email: z.email("Enter a valid email").max(254),
- password: z.string().min(8, "Password must be at least 8 characters"),
+ password: z.string().min(1, "Password is required"),
});
function Login() {
@@ -58,7 +58,7 @@ function Login() {
const { fieldErrors: serverFieldErrors, formError } = parseServerError(
login.error,
{
- 409: { email: "An account with this email already exists" },
+ 401: "Invalid email or password",
},
);
diff --git a/web/src/routes/(auth)/register.tsx b/web/src/routes/_guest/register.tsx
similarity index 99%
rename from web/src/routes/(auth)/register.tsx
rename to web/src/routes/_guest/register.tsx
index 6ce60a3..af9821d 100644
--- a/web/src/routes/(auth)/register.tsx
+++ b/web/src/routes/_guest/register.tsx
@@ -14,7 +14,7 @@ import {
import { Checkbox } from "@/components/motion/checkbox";
import { Input } from "@/components/motion/input";
-export const Route = createFileRoute("/(auth)/register")({
+export const Route = createFileRoute("/_guest/register")({
component: Register,
});