Add auth service, storage service, types, and logging middleware
- Auth service: hashPassword/verifyPassword via bcrypt - Storage service: saveFile, getFilePath, deleteStoredFile with ENOENT handling - Types: JwtPayload interface - Logging middleware: createLogger writing AUTH_FAILURE, AUTH_SUCCESS, FILE_NOT_FOUND - 27 tests passing Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
40
src/middleware/logging.ts
Normal file
40
src/middleware/logging.ts
Normal file
@@ -0,0 +1,40 @@
|
||||
import { appendFile } from 'fs/promises';
|
||||
|
||||
interface AuthLogParams {
|
||||
ip: string;
|
||||
userAgent: string;
|
||||
username: string;
|
||||
}
|
||||
|
||||
interface FileNotFoundParams {
|
||||
ip: string;
|
||||
userAgent: string;
|
||||
fileId: string;
|
||||
}
|
||||
|
||||
export interface Logger {
|
||||
authSuccess(params: AuthLogParams): Promise<void>;
|
||||
authFailure(params: AuthLogParams): Promise<void>;
|
||||
fileNotFound(params: FileNotFoundParams): Promise<void>;
|
||||
}
|
||||
|
||||
export function createLogger(logFile: string): Logger {
|
||||
function timestamp(): string {
|
||||
return new Date().toISOString();
|
||||
}
|
||||
|
||||
async function write(line: string): Promise<void> {
|
||||
await appendFile(logFile, line + '\n');
|
||||
}
|
||||
|
||||
function authLine(event: string, { ip, userAgent, username }: AuthLogParams): string {
|
||||
return `[${timestamp()}] ${event} ip=${ip} user-agent="${userAgent}" username="${username}"`;
|
||||
}
|
||||
|
||||
return {
|
||||
authSuccess: (params) => write(authLine('AUTH_SUCCESS', params)),
|
||||
authFailure: (params) => write(authLine('AUTH_FAILURE', params)),
|
||||
fileNotFound: ({ ip, userAgent, fileId }) =>
|
||||
write(`[${timestamp()}] FILE_NOT_FOUND ip=${ip} user-agent="${userAgent}" file_id="${fileId}"`),
|
||||
};
|
||||
}
|
||||
11
src/services/auth.ts
Normal file
11
src/services/auth.ts
Normal file
@@ -0,0 +1,11 @@
|
||||
import bcrypt from 'bcrypt';
|
||||
|
||||
const SALT_ROUNDS = 12;
|
||||
|
||||
export async function hashPassword(password: string): Promise<string> {
|
||||
return bcrypt.hash(password, SALT_ROUNDS);
|
||||
}
|
||||
|
||||
export async function verifyPassword(password: string, hash: string): Promise<boolean> {
|
||||
return bcrypt.compare(password, hash);
|
||||
}
|
||||
21
src/services/storage.ts
Normal file
21
src/services/storage.ts
Normal file
@@ -0,0 +1,21 @@
|
||||
import { writeFile, unlink } from 'fs/promises';
|
||||
import { join } from 'path';
|
||||
|
||||
export async function saveFile(uploadDir: string, storedName: string, data: Buffer): Promise<string> {
|
||||
await writeFile(join(uploadDir, storedName), data);
|
||||
return storedName;
|
||||
}
|
||||
|
||||
export function getFilePath(uploadDir: string, storedName: string): string {
|
||||
return join(uploadDir, storedName);
|
||||
}
|
||||
|
||||
export async function deleteStoredFile(uploadDir: string, storedName: string): Promise<void> {
|
||||
try {
|
||||
await unlink(join(uploadDir, storedName));
|
||||
} catch (err: unknown) {
|
||||
if ((err as NodeJS.ErrnoException).code !== 'ENOENT') {
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
}
|
||||
4
src/types.ts
Normal file
4
src/types.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
export interface JwtPayload {
|
||||
sub: number;
|
||||
username: string;
|
||||
}
|
||||
Reference in New Issue
Block a user