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.
7.0 KiB
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
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
- Pull latest and rebuild (OIDC button detection changed recently):
git pull docker compose up -d --build - In your IdP, create a confidential OIDC application.
- Set the redirect / callback URI to exactly:
Example:
{AUTH_URL}/api/auth/callback/oidchttps://rfid.atlashorizon.net/api/auth/callback/oidc - Put these in
.env: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 - Restart, then verify the running app sees config (no secrets returned):
You want
curl -s https://rfid.atlashorizon.net/api/v1/auth/config | jq"oidcEnabled": trueand"callbackUrl"matching your IdP. - Open
/login— you should see Sign in with AtlasHorizon.
Notes:
AUTH_OIDC_ISSUERmust match discovery ({issuer}/.well-known/openid-configuration).- The app requests
openid email profileand loads profile from UserInfo (Authelia putsemailthere by default). - Optional Authelia hardening — also put email on the ID token:
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
# 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
npm test
npm run lint
npm run typecheck
npm run build
GitHub Actions on every pull request and push to main:
- lint / typecheck / test / Next.js build
- Docker image — build on PRs; on
main(andv*tags) push to GHCR:ghcr.io/chewbaccalakis/rfid-database
Pull the published image
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:
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)
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):
docker compose exec rfid-database \
npx tsx scripts/create-user.ts --email [email protected] --password 'secret' --name You
Stop / remove (keeps volume):
docker compose down
Plain docker
docker build -t rfid-database .
docker run --rm -p 3000:3000 \
-e AUTH_SECRET=your-long-secret \
-e CREATE_USER_EMAIL=[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.