mirror of
https://github.com/Chewbaccalakis/rfid-database.git
synced 2026-09-10 00:11:56 -07:00
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:
co-authored by
Cursor Agent
parent
5029945cc2
commit
8500d2f673
@@ -0,0 +1,62 @@
|
||||
import type { NextRequest } from "next/server";
|
||||
import { eq } from "drizzle-orm";
|
||||
import { getDb } from "@/db/client";
|
||||
import { apiTokens, users, type User } from "@/db/schema";
|
||||
import { auth } from "@/lib/auth/auth";
|
||||
import { hashToken, isApiTokenFormat } from "@/lib/auth/tokens";
|
||||
import { jsonError, jsonOk } from "@/lib/api/errors";
|
||||
|
||||
export type AuthUser = Pick<User, "id" | "email" | "name">;
|
||||
|
||||
export { jsonError, jsonOk };
|
||||
|
||||
export async function requireApiUser(
|
||||
request: NextRequest
|
||||
): Promise<AuthUser | Response> {
|
||||
const header = request.headers.get("authorization");
|
||||
if (header?.toLowerCase().startsWith("bearer ")) {
|
||||
const token = header.slice(7).trim();
|
||||
if (!isApiTokenFormat(token)) {
|
||||
return jsonError(401, "UNAUTHORIZED", "Invalid API token");
|
||||
}
|
||||
const db = getDb();
|
||||
const tokenHash = hashToken(token);
|
||||
const row = db
|
||||
.select({
|
||||
tokenId: apiTokens.id,
|
||||
expiresAt: apiTokens.expiresAt,
|
||||
userId: users.id,
|
||||
email: users.email,
|
||||
name: users.name,
|
||||
})
|
||||
.from(apiTokens)
|
||||
.innerJoin(users, eq(apiTokens.userId, users.id))
|
||||
.where(eq(apiTokens.tokenHash, tokenHash))
|
||||
.get();
|
||||
|
||||
if (!row) {
|
||||
return jsonError(401, "UNAUTHORIZED", "Invalid API token");
|
||||
}
|
||||
if (row.expiresAt && row.expiresAt.getTime() < Date.now()) {
|
||||
return jsonError(401, "UNAUTHORIZED", "API token expired");
|
||||
}
|
||||
|
||||
db.update(apiTokens)
|
||||
.set({ lastUsedAt: new Date() })
|
||||
.where(eq(apiTokens.id, row.tokenId))
|
||||
.run();
|
||||
|
||||
return { id: row.userId, email: row.email, name: row.name };
|
||||
}
|
||||
|
||||
const session = await auth();
|
||||
if (!session?.user?.id) {
|
||||
return jsonError(401, "UNAUTHORIZED", "Authentication required");
|
||||
}
|
||||
|
||||
return {
|
||||
id: session.user.id,
|
||||
email: session.user.email ?? "",
|
||||
name: session.user.name ?? "",
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
export function jsonError(
|
||||
status: number,
|
||||
code: string,
|
||||
message: string
|
||||
): Response {
|
||||
return Response.json({ error: { code, message } }, { status });
|
||||
}
|
||||
|
||||
export function jsonOk<T>(data: T, status = 200): Response {
|
||||
return Response.json(data, { status });
|
||||
}
|
||||
Reference in New Issue
Block a user