Files
zopu-code/packages/backend/convex/auth.ts
-Puter 477e54240d feat: consolidate web auth and add Effect v4 Project primitives
Auth:
- Add useWebAuth hook as the single web-facing auth interface with four
  states (loading/unauthenticated/authenticated/inconsistent)
- Normalize getCurrentUser to return {id: tokenIdentifier, name, email}
- Rename requireOwnerId to requireAuthUserId across all backend callers
- Set expectAuth:true on ConvexReactClient singleton
- Migrate _app, _auth, user-menu, use-personal-organization to useWebAuth
- Remove unused @code/auth/server export (zero callers confirmed)

Primitives:
- Add packages/primitives/src/project.ts with Effect v4 Project domain:
  URL normalization, six canonical context kinds, strict remote decoding,
  context write decisioning with revision tracking, and no-op detection
- Define PublicGit and ProjectStore replaceable ports
- Define ProjectApplication service orchestrating domain+ports with
  deterministic error mapping
- Add comprehensive test suite (28 tests) with in-memory store and fake
  PublicGit covering normalization, seeds, idempotency, tenant isolation,
  last-write-wins, and typed error mapping
2026-07-23 18:04:02 +05:30

69 lines
2.0 KiB
TypeScript

import { expo } from "@better-auth/expo";
import { env } from "@code/env/convex";
import { createClient } from "@convex-dev/better-auth";
import type { GenericCtx } from "@convex-dev/better-auth";
import { convex, crossDomain } from "@convex-dev/better-auth/plugins";
import { betterAuth } from "better-auth/minimal";
import { components } from "./_generated/api";
import type { DataModel } from "./_generated/dataModel";
import { query } from "./_generated/server";
import authConfig from "./auth.config";
const siteUrl = env.SITE_URL;
const nativeAppUrl = env.NATIVE_APP_URL ?? "code://";
export const authComponent = createClient<DataModel>(components.betterAuth);
const createAuth = (ctx: GenericCtx<DataModel>) =>
betterAuth({
baseURL: env.CONVEX_SITE_URL,
database: authComponent.adapter(ctx),
emailAndPassword: {
enabled: true,
requireEmailVerification: false,
},
plugins: [
expo(),
crossDomain({ siteUrl }),
convex({
authConfig,
jwksRotateOnTokenGenerationError: true,
}),
],
trustedOrigins: [siteUrl, nativeAppUrl, "exp://"],
});
export { createAuth };
export interface AuthUserView {
readonly id: string;
readonly name: string;
readonly email: string;
}
/**
* Resolve the authenticated user to the canonical web view. The returned `id`
* is the Convex `identity.tokenIdentifier`, the stable authenticated identity
* used across organization/conversation/Signal ownership. Returns null when no
* identity is present or the Better Auth user row cannot be resolved.
*/
export const getCurrentUser = query({
args: {},
handler: async (ctx): Promise<AuthUserView | null> => {
const identity = await ctx.auth.getUserIdentity();
if (!identity) {
return null;
}
const authUser = await authComponent.safeGetAuthUser(ctx);
if (!authUser) {
return null;
}
return {
email: authUser.email,
id: identity.tokenIdentifier,
name: authUser.name,
};
},
});