Compare commits
6 Commits
7668fa69cc
...
bf2300a6be
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
bf2300a6be | ||
|
|
0657037c84 | ||
|
|
64a783b445 | ||
|
|
ed28943e7a | ||
|
|
9e148489f0 | ||
|
|
25f86d94cc |
1
.gitignore
vendored
1
.gitignore
vendored
@@ -54,3 +54,4 @@ coverage
|
||||
.cache
|
||||
tmp
|
||||
temp
|
||||
.env.*
|
||||
|
||||
3
.vscode/settings.json
vendored
3
.vscode/settings.json
vendored
@@ -1,9 +1,6 @@
|
||||
{
|
||||
"typescript.preferences.autoImportFileExcludePatterns": ["repos/**"],
|
||||
"javascript.preferences.autoImportFileExcludePatterns": ["repos/**"],
|
||||
"files.exclude": {
|
||||
"repos/**": true
|
||||
},
|
||||
"files.watcherExclude": {
|
||||
"repos/**": true
|
||||
},
|
||||
|
||||
@@ -7,14 +7,17 @@ import { defineConfig } from "vite-plus";
|
||||
export default defineConfig({
|
||||
envDir: path.resolve(import.meta.dirname, "../.."),
|
||||
plugins: [tailwindcss(), reactRouter()],
|
||||
ssr: {
|
||||
noExternal: true,
|
||||
},
|
||||
resolve: {
|
||||
dedupe: ["convex", "react", "react-dom"],
|
||||
tsconfigPaths: true,
|
||||
},
|
||||
server: {
|
||||
allowedHosts: true,
|
||||
proxy: {
|
||||
"/api/auth": {
|
||||
changeOrigin: true,
|
||||
target: "https://befitting-dalmatian-161.convex.site",
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
78
docs/auth-proxy.md
Normal file
78
docs/auth-proxy.md
Normal file
@@ -0,0 +1,78 @@
|
||||
# Auth Proxy — Production Ingress Requirement
|
||||
|
||||
The application uses **same-origin authentication**: the browser and React Router SSR both hit
|
||||
`/api/auth/*` on the public application domain. This keeps cookies first-party, avoids
|
||||
cross-origin credentials, and gives development and production the same API surface.
|
||||
|
||||
## Required production route
|
||||
|
||||
At the public application domain, route:
|
||||
|
||||
| Prefix | Target |
|
||||
|---|---|
|
||||
| `/api/auth/*` | Convex HTTP site |
|
||||
| `/*` | React Router frontend |
|
||||
|
||||
### Caddy
|
||||
|
||||
```caddy
|
||||
zopu.example.com {
|
||||
handle /api/auth/* {
|
||||
reverse_proxy https://befitting-dalmatian-161.convex.site {
|
||||
header_up Host befitting-dalmatian-161.convex.site
|
||||
}
|
||||
}
|
||||
|
||||
handle {
|
||||
reverse_proxy frontend:3000
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Dokploy / Traefik
|
||||
|
||||
Create a higher-priority path router for `/api/auth` that forwards to the external Convex
|
||||
site URL. Ensure it:
|
||||
|
||||
- Preserves the original browser `Cookie` header
|
||||
- Passes `Set-Cookie` responses back (rewrite domain if Convex emits an explicit one)
|
||||
- Preserves the original method and body
|
||||
- Forwards `X-Forwarded-Host` and `X-Forwarded-Proto`
|
||||
- Passes the full path unchanged (e.g. `/api/auth/convex/token`)
|
||||
- Does not cache auth responses
|
||||
- Allows OAuth callback routes under the same prefix
|
||||
|
||||
## Convex environment
|
||||
|
||||
The Convex deployment `SITE_URL` must match the public origin users visit, not the Convex
|
||||
site URL:
|
||||
|
||||
```bash
|
||||
# Production
|
||||
npx convex env set SITE_URL 'https://zopu.example.com'
|
||||
|
||||
# Local
|
||||
npx convex env set SITE_URL 'http://100.101.157.28:5173'
|
||||
|
||||
# Staging
|
||||
npx convex env set SITE_URL 'https://zopu.cheaptricks.puter.wtf'
|
||||
```
|
||||
|
||||
This value populates Better Auth's `trustedOrigins`. The Convex deployment also uses
|
||||
`CONVEX_SITE_URL` as the internal `baseURL` for route registration; that value is the HTTP
|
||||
site URL (e.g. `https://befitting-dalmatian-161.convex.site`).
|
||||
|
||||
## Why same-origin
|
||||
|
||||
1. **First-party cookies.** No `SameSite=None`, no third-party cookie restrictions.
|
||||
2. **SSR consistency.** The React Router server uses the same auth surface the browser
|
||||
sees; no cross-host credential translation.
|
||||
3. **No CORS complexity.** The browser talks only to its own origin.
|
||||
4. **The reverse proxy handles the cross-host hop server-side.**
|
||||
|
||||
## Related
|
||||
|
||||
- [Better Auth: Trusted Origins](https://github.com/better-auth/better-auth/blob/main/docs/content/docs/reference/security.mdx)
|
||||
- `apps/web/vite.config.ts` — Vite dev proxy (`/api/auth` → Convex site)
|
||||
- `packages/auth/src/web/auth-client.ts` — `window.location.origin`
|
||||
- `apps/web/src/lib/auth.server.ts` — SSR token loader via request origin
|
||||
@@ -80,9 +80,10 @@ export const executeAgentOsAttempt = async (
|
||||
decodeWorkAttemptExecutionInput(rawInput)
|
||||
);
|
||||
const env = parseAgentEnv(process.env);
|
||||
const endpoint = env.RIVET_PUBLIC_ENDPOINT ?? env.RIVET_ENDPOINT;
|
||||
const client = createClient<typeof runtimeRegistry>({
|
||||
disableMetadataLookup: true,
|
||||
endpoint: env.RIVET_PUBLIC_ENDPOINT ?? env.RIVET_ENDPOINT,
|
||||
...(endpoint ? { endpoint } : {}),
|
||||
});
|
||||
const vm = client.workspace.getOrCreate([input.workspaceKey], {
|
||||
params: { token: env.RIVET_WORKSPACE_TOKEN },
|
||||
@@ -243,9 +244,10 @@ export const cancelAgentOsAttempt = async (
|
||||
attemptId: string
|
||||
) => {
|
||||
const env = parseAgentEnv(process.env);
|
||||
const endpoint = env.RIVET_PUBLIC_ENDPOINT ?? env.RIVET_ENDPOINT;
|
||||
const client = createClient<typeof runtimeRegistry>({
|
||||
disableMetadataLookup: true,
|
||||
endpoint: env.RIVET_PUBLIC_ENDPOINT ?? env.RIVET_ENDPOINT,
|
||||
...(endpoint ? { endpoint } : {}),
|
||||
});
|
||||
await client.workspace
|
||||
.getOrCreate([workspaceKey], {
|
||||
|
||||
@@ -17,6 +17,7 @@ export const authComponent = createClient<DataModel>(components.betterAuth);
|
||||
|
||||
const createAuth = (ctx: GenericCtx<DataModel>) =>
|
||||
betterAuth({
|
||||
advanced: { useSecureCookies: siteUrl.startsWith("https://") },
|
||||
baseURL: env.CONVEX_SITE_URL,
|
||||
database: authComponent.adapter(ctx),
|
||||
emailAndPassword: {
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
"compilerOptions": {
|
||||
/* These settings are not required by Convex and can be modified. */
|
||||
"allowJs": true,
|
||||
"allowImportingTsExtensions": true,
|
||||
"strict": true,
|
||||
"moduleResolution": "Bundler",
|
||||
"jsx": "react-jsx",
|
||||
|
||||
10
packages/env/src/agent.ts
vendored
10
packages/env/src/agent.ts
vendored
@@ -14,8 +14,14 @@ const agentEnvSchema = z.object({
|
||||
GITEA_TOKEN: z.string().min(1).optional(),
|
||||
GITEA_URL: z.url().default("https://git.openputer.com"),
|
||||
OPENROUTER_API_KEY: z.string().min(1).optional(),
|
||||
RIVET_ENDPOINT: z.url(),
|
||||
RIVET_PUBLIC_ENDPOINT: z.url().optional(),
|
||||
RIVET_ENDPOINT: z.preprocess(
|
||||
(value) => (value === "" ? undefined : value),
|
||||
z.url().optional()
|
||||
),
|
||||
RIVET_PUBLIC_ENDPOINT: z.preprocess(
|
||||
(value) => (value === "" ? undefined : value),
|
||||
z.url().optional()
|
||||
),
|
||||
RIVET_WORKSPACE_TOKEN: z.string().min(32),
|
||||
ZOPU_SOURCE_REPOSITORY: z.string().min(1).default("/opt/zopu-source"),
|
||||
});
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
/* eslint-disable max-classes-per-file -- deep Effect v4 domain module: branded schemas, tagged errors, and context services */
|
||||
import { Context, Effect, Layer, Schema } from "effect";
|
||||
|
||||
import { OrganizationId, ProjectId, TimestampMs } from "./signal.ts";
|
||||
import { OrganizationId, ProjectId, TimestampMs } from "./signal";
|
||||
|
||||
export type { OrganizationId, ProjectId } from "./signal.ts";
|
||||
export type { OrganizationId, ProjectId } from "./signal";
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Domain constants
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { Array as EffectArray, Effect, Order, Schema } from "effect";
|
||||
|
||||
export { WorkStatus } from "./work-lifecycle.ts";
|
||||
export type { WorkStatus as WorkLifecycleStatus } from "./work-lifecycle.ts";
|
||||
export { WorkStatus } from "./work-lifecycle";
|
||||
export type { WorkStatus as WorkLifecycleStatus } from "./work-lifecycle";
|
||||
|
||||
const MeaningfulString = Schema.String.check(
|
||||
Schema.makeFilter((value) => value.trim().length > 0, {
|
||||
|
||||
Reference in New Issue
Block a user