mirror of
https://github.com/snapotter-hq/SnapOtter.git
synced 2026-08-03 07:46:42 +02:00
feat: multi-arch Docker support, security hardening, and test improvements
Remove hardcoded --platform=linux/amd64 from Dockerfile so buildx produces native arm64 images for Apple Silicon and Raspberry Pi. Add audit logging for auth events, harden file storage with extension whitelists and double-extension attack prevention, reject null-byte buffers in validation, add data-testid attributes to all tool settings components, update deployment docs with architecture notes and correct CI workflow references, and fix unit test mock to match throwWithMessage error extraction.
This commit is contained in:
@@ -9,6 +9,7 @@ import { randomBytes, randomUUID } from "node:crypto";
|
||||
import { and, eq } from "drizzle-orm";
|
||||
import type { FastifyInstance, FastifyReply, FastifyRequest } from "fastify";
|
||||
import { db, schema } from "../db/index.js";
|
||||
import { auditLog } from "../lib/audit.js";
|
||||
import { computeKeyPrefix, hashPassword, requireAuth } from "../plugins/auth.js";
|
||||
|
||||
export async function apiKeyRoutes(app: FastifyInstance): Promise<void> {
|
||||
@@ -43,6 +44,8 @@ export async function apiKeyRoutes(app: FastifyInstance): Promise<void> {
|
||||
})
|
||||
.run();
|
||||
|
||||
auditLog(request.log, "API_KEY_CREATED", { userId: user.id, keyId: id, keyName: name });
|
||||
|
||||
// Return the raw key ONCE — it cannot be retrieved again
|
||||
return reply.status(201).send({
|
||||
id,
|
||||
@@ -103,6 +106,8 @@ export async function apiKeyRoutes(app: FastifyInstance): Promise<void> {
|
||||
|
||||
db.delete(schema.apiKeys).where(eq(schema.apiKeys.id, id)).run();
|
||||
|
||||
auditLog(request.log, "API_KEY_DELETED", { userId: user.id, keyId: id });
|
||||
|
||||
return reply.send({ ok: true });
|
||||
},
|
||||
);
|
||||
|
||||
@@ -16,6 +16,7 @@ import { and, desc, eq, like, sql } from "drizzle-orm";
|
||||
import type { FastifyInstance, FastifyReply, FastifyRequest } from "fastify";
|
||||
import sharp from "sharp";
|
||||
import { db, schema, sqlite } from "../db/index.js";
|
||||
import { auditLog } from "../lib/audit.js";
|
||||
import { deleteStoredFile, getStoredFilePath, saveFile } from "../lib/file-storage.js";
|
||||
import { validateImageBuffer } from "../lib/file-validation.js";
|
||||
import { sanitizeFilename } from "../lib/filename.js";
|
||||
@@ -205,6 +206,12 @@ export async function userFileRoutes(app: FastifyInstance): Promise<void> {
|
||||
return reply.status(400).send({ error: "No valid files uploaded" });
|
||||
}
|
||||
|
||||
auditLog(request.log, "FILE_UPLOADED", {
|
||||
userId,
|
||||
count: created.length,
|
||||
files: created.map((f) => f.originalName),
|
||||
});
|
||||
|
||||
return reply.status(201).send({ files: created });
|
||||
});
|
||||
|
||||
@@ -410,6 +417,9 @@ export async function userFileRoutes(app: FastifyInstance): Promise<void> {
|
||||
}
|
||||
}
|
||||
|
||||
const user = getAuthUser(request);
|
||||
auditLog(request.log, "FILE_DELETED", { userId: user?.id, count: deletedCount, ids });
|
||||
|
||||
return reply.send({ deleted: deletedCount });
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user