Introduce granular role-based permissions with SuperAdmin env override, admin roles UI, and permission-gated API routes. Fix admin user Nostr metadata by batching relay profile fetches, normalizing npub pubkeys to hex, and adding reusable NostrAvatar/useNostrProfile components. Co-authored-by: Cursor <cursoragent@cursor.com>
146 lines
4.5 KiB
Markdown
146 lines
4.5 KiB
Markdown
# Belgian Bitcoin Embassy
|
|
|
|
A Nostr-powered community website for Belgium's monthly Bitcoin meetup.
|
|
|
|
## Tech Stack
|
|
|
|
- **Frontend**: Next.js 14 (App Router), TypeScript, Tailwind CSS, Shadcn/ui
|
|
- **Backend**: Express.js, TypeScript, Prisma ORM
|
|
- **Database**: SQLite (default), PostgreSQL supported
|
|
- **Auth**: Nostr login (NIP-07), JWT sessions
|
|
|
|
## Quick Start
|
|
|
|
### 1. Clone and install
|
|
|
|
```bash
|
|
# Install backend dependencies
|
|
cd backend
|
|
npm install
|
|
|
|
# Install frontend dependencies
|
|
cd ../frontend
|
|
npm install
|
|
```
|
|
|
|
### 2. Configure environment
|
|
|
|
```bash
|
|
# From project root
|
|
cp .env.example backend/.env
|
|
cp .env.example frontend/.env.local
|
|
```
|
|
|
|
Edit `backend/.env` with your SuperAdmin pubkeys (`SUPERADMIN_PUBKEYS`) and a secure JWT secret.
|
|
|
|
### 3. Set up database
|
|
|
|
```bash
|
|
cd backend
|
|
npx prisma generate
|
|
npx prisma db push
|
|
npx prisma db seed
|
|
```
|
|
|
|
### 4. Run development servers
|
|
|
|
```bash
|
|
# Terminal 1: Backend
|
|
cd backend
|
|
npm run dev
|
|
|
|
# Terminal 2: Frontend
|
|
cd frontend
|
|
npm run dev
|
|
```
|
|
|
|
- Frontend: http://localhost:3000
|
|
- Backend API: http://localhost:4000/api
|
|
|
|
## Project Structure
|
|
|
|
```
|
|
/frontend Next.js application
|
|
/app App Router pages
|
|
/components React components
|
|
/lib Utilities and API client
|
|
/hooks Custom React hooks
|
|
|
|
/backend Express API server
|
|
/src Source code
|
|
/api Route handlers
|
|
/services Business logic
|
|
/middleware Auth middleware
|
|
/prisma Database schema and migrations
|
|
|
|
/context Design specs (reference only)
|
|
```
|
|
|
|
## API Endpoints
|
|
|
|
| Method | Path | Description |
|
|
|--------|------|-------------|
|
|
| POST | /api/auth/challenge | Get auth challenge |
|
|
| POST | /api/auth/verify | Verify Nostr signature |
|
|
| GET | /api/posts | List blog posts |
|
|
| GET | /api/posts/:slug | Get post by slug |
|
|
| POST | /api/posts/import | Import Nostr post |
|
|
| PATCH | /api/posts/:id | Update post |
|
|
| GET | /api/meetups | List meetups |
|
|
| POST | /api/meetups | Create meetup |
|
|
| PATCH | /api/meetups/:id | Update meetup |
|
|
| POST | /api/moderation/hide | Hide content |
|
|
| POST | /api/moderation/block | Block pubkey |
|
|
| GET | /api/auth/me | Current user's effective role and permissions |
|
|
| GET | /api/users | List users |
|
|
| PUT | /api/users/:pubkey/role | Assign a role to a user |
|
|
| GET | /api/admin/permissions | Permission registry (keys, labels, groups) |
|
|
| GET | /api/admin/roles | Each role and its permission set |
|
|
| PUT | /api/admin/roles/:role/permissions | Update a role's permissions |
|
|
| GET | /api/categories | List categories |
|
|
| POST | /api/categories | Create category |
|
|
|
|
## Roles and permissions
|
|
|
|
Access is controlled by an ordered role hierarchy backed by a runtime-editable,
|
|
granular permission system. From highest to lowest:
|
|
|
|
1. **SuperAdmin** sourced only from the `SUPERADMIN_PUBKEYS` env var. Always has
|
|
every permission. Cannot be created, edited, demoted, or removed through the
|
|
UI. This replaces the old "admin set in env" concept. If `SUPERADMIN_PUBKEYS`
|
|
is unset, the app falls back to the legacy `ADMIN_PUBKEYS` variable.
|
|
2. **Admin** highest role assignable through the dashboard.
|
|
3. **Moderator**
|
|
4. **Writer**
|
|
5. **Guest** the fallback for any logged-in pubkey with no assigned role. Guest is
|
|
not a stored record, it is simply the absence of a role.
|
|
|
|
Each assignable role (Admin, Moderator, Writer) maps to a set of permission keys
|
|
stored in the `RolePermission` table. SuperAdmins manage these from the dashboard
|
|
**Roles** page, which renders a permission-by-role matrix. The dashboard nav and
|
|
actions are gated on the current user's resolved permissions, not on role names.
|
|
|
|
### Safeguards
|
|
|
|
- SuperAdmin is never assignable through the UI or API. Only the env var grants it.
|
|
- A user cannot assign a role at or above their own, or modify a SuperAdmin.
|
|
- A user cannot grant a role a permission they do not themselves hold.
|
|
- A user cannot remove their own `users.assign_role` or `roles.edit_permissions`.
|
|
SuperAdmin (env-sourced) is always the recovery path.
|
|
- Unknown role values and permission keys are rejected by the server.
|
|
|
|
### Adding a new permission key
|
|
|
|
1. Add an entry to `PERMISSIONS` in
|
|
[backend/src/constants/permissions.ts](backend/src/constants/permissions.ts)
|
|
with a `key`, `label`, and `group`.
|
|
2. The key automatically appears in `GET /admin/permissions` and the Roles matrix
|
|
page, so no UI changes are needed.
|
|
3. Apply it to the relevant endpoints with the `requires('<key>')` guard.
|
|
4. Optionally grant it to roles by default in `DEFAULT_ROLE_PERMISSIONS` (used by
|
|
the seed) and in the migration so fresh and existing databases get it.
|
|
|
|
## License
|
|
|
|
MIT
|