feat: implement banned prefix list, security fixes, validation fixes for some endpoints
This commit is contained in:
parent
b2906afe5d
commit
5504091e4d
60
data/bannedprefix.txt
Normal file
60
data/bannedprefix.txt
Normal file
@ -0,0 +1,60 @@
|
|||||||
|
postmaster
|
||||||
|
webmaster
|
||||||
|
admin
|
||||||
|
administrator
|
||||||
|
hostmaster
|
||||||
|
info
|
||||||
|
support
|
||||||
|
contact
|
||||||
|
abuse
|
||||||
|
security
|
||||||
|
root
|
||||||
|
billing
|
||||||
|
sales
|
||||||
|
service
|
||||||
|
helpdesk
|
||||||
|
management
|
||||||
|
moderator
|
||||||
|
owner
|
||||||
|
verify
|
||||||
|
verification
|
||||||
|
access
|
||||||
|
login
|
||||||
|
register
|
||||||
|
registration
|
||||||
|
subscribe
|
||||||
|
subscription
|
||||||
|
unsubscribe
|
||||||
|
unsubscription
|
||||||
|
password
|
||||||
|
reset
|
||||||
|
help
|
||||||
|
data
|
||||||
|
manager
|
||||||
|
recovery
|
||||||
|
restore
|
||||||
|
sysadmin
|
||||||
|
audit
|
||||||
|
domainadmin
|
||||||
|
dns
|
||||||
|
auth
|
||||||
|
2fa
|
||||||
|
mfa
|
||||||
|
otp
|
||||||
|
it
|
||||||
|
finance
|
||||||
|
legal
|
||||||
|
compliance
|
||||||
|
test
|
||||||
|
example
|
||||||
|
sandbox
|
||||||
|
alert
|
||||||
|
alerts
|
||||||
|
notice
|
||||||
|
vaultwarden
|
||||||
|
pass
|
||||||
|
gitea
|
||||||
|
notification
|
||||||
|
feedback
|
||||||
|
no-reply
|
||||||
|
noreply
|
@ -6,18 +6,31 @@ import { eq } from "drizzle-orm";
|
|||||||
import fs from "fs/promises";
|
import fs from "fs/promises";
|
||||||
import { drizzle } from "drizzle-orm/bun-sqlite";
|
import { drizzle } from "drizzle-orm/bun-sqlite";
|
||||||
import { updateAccountsCache } from "../../utils/updateAccountsCache";
|
import { updateAccountsCache } from "../../utils/updateAccountsCache";
|
||||||
|
import { isBannedPrefix } from "../../utils/validators";
|
||||||
|
|
||||||
const db = drizzle(process.env.DB_FILE_NAME!);
|
const db = drizzle(process.env.DB_FILE_NAME!);
|
||||||
|
|
||||||
export const addAccount = async (req: Request, res: Response): Promise<void> => {
|
export const addAccount = async (req: Request, res: Response): Promise<void> => {
|
||||||
const { email, password, migrate } = req.body;
|
const { email, password, migrate } = req.body;
|
||||||
|
|
||||||
|
if (!email || !password) {
|
||||||
|
console.log("[!] Error\nTASK| addAccount\nERR | Missing email or password");
|
||||||
|
res.status(400).json({ error: "Missing email or password" });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (!validateEmail(email)) {
|
if (!validateEmail(email)) {
|
||||||
console.log("[!] Error\nTASK| addAccount\nERR | Invalid email format");
|
console.log("[!] Error\nTASK| addAccount\nERR | Invalid email format");
|
||||||
res.status(400).json({ error: "Invalid email format" });
|
res.status(400).json({ error: "Invalid email format" });
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (await isBannedPrefix(email)) {
|
||||||
|
console.log("[!] Error\nTASK| addAccount\nERR | Banned email prefix\nACC |", email);
|
||||||
|
res.status(400).json({ error: "Banned email prefix" });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
let finalPassword = password;
|
let finalPassword = password;
|
||||||
|
|
||||||
if (migrate) {
|
if (migrate) {
|
||||||
@ -27,6 +40,8 @@ export const addAccount = async (req: Request, res: Response): Promise<void> =>
|
|||||||
const line = data.split("\n").find(l => l.trim() === email);
|
const line = data.split("\n").find(l => l.trim() === email);
|
||||||
if (!line) {
|
if (!line) {
|
||||||
console.log("[!] Error\nTASK| addAccount (subtask: migrate)\nERR | Account not found in migrate.txt\nACC |", email);
|
console.log("[!] Error\nTASK| addAccount (subtask: migrate)\nERR | Account not found in migrate.txt\nACC |", email);
|
||||||
|
|
||||||
|
// A backend error is returned so users do not attempt to abuse the migration form
|
||||||
res.status(500).json({ error: "Backend error" });
|
res.status(500).json({ error: "Backend error" });
|
||||||
return;
|
return;
|
||||||
} else {
|
} else {
|
||||||
|
@ -8,7 +8,12 @@ const db = drizzle(process.env.DB_FILE_NAME!);
|
|||||||
|
|
||||||
export const getUserAccount = async (req: Request, res: Response): Promise<void> => {
|
export const getUserAccount = async (req: Request, res: Response): Promise<void> => {
|
||||||
const { email } = req.body;
|
const { email } = req.body;
|
||||||
console.log("[*] Task started\nTASK| getUserAccount\nACC |", email);
|
|
||||||
|
if (!email) {
|
||||||
|
console.log("[!] Error\nTASK| getUserAccount\nERR | Missing email\nACC |", email);
|
||||||
|
res.status(400).json({error: "Missing email"});
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
if (!validateEmail(email)) {
|
if (!validateEmail(email)) {
|
||||||
console.log("[!] Error\nTASK| getUserAccount\nERR | Invalid email format\nACC |", email);
|
console.log("[!] Error\nTASK| getUserAccount\nERR | Invalid email format\nACC |", email);
|
||||||
@ -16,6 +21,8 @@ export const getUserAccount = async (req: Request, res: Response): Promise<void>
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
console.log("[*] Task started\nTASK| getUserAccount\nACC |", email);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const account = await db
|
const account = await db
|
||||||
.select()
|
.select()
|
||||||
|
@ -33,12 +33,12 @@ app.listen(PORT, () => {
|
|||||||
figlet('mail-connect', (err, data) => {
|
figlet('mail-connect', (err, data) => {
|
||||||
if (err) {
|
if (err) {
|
||||||
console.log('mail-connect');
|
console.log('mail-connect');
|
||||||
console.log('Version: 0.1.0');
|
console.log('Version: 0.1.1');
|
||||||
console.log(`API listening on port ${PORT}\n`);
|
console.log(`API listening on port ${PORT}\n`);
|
||||||
console.dir("[!] " + err);
|
console.dir("[!] " + err);
|
||||||
} else {
|
} else {
|
||||||
console.log(data);
|
console.log(data);
|
||||||
console.log('Version: 0.1.0');
|
console.log('Version: 0.1.1');
|
||||||
console.log(`API listening on port ${PORT}\n`);
|
console.log(`API listening on port ${PORT}\n`);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import rateLimit from "express-rate-limit";
|
import rateLimit from "express-rate-limit";
|
||||||
import fs from "fs/promises";
|
import fs from "fs/promises";
|
||||||
|
import path from "path";
|
||||||
|
|
||||||
interface RateLimitOptions {
|
interface RateLimitOptions {
|
||||||
windowMs: number;
|
windowMs: number;
|
||||||
@ -13,8 +14,9 @@ interface RateLimitConfig {
|
|||||||
|
|
||||||
export const loadRateLimitConfig = async (): Promise<RateLimitConfig> => {
|
export const loadRateLimitConfig = async (): Promise<RateLimitConfig> => {
|
||||||
try {
|
try {
|
||||||
await fs.access(`${process.env.MAILCONNECT_ROOT_DIR}/ratelimit.json`);
|
const rateLimitPath = path.join(process.cwd(), "ratelimit.json");
|
||||||
const data = await fs.readFile(`${process.env.MAILCONNECT_ROOT_DIR}/ratelimit.json`, "utf8");
|
await fs.access(rateLimitPath);
|
||||||
|
const data = await fs.readFile(rateLimitPath, "utf8");
|
||||||
return JSON.parse(data);
|
return JSON.parse(data);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error("[!] Error loading ratelimit config:\n", err);
|
console.error("[!] Error loading ratelimit config:\n", err);
|
||||||
|
@ -1,8 +1,27 @@
|
|||||||
import validator from "validator";
|
import validator from "validator";
|
||||||
import PasswordValidator from "password-validator";
|
import PasswordValidator from "password-validator";
|
||||||
|
import fs from "fs/promises";
|
||||||
|
import path from "path";
|
||||||
|
|
||||||
|
let bannedPrefixesCache: string[] | null = null;
|
||||||
|
|
||||||
export const validateEmail = (email: string): boolean => validator.isEmail(email);
|
export const validateEmail = (email: string): boolean => validator.isEmail(email);
|
||||||
|
|
||||||
|
export const isBannedPrefix = async (email: string): Promise<boolean> => {
|
||||||
|
try {
|
||||||
|
if (!bannedPrefixesCache) {
|
||||||
|
const filePath = path.join(process.cwd(), "data", "bannedprefix.txt");
|
||||||
|
const data = await fs.readFile(filePath, "utf8");
|
||||||
|
bannedPrefixesCache = data.split("\n").filter(prefix => prefix.trim());
|
||||||
|
}
|
||||||
|
const prefix = email.split("@")[0];
|
||||||
|
return bannedPrefixesCache.includes(prefix);
|
||||||
|
} catch (error) {
|
||||||
|
console.error("[!] Error checking for banned prefix:", error);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export const passwordSchema = new PasswordValidator();
|
export const passwordSchema = new PasswordValidator();
|
||||||
passwordSchema.is().min(8).is().max(64).has().letters().has().digits().has().not().spaces();
|
passwordSchema.is().min(8).is().max(64).has().letters().has().digits().has().not().spaces();
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user