mirror of
https://github.com/Chewbaccalakis/rfid-database.git
synced 2026-09-09 16:01:56 -07:00
Parse object-shaped mfc v2 dumps (the format Proxmark writes today) and let the new-tag form upload dump.json/dump.bin plus an optional binary hf-mf-*-key.bin instead of only pasting text.
225 lines
7.0 KiB
Markdown
225 lines
7.0 KiB
Markdown
# RFID Database
|
|
|
|
PWA + REST API for storing LF/HF RFID tag dumps organized by site. Use the web UI in the field, or call `/api/v1` from scripts (and a future Proxmark CLI).
|
|
|
|
## Features
|
|
|
|
- Sites (A, B, C, …) with tag records (UID, protocol, keys, full dump)
|
|
- Multi-user auth (shared workspace, no RBAC yet) — local password + optional OIDC
|
|
- Personal API tokens (Settings UI) for Bearer auth
|
|
- Export: JSON, Proxmark, MCT, hex
|
|
- Import dumps (file upload or paste) + full JSON backup
|
|
- Installable PWA with offline read of cached pages
|
|
- Vitest unit/integration tests + GitHub Actions CI
|
|
|
|
## Quick start
|
|
|
|
```bash
|
|
cp .env.example .env
|
|
# set AUTH_SECRET to a long random string
|
|
|
|
npm install
|
|
npm run create-user -- --email [email protected] --password 'changeme' --name You
|
|
npm run dev
|
|
```
|
|
|
|
Open http://localhost:3000 and sign in.
|
|
|
|
## Environment
|
|
|
|
| Variable | Required | Description |
|
|
|----------|----------|-------------|
|
|
| `AUTH_SECRET` | yes | NextAuth secret |
|
|
| `AUTH_URL` | recommended | Public app URL (e.g. `https://rfid.example.com`) |
|
|
| `RFID_DB_PATH` | no | SQLite path (default `./data/rfid.db`) |
|
|
| `AUTH_OIDC_ISSUER` | for OIDC | Issuer URL (must expose `/.well-known/openid-configuration`) |
|
|
| `AUTH_OIDC_CLIENT_ID` | for OIDC | OIDC client id |
|
|
| `AUTH_OIDC_CLIENT_SECRET` | for OIDC | OIDC client secret |
|
|
| `AUTH_OIDC_NAME` | no | SSO button label (default `SSO`) |
|
|
|
|
### Enabling OIDC
|
|
|
|
1. **Pull latest and rebuild** (OIDC button detection changed recently):
|
|
```bash
|
|
git pull
|
|
docker compose up -d --build
|
|
```
|
|
2. In your IdP, create a **confidential** OIDC application.
|
|
3. Set the redirect / callback URI to **exactly**:
|
|
```
|
|
{AUTH_URL}/api/auth/callback/oidc
|
|
```
|
|
Example: `https://rfid.atlashorizon.net/api/auth/callback/oidc`
|
|
4. Put these in `.env`:
|
|
```bash
|
|
AUTH_URL=https://rfid.atlashorizon.net
|
|
AUTH_SECRET=...long random...
|
|
AUTH_OIDC_ISSUER=https://auth.atlashorizon.net
|
|
AUTH_OIDC_CLIENT_ID=rfiddb
|
|
AUTH_OIDC_CLIENT_SECRET=...
|
|
AUTH_OIDC_NAME=AtlasHorizon
|
|
```
|
|
5. Restart, then verify the running app sees config (no secrets returned):
|
|
```bash
|
|
curl -s https://rfid.atlashorizon.net/api/v1/auth/config | jq
|
|
```
|
|
You want `"oidcEnabled": true` and `"callbackUrl"` matching your IdP.
|
|
6. Open `/login` — you should see **Sign in with AtlasHorizon**.
|
|
|
|
Notes:
|
|
- `AUTH_OIDC_ISSUER` must match discovery (`{issuer}/.well-known/openid-configuration`).
|
|
- The app requests `openid email profile` and loads profile from **UserInfo** (Authelia puts `email` there by default).
|
|
- Optional Authelia hardening — also put email on the ID token:
|
|
|
|
```yaml
|
|
identity_providers:
|
|
oidc:
|
|
claims_policies:
|
|
rfiddb:
|
|
id_token:
|
|
- 'email'
|
|
- 'email_verified'
|
|
- 'preferred_username'
|
|
- 'name'
|
|
clients:
|
|
- client_id: 'rfiddb'
|
|
claims_policy: 'rfiddb'
|
|
# ...rest of client...
|
|
```
|
|
|
|
- Local password login stays available alongside SSO.
|
|
- If the button is missing, you are almost certainly on an old image — rebuild.
|
|
|
|
## REST API (`/api/v1`)
|
|
|
|
Authenticate with a session cookie **or** `Authorization: Bearer rfid_…` (create tokens under **Settings → API tokens**).
|
|
|
|
| Method | Path | Purpose |
|
|
|--------|------|---------|
|
|
| GET/POST | `/api/v1/sites` | List / create sites |
|
|
| GET/PATCH/DELETE | `/api/v1/sites/{id}` | Site detail |
|
|
| GET/POST | `/api/v1/tags` | List / create tags |
|
|
| GET/PATCH/DELETE | `/api/v1/tags/{id}` | Tag detail |
|
|
| PUT | `/api/v1/tags/by-uid/{uid}?siteId=` | Upsert by UID |
|
|
| GET | `/api/v1/tags/{id}/export?format=` | `json` \| `proxmark` \| `mct` \| `hex` |
|
|
| POST | `/api/v1/tags/import` | Parse dump → create tag (JSON body or multipart files) |
|
|
| GET | `/api/v1/search?q=` | Cross-site search |
|
|
| GET/POST | `/api/v1/backup` | Full backup export / import |
|
|
| GET/POST | `/api/v1/tokens` | List / create PATs |
|
|
| DELETE | `/api/v1/tokens/{id}` | Revoke PAT |
|
|
| GET | `/api/v1/me` | Current user |
|
|
|
|
### curl examples
|
|
|
|
```bash
|
|
# Create a token in the UI first, then:
|
|
export TOKEN=rfid_…
|
|
export HOST=http://localhost:3000
|
|
|
|
curl -sH "Authorization: Bearer $TOKEN" "$HOST/api/v1/sites"
|
|
|
|
curl -sH "Authorization: Bearer $TOKEN" \
|
|
"$HOST/api/v1/tags/$ID/export?format=proxmark" -o dump.json
|
|
|
|
curl -sH "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
|
|
-X PUT "$HOST/api/v1/tags/by-uid/04A1B2C3?siteId=$SITE" \
|
|
-d '{"label":"Dock fob","frequency":"HF","protocol":"MIFARE_CLASSIC_1K","uid":"04A1B2C3","dumpData":{"size":"1K","sectors":[]},"keys":{"A":["FFFFFFFFFFFF"]}}'
|
|
|
|
# Import a Proxmark dump.json (optional key.bin is binary — use multipart)
|
|
curl -sH "Authorization: Bearer $TOKEN" \
|
|
-F "siteId=$SITE" \
|
|
-F "label=Front door" \
|
|
-F "[email protected]" \
|
|
-F "[email protected]" \
|
|
"$HOST/api/v1/tags/import"
|
|
```
|
|
|
|
### Proxmark files
|
|
|
|
On **New tag → Import dump** you can upload files or paste JSON:
|
|
|
|
| File | Typical Proxmark name | Notes |
|
|
|------|------------------------|--------|
|
|
| Dump JSON | `hf-mf-<UID>-dump.json` | `FileType` `mfc v2` (blocks as an object + `SectorKeys`) or older `mfcard` (blocks array) |
|
|
| Binary dump | `hf-mf-<UID>.bin` / `dump.bin` | 1024 bytes (1K) or 4096 bytes (4K) |
|
|
| Keys | `hf-mf-<UID>-key.bin` | **Binary**, 12 bytes/sector (192 for 1K, 480 for 4K). Not needed if the JSON already has `SectorKeys`. |
|
|
| MCT / EML | `*.mct` / `*.eml` | Text dumps |
|
|
|
|
Pasting `mfc v2` JSON is supported; older parsers rejected object-shaped `blocks`.
|
|
|
|
## Tests & CI
|
|
|
|
```bash
|
|
npm test
|
|
npm run lint
|
|
npm run typecheck
|
|
npm run build
|
|
```
|
|
|
|
GitHub Actions on every pull request and push to `main`:
|
|
|
|
1. **lint / typecheck / test / Next.js build**
|
|
2. **Docker image** — build on PRs; on `main` (and `v*` tags) push to GHCR:
|
|
`ghcr.io/chewbaccalakis/rfid-database`
|
|
|
|
### Pull the published image
|
|
|
|
```bash
|
|
docker pull ghcr.io/chewbaccalakis/rfid-database:latest
|
|
# or a specific commit: ghcr.io/chewbaccalakis/rfid-database:sha-<shortsha>
|
|
```
|
|
|
|
If the package is private, authenticate first:
|
|
|
|
```bash
|
|
echo $GITHUB_TOKEN | docker login ghcr.io -u USERNAME --password-stdin
|
|
```
|
|
|
|
Or in Compose, set `image: ghcr.io/chewbaccalakis/rfid-database:latest` and comment out `build:`.
|
|
|
|
## Docker
|
|
|
|
### docker compose (recommended)
|
|
|
|
```bash
|
|
cp .env.example .env
|
|
# set AUTH_SECRET to a long random string
|
|
|
|
# optional first user on boot:
|
|
# echo '[email protected]' >> .env
|
|
# echo 'CREATE_USER_PASSWORD=changeme' >> .env
|
|
|
|
docker compose up -d --build
|
|
```
|
|
|
|
App: http://localhost:3000 — SQLite persists in the `rfid-data` volume.
|
|
|
|
Create a user later (if you skipped bootstrap):
|
|
|
|
```bash
|
|
docker compose exec rfid-database \
|
|
npx tsx scripts/create-user.ts --email [email protected] --password 'secret' --name You
|
|
```
|
|
|
|
Stop / remove (keeps volume):
|
|
|
|
```bash
|
|
docker compose down
|
|
```
|
|
|
|
### Plain docker
|
|
|
|
```bash
|
|
docker build -t rfid-database .
|
|
docker run --rm -p 3000:3000 \
|
|
-e AUTH_SECRET=your-long-secret \
|
|
-e [email protected] \
|
|
-e CREATE_USER_PASSWORD=changeme \
|
|
-v rfid-data:/data \
|
|
rfid-database
|
|
```
|
|
|
|
## Security
|
|
|
|
Tag dumps often include sector keys. Keep the app behind HTTPS, do not expose it publicly without auth, and treat `data/rfid.db` as sensitive.
|