move environment to src/environment add smtp env vars, move some other items
Some checks failed
Code quality checks / biome (push) Failing after 13s

This commit is contained in:
creations 2025-06-10 15:16:31 -04:00
parent 421043c9b5
commit 00a7417936
Signed by: creations
GPG key ID: 8F553AA4320FC711
30 changed files with 470 additions and 42 deletions

93
src/environment/config.ts Normal file
View file

@ -0,0 +1,93 @@
import { echo } from "@atums/echo";
import { validateJWTConfig, validateMailerConfig } from "#lib/validation";
import { isValidUrl } from "#lib/validation/url";
import { cassandraConfig, validateCassandraConfig } from "./database/cassandra";
import { jwt } from "./jwt";
import { mailerConfig } from "./mailer";
import type { Environment } from "#types/config";
const environment: Environment = {
port: Number.parseInt(process.env.PORT || "8080", 10),
host: process.env.HOST || "0.0.0.0",
development:
process.env.NODE_ENV === "development" || process.argv.includes("--dev"),
fqdn: process.env.FRONTEND_FQDN || "",
};
function verifyRequiredVariables(): void {
const requiredVariables = [
"HOST",
"PORT",
"REDIS_URL",
"REDIS_TTL",
"CASSANDRA_HOST",
"CASSANDRA_PORT",
"CASSANDRA_CONTACT_POINTS",
"CASSANDRA_AUTH_ENABLED",
"CASSANDRA_DATACENTER",
"JWT_SECRET",
"JWT_EXPIRATION",
"JWT_ISSUER",
"FRONTEND_FQDN",
];
let hasError = false;
for (const key of requiredVariables) {
const value = process.env[key];
if (value === undefined || value.trim() === "") {
echo.error(`Missing or empty environment variable: ${key}`);
hasError = true;
}
}
const validateCassandra = validateCassandraConfig(cassandraConfig);
if (!validateCassandra.isValid) {
echo.error("Cassandra configuration validation failed:");
for (const error of validateCassandra.errors) {
echo.error(`- ${error}`);
}
hasError = true;
}
const validateJWT = validateJWTConfig(jwt);
if (!validateJWT.valid) {
echo.error("JWT configuration validation failed:");
echo.error(`- ${validateJWT.error}`);
hasError = true;
}
const validateMailer = validateMailerConfig(mailerConfig);
if (!validateMailer.isValid) {
echo.error("Mailer configuration validation failed:");
for (const error of validateMailer.errors) {
echo.error(`- ${error}`);
}
hasError = true;
}
const urlValidation = isValidUrl(environment.fqdn, {
requireProtocol: true,
failOnTrailingSlash: true,
allowedProtocols: ["http", "https"],
allowLocalhost: environment.development,
allowIP: environment.development,
});
if (!urlValidation.valid) {
echo.error("FRONTEND_FQDN validation failed:");
echo.error(`- ${urlValidation.error}`);
hasError = true;
}
if (hasError) {
process.exit(1);
}
}
export { environment, verifyRequiredVariables };

View file

@ -0,0 +1,5 @@
import { resolve } from "node:path";
const migrationsPath = resolve("src", "environment", "database", "migrations");
export { migrationsPath };

View file

@ -0,0 +1,3 @@
export * from "./server";
export * from "./validation";
export * from "./database";

View file

@ -0,0 +1,6 @@
const reqLoggerIgnores = {
ignoredStartsWith: ["/public"],
ignoredPaths: [""],
};
export { reqLoggerIgnores };

View file

@ -0,0 +1,37 @@
import type { genericValidation } from "#types/lib";
const nameRestrictions: genericValidation = {
length: { min: 3, max: 20 },
regex: /^[\p{L}\p{N}._-]+$/u,
};
const displayNameRestrictions: genericValidation = {
length: { min: 1, max: 32 },
regex: /^[\p{L}\p{N}\p{M}\p{S}\p{P}\s]+$/u,
};
const forbiddenDisplayNamePatterns = [
/[\r\n\t]/,
/\s{3,}/,
/^\s|\s$/,
/#everyone|#here/i,
/\p{Cf}/u,
/\p{Cc}/u,
];
const passwordRestrictions: genericValidation = {
length: { min: 12, max: 64 },
regex: /^(?=.*\p{Ll})(?=.*\p{Lu})(?=.*\d)(?=.*[^\w\s]).{12,64}$/u,
};
const emailRestrictions: { regex: RegExp } = {
regex: /^[^\s#]+#[^\s#]+\.[^\s#]+$/,
};
export {
nameRestrictions,
displayNameRestrictions,
forbiddenDisplayNamePatterns,
passwordRestrictions,
emailRestrictions,
};

View file

@ -0,0 +1,114 @@
import type { CassandraConfig } from "#types/config";
function isValidHost(host: string): boolean {
if (!host || host.trim().length === 0) return false;
if (host === "localhost") return true;
const ipv4Regex =
/^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/;
if (ipv4Regex.test(host)) return true;
const hostnameRegex =
/^[a-zA-Z0-9]([a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(\.[a-zA-Z0-9]([a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$/;
return hostnameRegex.test(host);
}
function isValidPort(port: number): boolean {
return Number.isInteger(port) && port > 0 && port <= 65535;
}
function isValidKeyspace(keyspace: string): boolean {
if (!keyspace || keyspace.trim().length === 0) return false;
const keyspaceRegex = /^[a-zA-Z][a-zA-Z0-9_]{0,47}$/;
return keyspaceRegex.test(keyspace);
}
function isValidContactPoints(contactPoints: string[]): boolean {
if (!Array.isArray(contactPoints) || contactPoints.length === 0) return false;
return contactPoints.every((point) => {
const trimmed = point.trim();
return trimmed.length > 0 && isValidHost(trimmed);
});
}
function isValidCredentials(
username: string,
password: string,
authEnabled: boolean,
): boolean {
if (!authEnabled) return true;
return username.trim().length > 0 && password.trim().length > 0;
}
function isValidDatacenter(datacenter: string, authEnabled: boolean): boolean {
if (!authEnabled) return true;
return datacenter.trim().length > 0;
}
function validateCassandraConfig(config: CassandraConfig): {
isValid: boolean;
errors: string[];
} {
const errors: string[] = [];
if (!isValidHost(config.host)) {
errors.push(`Invalid host: ${config.host}`);
}
if (!isValidPort(config.port)) {
errors.push(
`Invalid port: ${config.port}. Port must be between 1 and 65535`,
);
}
if (!isValidKeyspace(config.keyspace)) {
errors.push(
`Invalid keyspace: ${config.keyspace}. Must start with letter, contain only alphanumeric and underscores, max 48 chars`,
);
}
if (!isValidContactPoints(config.contactPoints)) {
errors.push(
`Invalid contact points: ${config.contactPoints.join(", ")}. All contact points must be valid hosts`,
);
}
if (
!isValidCredentials(config.username, config.password, config.authEnabled)
) {
errors.push(
"Invalid credentials: Username and password are required when authentication is enabled",
);
}
if (!isValidDatacenter(config.datacenter, config.authEnabled)) {
errors.push(
"Invalid datacenter: Datacenter is required when authentication is enabled",
);
}
return {
isValid: errors.length === 0,
errors,
};
}
const rawConfig: CassandraConfig = {
host: process.env.CASSANDRA_HOST || "localhost",
port: Number.parseInt(process.env.CASSANDRA_PORT || "9042", 10),
keyspace: process.env.CASSANDRA_KEYSPACE || "void_db",
username: process.env.CASSANDRA_USERNAME || "",
password: process.env.CASSANDRA_PASSWORD || "",
datacenter: process.env.CASSANDRA_DATACENTER || "",
contactPoints: (process.env.CASSANDRA_CONTACT_POINTS || "localhost")
.split(",")
.map((point) => point.trim()),
authEnabled: process.env.CASSANDRA_AUTH_ENABLED !== "false",
};
export { rawConfig as cassandraConfig, validateCassandraConfig };

View file

@ -0,0 +1 @@
export * from "./cassandra";

View file

@ -0,0 +1,14 @@
CREATE TABLE IF NOT EXISTS users (
id TEXT PRIMARY KEY,
username TEXT,
display_name TEXT,
email TEXT,
password TEXT,
avatar_url TEXT,
is_verified BOOLEAN,
created_at TIMESTAMP,
updated_at TIMESTAMP
);
CREATE INDEX IF NOT EXISTS users_username_idx ON users (username);
CREATE INDEX IF NOT EXISTS users_email_idx ON users (email);

21
src/environment/jwt.ts Normal file
View file

@ -0,0 +1,21 @@
import { getExpirationInSeconds } from "#lib/utils";
import type { JWTConfig } from "#types/config";
function createJWTConfig(): JWTConfig {
const jwtSecret = process.env.JWT_SECRET || "";
const jwtExpiration = process.env.JWT_EXPIRATION || "1h";
const jwtIssuer = process.env.JWT_ISSUER || "";
const jwtAlgorithm = process.env.JWT_ALGORITHM || "HS256";
const configForValidation: JWTConfig = {
secret: jwtSecret,
expiration: getExpirationInSeconds(jwtExpiration),
issuer: jwtIssuer,
algorithm: jwtAlgorithm,
};
return configForValidation;
}
export const jwt = createJWTConfig();

39
src/environment/mailer.ts Normal file
View file

@ -0,0 +1,39 @@
import type { MailerConfig } from "#types/config";
function createMailerConfig(): MailerConfig {
const smtpAddress = process.env.SMTP_ADDRESS || "";
const smtpPort = process.env.SMTP_PORT || "587";
const smtpFrom = process.env.SMTP_FROM || "";
const smtpUsername = process.env.SMTP_USERNAME || "";
const smtpPassword = process.env.SMTP_PASSWORD || "";
const smtpSecure = process.env.SMTP_SECURE || "false";
const smtpPool = process.env.SMTP_POOL || "true";
const configForValidation: MailerConfig = {
address: smtpAddress,
port: Number.parseInt(smtpPort, 10),
from: smtpFrom,
username: smtpUsername,
password: smtpPassword,
secure: smtpSecure === "true",
pool: smtpPool === "true",
connectionTimeout: Number.parseInt(
process.env.SMTP_CONNECTION_TIMEOUT || "30000",
10,
),
socketTimeout: Number.parseInt(
process.env.SMTP_SOCKET_TIMEOUT || "30000",
10,
),
maxConnections: Number.parseInt(
process.env.SMTP_MAX_CONNECTIONS || "5",
10,
),
maxMessages: Number.parseInt(process.env.SMTP_MAX_MESSAGES || "100", 10),
replyTo: process.env.SMTP_REPLY_TO || smtpFrom,
};
return configForValidation;
}
export const mailerConfig = createMailerConfig();