refactor: convert repository to monorepo with npm workspaces
Restructured the repository into a monorepo to better organize application code and maintenance scripts. ## Workspace Structure - web-app: Next.js application (all app code moved from root) - housekeeping: Database backup and maintenance scripts ## Key Changes - Moved all application code to web-app/ using git mv - Moved database scripts to housekeeping/ workspace - Updated Dockerfile for monorepo build process - Updated docker-compose files (volume paths: ./web-app/etc/hosts/) - Updated .gitignore for workspace-level node_modules - Updated documentation (README.md, CLAUDE.md, CHANGELOG.md) ## Migration Impact - Root package.json now manages workspaces - Build commands delegate to web-app workspace - All file history preserved via git mv - Docker build process updated for workspace structure 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
86
web-app/app/lib/shareChecksum.ts
Normal file
86
web-app/app/lib/shareChecksum.ts
Normal file
@@ -0,0 +1,86 @@
|
||||
import crypto from 'crypto';
|
||||
|
||||
/**
|
||||
* Checksum length in hex characters (16 chars = 64 bits of entropy)
|
||||
*/
|
||||
export const CHECKSUM_LENGTH = 16;
|
||||
|
||||
/**
|
||||
* Generate share link checksum for location
|
||||
* Uses HMAC-SHA256 for cryptographic integrity
|
||||
*
|
||||
* SECURITY: Prevents location ID enumeration while allowing stateless validation
|
||||
*/
|
||||
export function generateShareChecksum(locationId: string): string {
|
||||
const secret = process.env.SHARE_LINK_SECRET;
|
||||
|
||||
if (!secret) {
|
||||
throw new Error('SHARE_LINK_SECRET environment variable not configured');
|
||||
}
|
||||
|
||||
return crypto
|
||||
.createHmac('sha256', secret)
|
||||
.update(locationId)
|
||||
.digest('hex')
|
||||
.substring(0, CHECKSUM_LENGTH);
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate share link checksum
|
||||
* Uses constant-time comparison to prevent timing attacks
|
||||
*
|
||||
* @param locationId - The location ID from URL
|
||||
* @param providedChecksum - The checksum from URL
|
||||
* @returns true if checksum is valid
|
||||
*/
|
||||
export function validateShareChecksum(
|
||||
locationId: string,
|
||||
providedChecksum: string
|
||||
): boolean {
|
||||
try {
|
||||
const expectedChecksum = generateShareChecksum(locationId);
|
||||
|
||||
// Convert to buffers for timing-safe comparison
|
||||
const expected = Buffer.from(expectedChecksum);
|
||||
const provided = Buffer.from(providedChecksum);
|
||||
|
||||
// Length check (prevents timing attack on different lengths)
|
||||
if (expected.length !== provided.length) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Constant-time comparison (prevents timing attacks)
|
||||
return crypto.timingSafeEqual(expected, provided);
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate combined location ID with checksum appended
|
||||
* @param locationId - The MongoDB location ID (24 chars)
|
||||
* @returns Combined ID: locationId + checksum (40 chars total)
|
||||
*/
|
||||
export function generateShareId(locationId: string): string {
|
||||
const checksum = generateShareChecksum(locationId);
|
||||
return locationId + checksum;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract location ID and checksum from combined share ID
|
||||
* @param shareId - Combined ID (locationId + checksum)
|
||||
* @returns Object with locationId and checksum, or null if invalid format
|
||||
*/
|
||||
export function extractShareId(shareId: string): { locationId: string; checksum: string } | null {
|
||||
// MongoDB ObjectID is 24 chars, checksum is 16 chars = 40 total
|
||||
const expectedLength = 24 + CHECKSUM_LENGTH;
|
||||
|
||||
if (shareId.length !== expectedLength) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const locationId = shareId.substring(0, 24);
|
||||
const checksum = shareId.substring(24);
|
||||
|
||||
return { locationId, checksum };
|
||||
}
|
||||
Reference in New Issue
Block a user