@lunora/cli 1.0.0-alpha.47 → 1.0.0-alpha.470
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/LICENSE.md +33 -0
- package/README.md +3 -1
- package/dist/bin.mjs +3 -10
- package/dist/cli.d.mts +31 -0
- package/dist/cli.d.ts +31 -0
- package/dist/cli.mjs +1 -0
- package/dist/index.d.mts +1167 -416
- package/dist/index.d.ts +1167 -416
- package/dist/index.mjs +1 -19
- package/dist/packem_chunks/cli.mjs +2 -0
- package/dist/packem_chunks/dispatch.mjs +1 -0
- package/dist/packem_chunks/handler.mjs +55 -158
- package/dist/packem_chunks/handler10.mjs +2 -16
- package/dist/packem_chunks/handler11.mjs +1 -22
- package/dist/packem_chunks/handler12.mjs +1 -192
- package/dist/packem_chunks/handler13.mjs +1 -131
- package/dist/packem_chunks/handler14.mjs +3 -65
- package/dist/packem_chunks/handler15.mjs +1 -58
- package/dist/packem_chunks/handler16.mjs +1 -80
- package/dist/packem_chunks/handler17.mjs +1 -43
- package/dist/packem_chunks/handler18.mjs +1 -105
- package/dist/packem_chunks/handler19.mjs +1 -170
- package/dist/packem_chunks/handler2.mjs +1 -114
- package/dist/packem_chunks/handler20.mjs +1 -94
- package/dist/packem_chunks/handler21.mjs +7 -94
- package/dist/packem_chunks/handler22.mjs +1 -0
- package/dist/packem_chunks/handler23.mjs +3 -0
- package/dist/packem_chunks/handler24.mjs +3 -0
- package/dist/packem_chunks/handler25.mjs +2 -0
- package/dist/packem_chunks/handler26.mjs +1 -0
- package/dist/packem_chunks/handler27.mjs +2 -0
- package/dist/packem_chunks/handler28.mjs +99 -0
- package/dist/packem_chunks/handler29.mjs +3 -0
- package/dist/packem_chunks/handler3.mjs +1 -204
- package/dist/packem_chunks/handler30.mjs +15 -0
- package/dist/packem_chunks/handler31.mjs +1 -0
- package/dist/packem_chunks/handler32.mjs +1 -0
- package/dist/packem_chunks/handler4.mjs +1 -33
- package/dist/packem_chunks/handler5.mjs +1 -49
- package/dist/packem_chunks/handler6.mjs +3 -91
- package/dist/packem_chunks/handler7.mjs +1 -42
- package/dist/packem_chunks/handler8.mjs +1 -174
- package/dist/packem_chunks/handler9.mjs +1 -315
- package/dist/packem_chunks/runCodegenCommand.mjs +4 -52
- package/dist/packem_chunks/runDevCommand.mjs +6 -0
- package/dist/packem_chunks/runInitCommand.mjs +423 -1447
- package/dist/packem_chunks/runResetCommand.mjs +1 -41
- package/dist/packem_chunks/runRpcCommand.mjs +1 -68
- package/dist/packem_shared/DEFAULT_IMPORT_BATCH_SIZE-Ck9tABfr.mjs +1 -0
- package/dist/packem_shared/EXIT_CODE-DQVhSE-1.mjs +1 -0
- package/dist/packem_shared/admin-token-VdUnvnKW.mjs +1 -0
- package/dist/packem_shared/admin-url-DJB8P6Jh.mjs +1 -0
- package/dist/packem_shared/api-spec-lYyq4vL6.mjs +1 -0
- package/dist/packem_shared/binding-manifest-file-CQ1eQYy1.mjs +2 -0
- package/dist/packem_shared/buildRegistryIndex-Dc8D8AR6.mjs +1 -0
- package/dist/packem_shared/cap-error-body-YBKO32BF.mjs +1 -0
- package/dist/packem_shared/catalog-CahzmDLV.mjs +1 -0
- package/dist/packem_shared/cli-manifest-DoQ_3bzs.mjs +1 -0
- package/dist/packem_shared/cloudflare-credentials-BVmZnDZk.mjs +1 -0
- package/dist/packem_shared/codegen-error-AmH54ofi.mjs +3 -0
- package/dist/packem_shared/command-BpMv2n0o.mjs +1 -0
- package/dist/packem_shared/commands-CkuCMUP_.mjs +13 -0
- package/dist/packem_shared/createLogger-4TujevyB.mjs +2 -0
- package/dist/packem_shared/createRecordingSpawner-DchLi0gR.mjs +1 -0
- package/dist/packem_shared/deploy-target-resolve-BTCW1Iv4.mjs +1 -0
- package/dist/packem_shared/diffSnapshots-Y1G8am6C.mjs +7 -0
- package/dist/packem_shared/docker-DpVxvYpL.mjs +1 -0
- package/dist/packem_shared/export-rows-EzdNDYpa.mjs +3 -0
- package/dist/packem_shared/free-port-DjbFhaaT.mjs +1 -0
- package/dist/packem_shared/health-probe-BLnoq_oC.mjs +13 -0
- package/dist/packem_shared/import-3Y59Ry9F.mjs +13 -0
- package/dist/packem_shared/index-oHZBGBwB.mjs +1 -0
- package/dist/packem_shared/insertSchemaExtension-CbTH3lCQ.mjs +10 -0
- package/dist/packem_shared/js-literal-FWLi94Lh.mjs +1 -0
- package/dist/packem_shared/lint-ignore-report-DKZagpqk.mjs +2 -0
- package/dist/packem_shared/mcp-config-file-GR1Y_CVw.mjs +2 -0
- package/dist/packem_shared/open-url-EnKy--w-.mjs +1 -0
- package/dist/packem_shared/output-format-DWe_tkTR.mjs +2 -0
- package/dist/packem_shared/parseManifest-CwPTKdtS.mjs +1 -0
- package/dist/packem_shared/path-containment-CgxYZggb.mjs +1 -0
- package/dist/packem_shared/plan-pog5AvMk.mjs +5 -0
- package/dist/packem_shared/planDevCommand-Bl6OrwXm.mjs +1 -0
- package/dist/packem_shared/platform-diagnostics-Cn2g6Jh-.mjs +4 -0
- package/dist/packem_shared/prompt-cancelled-BvsNxg_Q.mjs +1 -0
- package/dist/packem_shared/render-lunora-error-JsLelWwR.mjs +2 -0
- package/dist/packem_shared/resolve-Bdc9KEUv.mjs +5 -0
- package/dist/packem_shared/resolve-target-BRvw8Pf7.mjs +1 -0
- package/dist/packem_shared/runAddCommand-gThKW-ZB.mjs +1 -0
- package/dist/packem_shared/runDeployCommand-CNgOUx91.mjs +1 -0
- package/dist/packem_shared/runExportCommand-_PtkJJDT.mjs +5 -0
- package/dist/packem_shared/runMigrateGenerateCommand-CguLEIZp.mjs +11 -0
- package/dist/packem_shared/schemaIrToSnapshot-Ck7DSPdf.mjs +1 -0
- package/dist/packem_shared/shared-CV7ESvuj.mjs +2 -0
- package/dist/packem_shared/storage-3GOIFVZm.mjs +1 -0
- package/dist/packem_shared/tui-prompts-B3YwUhGw.mjs +4 -0
- package/dist/packem_shared/variables-CPYyJtsL.mjs +1 -0
- package/dist/packem_shared/vectorize-metadata-DK61NmVx.mjs +2 -0
- package/dist/packem_shared/wrangler-name-Dsk5K1f-.mjs +1 -0
- package/dist/packem_shared/wrangler-secrets-C9siMBwN.mjs +1 -0
- package/package.json +45 -19
- package/skills/README.md +36 -17
- package/skills/lunora/SKILL.md +158 -65
- package/skills/lunora-cloudflare-audit/SKILL.md +164 -0
- package/skills/lunora-create-package/SKILL.md +97 -84
- package/skills/lunora-deploy/SKILL.md +212 -102
- package/skills/lunora-functions/SKILL.md +367 -60
- package/skills/lunora-migration-helper/SKILL.md +194 -149
- package/skills/lunora-performance-audit/SKILL.md +137 -95
- package/skills/lunora-quickstart/SKILL.md +136 -136
- package/skills/lunora-realtime/SKILL.md +217 -97
- package/skills/lunora-setup-auth/SKILL.md +203 -101
- package/skills/lunora-setup-hyperdrive/SKILL.md +153 -110
- package/skills/lunora-setup-hyperdrive-global/SKILL.md +152 -94
- package/skills/lunora-setup-mail/SKILL.md +87 -113
- package/skills/lunora-setup-scheduler/SKILL.md +133 -97
- package/skills/lunora-setup-storage/SKILL.md +189 -95
- package/dist/packem_chunks/planDevCommand.mjs +0 -541
- package/dist/packem_chunks/runDeployCommand.mjs +0 -594
- package/dist/packem_chunks/runMigrateGenerateCommand.mjs +0 -397
- package/dist/packem_shared/COMMANDS-B0ftFD_3.mjs +0 -948
- package/dist/packem_shared/DEFAULT_IMPORT_BATCH_SIZE-Ck-2bU08.mjs +0 -244
- package/dist/packem_shared/admin-url-4UzT-CI4.mjs +0 -19
- package/dist/packem_shared/api-spec-CtA6ilu4.mjs +0 -13
- package/dist/packem_shared/buildRegistryIndex-BcYe607_.mjs +0 -38
- package/dist/packem_shared/codegen-error-DJG-ghs_.mjs +0 -31
- package/dist/packem_shared/command-D3lB_4Az.mjs +0 -19
- package/dist/packem_shared/commands-B-gR09Z_.mjs +0 -845
- package/dist/packem_shared/createLogger-B40gPzQo.mjs +0 -78
- package/dist/packem_shared/createRecordingSpawner-DxI3mebw.mjs +0 -43
- package/dist/packem_shared/detect-package-manager-DYp7n3mJ.mjs +0 -61
- package/dist/packem_shared/diffSnapshots-BeDvvNiF.mjs +0 -161
- package/dist/packem_shared/docker-hMQ97KSQ.mjs +0 -21
- package/dist/packem_shared/insertSchemaExtension-DAqbfr9Z.mjs +0 -64
- package/dist/packem_shared/open-url-Dfq6fAyT.mjs +0 -41
- package/dist/packem_shared/output-format-wUvAN6AL.mjs +0 -17
- package/dist/packem_shared/parseArgs-YXFuKdEk.mjs +0 -56
- package/dist/packem_shared/parseManifest--vZf2FY1.mjs +0 -94
- package/dist/packem_shared/prompt-cancelled-APzX1Im-.mjs +0 -9
- package/dist/packem_shared/resolve-target-qbsJ_5sF.mjs +0 -16
- package/dist/packem_shared/runAddCommand-bnY6-HKb.mjs +0 -4
- package/dist/packem_shared/schema-drift-gate-BtBt0as0.mjs +0 -79
- package/dist/packem_shared/schemaIrToSnapshot-DdsljJT-.mjs +0 -43
- package/dist/packem_shared/storage-BIsph-Vk.mjs +0 -84
- package/dist/packem_shared/tui-prompts-BjEN8XgP.mjs +0 -658
- package/dist/packem_shared/wrangler-name-cy4yhm9j.mjs +0 -12
- package/dist/packem_shared/wrangler-secrets-P2_ZUR-k.mjs +0 -47
|
@@ -1,110 +1,195 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: lunora-setup-auth
|
|
3
|
-
description: Adds authentication to a Lunora app.
|
|
4
|
-
OAuth (Clerk, Auth0), magic link, or email OTP via `lunora registry add auth`,
|
|
5
|
-
wiring the auth handler into the Worker, and gating functions on the session.
|
|
3
|
+
description: Adds authentication to a Lunora app with the `auth` registry item (better-auth via `@lunora/auth`, users and sessions in D1). Covers email/password sign-up and sign-in, Clerk or Auth0 OAuth, magic link, email OTP, copy-in auth screens (`lunora add auth-ui`), wiring `/api/auth/*` and `resolveIdentity` into the Worker, and gating functions on `ctx.auth.userId`. Use when the user asks to "add auth", "add login/sign-in/sign-up", "protect this query", or "add Clerk/Auth0/magic link/OTP", wants to keep an external identity provider (Clerk, WorkOS, Auth0, Supabase, any OIDC JWT) as the session owner, runs `lunora add auth` or `lunora registry add auth`, edits `lunora/auth/index.ts`, or `ctx.auth.userId` is unexpectedly `null`.
|
|
6
4
|
---
|
|
7
5
|
|
|
8
6
|
# Lunora Setup Auth
|
|
9
7
|
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
8
|
+
The `auth` registry item is built on `@lunora/auth`, a thin wrapper over
|
|
9
|
+
[better-auth](https://www.better-auth.com). Users and sessions are stored in D1.
|
|
10
|
+
Lunora's `SessionDO` is a separate TTL'd token store that `@lunora/auth` never
|
|
11
|
+
calls, so don't wire it for auth. If you need auth state in a Durable Object
|
|
12
|
+
instead of D1, use `LunoraAuthDO` / `.auth({ namespace, internalSecret })`.
|
|
14
13
|
|
|
15
|
-
|
|
14
|
+
If the project has no Lunora backend yet, start with `lunora-quickstart`.
|
|
16
15
|
|
|
17
|
-
|
|
18
|
-
- Adding an OAuth/OIDC provider (Clerk, Auth0), magic link, or email OTP.
|
|
19
|
-
- Gating queries/mutations on the signed-in user.
|
|
16
|
+
## Already have an identity provider? Skip better-auth
|
|
20
17
|
|
|
21
|
-
|
|
18
|
+
If the app's users sign in through Clerk, WorkOS AuthKit, Auth0, Supabase or any
|
|
19
|
+
OIDC issuer, and that provider should keep owning the session, don't add the
|
|
20
|
+
`auth` item. Verify the provider's JWT instead with `@lunora/jwt-auth`:
|
|
22
21
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
22
|
+
```bash
|
|
23
|
+
lunora init --auth=clerk # new project: or workos | auth0 (wires everything below)
|
|
24
|
+
lunora registry add auth-clerk-jwt # existing project: or auth-workos-jwt | auth-auth0-jwt
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
```ts
|
|
28
|
+
import { clerk } from "@lunora/jwt-auth"; // workos, auth0, supabase, jwtIdentityResolver
|
|
29
|
+
import { memoizeIdentity } from "lunorash/runtime";
|
|
30
|
+
|
|
31
|
+
defineApp<Env>()
|
|
32
|
+
.shard((env) => env.SHARD)
|
|
33
|
+
.extend((env) => ({
|
|
34
|
+
resolveIdentity: memoizeIdentity(clerk({ publishableKey: env.CLERK_PUBLISHABLE_KEY ?? "", authorizedParties: env.CLERK_AUTHORIZED_PARTIES ?? "" })),
|
|
35
|
+
}))
|
|
36
|
+
.build();
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
On the client, create the `LunoraClient` with `authTokenOnUpgrade: true` and,
|
|
40
|
+
once signed in, call `setAuthTokenProvider(client, getToken, { subject: userId })`
|
|
41
|
+
from `lunorash/client`. It refreshes before `exp` and on `onTokenExpired`; map
|
|
42
|
+
the provider's "signed out" error (WorkOS `LoginRequiredError`, Auth0
|
|
43
|
+
`login_required`) to `null`. `authTokenOnUpgrade` is what authenticates
|
|
44
|
+
live-query sockets: the browser offers the bearer as a `Sec-WebSocket-Protocol`
|
|
45
|
+
entry. Without it, a WorkOS or Auth0 socket, or a cross-origin Clerk one, is
|
|
46
|
+
anonymous.
|
|
26
47
|
|
|
27
|
-
|
|
48
|
+
Org claims arrive as `orgId` with the org role (`orgRole` for Clerk, `role` /
|
|
49
|
+
`orgRoles` for WorkOS); no preset sets the app-wide `roles` RLS reads, since
|
|
50
|
+
any Clerk user can create an organization and be its admin. Check the org role
|
|
51
|
+
together with `orgId` in a policy. `CLERK_AUTHORIZED_PARTIES` is required.
|
|
52
|
+
Full guide:
|
|
53
|
+
`/docs/concepts/external-auth`, with runnable apps in `examples/clerk-auth` and
|
|
54
|
+
`examples/workos-auth`.
|
|
28
55
|
|
|
29
|
-
|
|
30
|
-
2. Mount the auth request handler in the Worker entry.
|
|
31
|
-
3. Configure env vars and the D1 database.
|
|
32
|
-
4. (Optional) Layer a provider item (Clerk / Auth0 / magic link / OTP) on top.
|
|
33
|
-
5. Gate functions on `ctx.auth.userId`; gate UI with the auth gates/hooks.
|
|
56
|
+
The rest of this skill covers the better-auth path.
|
|
34
57
|
|
|
35
|
-
## Step 1: Add the
|
|
58
|
+
## Step 1: Add the item
|
|
36
59
|
|
|
37
60
|
```bash
|
|
38
|
-
lunora
|
|
61
|
+
lunora add auth # asks for the provider and D1 database name
|
|
62
|
+
lunora add auth --provider clerk --db my-app-db # non-interactive (auth | clerk | auth0)
|
|
63
|
+
lunora registry add auth # low-level: writes placeholder names
|
|
39
64
|
```
|
|
40
65
|
|
|
41
|
-
|
|
66
|
+
Then run `pnpm install`. The item does the following:
|
|
42
67
|
|
|
43
|
-
1. Adds `@lunora/auth`, `@lunora/mail
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
**yours** to edit.
|
|
48
|
-
3. Adds a D1 `DB` binding to `wrangler.jsonc` (better-auth persists
|
|
49
|
-
users/sessions there).
|
|
50
|
-
4. Scaffolds `BETTER_AUTH_SECRET`, `BETTER_AUTH_URL`, and `MAIL_FROM` into
|
|
51
|
-
`.dev.vars`.
|
|
68
|
+
1. Adds `@lunora/auth`, `@lunora/mail` and `@lunora/server` to `package.json`.
|
|
69
|
+
2. Copies `lunora/auth/index.ts` into the project. The project owns this file and can edit it. It exports `buildAuth(env)`, `getAuth(env)` (memoized per isolate) and `mountAuth(env, request)`, which routes `/api/auth/*` and returns `undefined` for any other path. The scaffold turns on email/password with `requireEmailVerification: true` and sends verification and reset mail through `@lunora/mail`. It also adds the `uiConfig()` plugin.
|
|
70
|
+
3. Adds a D1 `DB` binding to `wrangler.jsonc`. The `database_id` is a placeholder.
|
|
71
|
+
4. Writes `BETTER_AUTH_SECRET`, `BETTER_AUTH_URL` and `MAIL_FROM` to `.dev.vars`.
|
|
52
72
|
|
|
53
|
-
## Step 2:
|
|
73
|
+
## Step 2: Database and env vars
|
|
54
74
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
75
|
+
| Var | Secret | Notes |
|
|
76
|
+
| -------------------- | ------ | --------------------------------------------------------------------------------- |
|
|
77
|
+
| `BETTER_AUTH_SECRET` | yes | At least 32 chars (`openssl rand -base64 32`). Production: `wrangler secret put`. |
|
|
78
|
+
| `BETTER_AUTH_URL` | no | Public base URL. better-auth uses it for callbacks and cookies. |
|
|
79
|
+
| `MAIL_FROM` | no | Sender for verification and reset mail. |
|
|
58
80
|
|
|
59
|
-
|
|
81
|
+
```bash
|
|
82
|
+
wrangler d1 create my-app-db # paste the returned id into the DB binding
|
|
83
|
+
lunora doctor # flags a placeholder database_id
|
|
84
|
+
```
|
|
60
85
|
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
86
|
+
Don't declare the better-auth tables (`user`, `session`, `account`,
|
|
87
|
+
`verification`) in `lunora/schema.ts`. better-auth owns them in D1. In dev the
|
|
88
|
+
schema is auto-applied on the first `/api/auth/*` request. For production,
|
|
89
|
+
pre-apply it with `compileMigrationsSql(auth.options)` piped to
|
|
90
|
+
`wrangler d1 execute <db> --remote --file -`. The item README has the script.
|
|
91
|
+
Re-run that migration after adding a plugin that brings its own tables, such as
|
|
92
|
+
`organization` or `twoFactor`.
|
|
66
93
|
|
|
67
|
-
|
|
68
|
-
`wrangler.jsonc`:
|
|
94
|
+
## Step 3: Wire the Worker
|
|
69
95
|
|
|
70
|
-
|
|
71
|
-
|
|
96
|
+
There are two parts. `/api/auth/*` has to reach better-auth. `resolveIdentity`
|
|
97
|
+
has to turn the session cookie into `ctx.auth`. If `resolveIdentity` is missing,
|
|
98
|
+
sign-in still works but `ctx.auth.userId` stays `null` in every function.
|
|
99
|
+
|
|
100
|
+
**Through the generated `defineApp` builder.** This is the default for
|
|
101
|
+
templates. `.auth()` does both parts: it builds the instance lazily, runs
|
|
102
|
+
`ensureMigrated`, dispatches `/api/auth/*` and sets `resolveIdentity`. A
|
|
103
|
+
Vite-first app (`"main": "virtual:lunora/worker"`) has no entry file, so chain
|
|
104
|
+
the call in the `lunora.config.ts` `app` hook. A hand-written entry
|
|
105
|
+
(`src/server.ts`) chains it on `defineApp()` directly:
|
|
106
|
+
|
|
107
|
+
```ts
|
|
108
|
+
// lunora.config.ts
|
|
109
|
+
import type { AppBuilder, LunoraConfig } from "./lunora/_generated/app";
|
|
110
|
+
// Move the object `buildAuth` passes to `createAuth` (minus `database`; the
|
|
111
|
+
// builder supplies the D1 adapter) into an exported `authOptions(env)` in
|
|
112
|
+
// lunora/auth/index.ts, and have `buildAuth` spread it too.
|
|
113
|
+
import { authOptions } from "./lunora/auth/index.js";
|
|
114
|
+
|
|
115
|
+
interface Env {
|
|
116
|
+
BETTER_AUTH_SECRET: string;
|
|
117
|
+
BETTER_AUTH_URL: string;
|
|
118
|
+
DB: D1Database;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
export default {
|
|
122
|
+
target: "cloudflare",
|
|
123
|
+
app: (app: AppBuilder<Env>): AppBuilder<Env> =>
|
|
124
|
+
app.auth({
|
|
125
|
+
d1: (env) => env.DB,
|
|
126
|
+
options: (env) => authOptions(env),
|
|
127
|
+
}),
|
|
128
|
+
} satisfies LunoraConfig<Env>;
|
|
72
129
|
```
|
|
73
130
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
`
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
131
|
+
Reuse the scaffold's options rather than retyping a subset: a hand-written
|
|
132
|
+
`{ emailAndPassword: { enabled: true } }` drops `requireEmailVerification`,
|
|
133
|
+
`sendResetPassword` and `sendVerificationEmail`, so users sign in unverified and
|
|
134
|
+
no verification or reset mail goes out.
|
|
135
|
+
|
|
136
|
+
`.auth()` appears on the builder only after codegen sees `@lunora/auth`
|
|
137
|
+
installed, so run `lunora codegen` first.
|
|
80
138
|
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
139
|
+
**Hand-rolled `createWorker` entry.** Call the scaffolded `mountAuth` first in
|
|
140
|
+
`fetch`, then hand every other request to the Lunora worker, which resolves the
|
|
141
|
+
identity:
|
|
84
142
|
|
|
85
|
-
|
|
143
|
+
```ts
|
|
144
|
+
import type { AuthEnv } from "../../lunora/auth/index.js";
|
|
145
|
+
import { getAuth, mountAuth } from "../../lunora/auth/index.js";
|
|
146
|
+
|
|
147
|
+
const worker = createWorker({
|
|
148
|
+
resolveIdentity: async (req, env) => {
|
|
149
|
+
const session = await getAuth(env as AuthEnv).api.getSession({ headers: req.headers });
|
|
150
|
+
return session?.user ? { userId: session.user.id } : null;
|
|
151
|
+
},
|
|
152
|
+
// …
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
export default {
|
|
156
|
+
async fetch(request: Request, env: AuthEnv, ctx: ExecutionContext): Promise<Response> {
|
|
157
|
+
const authResponse = await mountAuth(env, request);
|
|
158
|
+
if (authResponse) return authResponse;
|
|
159
|
+
return worker.fetch(request, env, ctx); // RPC, WebSocket, everything else
|
|
160
|
+
},
|
|
161
|
+
};
|
|
162
|
+
```
|
|
86
163
|
|
|
87
|
-
|
|
164
|
+
## Step 4: Providers and add-ons (optional)
|
|
165
|
+
|
|
166
|
+
Each of these items requires the base `auth` item and installs it if it is
|
|
167
|
+
missing:
|
|
88
168
|
|
|
89
169
|
```bash
|
|
90
|
-
lunora
|
|
91
|
-
lunora
|
|
92
|
-
lunora
|
|
93
|
-
lunora
|
|
170
|
+
lunora add auth-clerk # Clerk OIDC (genericOAuth): CLERK_CLIENT_ID / _SECRET / CLERK_ISSUER_URL
|
|
171
|
+
lunora add auth-auth0 # Auth0 OIDC: AUTH0_CLIENT_ID / _SECRET / AUTH0_DOMAIN
|
|
172
|
+
lunora add auth-magic-link # passwordless link, sent via @lunora/mail
|
|
173
|
+
lunora add auth-otp # email one-time code, sent via @lunora/mail
|
|
174
|
+
lunora add auth-emails # styled React templates for the auth mails (renderEmail)
|
|
175
|
+
lunora add auth-ui # copy-in screens; framework auto-detected (no React Native port)
|
|
94
176
|
```
|
|
95
177
|
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
`.
|
|
178
|
+
The provider items copy a file into `lunora/auth/` but don't register it
|
|
179
|
+
anywhere. Add the plugin to the `plugins` array yourself, in `buildAuth` or in
|
|
180
|
+
`.auth({ options })`. For example, `plugins: [uiConfig(), clerk(env)]` with
|
|
181
|
+
`import { clerk } from "./clerk.js"`. The other exports are `auth0`,
|
|
182
|
+
`magicLinkPlugin` and `emailOtpPlugin`. The OAuth callback URL is
|
|
183
|
+
`<BETTER_AUTH_URL>/api/auth/oauth2/callback/<clerk|auth0>`.
|
|
99
184
|
|
|
100
185
|
## Step 5: Use the session
|
|
101
186
|
|
|
102
187
|
### In functions
|
|
103
188
|
|
|
104
|
-
The runtime resolves the session and exposes the user on every context:
|
|
105
|
-
|
|
106
189
|
```ts
|
|
107
|
-
import { LunoraError
|
|
190
|
+
import { LunoraError } from "lunorash/server";
|
|
191
|
+
|
|
192
|
+
import { mutation, v } from "#lunora/_generated/server.js";
|
|
108
193
|
|
|
109
194
|
export const createDocument = mutation.input({ title: v.string() }).mutation(async ({ ctx, args: { title } }) => {
|
|
110
195
|
if (!ctx.auth.userId) {
|
|
@@ -114,27 +199,41 @@ export const createDocument = mutation.input({ title: v.string() }).mutation(asy
|
|
|
114
199
|
});
|
|
115
200
|
```
|
|
116
201
|
|
|
117
|
-
|
|
118
|
-
|
|
202
|
+
`ctx.auth.getIdentity()` returns the full session claims, such as email and
|
|
203
|
+
name. For org or role checks, compose `withAuthPlugins(auth)` and call the
|
|
204
|
+
better-auth server API.
|
|
205
|
+
|
|
206
|
+
### In the client
|
|
119
207
|
|
|
120
|
-
|
|
208
|
+
The default is a better-auth **cookie session**. `LunoraClient` needs no token,
|
|
209
|
+
so don't call `setToken` / `setAuthToken` for it. Sign in with the better-auth
|
|
210
|
+
client. Build that client with `createLunoraAuthClient`, which installs
|
|
211
|
+
`lunoraSessionSync()`. That plugin tells every open `LunoraClient`, including
|
|
212
|
+
ones in other tabs, to re-resolve the session after sign-in or sign-out. Without
|
|
213
|
+
it, live queries keep serving the previous user's rows. If you call
|
|
214
|
+
`createAuthClient` yourself, add `lunoraSessionSync()` to its plugins.
|
|
121
215
|
|
|
122
216
|
```tsx
|
|
123
|
-
import {
|
|
217
|
+
import { createLunoraAuthClient } from "@lunora/auth/plugins/client";
|
|
218
|
+
import { Authenticated, AuthLoading, Unauthenticated, useAuth } from "@lunora/react";
|
|
219
|
+
import { createAuthClient } from "better-auth/react";
|
|
124
220
|
|
|
125
|
-
|
|
126
|
-
|
|
221
|
+
export const authClient = createLunoraAuthClient(createAuthClient);
|
|
222
|
+
|
|
223
|
+
function Account({ email, password }: { email: string; password: string }) {
|
|
224
|
+
const { user } = useAuth(); // { setToken, status, token, user }
|
|
127
225
|
|
|
128
226
|
return (
|
|
129
227
|
<>
|
|
228
|
+
<AuthLoading>…</AuthLoading>
|
|
130
229
|
<Authenticated>
|
|
131
230
|
<span>Signed in as {user?.email}</span>
|
|
132
|
-
<button type="button" onClick={() => signOut()}>
|
|
231
|
+
<button type="button" onClick={() => authClient.signOut()}>
|
|
133
232
|
Sign out
|
|
134
233
|
</button>
|
|
135
234
|
</Authenticated>
|
|
136
235
|
<Unauthenticated>
|
|
137
|
-
<button type="button" onClick={() => signIn()}>
|
|
236
|
+
<button type="button" onClick={() => authClient.signIn.email({ email, password })}>
|
|
138
237
|
Sign in
|
|
139
238
|
</button>
|
|
140
239
|
</Unauthenticated>
|
|
@@ -143,28 +242,31 @@ function Account() {
|
|
|
143
242
|
}
|
|
144
243
|
```
|
|
145
244
|
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
-
|
|
166
|
-
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
245
|
+
A client plugin toggle such as `{ plugins: { twoFactor: true } }` only adds
|
|
246
|
+
the client half. Register the matching server plugin (`twoFactor()` from
|
|
247
|
+
`@lunora/auth/plugins`) in `buildAuth`'s `plugins` too, or its endpoints don't
|
|
248
|
+
exist.
|
|
249
|
+
|
|
250
|
+
`setToken` is only for bearer-token setups, such as React Native or a JWT from
|
|
251
|
+
an external IdP.
|
|
252
|
+
|
|
253
|
+
Branch on `status`, not on `user === null`. `status` takes one of four values:
|
|
254
|
+
`"unauthenticated"`, `"loading"`, `"authenticated"` or `"unreachable"`. The gates
|
|
255
|
+
in `@lunora/vue`, `@lunora/solid`, `@lunora/svelte` and `@lunora/angular` use the
|
|
256
|
+
same four. `"unreachable"` means a credential is held but the session endpoint
|
|
257
|
+
failed, for example on an offline reload. The gates treat it as authenticated,
|
|
258
|
+
yet `user` can still be `null` at that point. A UI that reads that `null` as
|
|
259
|
+
"signed out" shows a signed-in user the signed-out screen. The full contract is
|
|
260
|
+
documented on `AuthStatus` in `@lunora/client/auth`.
|
|
261
|
+
|
|
262
|
+
## Pitfalls
|
|
263
|
+
|
|
264
|
+
- **Sign-in rejected right after sign-up.** The scaffold sets `requireEmailVerification: true`, so the user has to open the verification link first. In dev, that link is in the Studio Mail tab.
|
|
265
|
+
- **Mail in production.** Dev captures mail into the Studio. In production with no `MAIL_FROM`, the scaffold throws rather than logging the link. Auth links are bearer credentials, so this is intentional. Production needs `lunora add email` (the `SEND_EMAIL` binding) or `RESEND_API_KEY`, plus a verified sender domain. See `lunora-setup-mail`.
|
|
266
|
+
- **Unauthenticated storage or other items.** Items that call `requireOwner` throw `UNAUTHORIZED` until `resolveIdentity` is wired (Step 3).
|
|
267
|
+
|
|
268
|
+
## Verify
|
|
269
|
+
|
|
270
|
+
1. Run `lunora doctor`. It should report no placeholder `database_id`.
|
|
271
|
+
2. Run `lunora dev`. Sign up, open the verification link from the Studio Mail tab, then sign in.
|
|
272
|
+
3. Call a query that returns `ctx.auth.userId`. It should return the user's id, not `null`.
|