kitcn 0.0.1 → 0.12.1
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.
- package/bin/intent.js +3 -0
- package/dist/aggregate/index.d.ts +388 -0
- package/dist/aggregate/index.js +37 -0
- package/dist/api-entry-BckXqaLb.js +66 -0
- package/dist/auth/client/index.d.ts +37 -0
- package/dist/auth/client/index.js +217 -0
- package/dist/auth/config/index.d.ts +45 -0
- package/dist/auth/config/index.js +24 -0
- package/dist/auth/generated/index.d.ts +2 -0
- package/dist/auth/generated/index.js +3 -0
- package/dist/auth/http/index.d.ts +64 -0
- package/dist/auth/http/index.js +461 -0
- package/dist/auth/index.d.ts +221 -0
- package/dist/auth/index.js +1398 -0
- package/dist/auth/nextjs/index.d.ts +50 -0
- package/dist/auth/nextjs/index.js +81 -0
- package/dist/auth-store-Cljlmdmi.js +197 -0
- package/dist/builder-CBdG5W6A.js +1974 -0
- package/dist/caller-factory-cTXNvYdz.js +216 -0
- package/dist/cli.mjs +13264 -0
- package/dist/codegen-lF80HSWu.mjs +3416 -0
- package/dist/context-utils-HPC5nXzx.d.ts +17 -0
- package/dist/create-schema-odyF4kCy.js +156 -0
- package/dist/create-schema-orm-DOyiNDCx.js +246 -0
- package/dist/crpc/index.d.ts +105 -0
- package/dist/crpc/index.js +169 -0
- package/dist/customFunctions-C0voKmtx.js +144 -0
- package/dist/error-BZEnI7Sq.js +41 -0
- package/dist/generated-contract-disabled-Cih4eITO.js +50 -0
- package/dist/generated-contract-disabled-D-sOFy92.d.ts +354 -0
- package/dist/http-types-DqJubRPJ.d.ts +292 -0
- package/dist/meta-utils-0Pu0Nrap.js +117 -0
- package/dist/middleware-BUybuv9n.d.ts +34 -0
- package/dist/middleware-C2qTZ3V7.js +84 -0
- package/dist/orm/index.d.ts +17 -0
- package/dist/orm/index.js +10713 -0
- package/dist/plugins/index.d.ts +2 -0
- package/dist/plugins/index.js +3 -0
- package/dist/procedure-caller-DtxLmGwA.d.ts +1467 -0
- package/dist/procedure-caller-MWcxhQDv.js +349 -0
- package/dist/query-context-B8o6-8kC.js +1518 -0
- package/dist/query-context-CFZqIvD7.d.ts +42 -0
- package/dist/query-options-Dw7cOyXl.js +121 -0
- package/dist/ratelimit/index.d.ts +269 -0
- package/dist/ratelimit/index.js +856 -0
- package/dist/ratelimit/react/index.d.ts +76 -0
- package/dist/ratelimit/react/index.js +183 -0
- package/dist/react/index.d.ts +1284 -0
- package/dist/react/index.js +2526 -0
- package/dist/rsc/index.d.ts +276 -0
- package/dist/rsc/index.js +233 -0
- package/dist/runtime-CtvJPkur.js +2453 -0
- package/dist/server/index.d.ts +5 -0
- package/dist/server/index.js +6 -0
- package/dist/solid/index.d.ts +1221 -0
- package/dist/solid/index.js +2940 -0
- package/dist/transformer-DtDhR3Lc.js +194 -0
- package/dist/types-BTb_4BaU.d.ts +42 -0
- package/dist/types-BiJE7qxR.d.ts +4 -0
- package/dist/types-DEJpkIhw.d.ts +88 -0
- package/dist/types-HhO_R6pd.d.ts +213 -0
- package/dist/validators-B7oIJCAp.js +279 -0
- package/dist/validators-vzRKjBJC.d.ts +88 -0
- package/dist/watcher.mjs +96 -0
- package/dist/where-clause-compiler-DdjN63Io.d.ts +4756 -0
- package/package.json +107 -34
- package/skills/convex/SKILL.md +486 -0
- package/skills/convex/references/features/aggregates.md +353 -0
- package/skills/convex/references/features/auth-admin.md +446 -0
- package/skills/convex/references/features/auth-organizations.md +1141 -0
- package/skills/convex/references/features/auth-polar.md +579 -0
- package/skills/convex/references/features/auth.md +470 -0
- package/skills/convex/references/features/create-plugins.md +153 -0
- package/skills/convex/references/features/http.md +676 -0
- package/skills/convex/references/features/migrations.md +162 -0
- package/skills/convex/references/features/orm.md +1166 -0
- package/skills/convex/references/features/react.md +657 -0
- package/skills/convex/references/features/scheduling.md +267 -0
- package/skills/convex/references/features/testing.md +209 -0
- package/skills/convex/references/setup/auth.md +501 -0
- package/skills/convex/references/setup/biome.md +190 -0
- package/skills/convex/references/setup/doc-guidelines.md +145 -0
- package/skills/convex/references/setup/index.md +761 -0
- package/skills/convex/references/setup/next.md +116 -0
- package/skills/convex/references/setup/react.md +175 -0
- package/skills/convex/references/setup/server.md +473 -0
- package/skills/convex/references/setup/start.md +67 -0
- package/LICENSE +0 -21
- package/README.md +0 -0
- package/dist/index.d.mts +0 -5
- package/dist/index.d.mts.map +0 -1
- package/dist/index.mjs +0 -6
- package/dist/index.mjs.map +0 -1
|
@@ -0,0 +1,501 @@
|
|
|
1
|
+
## 6. Auth Core (Better Auth)
|
|
2
|
+
|
|
3
|
+
Feature gate: only apply this section if auth is enabled.
|
|
4
|
+
|
|
5
|
+
### 6.1 Install auth with CLI
|
|
6
|
+
|
|
7
|
+
If kitcn is not bootstrapped yet, start there first:
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
bunx kitcn init -t next --yes
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
Use `bunx kitcn init --yes` instead for in-place adoption of the
|
|
14
|
+
current supported app.
|
|
15
|
+
|
|
16
|
+
Then install auth:
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
bunx kitcn add auth --yes
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
Local Convex rule:
|
|
23
|
+
|
|
24
|
+
1. `add auth --yes` installs the auth scaffold and finishes the first local auth bootstrap in one pass.
|
|
25
|
+
2. `kitcn dev` is the long-running local runtime; later edits to `convex/.env` auto-sync while it is running.
|
|
26
|
+
3. `kitcn env push` stays for `--prod`, `--rotate`, or explicit repair against an already active deployment.
|
|
27
|
+
|
|
28
|
+
### 6.2 Auth config provider
|
|
29
|
+
|
|
30
|
+
**Create:** `convex/functions/auth.config.ts`
|
|
31
|
+
|
|
32
|
+
```ts
|
|
33
|
+
import { getAuthConfigProvider } from "kitcn/auth/config";
|
|
34
|
+
import type { AuthConfig } from "convex/server";
|
|
35
|
+
import { getEnv } from "../lib/get-env";
|
|
36
|
+
|
|
37
|
+
export default {
|
|
38
|
+
providers: [
|
|
39
|
+
getEnv().JWKS
|
|
40
|
+
? getAuthConfigProvider({ jwks: getEnv().JWKS })
|
|
41
|
+
: getAuthConfigProvider(),
|
|
42
|
+
],
|
|
43
|
+
} satisfies AuthConfig;
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
Treat generated auth secrets as owned by the CLI flow. Do not manually set
|
|
47
|
+
`BETTER_AUTH_SECRET` in setup/simulation unless explicitly requested.
|
|
48
|
+
Malformed `JWKS` values can fail Convex module analysis during push/codegen.
|
|
49
|
+
|
|
50
|
+
### 6.3 Define auth contract
|
|
51
|
+
|
|
52
|
+
**Create:** `<functionsDir>/auth.ts`
|
|
53
|
+
|
|
54
|
+
`functionsDir` comes from `convex.json.functions` (default: `convex`).
|
|
55
|
+
Scaffolded kitcn apps use `convex/functions/auth.ts`.
|
|
56
|
+
|
|
57
|
+
```ts
|
|
58
|
+
import { convex } from "kitcn/auth";
|
|
59
|
+
import { getEnv } from "../lib/get-env";
|
|
60
|
+
import authConfig from "./auth.config";
|
|
61
|
+
import { defineAuth } from "./generated/auth";
|
|
62
|
+
|
|
63
|
+
export default defineAuth(() => ({
|
|
64
|
+
emailAndPassword: {
|
|
65
|
+
enabled: true,
|
|
66
|
+
},
|
|
67
|
+
baseURL: getEnv().SITE_URL,
|
|
68
|
+
plugins: [
|
|
69
|
+
convex({
|
|
70
|
+
authConfig,
|
|
71
|
+
jwks: getEnv().JWKS,
|
|
72
|
+
}),
|
|
73
|
+
],
|
|
74
|
+
session: {
|
|
75
|
+
expiresIn: 60 * 60 * 24 * 30,
|
|
76
|
+
updateAge: 60 * 60 * 24 * 15,
|
|
77
|
+
},
|
|
78
|
+
telemetry: { enabled: false },
|
|
79
|
+
trustedOrigins: [getEnv().SITE_URL],
|
|
80
|
+
}));
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
Canonical rule:
|
|
84
|
+
|
|
85
|
+
1. `bunx kitcn init --yes`, `bunx kitcn dev`, and `bunx kitcn add auth --yes` all drive generation of `convex/functions/generated/` when they own the local Convex flow.
|
|
86
|
+
2. `auth.ts` default-exports `defineAuth(() => ({ ...options, triggers }))` imported from `./generated/auth`.
|
|
87
|
+
3. Import runtime auth contract (`getAuth`, `authClient`, CRUD/triggers, `auth`) from `<functionsDir>/generated/auth`.
|
|
88
|
+
4. If `auth.ts` is missing or incomplete, codegen still succeeds and generated runtime exports `authEnabled = false` with setup guidance at call time.
|
|
89
|
+
|
|
90
|
+
Do not manually create `authClient`, `createApi` exports, or static `auth` in `auth.ts`.
|
|
91
|
+
|
|
92
|
+
### 6.3.1 User session query module
|
|
93
|
+
|
|
94
|
+
Ordering note:
|
|
95
|
+
|
|
96
|
+
1. This module intentionally uses `publicQuery` + `getAuth(ctx)` so it works before Section 6.9 upgrades cRPC auth builders.
|
|
97
|
+
|
|
98
|
+
**Create:** `convex/functions/user.ts`
|
|
99
|
+
|
|
100
|
+
```ts
|
|
101
|
+
import { z } from "zod";
|
|
102
|
+
import { getHeaders } from "kitcn/auth";
|
|
103
|
+
|
|
104
|
+
import { getAuth } from "./generated/auth";
|
|
105
|
+
import { publicQuery } from "../lib/crpc";
|
|
106
|
+
|
|
107
|
+
export const getSessionUser = publicQuery
|
|
108
|
+
.output(
|
|
109
|
+
z.union([
|
|
110
|
+
z.object({
|
|
111
|
+
id: z.string(),
|
|
112
|
+
image: z.string().nullish(),
|
|
113
|
+
isAdmin: z.boolean(),
|
|
114
|
+
name: z.string().optional(),
|
|
115
|
+
plan: z.string().optional(),
|
|
116
|
+
}),
|
|
117
|
+
z.null(),
|
|
118
|
+
])
|
|
119
|
+
)
|
|
120
|
+
.query(async ({ ctx }) => {
|
|
121
|
+
const auth = getAuth(ctx);
|
|
122
|
+
const session = await auth.api.getSession({
|
|
123
|
+
headers: await getHeaders(ctx),
|
|
124
|
+
});
|
|
125
|
+
const user = session?.user;
|
|
126
|
+
if (!user) {
|
|
127
|
+
return null;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
return {
|
|
131
|
+
id: user.id,
|
|
132
|
+
image: user.image,
|
|
133
|
+
isAdmin: user.isAdmin ?? false,
|
|
134
|
+
name: user.name,
|
|
135
|
+
plan: user.plan,
|
|
136
|
+
};
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
export const getIsAuthenticated = publicQuery
|
|
140
|
+
.output(z.boolean())
|
|
141
|
+
.query(async ({ ctx }) => !!(await ctx.auth.getUserIdentity()));
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
### 6.3.2 Shared auth type contract
|
|
145
|
+
|
|
146
|
+
**Create:** `convex/shared/auth-shared.ts`
|
|
147
|
+
|
|
148
|
+
```ts
|
|
149
|
+
import type { getAuth } from "../functions/generated/auth";
|
|
150
|
+
import type { Select } from "./api";
|
|
151
|
+
|
|
152
|
+
export type Auth = ReturnType<typeof getAuth>;
|
|
153
|
+
|
|
154
|
+
export type SessionUser = Select<"user"> & {
|
|
155
|
+
isAdmin: boolean;
|
|
156
|
+
session: Select<"session">;
|
|
157
|
+
impersonatedBy?: string | null;
|
|
158
|
+
plan?: "premium" | "team";
|
|
159
|
+
};
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
### 6.4 Define auth tables in schema
|
|
163
|
+
|
|
164
|
+
If you used the kitcn scaffold, install auth once with:
|
|
165
|
+
|
|
166
|
+
```bash
|
|
167
|
+
bunx kitcn add auth --yes
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
After changing plugins or auth fields in `<functionsDir>/auth.ts`, refresh only
|
|
171
|
+
the auth-owned schema blocks with:
|
|
172
|
+
|
|
173
|
+
```bash
|
|
174
|
+
bunx kitcn add auth --schema --yes
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
Use the raw Convex preset only when the app stays on the plain Convex auth
|
|
178
|
+
path:
|
|
179
|
+
|
|
180
|
+
```bash
|
|
181
|
+
bunx kitcn add auth --preset convex --yes
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
That raw Convex path refreshes `authSchema.ts` and `schema.ts` together. It
|
|
185
|
+
assumes the raw Convex app is already initialized and does not support
|
|
186
|
+
`--schema`.
|
|
187
|
+
|
|
188
|
+
If you used section 5.1's schema template, these tables already exist.
|
|
189
|
+
Otherwise add:
|
|
190
|
+
|
|
191
|
+
- `user`
|
|
192
|
+
- `session`
|
|
193
|
+
- `account`
|
|
194
|
+
- `verification`
|
|
195
|
+
- `jwks`
|
|
196
|
+
|
|
197
|
+
Keep all auth reads/writes on ORM table definitions in `convex/functions/schema.ts`.
|
|
198
|
+
|
|
199
|
+
### 6.5 Register auth HTTP routes
|
|
200
|
+
|
|
201
|
+
Use `kitcn/auth/http` for `authMiddleware` or `registerRoutes`.
|
|
202
|
+
It auto-installs the Convex-safe `MessageChannel` polyfill, so no manual `http-polyfills.ts` file is needed.
|
|
203
|
+
|
|
204
|
+
**Create:** `convex/functions/http.ts`
|
|
205
|
+
|
|
206
|
+
Bootstrap note:
|
|
207
|
+
|
|
208
|
+
1. `http.ts` is parsed during startup/codegen.
|
|
209
|
+
2. Keep imports static (no lazy imports in Convex code).
|
|
210
|
+
3. If `_generated/*` modules are missing, run `bunx kitcn dev` first, then continue.
|
|
211
|
+
|
|
212
|
+
cRPC + Hono route shape:
|
|
213
|
+
|
|
214
|
+
```ts
|
|
215
|
+
import { authMiddleware } from "kitcn/auth/http";
|
|
216
|
+
import { createHttpRouter } from "kitcn/server";
|
|
217
|
+
import { Hono } from "hono";
|
|
218
|
+
import { cors } from "hono/cors";
|
|
219
|
+
import { getEnv } from "../lib/get-env";
|
|
220
|
+
|
|
221
|
+
import { router } from "../lib/crpc";
|
|
222
|
+
import { getAuth } from "./generated/auth";
|
|
223
|
+
|
|
224
|
+
const app = new Hono();
|
|
225
|
+
|
|
226
|
+
app.use(
|
|
227
|
+
"/api/*",
|
|
228
|
+
cors({
|
|
229
|
+
origin: getEnv().SITE_URL,
|
|
230
|
+
allowHeaders: ["Content-Type", "Authorization", "Better-Auth-Cookie"],
|
|
231
|
+
exposeHeaders: ["Set-Better-Auth-Cookie"],
|
|
232
|
+
credentials: true,
|
|
233
|
+
})
|
|
234
|
+
);
|
|
235
|
+
|
|
236
|
+
app.use(authMiddleware(getAuth));
|
|
237
|
+
|
|
238
|
+
export const httpRouter = router({
|
|
239
|
+
// register routers here
|
|
240
|
+
});
|
|
241
|
+
|
|
242
|
+
export default createHttpRouter(app, httpRouter);
|
|
243
|
+
```
|
|
244
|
+
|
|
245
|
+
### 6.6 Sync env and JWKS
|
|
246
|
+
|
|
247
|
+
`convex/.env` comes from base setup. Keep `SITE_URL` and any provider
|
|
248
|
+
credentials current there. For the normal local path, `SITE_URL` should stay on
|
|
249
|
+
`http://localhost:3000`.
|
|
250
|
+
|
|
251
|
+
Typical local values:
|
|
252
|
+
|
|
253
|
+
```bash
|
|
254
|
+
SITE_URL=http://localhost:3000
|
|
255
|
+
GOOGLE_CLIENT_ID=...
|
|
256
|
+
GOOGLE_CLIENT_SECRET=...
|
|
257
|
+
```
|
|
258
|
+
|
|
259
|
+
Local Convex:
|
|
260
|
+
|
|
261
|
+
```bash
|
|
262
|
+
bunx kitcn dev
|
|
263
|
+
```
|
|
264
|
+
|
|
265
|
+
`kitcn init --yes`, `kitcn dev`, and `kitcn add auth --yes`
|
|
266
|
+
already handle the first local auth bootstrap pass when they own the flow.
|
|
267
|
+
While `kitcn dev` is running, later edits to `convex/.env` auto-sync.
|
|
268
|
+
|
|
269
|
+
Repair / remote sync:
|
|
270
|
+
|
|
271
|
+
```bash
|
|
272
|
+
bunx kitcn env push
|
|
273
|
+
```
|
|
274
|
+
|
|
275
|
+
Use this for `--prod` or explicit repair against an already active deployment.
|
|
276
|
+
|
|
277
|
+
Rotate later:
|
|
278
|
+
|
|
279
|
+
```bash
|
|
280
|
+
bunx kitcn env push --rotate
|
|
281
|
+
```
|
|
282
|
+
|
|
283
|
+
### 6.7 Production bootstrap notes
|
|
284
|
+
|
|
285
|
+
First prod deploy requires JWKS initialization:
|
|
286
|
+
|
|
287
|
+
```bash
|
|
288
|
+
bunx convex deploy --prod
|
|
289
|
+
bunx kitcn env push --prod
|
|
290
|
+
```
|
|
291
|
+
|
|
292
|
+
### 6.9 Upgrade `convex/lib/crpc.ts` to auth-aware builders (only after Section 11.2 passes)
|
|
293
|
+
|
|
294
|
+
After non-auth baseline is green, replace `convex/lib/crpc.ts` with this auth-aware variant:
|
|
295
|
+
|
|
296
|
+
```ts
|
|
297
|
+
import { getHeaders } from "kitcn/auth";
|
|
298
|
+
import { CRPCError } from "kitcn/server";
|
|
299
|
+
|
|
300
|
+
import { getAuth } from "../functions/generated/auth";
|
|
301
|
+
import { initCRPC } from "../functions/generated/server";
|
|
302
|
+
|
|
303
|
+
const c = initCRPC
|
|
304
|
+
.meta<{
|
|
305
|
+
auth?: "optional" | "required";
|
|
306
|
+
role?: "admin";
|
|
307
|
+
ratelimit?: string;
|
|
308
|
+
}>()
|
|
309
|
+
.create();
|
|
310
|
+
|
|
311
|
+
const roleMiddleware = c.middleware(({ meta, ctx, next }) => {
|
|
312
|
+
if (meta.role !== "admin") return next({ ctx });
|
|
313
|
+
|
|
314
|
+
const user = (ctx as { user?: { isAdmin?: boolean } }).user;
|
|
315
|
+
if (!user?.isAdmin) {
|
|
316
|
+
throw new CRPCError({
|
|
317
|
+
code: "FORBIDDEN",
|
|
318
|
+
message: "Admin access required",
|
|
319
|
+
});
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
return next({ ctx });
|
|
323
|
+
});
|
|
324
|
+
|
|
325
|
+
function requireAuth<T>(user: T | null): T {
|
|
326
|
+
if (!user) {
|
|
327
|
+
throw new CRPCError({ code: "UNAUTHORIZED", message: "Not authenticated" });
|
|
328
|
+
}
|
|
329
|
+
return user;
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
export const publicQuery = c.query.meta({ auth: "optional" });
|
|
333
|
+
export const publicAction = c.action;
|
|
334
|
+
export const publicMutation = c.mutation;
|
|
335
|
+
|
|
336
|
+
export const privateQuery = c.query.internal();
|
|
337
|
+
export const privateMutation = c.mutation.internal();
|
|
338
|
+
export const privateAction = c.action.internal();
|
|
339
|
+
|
|
340
|
+
export const optionalAuthQuery = c.query
|
|
341
|
+
.meta({ auth: "optional" })
|
|
342
|
+
.use(async ({ ctx, next }) => {
|
|
343
|
+
const auth = getAuth(ctx);
|
|
344
|
+
const session = await auth.api.getSession({
|
|
345
|
+
headers: await getHeaders(ctx),
|
|
346
|
+
});
|
|
347
|
+
|
|
348
|
+
return next({
|
|
349
|
+
ctx: {
|
|
350
|
+
...ctx,
|
|
351
|
+
user: session?.user ?? null,
|
|
352
|
+
userId: session?.user?.id ?? null,
|
|
353
|
+
},
|
|
354
|
+
});
|
|
355
|
+
});
|
|
356
|
+
|
|
357
|
+
export const authQuery = c.query
|
|
358
|
+
.meta({ auth: "required" })
|
|
359
|
+
.use(async ({ ctx, next }) => {
|
|
360
|
+
const auth = getAuth(ctx);
|
|
361
|
+
const session = await auth.api.getSession({
|
|
362
|
+
headers: await getHeaders(ctx),
|
|
363
|
+
});
|
|
364
|
+
const user = requireAuth(session?.user ?? null);
|
|
365
|
+
return next({ ctx: { ...ctx, user, userId: user.id } });
|
|
366
|
+
})
|
|
367
|
+
.use(roleMiddleware);
|
|
368
|
+
|
|
369
|
+
export const optionalAuthMutation = c.mutation
|
|
370
|
+
.meta({ auth: "optional" })
|
|
371
|
+
.use(async ({ ctx, next }) => {
|
|
372
|
+
const auth = getAuth(ctx);
|
|
373
|
+
const session = await auth.api.getSession({
|
|
374
|
+
headers: await getHeaders(ctx),
|
|
375
|
+
});
|
|
376
|
+
|
|
377
|
+
return next({
|
|
378
|
+
ctx: {
|
|
379
|
+
...ctx,
|
|
380
|
+
user: session?.user ?? null,
|
|
381
|
+
userId: session?.user?.id ?? null,
|
|
382
|
+
},
|
|
383
|
+
});
|
|
384
|
+
});
|
|
385
|
+
|
|
386
|
+
export const authMutation = c.mutation
|
|
387
|
+
.meta({ auth: "required" })
|
|
388
|
+
.use(async ({ ctx, next }) => {
|
|
389
|
+
const auth = getAuth(ctx);
|
|
390
|
+
const session = await auth.api.getSession({
|
|
391
|
+
headers: await getHeaders(ctx),
|
|
392
|
+
});
|
|
393
|
+
const user = requireAuth(session?.user ?? null);
|
|
394
|
+
return next({ ctx: { ...ctx, user, userId: user.id } });
|
|
395
|
+
})
|
|
396
|
+
.use(roleMiddleware);
|
|
397
|
+
|
|
398
|
+
export const authAction = c.action
|
|
399
|
+
.meta({ auth: "required" })
|
|
400
|
+
.use(async ({ ctx, next }) => {
|
|
401
|
+
const auth = getAuth(ctx);
|
|
402
|
+
const session = await auth.api.getSession({
|
|
403
|
+
headers: await getHeaders(ctx),
|
|
404
|
+
});
|
|
405
|
+
const user = requireAuth(session?.user ?? null);
|
|
406
|
+
return next({ ctx: { ...ctx, user, userId: user.id } });
|
|
407
|
+
});
|
|
408
|
+
|
|
409
|
+
export const publicRoute = c.httpAction;
|
|
410
|
+
export const authRoute = c.httpAction.use(async ({ ctx, next }) => {
|
|
411
|
+
const identity = await ctx.auth.getUserIdentity();
|
|
412
|
+
if (!identity) {
|
|
413
|
+
throw new CRPCError({ code: "UNAUTHORIZED", message: "Not authenticated" });
|
|
414
|
+
}
|
|
415
|
+
return next({
|
|
416
|
+
ctx: {
|
|
417
|
+
...ctx,
|
|
418
|
+
userId: identity.subject,
|
|
419
|
+
user: {
|
|
420
|
+
id: identity.subject,
|
|
421
|
+
email: identity.email,
|
|
422
|
+
name: identity.name,
|
|
423
|
+
},
|
|
424
|
+
},
|
|
425
|
+
});
|
|
426
|
+
});
|
|
427
|
+
export const optionalAuthRoute = c.httpAction.use(async ({ ctx, next }) => {
|
|
428
|
+
const identity = await ctx.auth.getUserIdentity();
|
|
429
|
+
return next({
|
|
430
|
+
ctx: {
|
|
431
|
+
...ctx,
|
|
432
|
+
userId: identity ? identity.subject : null,
|
|
433
|
+
user: identity
|
|
434
|
+
? {
|
|
435
|
+
id: identity.subject,
|
|
436
|
+
email: identity.email,
|
|
437
|
+
name: identity.name,
|
|
438
|
+
}
|
|
439
|
+
: null,
|
|
440
|
+
},
|
|
441
|
+
});
|
|
442
|
+
});
|
|
443
|
+
export const router = c.router;
|
|
444
|
+
```
|
|
445
|
+
|
|
446
|
+
### 6.10 Auth sign-in gate (required before Section 7+ and all optional modules/plugins)
|
|
447
|
+
|
|
448
|
+
Do not continue until all checks below pass:
|
|
449
|
+
|
|
450
|
+
1. Start local runtime with `bunx kitcn dev`
|
|
451
|
+
2. `bun run typecheck || bunx tsc --noEmit`
|
|
452
|
+
3. `bun test`
|
|
453
|
+
4. `bun run build`
|
|
454
|
+
5. Headed browser auth verification:
|
|
455
|
+
- Open `/auth`
|
|
456
|
+
- Complete sign-in with configured provider/credentials
|
|
457
|
+
- Confirm session is established (signed-in UI/state visible)
|
|
458
|
+
- Execute one protected query or mutation and confirm it succeeds (no `UNAUTHORIZED`)
|
|
459
|
+
6. Signed-out enforcement check:
|
|
460
|
+
- In a signed-out context, call one protected path and confirm `UNAUTHORIZED` is returned.
|
|
461
|
+
|
|
462
|
+
Stop/go rule:
|
|
463
|
+
|
|
464
|
+
1. If any sign-in gate check fails, fix auth wiring first.
|
|
465
|
+
2. Do not continue to Section 7, 8, 9, or 10 until this gate is green.
|
|
466
|
+
|
|
467
|
+
## 10. Plugin Setup Modules
|
|
468
|
+
|
|
469
|
+
Feature gate each plugin independently after auth core.
|
|
470
|
+
|
|
471
|
+
### 10.1 Admin plugin
|
|
472
|
+
|
|
473
|
+
Server:
|
|
474
|
+
|
|
475
|
+
```ts
|
|
476
|
+
import { admin } from "better-auth/plugins";
|
|
477
|
+
|
|
478
|
+
plugins: [
|
|
479
|
+
admin({
|
|
480
|
+
defaultRole: "user",
|
|
481
|
+
}),
|
|
482
|
+
];
|
|
483
|
+
```
|
|
484
|
+
|
|
485
|
+
Client:
|
|
486
|
+
|
|
487
|
+
```ts
|
|
488
|
+
import { adminClient } from "better-auth/client/plugins";
|
|
489
|
+
|
|
490
|
+
plugins: [adminClient()];
|
|
491
|
+
```
|
|
492
|
+
|
|
493
|
+
Schema needs admin fields on `user` + `impersonatedBy` on `session`.
|
|
494
|
+
|
|
495
|
+
### 10.2 Organizations plugin
|
|
496
|
+
|
|
497
|
+
Server: add `organization({...})` plugin config.
|
|
498
|
+
|
|
499
|
+
Client: add `organizationClient({...})` plugin config.
|
|
500
|
+
|
|
501
|
+
Schema: add `organization`, `member`, `invitation` (+ optional `team`, `teamMember`), and session fields `activeOrganizationId`/`activeTeamId`.
|
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
## Biome / Lint Setup
|
|
2
|
+
|
|
3
|
+
One-time config. Enforces import boundaries between `src/`, `convex/`, and `convex/shared/`.
|
|
4
|
+
|
|
5
|
+
### Install
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
bun add -D @biomejs/biome ultracite
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
### Architecture: 3-Layer Import Boundary
|
|
12
|
+
|
|
13
|
+
```
|
|
14
|
+
src/ ──(alias @convex/*)──> convex/shared/ ──(type-only)──> convex/functions/generated/
|
|
15
|
+
✗ convex/functions/ ✗ convex/lib/
|
|
16
|
+
✗ convex/lib/ ✗ convex/routers/
|
|
17
|
+
✗ convex/* packages ✗ convex/functions/*
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
Rationale:
|
|
21
|
+
|
|
22
|
+
1. **`src/` → `convex/shared/`**: Client code reads shared types via `@convex/*` alias. Cannot reach `convex/functions/` or `convex/lib/` (server-only).
|
|
23
|
+
2. **`convex/` → `convex/`**: Backend files cannot import from `src/`.
|
|
24
|
+
3. **`convex/shared/`**: Strictest — client-importable, so no lib/routers/functions imports. Only type-only imports from `generated/auth` (via biome-ignore).
|
|
25
|
+
|
|
26
|
+
### File Exclusions
|
|
27
|
+
|
|
28
|
+
```jsonc
|
|
29
|
+
"files": {
|
|
30
|
+
"includes": [
|
|
31
|
+
"!**/_generated", // Convex auto-generated (raw types)
|
|
32
|
+
"!**/generated", // kitcn codegen (ORM-wrapped types)
|
|
33
|
+
"!**/convex/shared/api.ts" // generated shared API
|
|
34
|
+
]
|
|
35
|
+
}
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
Both `_generated/` and `generated/` are excluded from all linting — rules only apply to user-written files.
|
|
39
|
+
|
|
40
|
+
### Key Rules
|
|
41
|
+
|
|
42
|
+
#### `_generated/server` is forbidden
|
|
43
|
+
|
|
44
|
+
All convex files must import from `generated/server` (ORM-wrapped types) instead of `_generated/server` (raw Convex types):
|
|
45
|
+
|
|
46
|
+
- `QueryCtx`, `MutationCtx`, `ActionCtx` — wrapped with ORM context
|
|
47
|
+
- `initCRPC` — prewired with `DataModel` and ORM context
|
|
48
|
+
- `orm`, `withOrm` — ORM instance
|
|
49
|
+
|
|
50
|
+
Exception: `generated/server.ts` itself imports from `_generated/server` (excluded from linting).
|
|
51
|
+
|
|
52
|
+
#### `ConvexError` is forbidden
|
|
53
|
+
|
|
54
|
+
Use `CRPCError` from `kitcn/crpc` instead of `ConvexError` from `convex/values`.
|
|
55
|
+
|
|
56
|
+
#### `convex/react` and `convex/nextjs` are forbidden in `src/`
|
|
57
|
+
|
|
58
|
+
- Use `useCRPC` from `@/lib/convex/crpc` instead of `convex/react`.
|
|
59
|
+
- Use `caller` from `@/lib/convex/rsc` or `createContext().caller` from `@/lib/convex/server` instead of `convex/nextjs`.
|
|
60
|
+
|
|
61
|
+
#### `convex/shared/` cannot import from `convex/functions/`
|
|
62
|
+
|
|
63
|
+
Exception: type-only imports from `generated/auth` need `biome-ignore`:
|
|
64
|
+
|
|
65
|
+
```ts
|
|
66
|
+
// biome-ignore lint/style/noRestrictedImports: types
|
|
67
|
+
import type { getAuth } from '../functions/generated/auth';
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
### Adding Exceptions
|
|
71
|
+
|
|
72
|
+
Use `biome-ignore` inline comments for legitimate rule violations:
|
|
73
|
+
|
|
74
|
+
```ts
|
|
75
|
+
// biome-ignore lint/style/noRestrictedImports: types
|
|
76
|
+
import type { ActionCtx } from './_generated/server';
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
### Full Config
|
|
80
|
+
|
|
81
|
+
```jsonc
|
|
82
|
+
{
|
|
83
|
+
"extends": ["ultracite/core", "ultracite/react", "ultracite/next"],
|
|
84
|
+
"files": {
|
|
85
|
+
"includes": [
|
|
86
|
+
"!**/_generated",
|
|
87
|
+
"!**/generated",
|
|
88
|
+
"!**/convex/shared/api.ts"
|
|
89
|
+
]
|
|
90
|
+
},
|
|
91
|
+
"overrides": [
|
|
92
|
+
{
|
|
93
|
+
// src/ cannot import from convex/* packages directly
|
|
94
|
+
"includes": ["src/**/*.ts*"],
|
|
95
|
+
"linter": {
|
|
96
|
+
"rules": {
|
|
97
|
+
"style": {
|
|
98
|
+
"noRestrictedImports": {
|
|
99
|
+
"level": "error",
|
|
100
|
+
"options": {
|
|
101
|
+
"paths": {
|
|
102
|
+
"convex/values": {
|
|
103
|
+
"importNames": ["ConvexError"],
|
|
104
|
+
"message": "Use CRPCError from 'kitcn/crpc' instead."
|
|
105
|
+
},
|
|
106
|
+
"convex/react": "Use useCRPC from '@/lib/convex/crpc' instead.",
|
|
107
|
+
"convex/nextjs": "Use caller from '@/lib/convex/rsc' or createContext({ headers }).caller from '@/lib/convex/server' instead."
|
|
108
|
+
},
|
|
109
|
+
"patterns": [{
|
|
110
|
+
"group": ["**/../convex/**"],
|
|
111
|
+
"message": "Use @convex/* alias instead of relative convex imports."
|
|
112
|
+
}]
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
},
|
|
119
|
+
{
|
|
120
|
+
// convex/ cannot import from src/ or _generated/server
|
|
121
|
+
"includes": ["convex/**/*.ts*"],
|
|
122
|
+
"linter": {
|
|
123
|
+
"rules": {
|
|
124
|
+
"style": {
|
|
125
|
+
"noRestrictedImports": {
|
|
126
|
+
"level": "error",
|
|
127
|
+
"options": {
|
|
128
|
+
"paths": {
|
|
129
|
+
"convex/values": {
|
|
130
|
+
"importNames": ["ConvexError"],
|
|
131
|
+
"message": "Use CRPCError from 'kitcn/crpc' instead."
|
|
132
|
+
}
|
|
133
|
+
},
|
|
134
|
+
"patterns": [
|
|
135
|
+
{
|
|
136
|
+
"group": ["@/*", "**/src/**"],
|
|
137
|
+
"message": "Convex files cannot import from src/."
|
|
138
|
+
},
|
|
139
|
+
{
|
|
140
|
+
"group": ["**/_generated/server*"],
|
|
141
|
+
"message": "Use convex/functions/generated/server instead of _generated/server."
|
|
142
|
+
}
|
|
143
|
+
]
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
},
|
|
150
|
+
{
|
|
151
|
+
// convex/shared/ is client-importable, so restrict its imports
|
|
152
|
+
"includes": ["convex/shared/**/*.ts*"],
|
|
153
|
+
"linter": {
|
|
154
|
+
"rules": {
|
|
155
|
+
"style": {
|
|
156
|
+
"noRestrictedImports": {
|
|
157
|
+
"level": "error",
|
|
158
|
+
"options": {
|
|
159
|
+
"patterns": [
|
|
160
|
+
{
|
|
161
|
+
"group": ["@/*", "**/src/**"],
|
|
162
|
+
"message": "Convex files cannot import from src/."
|
|
163
|
+
},
|
|
164
|
+
{
|
|
165
|
+
"group": ["**/convex/lib/**", "../lib/**"],
|
|
166
|
+
"message": "convex/shared cannot import from convex/lib."
|
|
167
|
+
},
|
|
168
|
+
{
|
|
169
|
+
"group": ["**/convex/routers/**", "../routers/**"],
|
|
170
|
+
"message": "convex/shared cannot import from convex/routers."
|
|
171
|
+
},
|
|
172
|
+
{
|
|
173
|
+
"group": ["**/_generated/server*"],
|
|
174
|
+
"message": "Use convex/functions/generated/server instead of _generated/server."
|
|
175
|
+
},
|
|
176
|
+
{
|
|
177
|
+
"group": ["**/../functions/*"],
|
|
178
|
+
"message": "convex/shared cannot import from convex/functions."
|
|
179
|
+
}
|
|
180
|
+
]
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
]
|
|
188
|
+
}
|
|
189
|
+
```
|
|
190
|
+
|