feat(syncServer): add email verification route and user deletion script

This commit is contained in:
Johannes Millan 2025-11-27 19:36:56 +01:00
parent 3a319eb1de
commit 8e26c96760
5 changed files with 114 additions and 5 deletions

View file

@ -6,7 +6,8 @@
"scripts": {
"build": "tsc",
"start": "node dist/index.js",
"dev": "ts-node src/index.ts"
"dev": "ts-node src/index.ts",
"delete-user": "ts-node scripts/delete-user.ts"
},
"dependencies": {
"@fastify/cors": "^11.1.0",

View file

@ -0,0 +1,50 @@
import Database from 'better-sqlite3';
import * as path from 'path';
import * as fs from 'fs';
import { loadConfigFromEnv } from '../src/config';
const deleteUser = (email: string) => {
try {
// Load config to get data directory
const config = loadConfigFromEnv();
const dbPath = path.join(config.dataDir, 'database.sqlite');
if (!fs.existsSync(dbPath)) {
console.error(`Database not found at ${dbPath}`);
process.exit(1);
}
const db = new Database(dbPath);
// Check if user exists first
const user = db.prepare('SELECT * FROM users WHERE email = ?').get(email);
if (!user) {
console.log(`User with email "${email}" not found.`);
return;
}
// Delete user
const info = db.prepare('DELETE FROM users WHERE email = ?').run(email);
if (info.changes > 0) {
console.log(`Successfully deleted user: ${email}`);
} else {
console.log(`Failed to delete user: ${email}`);
}
} catch (error) {
console.error('Error deleting user:', error);
process.exit(1);
}
};
// Get email from command line arguments
const email = process.argv[2];
if (!email) {
console.error('Please provide an email address.');
console.error('Usage: npm run delete-user -- <email>');
process.exit(1);
}
deleteUser(email);

View file

@ -77,10 +77,14 @@ export const loadConfigFromEnv = (
// SMTP Configuration
if (process.env.SMTP_HOST) {
const port = parseInt(process.env.SMTP_PORT || '587', 10);
config.smtp = {
host: process.env.SMTP_HOST,
port: parseInt(process.env.SMTP_PORT || '587', 10),
secure: process.env.SMTP_SECURE === 'true',
port,
secure:
process.env.SMTP_SECURE !== undefined
? process.env.SMTP_SECURE === 'true'
: port === 465,
user: process.env.SMTP_USER,
pass: process.env.SMTP_PASS,
from: process.env.SMTP_FROM || '"SuperSync" <noreply@example.com>',

View file

@ -0,0 +1,47 @@
import { FastifyInstance } from 'fastify';
import { verifyEmail } from './auth';
import { Logger } from './logger';
// Error response helper
const errorMessage = (err: unknown): string =>
err instanceof Error ? err.message : 'Unknown error';
interface VerifyEmailQuery {
token?: string;
}
export async function pageRoutes(fastify: FastifyInstance) {
fastify.get<{ Querystring: VerifyEmailQuery }>('/verify-email', async (req, reply) => {
try {
const { token } = req.query;
if (!token) {
return reply.status(400).send('Token is required');
}
verifyEmail(token);
return reply.type('text/html').send(`
<html>
<head>
<title>Email Verified</title>
<style>
body { font-family: sans-serif; display: flex; justify-content: center; align-items: center; height: 100vh; background: #0f172a; color: white; }
.container { text-align: center; padding: 2rem; background: rgba(30, 41, 59, 0.7); border-radius: 1rem; border: 1px solid rgba(255,255,255,0.1); }
h1 { color: #10b981; }
a { color: #3b82f6; text-decoration: none; margin-top: 1rem; display: inline-block; }
</style>
</head>
<body>
<div class="container">
<h1>Email Verified!</h1>
<p>Your account has been successfully verified.</p>
<a href="/">Return to Login</a>
</div>
</body>
</html>
`);
} catch (err) {
Logger.error(`Verification error: ${errorMessage(err)}`);
return reply.status(400).send(`Verification failed: ${errorMessage(err)}`);
}
});
}

View file

@ -8,6 +8,7 @@ import { loadConfigFromEnv, ServerConfig } from './config';
import { Logger } from './logger';
import { initDb } from './db';
import { apiRoutes } from './api';
import { pageRoutes } from './pages';
import { verifyToken } from './auth';
export { ServerConfig, loadConfigFromEnv };
@ -162,6 +163,9 @@ export const createServer = (
// API Routes
await fastifyServer.register(apiRoutes, { prefix: '/api' });
// Page Routes
await fastifyServer.register(pageRoutes, { prefix: '/' });
// WebDAV Handler (Catch-all via hook)
// We use a hook because Fastify's router validates HTTP methods and might not support all WebDAV methods
fastifyServer.addHook('onRequest', (req, reply, done) => {
@ -170,10 +174,13 @@ export const createServer = (
return;
}
// Allow static files to be handled by Fastify
// Allow static files and verify-email route to be handled by Fastify
const staticFiles = ['/', '/index.html', '/style.css', '/app.js', '/favicon.ico'];
const urlPath = req.url.split('?')[0];
if (req.method === 'GET' && staticFiles.includes(urlPath)) {
if (
(req.method === 'GET' && staticFiles.includes(urlPath)) ||
urlPath === '/verify-email'
) {
done();
return;
}