chore: remove password-lock; switch to ADMIN_TOKEN env; update docs
This commit is contained in:
14
.env.example
14
.env.example
@@ -1,12 +1,10 @@
|
||||
# Admin Credentials (REQUIRED)
|
||||
# Set a strong username and password for the admin account
|
||||
ADMIN_USERNAME=admin
|
||||
ADMIN_PASSWORD=changeme
|
||||
# Admin Token (REQUIRED)
|
||||
# Long random string used to authenticate the admin via ?adm=<token> in the URL.
|
||||
# Generate with: openssl rand -hex 32
|
||||
ADMIN_TOKEN=replace-with-32-byte-random-hex
|
||||
|
||||
# JWT Secret (Optional - auto-generated if not provided)
|
||||
# For production, generate a secure random string:
|
||||
# openssl rand -base64 32
|
||||
SECRET=
|
||||
# Public base URL used by the guest CLI when generating links
|
||||
PUBLIC_BASE_URL=http://localhost:3000
|
||||
|
||||
# Application Settings (Optional)
|
||||
NODE_ENV=production
|
||||
|
||||
23
CLAUDE.md
23
CLAUDE.md
@@ -49,17 +49,28 @@ UI strings are hardcoded in the source. The PT-BR localization lives in:
|
||||
- `app/layout.tsx` — `<html lang="pt-BR">` and metadata description
|
||||
- `app/[slug]/page.tsx` — `Intl.NumberFormat('pt-BR', ...)` for prices
|
||||
- `components/share-button.tsx` — "Compartilhar" label
|
||||
- `app/lock/page.tsx`, `app/not-found.tsx`, `app/page.tsx` — page copy
|
||||
- `app/not-found.tsx`, `app/page.tsx` — page copy
|
||||
|
||||
To switch language, update those files and rebuild the image.
|
||||
|
||||
### Admin credentials
|
||||
### Admin token
|
||||
|
||||
Set via environment variables in `docker-compose.yml`:
|
||||
The admin authenticates by visiting the site with `?adm=<token>` once. The token is set as an env var:
|
||||
|
||||
```yaml
|
||||
- ADMIN_USERNAME=your_username
|
||||
- ADMIN_PASSWORD=your_password
|
||||
- ADMIN_TOKEN=long-random-hex-at-least-16-chars
|
||||
```
|
||||
|
||||
The token is also accepted from the cookie `adm_token` after first visit.
|
||||
|
||||
### Guests
|
||||
|
||||
Each guest gets their own URL: `https://chadebebe.omeu.website/<slug>?usr=<token>`. Manage guests at `/admin/guests` or via CLI:
|
||||
|
||||
```bash
|
||||
npm run guest:create -- --name="Martin"
|
||||
npm run guest:list
|
||||
npm run guest:delete -- --id=<guest-id>
|
||||
```
|
||||
|
||||
## Deployment
|
||||
@@ -80,7 +91,7 @@ cd chadebebe
|
||||
docker compose up -d --build
|
||||
```
|
||||
|
||||
The `data/db/wishlist.db` snapshot committed in the repo will be used as the initial database. On first run the app writes `data/secrets.json` (JWT keys) — this file is gitignored and must not be committed.
|
||||
The `data/db/wishlist.db` snapshot committed in the repo will be used as the initial database.
|
||||
|
||||
### Updating the app
|
||||
|
||||
|
||||
@@ -2,20 +2,17 @@ import { NextRequest, NextResponse } from 'next/server';
|
||||
import { eq } from 'drizzle-orm';
|
||||
import { db, settings } from '@/lib/db';
|
||||
import { verifyAdminToken } from '@/lib/auth/tokens';
|
||||
import crypto from 'crypto';
|
||||
|
||||
// GET /api/settings - Get all settings (public endpoint for reading only)
|
||||
export async function GET(request: NextRequest) {
|
||||
export async function GET() {
|
||||
try {
|
||||
const allSettings = await db.select().from(settings);
|
||||
|
||||
// Convert to key-value object
|
||||
const settingsObj = allSettings.reduce((acc, setting) => {
|
||||
acc[setting.key] = setting.value;
|
||||
return acc;
|
||||
}, {} as Record<string, string | boolean>);
|
||||
}, {} as Record<string, string>);
|
||||
|
||||
// Set defaults if not found
|
||||
if (!settingsObj.siteTitle) {
|
||||
settingsObj.siteTitle = 'Wishlist';
|
||||
}
|
||||
@@ -23,12 +20,12 @@ export async function GET(request: NextRequest) {
|
||||
settingsObj.homepageSubtext = 'Browse and explore available wishlists';
|
||||
}
|
||||
|
||||
// Convert passwordLockEnabled to boolean
|
||||
(settingsObj as any).passwordLockEnabled = settingsObj.passwordLockEnabled === 'true';
|
||||
|
||||
return NextResponse.json({
|
||||
success: true,
|
||||
settings: settingsObj,
|
||||
settings: {
|
||||
siteTitle: settingsObj.siteTitle,
|
||||
homepageSubtext: settingsObj.homepageSubtext,
|
||||
},
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Error fetching settings:', error);
|
||||
@@ -47,9 +44,8 @@ export async function PUT(request: NextRequest) {
|
||||
}
|
||||
|
||||
const body = await request.json();
|
||||
const { siteTitle, homepageSubtext, passwordLockEnabled, passwordLock } = body;
|
||||
const { siteTitle, homepageSubtext } = body;
|
||||
|
||||
// Update or insert siteTitle
|
||||
if (siteTitle !== undefined) {
|
||||
const existing = await db
|
||||
.select()
|
||||
@@ -70,7 +66,6 @@ export async function PUT(request: NextRequest) {
|
||||
}
|
||||
}
|
||||
|
||||
// Update or insert homepageSubtext
|
||||
if (homepageSubtext !== undefined) {
|
||||
const existing = await db
|
||||
.select()
|
||||
@@ -91,52 +86,6 @@ export async function PUT(request: NextRequest) {
|
||||
}
|
||||
}
|
||||
|
||||
// Update or insert passwordLockEnabled
|
||||
if (passwordLockEnabled !== undefined) {
|
||||
const value = passwordLockEnabled ? 'true' : 'false';
|
||||
const existing = await db
|
||||
.select()
|
||||
.from(settings)
|
||||
.where(eq(settings.key, 'passwordLockEnabled'))
|
||||
.limit(1);
|
||||
|
||||
if (existing.length > 0) {
|
||||
await db
|
||||
.update(settings)
|
||||
.set({ value, updatedAt: new Date() })
|
||||
.where(eq(settings.key, 'passwordLockEnabled'));
|
||||
} else {
|
||||
await db.insert(settings).values({
|
||||
key: 'passwordLockEnabled',
|
||||
value,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Update password hash if provided
|
||||
if (passwordLock && passwordLock.trim() !== '') {
|
||||
// Hash the password using SHA-256
|
||||
const hash = crypto.createHash('sha256').update(passwordLock).digest('hex');
|
||||
|
||||
const existing = await db
|
||||
.select()
|
||||
.from(settings)
|
||||
.where(eq(settings.key, 'passwordLockHash'))
|
||||
.limit(1);
|
||||
|
||||
if (existing.length > 0) {
|
||||
await db
|
||||
.update(settings)
|
||||
.set({ value: hash, updatedAt: new Date() })
|
||||
.where(eq(settings.key, 'passwordLockHash'));
|
||||
} else {
|
||||
await db.insert(settings).values({
|
||||
key: 'passwordLockHash',
|
||||
value: hash,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return NextResponse.json({
|
||||
success: true,
|
||||
message: 'Settings updated successfully',
|
||||
|
||||
@@ -5,8 +5,7 @@ services:
|
||||
container_name: chadebebe
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
- ADMIN_USERNAME=${ADMIN_USERNAME:-admin}
|
||||
- ADMIN_PASSWORD=${ADMIN_PASSWORD:-changeme}
|
||||
- ADMIN_TOKEN=${ADMIN_TOKEN:?ADMIN_TOKEN must be set}
|
||||
- PUID=${PUID:-1000}
|
||||
- PGID=${PGID:-1000}
|
||||
- COOKIE_SECURE=true
|
||||
|
||||
Reference in New Issue
Block a user