also looking at this
Single-user auth: login, first-run registration, page + API gates
#11
9 files
+254
-22
| @@ -0,0 +1,63 @@ | ||
| 1 | import type { RequestInstance } from '@stacksjs/types' | |
| 2 | import { Action } from '@stacksjs/actions' | |
| 3 | import { register } from '@stacksjs/auth' | |
| 4 | import { db } from '@stacksjs/database' | |
| 5 | import { response } from '@stacksjs/router' | |
| 6 | ||
| 7 | const database = db as any | |
| 8 | ||
| 9 | /** | |
| 10 | * Postline is a single-user workspace: the register endpoint only works | |
| 11 | * while no account exists, so a deployed instance can't be signed up on | |
| 12 | * by strangers. | |
| 13 | */ | |
| 14 | export default new Action({ | |
| 15 | name: 'Postline Register First User', | |
| 16 | description: 'Create the workspace account — only available on first run.', | |
| 17 | method: 'POST', | |
| 18 | ||
| 19 | async handle(request: RequestInstance) { | |
| 20 | const existing = await database | |
| 21 | .selectFrom('users') | |
| 22 | .select(['id']) | |
| 23 | .executeTakeFirst() | |
| 24 | ||
| 25 | if (existing) { | |
| 26 | return response.json({ | |
| 27 | ok: false, | |
| 28 | error: 'This workspace already has an account. Sign in instead.', | |
| 29 | }, { status: 403 }) | |
| 30 | } | |
| 31 | ||
| 32 | const email = String(request.get('email') || '').trim() | |
| 33 | const password = String(request.get('password') || '') | |
| 34 | const name = String(request.get('name') || '').trim() | |
| 35 | ||
| 36 | if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email)) | |
| 37 | return response.json({ ok: false, error: 'Email must be a valid email address.' }, { status: 422 }) | |
| 38 | if (password.length < 6 || password.length > 255) | |
| 39 | return response.json({ ok: false, error: 'Password must be between 6 and 255 characters.' }, { status: 422 }) | |
| 40 | if (name.length < 2 || name.length > 255) | |
| 41 | return response.json({ ok: false, error: 'Name must be between 2 and 255 characters.' }, { status: 422 }) | |
| 42 | ||
| 43 | try { | |
| 44 | const result = await register({ email, password, name }) | |
| 45 | if (!result) | |
| 46 | throw new Error('Registration failed.') | |
| 47 | ||
| 48 | return response.json({ | |
| 49 | ok: true, | |
| 50 | data: { | |
| 51 | access_token: (result as any).token, | |
| 52 | token_type: 'Bearer', | |
| 53 | }, | |
| 54 | }) | |
| 55 | } | |
| 56 | catch (error) { | |
| 57 | return response.json({ | |
| 58 | ok: false, | |
| 59 | error: error instanceof Error ? error.message : String(error), | |
| 60 | }, { status: 422 }) | |
| 61 | } | |
| 62 | }, | |
| 63 | }) | |