feat: Projects backend slice — auth, primitives, backend, UI (#3)
This commit is contained in:
@@ -5,7 +5,6 @@
|
||||
"type": "module",
|
||||
"exports": {
|
||||
"./native": "./src/native/index.ts",
|
||||
"./server": "./src/server/index.ts",
|
||||
"./web": "./src/web/index.ts"
|
||||
},
|
||||
"scripts": {
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
export { authComponent, createAuth } from "@code/backend/convex/auth";
|
||||
@@ -5,7 +5,7 @@ import type { ReactNode } from "react";
|
||||
|
||||
import { authClient } from "./auth-client";
|
||||
|
||||
const convex = new ConvexReactClient(env.VITE_CONVEX_URL);
|
||||
const convex = new ConvexReactClient(env.VITE_CONVEX_URL, { expectAuth: true });
|
||||
|
||||
export const WebAuthProvider = ({ children }: { children: ReactNode }) => (
|
||||
<ConvexBetterAuthProvider authClient={authClient} client={convex}>
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
export { authClient } from "./auth-client";
|
||||
export { WebAuthProvider } from "./auth-provider";
|
||||
export { useConvexAccessToken } from "./hooks";
|
||||
export { signOutWeb, useWebAuth } from "./use-web-auth";
|
||||
export type { WebAuthState, WebAuthUser } from "./use-web-auth";
|
||||
export { LoginForm } from "./login-form";
|
||||
export { SignupForm } from "./signup-form";
|
||||
|
||||
81
packages/auth/src/web/use-web-auth.ts
Normal file
81
packages/auth/src/web/use-web-auth.ts
Normal file
@@ -0,0 +1,81 @@
|
||||
import { api } from "@code/backend/convex/_generated/api";
|
||||
import { useConvexAuth, useQuery } from "convex/react";
|
||||
import { useEffect, useRef } from "react";
|
||||
|
||||
import { authClient } from "./auth-client";
|
||||
|
||||
export interface WebAuthUser {
|
||||
readonly id: string;
|
||||
readonly name: string;
|
||||
readonly email: string;
|
||||
}
|
||||
|
||||
export type WebAuthState =
|
||||
| { readonly status: "loading" }
|
||||
| { readonly status: "unauthenticated" }
|
||||
| { readonly status: "authenticated"; readonly user: WebAuthUser }
|
||||
| { readonly status: "inconsistent" };
|
||||
|
||||
/**
|
||||
* Sign out of the web session. Clears the Better Auth session, which in turn
|
||||
* invalidates the cached Convex JWT (the session-token resolver keys on the
|
||||
* session id). Protected Convex queries after sign-out are denied.
|
||||
*/
|
||||
export const signOutWeb = async (): Promise<void> => {
|
||||
await authClient.signOut({ fetchOptions: { throw: false } });
|
||||
};
|
||||
|
||||
/**
|
||||
* The single web-facing auth interface. Combines Convex auth state with the
|
||||
* normalized {@link api.auth.getCurrentUser} projection, skipping the user
|
||||
* query until Convex has authenticated. Reports `inconsistent` only when a JWT
|
||||
* is accepted but the Better Auth user row cannot be resolved.
|
||||
*/
|
||||
export const useWebAuth = (): WebAuthState => {
|
||||
const { isAuthenticated, isLoading } = useConvexAuth();
|
||||
|
||||
// Skip the user query until Convex has accepted the JWT. This keeps the
|
||||
// status `loading` during token restoration and `unauthenticated` when no
|
||||
// session exists, without firing a query that would return null.
|
||||
const userQuery = useQuery(
|
||||
api.auth.getCurrentUser,
|
||||
isAuthenticated ? {} : "skip"
|
||||
);
|
||||
|
||||
const signOutWebRef = useRef(signOutWeb);
|
||||
const inconsistent =
|
||||
isAuthenticated && userQuery !== undefined && userQuery === null;
|
||||
|
||||
// If Convex accepted the JWT but the Better Auth user row cannot be resolved,
|
||||
// the session is inconsistent: sign out so the next sign-in starts clean.
|
||||
useEffect(() => {
|
||||
if (inconsistent) {
|
||||
void signOutWebRef.current();
|
||||
}
|
||||
}, [inconsistent]);
|
||||
|
||||
if (isLoading) {
|
||||
return { status: "loading" };
|
||||
}
|
||||
|
||||
if (!isAuthenticated) {
|
||||
return { status: "unauthenticated" };
|
||||
}
|
||||
|
||||
if (userQuery === undefined) {
|
||||
return { status: "loading" };
|
||||
}
|
||||
|
||||
if (userQuery === null) {
|
||||
return { status: "inconsistent" };
|
||||
}
|
||||
|
||||
return {
|
||||
status: "authenticated",
|
||||
user: {
|
||||
email: userQuery.email,
|
||||
id: userQuery.id,
|
||||
name: userQuery.name,
|
||||
},
|
||||
};
|
||||
};
|
||||
Reference in New Issue
Block a user