@binary1702/auth 0.2.4 → 0.3.0
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/AGENTS.md +139 -0
- package/CHANGELOG.md +31 -0
- package/INSTALL.md +30 -17
- package/PROMPT.md +325 -37
- package/README.md +293 -56
- package/dist/cli.js +255 -22
- package/dist/core/admin-html.d.ts +26 -0
- package/dist/core/admin-html.js +252 -0
- package/dist/core/admin.d.ts +26 -0
- package/dist/core/admin.js +48 -0
- package/dist/core/config.d.ts +8 -1
- package/dist/core/config.js +20 -6
- package/dist/core/db.d.ts +138 -7
- package/dist/core/db.js +406 -47
- package/dist/core/email.d.ts +49 -0
- package/dist/core/email.js +213 -0
- package/dist/core/guard.d.ts +49 -0
- package/dist/core/guard.js +103 -0
- package/dist/core/html.d.ts +36 -0
- package/dist/core/html.js +106 -25
- package/dist/core/options.d.ts +76 -0
- package/dist/core/options.js +310 -0
- package/dist/core/org.d.ts +34 -0
- package/dist/core/org.js +55 -0
- package/dist/core/password.d.ts +16 -0
- package/dist/core/password.js +41 -12
- package/dist/core/rate.d.ts +26 -0
- package/dist/core/rate.js +70 -0
- package/dist/core/reset.d.ts +9 -0
- package/dist/core/reset.js +31 -0
- package/dist/core/session.d.ts +23 -2
- package/dist/core/session.js +45 -7
- package/dist/core/signup.d.ts +9 -6
- package/dist/core/signup.js +26 -15
- package/dist/core/tokens.d.ts +29 -0
- package/dist/core/tokens.js +64 -0
- package/dist/core/verify.d.ts +8 -0
- package/dist/core/verify.js +33 -0
- package/dist/core/yaml.d.ts +10 -0
- package/dist/core/yaml.js +85 -0
- package/dist/index.d.ts +17 -6
- package/dist/index.js +17 -6
- package/dist/next/index.d.ts +11 -4
- package/dist/next/index.js +455 -51
- package/dist/next/proxy.d.ts +2 -1
- package/dist/next/proxy.js +16 -7
- package/dist/react/index.d.ts +18 -0
- package/dist/react/index.js +55 -0
- package/package.json +16 -3
package/PROMPT.md
CHANGED
|
@@ -4,77 +4,365 @@ Copy everything below the line into an AI coding agent running inside the Next.j
|
|
|
4
4
|
|
|
5
5
|
---
|
|
6
6
|
|
|
7
|
-
Install `@binary1702/auth` in this project. It adds
|
|
7
|
+
Install `@binary1702/auth` in this project. It adds email + password sign-in to a Next.js App Router app: a sign-in page, a session cookie, and a guard in front of every page and API route. Follow the steps in order. After each step, note what happened; you will report it at the end.
|
|
8
8
|
|
|
9
|
-
##
|
|
9
|
+
## How the package works (read first)
|
|
10
10
|
|
|
11
|
-
|
|
11
|
+
Knowing the moving parts lets you fix problems instead of guessing.
|
|
12
12
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
13
|
+
```
|
|
14
|
+
users, orgs, members, the package's tables, created by migrations on first start. Do not create tables with
|
|
15
|
+
tokens, auth_migrations these names in the app; the installer refuses a database whose users table is not its own
|
|
16
|
+
AUTH_SECRET signs the session cookie; anyone with it can forge sessions
|
|
17
|
+
auth_session cookie set on sign-in, httpOnly, 7 days; says WHO, nothing else
|
|
18
|
+
proxy.ts runs before every request: valid cookie or public path? continue : sign in
|
|
19
|
+
getSession() in your server code: cookie valid AND user still allowed? → { user, organization, authorization }
|
|
20
|
+
b17_auth.yaml the app's settings: name, sign-up policy, which paths need sign-in, email, roles → permissions
|
|
21
|
+
.env.local secrets only; the yaml references them as ${NAME}
|
|
22
|
+
/auth/[...path] route serves sign-in, sign-up, sign-out, session, verify, forgot, reset
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
Two gates:
|
|
26
|
+
|
|
27
|
+
- **The proxy** checks only that the cookie is genuine. No database. It protects pages from being opened by signed-out visitors.
|
|
28
|
+
- **`getSession()` / `requireSession()` / `requirePermission()`** also check the database, so a deleted user, a changed password or (when required) an unverified email is refused. They protect data. Anything that reads or writes data for a user must call one of them; the proxy alone is not enough. `getUser()` / `requireUser()` are the same check returning only the user.
|
|
29
|
+
|
|
30
|
+
What the proxy lets through without a session:
|
|
31
|
+
|
|
32
|
+
| Path | Why |
|
|
33
|
+
|---|---|
|
|
34
|
+
| `/auth/*` | the sign-in pages themselves |
|
|
35
|
+
| `/_next/*` | Next.js internals |
|
|
36
|
+
| files ending in `.png .jpg .jpeg .gif .svg .ico .webp .avif .css .js .map .woff .woff2 .ttf .otf .txt .xml .webmanifest` | static assets from `public/` |
|
|
37
|
+
| everything not under `protected:` in `b17_auth.yaml`, when that key is set | the app decides what needs sign-in |
|
|
38
|
+
|
|
39
|
+
Everything else needs a session, including every path under `/api/` (even `/api/report.csv`). Signed-out page requests are redirected to `/auth/sign-in?next=<path>`; signed-out `/api/*` requests get `401` JSON.
|
|
40
|
+
|
|
41
|
+
POSTs to `/auth/*` that a browser marks as coming from another website are refused with `403`. Requests with no browser headers (curl, server code) are allowed.
|
|
42
|
+
|
|
43
|
+
## Step 1: Check before you start
|
|
44
|
+
|
|
45
|
+
Stop and tell me if any check fails. Do not work around a failed check on your own.
|
|
46
|
+
|
|
47
|
+
1. **App Router.** `app/` or `src/app/` exists. Pages Router (`pages/` only) is not supported.
|
|
48
|
+
2. **Next.js version.** Read `next` in `package.json`. Write it down: 16+ uses `proxy.ts`, 15 uses `middleware.ts` (Step 4).
|
|
49
|
+
3. **Node 20 or newer.** Run `node --version`.
|
|
50
|
+
4. **Database.** Look for `DATABASE_URL` or `NEON_DATABASE_URL` in `.env.local`, `.env`, or the environment. It must be a **Neon** connection string (host ends in `neon.tech`). The package talks to Postgres through Neon's HTTP driver, so plain Postgres (local Docker, RDS, Supabase) will not work. If there is none, ask me for one. Never invent one.
|
|
51
|
+
5. **Existing auth.** If the project already has `proxy.ts` / `middleware.ts` that checks sessions, or packages like `next-auth`, `better-auth`, `@clerk/nextjs`, `@supabase/auth-helpers-nextjs`, `@neondatabase/auth`, tell me before continuing. This package puts its own sign-in in front of everything.
|
|
52
|
+
6. **Pages that must stay public.** Ask me whether any page should be visible without signing in (landing page, pricing, a webhook under `/api/`). By default nothing is. If yes, you will add them in Step 4.
|
|
53
|
+
7. **Package manager.** Look for `pnpm-lock.yaml`, `yarn.lock` or `bun.lock*`. If one exists, install with that tool first (`pnpm add @binary1702/auth`, `yarn add …`, `bun add …`). The CLI uses npm otherwise, which would create a second lockfile.
|
|
54
|
+
8. **.gitignore.** Confirm `.env.local` (or `.env*`) is ignored. The CLI writes the secret there. If it is not ignored, add it before Step 2.
|
|
16
55
|
|
|
17
|
-
##
|
|
56
|
+
## Step 2: Run the installer
|
|
18
57
|
|
|
19
|
-
|
|
58
|
+
In the project root:
|
|
20
59
|
|
|
21
60
|
```bash
|
|
22
61
|
npx @binary1702/auth
|
|
23
62
|
```
|
|
24
63
|
|
|
25
|
-
It
|
|
64
|
+
It asks, in this order, only for what is missing:
|
|
26
65
|
|
|
27
|
-
|
|
66
|
+
| Prompt | When it appears | Answer |
|
|
67
|
+
|---|---|---|
|
|
68
|
+
| `Postgres connection string (DATABASE_URL)` | no `DATABASE_URL` in `.env.local` | the Neon URL from Step 1; a suggestion in parentheses is used if you press Enter |
|
|
69
|
+
| `Email transport (gmail / resend)` | first install, no `b17_auth.yaml` yet | press Enter to skip unless I gave you a Gmail service-account key file or a Resend key. Skipped: emails print to the dev console. |
|
|
70
|
+
| `Email` | the user table is empty | the first user's email, from me |
|
|
71
|
+
| `Password` / `Password again` | same | at least 8 characters, from me |
|
|
72
|
+
|
|
73
|
+
**Passwords come from me.** Never make one up, never write it to a file, never print it in your report. The simplest path: run the command and hand the terminal to me for the prompts. If you cannot run interactive commands, the CLI also reads answers from piped input, one per line, in the order above (an empty line skips the email prompt; skip lines for prompts that will not appear). Only do that with values I gave you, and tell me it may be stored in shell history.
|
|
74
|
+
|
|
75
|
+
What it does, skipping anything already done (safe to re-run):
|
|
28
76
|
|
|
29
77
|
```
|
|
30
78
|
✓ @binary1702/auth installed → package.json
|
|
31
79
|
✓ DATABASE_URL → .env.local
|
|
32
|
-
✓ AUTH_SECRET generated → .env.local
|
|
80
|
+
✓ AUTH_SECRET generated → .env.local → 43 random characters
|
|
81
|
+
· email not configured → or ✓ AUTH_GMAIL_… / AUTH_RESEND_… → .env.local
|
|
33
82
|
✓ app/auth/[...path]/route.ts → export { GET, POST } from "@binary1702/auth/next"
|
|
34
|
-
✓ proxy.ts → guard
|
|
35
|
-
✓
|
|
36
|
-
✓
|
|
83
|
+
✓ proxy.ts → the guard
|
|
84
|
+
✓ b17_auth.yaml written → the app's settings, name taken from package.json
|
|
85
|
+
✓ database schema ready (N migrations) → tables created in the database
|
|
86
|
+
✓ user "<email>" created
|
|
37
87
|
```
|
|
38
88
|
|
|
39
|
-
|
|
89
|
+
With `src/app/`, the files go to `src/app/auth/[...path]/route.ts` and `src/proxy.ts`.
|
|
40
90
|
|
|
41
|
-
|
|
91
|
+
If it fails:
|
|
42
92
|
|
|
43
|
-
|
|
93
|
+
| Message | Cause | Fix |
|
|
94
|
+
|---|---|---|
|
|
95
|
+
| `No package.json here` | wrong directory | `cd` to the project root |
|
|
96
|
+
| `No app/ directory` | Pages Router | stop, tell me |
|
|
97
|
+
| `That does not look like a postgres:// URL` | wrong value | re-run with the real URL |
|
|
98
|
+
| `Could not reach the database: …` | bad URL, wrong password, not Neon | check the URL with me; quote the message |
|
|
99
|
+
| `Input ended before all prompts were answered` | piped input too short | give one line per prompt |
|
|
44
100
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
101
|
+
## Step 3: Check the two files it wrote
|
|
102
|
+
|
|
103
|
+
**`app/auth/[...path]/route.ts`** must be exactly:
|
|
104
|
+
|
|
105
|
+
```ts
|
|
106
|
+
export { GET, POST } from "@binary1702/auth/next"
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
If `app/auth/` already had other files, keep them; a specific route like `app/auth/welcome/page.tsx` takes priority over the catch-all and is public.
|
|
110
|
+
|
|
111
|
+
**`proxy.ts`** (or `src/proxy.ts`) must be:
|
|
112
|
+
|
|
113
|
+
```ts
|
|
114
|
+
import { proxy } from "@binary1702/auth/next/proxy"
|
|
115
|
+
|
|
116
|
+
export default proxy
|
|
117
|
+
|
|
118
|
+
// Everything except Next's build output. The proxy itself lets the
|
|
119
|
+
// auth pages and static files through.
|
|
120
|
+
export const config = {
|
|
121
|
+
matcher: ["/((?!_next/static|_next/image|favicon.ico).*)"],
|
|
122
|
+
}
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
The matcher must be written inline. Next reads it from the file at build time and ignores an imported one.
|
|
126
|
+
|
|
127
|
+
## Step 4: Adjust the guard for this project
|
|
128
|
+
|
|
129
|
+
Pick the case that applies:
|
|
130
|
+
|
|
131
|
+
```
|
|
132
|
+
Next 15?
|
|
133
|
+
└─ rename proxy.ts → middleware.ts (same folder, same content) and add runtime: "nodejs"
|
|
134
|
+
to its config export (plus experimental.nodeMiddleware: true in next.config). Next 15
|
|
135
|
+
ignores proxy.ts, so without this nothing is protected.
|
|
136
|
+
|
|
137
|
+
A proxy.ts / middleware.ts already existed?
|
|
138
|
+
├─ CLI printed "has the old matcher" → an older version of this package; replace only the
|
|
139
|
+
│ matcher line with the one in Step 3
|
|
140
|
+
└─ CLI printed "exists, not touched" → merge by hand, see "Merging" below
|
|
141
|
+
|
|
142
|
+
Some pages must stay public (Step 1.6)?
|
|
143
|
+
└─ list what DOES need sign-in under protected: in b17_auth.yaml. No code. See "Protected paths" below.
|
|
144
|
+
|
|
145
|
+
Otherwise → nothing to do
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
**Merging into an existing proxy.** Keep the existing logic, call the package's proxy for the paths that need sign-in, and keep one inline matcher that covers both:
|
|
149
|
+
|
|
150
|
+
```ts
|
|
151
|
+
import { NextResponse, type NextRequest } from "next/server"
|
|
152
|
+
import { proxy as auth } from "@binary1702/auth/next/proxy"
|
|
153
|
+
|
|
154
|
+
export default async function proxy(request: NextRequest) {
|
|
155
|
+
// ...existing logic that must run first (redirects, headers, i18n)...
|
|
156
|
+
return auth(request)
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
export const config = {
|
|
160
|
+
matcher: ["/((?!_next/static|_next/image|favicon.ico).*)"],
|
|
161
|
+
}
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
If the existing logic builds its own response (adds headers, rewrites), run the auth check first and return its answer when it refuses; it lets a request through with status `200`:
|
|
165
|
+
|
|
166
|
+
```ts
|
|
167
|
+
export default async function proxy(request: NextRequest) {
|
|
168
|
+
const gate = await auth(request)
|
|
169
|
+
if (gate.status !== 200) return gate // redirect to sign-in, or 401
|
|
170
|
+
// ...existing logic, returning its own response...
|
|
171
|
+
}
|
|
172
|
+
```
|
|
53
173
|
|
|
54
|
-
|
|
174
|
+
Tell me what you merged.
|
|
55
175
|
|
|
56
|
-
|
|
176
|
+
**Protected paths.** One line in `b17_auth.yaml` listing what needs sign-in, exact paths or `prefix/*`; everything else is open. `/app/*` covers `/app` and everything below it and nothing beside it (`/apps` stays open).
|
|
57
177
|
|
|
58
|
-
|
|
178
|
+
```yaml
|
|
179
|
+
protected: /app/* /account /api/*
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
Without the key, everything needs sign-in (the default); then `public:` lists the exceptions instead. Never set both.
|
|
183
|
+
|
|
184
|
+
With `protected:`, static files are open unless their folder is listed. In the default mode, files with an extension outside the list in "How the package works" (for example `.pdf`, `.mp4`) need a session; add their folder to `public:` if signed-out visitors must load them.
|
|
185
|
+
|
|
186
|
+
## Step 5: Use the session in the code
|
|
187
|
+
|
|
188
|
+
The proxy only protects pages from opening. Every place that reads or changes data for a user must check the session itself. The session is one JSON shape everywhere:
|
|
189
|
+
|
|
190
|
+
```json
|
|
191
|
+
{ "user": { "id", "email" }, "organization": { "id", "name" } | null, "authorization": { "role", "permissions": [] } }
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
**Route handlers (`app/api/**/route.ts`):**
|
|
59
195
|
|
|
60
196
|
```ts
|
|
61
|
-
import {
|
|
197
|
+
import { requireSession, requirePermission, unauthorized } from "@binary1702/auth/next"
|
|
62
198
|
|
|
63
|
-
|
|
199
|
+
export async function GET() {
|
|
200
|
+
try {
|
|
201
|
+
const { user, organization } = await requireSession() // or requirePermission("invoices.read")
|
|
202
|
+
// ...query data WHERE org_id = organization.id (or owner_id = user.id)...
|
|
203
|
+
return Response.json(data)
|
|
204
|
+
} catch (e) {
|
|
205
|
+
return unauthorized(e) // 401 / 403 for auth errors, rethrows anything else
|
|
206
|
+
}
|
|
207
|
+
}
|
|
64
208
|
```
|
|
65
209
|
|
|
66
|
-
`
|
|
210
|
+
Permissions are names the app chooses (`invoices.create`), mapped from roles in `b17_auth.yaml` under `permissions:`. `owner` has everything by default. Memberships come from the CLI (`add-org`, `add-member`). Do not invent a permission system in the app; use `requirePermission()`.
|
|
211
|
+
|
|
212
|
+
**Server components (pages, layouts):**
|
|
213
|
+
|
|
214
|
+
```ts
|
|
215
|
+
import { redirect } from "next/navigation"
|
|
216
|
+
import { getUser } from "@binary1702/auth/next"
|
|
217
|
+
|
|
218
|
+
export default async function Page() {
|
|
219
|
+
const user = await getUser()
|
|
220
|
+
if (!user) redirect("/auth/sign-out") // clears a revoked cookie, lands on sign-in
|
|
221
|
+
// ...
|
|
222
|
+
}
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
Use `getUser()` + `redirect` in pages rather than `requireUser()`: `requireUser()` throws, which shows the error page instead of the sign-in page.
|
|
226
|
+
|
|
227
|
+
**Server actions:** call `requireUser()` at the top of every action. Actions can be called directly, so do not rely on the page having checked.
|
|
228
|
+
|
|
229
|
+
**Client components:** wrap the root layout once, then read the session anywhere without a request:
|
|
230
|
+
|
|
231
|
+
```tsx
|
|
232
|
+
// app/layout.tsx (server)
|
|
233
|
+
import { getSession } from "@binary1702/auth/next"
|
|
234
|
+
import { AuthProvider } from "@binary1702/auth/react"
|
|
235
|
+
export default async function Layout({ children }) {
|
|
236
|
+
return <AuthProvider session={await getSession()}>{children}</AuthProvider>
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
// any client component
|
|
240
|
+
import { useAuth } from "@binary1702/auth/react"
|
|
241
|
+
const { user, organization, authorization } = useAuth() // for rendering only; the server still checks
|
|
242
|
+
```
|
|
243
|
+
|
|
244
|
+
Without React: `fetch("/auth/session")` returns the same shape, or `401`.
|
|
245
|
+
|
|
246
|
+
Sign-out: a link to `/auth/sign-out`, or `fetch("/auth/sign-out", { method: "POST" })`.
|
|
247
|
+
|
|
248
|
+
**What the ids are:** `user.id` and `organization.id` are UUIDs and never change; use them as foreign keys in the app's own tables. The package authenticates and tells you the role. What rows a role may touch is the app's query: always filter by `organization.id` and/or `user.id`.
|
|
249
|
+
|
|
250
|
+
**Do not:**
|
|
251
|
+
- build a sign-in, sign-up, forgot-password or verification page (they exist under `/auth/`)
|
|
252
|
+
- read or parse the `auth_session` cookie yourself; use `getSession()`
|
|
253
|
+
- query or alter the package's tables (`users`, `orgs`, `members`, `tokens`); use the CLI commands below
|
|
254
|
+
- put a secret in `b17_auth.yaml`; secrets go in `.env.local` and the yaml references them as `${NAME}`
|
|
255
|
+
- edit anything in `node_modules/@binary1702/auth`
|
|
256
|
+
|
|
257
|
+
After the install, `node_modules/@binary1702/auth/AGENTS.md` is the reference for working in the app day to day.
|
|
258
|
+
|
|
259
|
+
**Custom sign-in form (only if I ask for one):** a page under `/auth/` with a form that POSTs `email`, `password` and optionally `next` to `/auth/sign-in`. It must be served from the same site; cross-site POSTs get `403`.
|
|
260
|
+
|
|
261
|
+
## Step 6: Settings
|
|
262
|
+
|
|
263
|
+
Only if I ask for them. All go in `b17_auth.yaml` (the CLI wrote it with comments showing every key). In development changes apply on the next request; in production restart.
|
|
264
|
+
|
|
265
|
+
| Key | Effect |
|
|
266
|
+
|---|---|
|
|
267
|
+
| `name: Acme` | page titles and email subjects |
|
|
268
|
+
| `signup: open` | anyone can create an account at `/auth/sign-up`; the sign-in page gets a "Create one" link |
|
|
269
|
+
| `signup: invite` + `inviteCode: ${AUTH_INVITE_CODE}` | the sign-up form also requires the code from `.env.local` |
|
|
270
|
+
| `emailDomain: example.com` | sign-up only accepts `@example.com` emails |
|
|
271
|
+
| `requireVerified: true` | no session until the emailed link is clicked (needs email configured) |
|
|
272
|
+
| `protected: /app/* /api/*` | only these need sign-in; everything else is open (default without it: everything needs sign-in, `public:` lists exceptions) |
|
|
273
|
+
| `email:` | Gmail API or Resend; see the README's "Email" section. Keys stay in `.env.local`. |
|
|
274
|
+
| `permissions:` / `organizations:` | role → permissions, and per-organization overrides |
|
|
275
|
+
|
|
276
|
+
Without `signup`, sign-up is closed, except while the user table is empty: then `/auth/sign-in` sends visitors to create the first account.
|
|
277
|
+
|
|
278
|
+
`admins: <email>` in `b17_auth.yaml` lets that user open `/auth/admin`, which does everything below from the browser.
|
|
279
|
+
|
|
280
|
+
User and organization management from the terminal (each asks for anything not given):
|
|
281
|
+
|
|
282
|
+
```bash
|
|
283
|
+
npx @binary1702/auth add-user [email]
|
|
284
|
+
npx @binary1702/auth set-password [email] # also signs them out everywhere
|
|
285
|
+
npx @binary1702/auth remove-user [email] # asks to type the email again
|
|
286
|
+
npx @binary1702/auth sign-out-everywhere [email]
|
|
287
|
+
npx @binary1702/auth list-users
|
|
288
|
+
npx @binary1702/auth add-org <name>
|
|
289
|
+
npx @binary1702/auth add-member <email> <org> [role] # role defaults to "member"
|
|
290
|
+
npx @binary1702/auth remove-member <email> <org>
|
|
291
|
+
npx @binary1702/auth list-orgs
|
|
292
|
+
```
|
|
293
|
+
|
|
294
|
+
## Step 7: Verify
|
|
295
|
+
|
|
296
|
+
Start the dev server (`npm run dev`, or the project's script). Restart it if it was already running, so it loads `.env.local`. Then check with real HTTP requests. Replace `3000` with the actual port. Use the email and password I gave you; do not print the password in your report.
|
|
297
|
+
|
|
298
|
+
```bash
|
|
299
|
+
B=http://localhost:3000
|
|
300
|
+
curl -s -o /dev/null -w "%{http_code} %{redirect_url}\n" $B/
|
|
301
|
+
curl -s -o /dev/null -w "%{http_code}\n" $B/auth/sign-in
|
|
302
|
+
curl -s -w " %{http_code}\n" $B/api/anything
|
|
303
|
+
curl -s -c jar.txt -w " %{http_code}\n" -H "content-type: application/json" \
|
|
304
|
+
-d '{"email":"<email>","password":"<password>"}' $B/auth/sign-in
|
|
305
|
+
curl -s -b jar.txt $B/auth/session
|
|
306
|
+
curl -s -w " %{http_code}\n" $B/auth/session
|
|
307
|
+
curl -s -b jar.txt -o /dev/null -w "%{http_code}\n" $B/
|
|
308
|
+
curl -s -w " %{http_code}\n" -H "sec-fetch-site: cross-site" -H "content-type: application/json" \
|
|
309
|
+
-d '{"email":"x@x.com","password":"x"}' $B/auth/sign-in
|
|
310
|
+
curl -s -o /dev/null -w "%{http_code} %{redirect_url}\n" "$B/auth/sign-in?next=/%5Cevil.com" -b jar.txt
|
|
311
|
+
curl -s -b jar.txt -o /dev/null -w "%{http_code} %{redirect_url}\n" $B/auth/sign-out
|
|
312
|
+
rm jar.txt
|
|
313
|
+
```
|
|
314
|
+
|
|
315
|
+
| # | Request | Expect |
|
|
316
|
+
|---|---|---|
|
|
317
|
+
| 1 | `GET /` no cookie | `307` to `/auth/sign-in` (or `200` if `/` is in your public list) |
|
|
318
|
+
| 2 | `GET /auth/sign-in` | `200` (or `307` to `/auth/sign-up` if there are no users yet) |
|
|
319
|
+
| 3 | `GET /api/anything` no cookie | `401` `{"error":"Unauthorized"}` |
|
|
320
|
+
| 4 | `POST /auth/sign-in` JSON | `200` `{"user":{...}}`; `jar.txt` contains `auth_session` |
|
|
321
|
+
| 5 | `GET /auth/session` with cookie | `{"user":{"id":"…","email":"…"},"organization":null,"authorization":{"role":null,"permissions":[]}}` (organization and role filled in once the user is in one) |
|
|
322
|
+
| 5b | `GET /auth/session` no cookie | `401` `{"error":"Unauthorized"}` |
|
|
323
|
+
| 6 | `GET /` with cookie | the page's normal status, not a redirect to sign-in |
|
|
324
|
+
| 7 | cross-site `POST /auth/sign-in` | `403` `{"error":"Cross-site request refused"}` |
|
|
325
|
+
| 8 | sign-in page with `next=/\evil.com`, signed in | redirect to `/` on this site, never to `evil.com` |
|
|
326
|
+
| 9 | `GET /auth/sign-out` | `303` to `/auth/sign-in` |
|
|
327
|
+
|
|
328
|
+
Also check, by request or in the browser:
|
|
329
|
+
- each public page from Step 4 opens signed out; a page next to it that is not public redirects
|
|
330
|
+
- an image from `public/` loads on the sign-in page and on public pages
|
|
331
|
+
- open `/auth/sign-in` in a browser, sign in, confirm you land on `/` (or on the `next` page)
|
|
332
|
+
|
|
333
|
+
If something does not match:
|
|
334
|
+
|
|
335
|
+
| Symptom | Likely cause | Fix |
|
|
336
|
+
|---|---|---|
|
|
337
|
+
| everything opens without signing in | Next 15 with `proxy.ts`; or proxy file in the wrong folder | Step 4; the file must be `src/proxy.ts` when the app is in `src/app/`, root `proxy.ts` when it is in `app/` |
|
|
338
|
+
| `500` with `AUTH_SECRET is missing` | dev server started before `.env.local` was written | restart the dev server |
|
|
339
|
+
| `500` with a database error | wrong `DATABASE_URL`, not a Neon URL | check the URL with me |
|
|
340
|
+
| `403 Cross-site request refused` from the app's own form | form or fetch served from a different origin/port than the app | serve it from the same origin |
|
|
341
|
+
| images or fonts redirect to sign-in | default mode and the extension is not in the static list | add the folder to `public:`, or switch to `protected:` |
|
|
342
|
+
| page shows the error screen when signed out | page uses `requireUser()` / `requireSession()` | use `getUser()` / `getSession()` + `redirect` (Step 5) |
|
|
343
|
+
| signed in, but pages still say signed out | user was removed or password changed after sign-in; or `requireVerified` is on and the email is unverified | sign out and in again; or follow the verification link |
|
|
344
|
+
| `b17_auth.yaml line N: …` on start | a typo in the settings file | fix that line; the message names the key |
|
|
345
|
+
| `… refers to ${NAME}, which is not set` | the yaml references a variable missing from `.env.local` | add it, restart |
|
|
346
|
+
|
|
347
|
+
## Step 8: Deploy
|
|
67
348
|
|
|
68
|
-
|
|
349
|
+
Copy every variable in `.env.local` (`DATABASE_URL`, `AUTH_SECRET`, the email variables such as `AUTH_GMAIL_CLIENT_EMAIL` + `AUTH_GMAIL_PRIVATE_KEY` or `AUTH_RESEND_API_KEY`, `AUTH_INVITE_CODE` if used) into the host's environment variables (Vercel: Project Settings → Environment Variables). `b17_auth.yaml` is committed with the code and needs nothing. Do not paste the values into your report or a commit.
|
|
69
350
|
|
|
70
|
-
|
|
351
|
+
- Use the same `AUTH_SECRET` for every instance of the app; changing it signs everyone out.
|
|
352
|
+
- If production uses a different database than development, the table is created on the first request but has no users, and while it is empty the first visitor can create the first account. Tell me right after the deploy, so I open `/auth/sign-in` on the live site and create my account before anyone else does. (Running `add-user` locally does not help: the CLI uses the `DATABASE_URL` in `.env.local`, which is the development database.)
|
|
353
|
+
- In production the cookie is `__Host-auth_session`: `Secure`, path `/`, no domain. The site must be served over HTTPS, and the cookie cannot be shared with a subdomain (that is the point).
|
|
71
354
|
|
|
72
|
-
##
|
|
355
|
+
## Step 9: Report
|
|
73
356
|
|
|
74
|
-
|
|
357
|
+
Tell me, in order:
|
|
75
358
|
|
|
76
|
-
|
|
359
|
+
1. each check from Step 1 and its result
|
|
360
|
+
2. each installer line from Step 2: done, skipped, or failed (error text verbatim)
|
|
361
|
+
3. what you changed in Step 4 and why (Next 15 rename, merge, public paths)
|
|
362
|
+
4. every file you changed in Step 5
|
|
363
|
+
5. each row of the Step 7 table with the actual status code, and the extra checks
|
|
364
|
+
6. anything that took more steps than this guide says, or did not match it
|
|
77
365
|
|
|
78
|
-
|
|
366
|
+
Never include `AUTH_SECRET`, `DATABASE_URL` or any password in the report. Do not modify the package itself, only this project.
|
|
79
367
|
|
|
80
368
|
Docs: https://www.npmjs.com/package/@binary1702/auth
|