Implement RFID tag dump PWA with REST API, auth, and CI (#1)

Add Next.js app with SQLite/Drizzle storage for sites and LF/HF tag
dumps, multi-user Auth.js (credentials + optional OIDC), personal API
tokens in Settings, versioned /api/v1 for UI and future CLI use,
MCT/Proxmark/JSON import-export, PWA offline shell, Vitest tests, and
GitHub Actions CI.

Co-authored-by: Cursor Agent <[email protected]>
This commit is contained in:
Nick Trochalakis
2026-08-23 15:24:12 -07:00
committed by GitHub
co-authored by Cursor Agent
parent 5029945cc2
commit 8500d2f673
65 changed files with 13689 additions and 1 deletions
+85
View File
@@ -0,0 +1,85 @@
import { z } from "zod";
import { FREQUENCIES, PROTOCOLS } from "@/lib/rfid/protocols";
export const mifareClassicDumpSchema = z.object({
size: z.enum(["1K", "4K"]),
sectors: z.array(
z.object({
index: z.number().int().min(0),
blocks: z.array(z.string().regex(/^[0-9A-Fa-f]+$/)).min(1),
})
),
});
export const ntagDumpSchema = z.object({
pages: z.array(
z.object({
index: z.number().int().min(0),
data: z.string().regex(/^[0-9A-Fa-f]+$/),
})
),
});
export const lfDumpSchema = z.object({
uidBytes: z.string().regex(/^[0-9A-Fa-f]+$/),
format: z.string().min(1),
});
export const dumpDataSchema = z.union([
mifareClassicDumpSchema,
ntagDumpSchema,
lfDumpSchema,
z.record(z.string(), z.unknown()),
]);
export const keysSchema = z
.object({
A: z.array(z.string()).optional(),
B: z.array(z.string()).optional(),
})
.passthrough()
.nullable()
.optional();
export const siteCreateSchema = z.object({
name: z.string().min(1).max(200),
code: z
.string()
.min(1)
.max(32)
.regex(/^[A-Za-z0-9_-]+$/, "Code must be alphanumeric"),
notes: z.string().max(5000).nullable().optional(),
});
export const siteUpdateSchema = siteCreateSchema.partial();
export const tagCreateSchema = z.object({
siteId: z.string().uuid(),
label: z.string().min(1).max(200),
frequency: z.enum(FREQUENCIES),
protocol: z.enum(PROTOCOLS),
uid: z.string().min(1),
dumpData: dumpDataSchema,
keys: keysSchema,
notes: z.string().max(5000).nullable().optional(),
lastWrittenAt: z.string().datetime().nullable().optional(),
});
export const tagUpdateSchema = tagCreateSchema.partial().omit({ siteId: true }).extend({
siteId: z.string().uuid().optional(),
});
export const tagUpsertSchema = tagCreateSchema;
export const tokenCreateSchema = z.object({
name: z.string().min(1).max(100),
expiresAt: z.string().datetime().nullable().optional(),
});
export const exportFormatSchema = z.enum(["json", "proxmark", "mct", "hex"]);
export type MifareClassicDump = z.infer<typeof mifareClassicDumpSchema>;
export type NtagDump = z.infer<typeof ntagDumpSchema>;
export type LfDump = z.infer<typeof lfDumpSchema>;
export type DumpData = z.infer<typeof dumpDataSchema>;
export type TagKeys = z.infer<typeof keysSchema>;