first commit
Some checks failed
Code quality checks / biome (push) Failing after 11s

This commit is contained in:
creations 2025-06-10 13:42:39 -04:00
commit 421043c9b5
Signed by: creations
GPG key ID: 8F553AA4320FC711
67 changed files with 3455 additions and 0 deletions

120
src/commands.ts Normal file
View file

@ -0,0 +1,120 @@
import { Echo } from "@atums/echo";
import { redis } from "bun";
import { verifyRequiredVariables } from "#environment/config";
import { cassandra } from "#lib/database";
const echo = new Echo({
disableFile: true,
});
async function resetCassandra(): Promise<void> {
echo.info("Resetting Cassandra database...");
try {
verifyRequiredVariables();
await cassandra.connect({ withKeyspace: false, logging: true });
await cassandra.dropEverything();
echo.info("Cassandra database reset complete");
echo.info(
"Restart your server to recreate the database and run migrations",
);
} catch (error) {
echo.error({ message: "Failed to reset Cassandra:", error });
process.exit(1);
}
}
async function resetRedis(): Promise<void> {
echo.info("Resetting Redis database...");
try {
verifyRequiredVariables();
const keys = await redis.keys("*");
if (keys.length > 0) {
echo.info(`Found ${keys.length} keys to delete`);
let deletedCount = 0;
for (const key of keys) {
await redis.del(key);
deletedCount++;
if (deletedCount % 100 === 0) {
echo.info(`Deleted ${deletedCount}/${keys.length} keys...`);
}
}
echo.info(`Deleted ${deletedCount} keys`);
} else {
echo.info("No keys found - Redis is already empty");
}
echo.info("Redis database reset complete");
echo.info("All Redis data has been cleared");
} catch (error) {
echo.error({ message: "Failed to reset Redis:", error });
process.exit(1);
}
}
async function resetAll(): Promise<void> {
echo.info("Resetting all databases...");
try {
await resetCassandra();
await resetRedis();
echo.info("All databases reset complete");
} catch (error) {
echo.error({ message: "Failed to reset databases:", error });
process.exit(1);
}
}
function showHelp(): void {
echo.info("Available commands:");
echo.info(" --reset cassandra Reset Cassandra database (drops keyspace)");
echo.info(" --reset redis Reset Redis database (flush all data)");
echo.info(" --reset all Reset both databases");
echo.info(" --help Show this help message");
echo.info("");
echo.info("Examples:");
echo.info(" bun run src/index.ts --reset cassandra");
echo.info(" bun run src/index.ts --reset redis");
echo.info(" bun run src/index.ts --reset all");
}
export async function handleCommands(): Promise<boolean> {
const args = process.argv.slice(2);
const resetIndex = args.indexOf("--reset");
if (resetIndex !== -1) {
const resetTarget = args[resetIndex + 1];
switch (resetTarget) {
case "cassandra":
await resetCassandra();
return true;
case "redis":
await resetRedis();
return true;
case "all":
await resetAll();
return true;
default:
echo.error(`Unknown reset target: ${resetTarget}`);
showHelp();
process.exit(1);
}
}
if (args.includes("--help") || args.includes("-h")) {
showHelp();
return true;
}
return false;
}

27
src/index.ts Normal file
View file

@ -0,0 +1,27 @@
import { Echo, echo } from "@atums/echo";
import { handleCommands } from "#commands";
import { verifyRequiredVariables } from "#environment/config";
import { migrationRunner } from "#lib/database";
import { serverHandler } from "#server";
const noFileLog = new Echo({
disableFile: true,
});
async function main(): Promise<void> {
const commandHandled = await handleCommands();
if (commandHandled) process.exit(0);
verifyRequiredVariables();
await migrationRunner.initialize();
serverHandler.initialize();
}
main().catch((error: Error) => {
echo.error({ message: "Error initializing the server:", error });
process.exit(1);
});
export { noFileLog };

52
src/lib/auth/cookies.ts Normal file
View file

@ -0,0 +1,52 @@
import { environment } from "#environment/config";
import { jwt } from "#environment/jwt";
import type { CookieOptions } from "#types/config";
class CookieService {
extractToken(request: Request): string | null {
return request.headers.get("Cookie")?.match(/session=([^;]+)/)?.[1] || null;
}
generateCookie(
token: string,
maxAge = jwt.expiration,
options?: CookieOptions,
): string {
const {
secure = !environment.development,
httpOnly = true,
sameSite = environment.development ? "Lax" : "None",
path = "/",
domain,
} = options || {};
let cookie = `session=${encodeURIComponent(token)}; Path=${path}; Max-Age=${maxAge}`;
if (httpOnly) cookie += "; HttpOnly";
if (secure) cookie += "; Secure";
if (sameSite) cookie += `; SameSite=${sameSite}`;
if (domain) cookie += `; Domain=${domain}`;
return cookie;
}
clearCookie(options?: Omit<CookieOptions, "httpOnly" | "secure">): string {
const {
sameSite = environment.development ? "Lax" : "None",
path = "/",
domain,
} = options || {};
let cookie = `session=; Path=${path}; Max-Age=0; HttpOnly`;
if (!environment.development) cookie += "; Secure";
if (sameSite) cookie += `; SameSite=${sameSite}`;
if (domain) cookie += `; Domain=${domain}`;
return cookie;
}
}
const cookieService = new CookieService();
export { CookieService, cookieService };

3
src/lib/auth/index.ts Normal file
View file

@ -0,0 +1,3 @@
export * from "./jwt";
export * from "./cookies";
export * from "./session";

34
src/lib/auth/jwt.ts Normal file
View file

@ -0,0 +1,34 @@
import { createDecoder, createSigner, createVerifier } from "fast-jwt";
import { jwt } from "#environment/jwt";
import type { UserSession } from "#types/config";
class JWTService {
private readonly signer;
private readonly verifier;
private readonly decoder;
constructor() {
this.signer = createSigner({
key: jwt.secret,
expiresIn: jwt.expiration,
});
this.verifier = createVerifier({ key: jwt.secret });
this.decoder = createDecoder();
}
sign(payload: UserSession): string {
return this.signer(payload);
}
verify(token: string): UserSession {
return this.verifier(token);
}
decode(token: string): UserSession {
return this.decoder(token);
}
}
export const jwtService = new JWTService();
export { JWTService };

167
src/lib/auth/session.ts Normal file
View file

@ -0,0 +1,167 @@
import { jwt } from "#environment/jwt";
import { cookieService } from "#lib/auth/cookies";
import { jwtService } from "#lib/auth/jwt";
import { redis } from "bun";
import type { CookieOptions, SessionData, UserSession } from "#types/config";
class SessionManager {
async createSession(
payload: UserSession,
userAgent: string,
cookieOptions?: CookieOptions,
): Promise<string> {
const token = jwtService.sign(payload);
const sessionKey = this.getSessionKey(payload.id, token);
const sessionData: SessionData = { ...payload, userAgent };
await redis.set(sessionKey, JSON.stringify(sessionData));
await redis.expire(sessionKey, jwt.expiration as number);
return cookieService.generateCookie(
token,
jwt.expiration as number,
cookieOptions,
);
}
async getSession(request: Request): Promise<UserSession | null> {
const token = cookieService.extractToken(request);
if (!token) return null;
return this.getSessionByToken(token);
}
async getSessionByToken(token: string): Promise<UserSession | null> {
const keys = await redis.keys(`session:*:${token}`);
if (!keys.length) return null;
const sessionKey = keys[0];
if (!sessionKey) return null;
const raw = await redis.get(sessionKey);
if (!raw) return null;
try {
const sessionData: SessionData = JSON.parse(raw);
const { userAgent, ...userSession } = sessionData;
return userSession;
} catch {
return null;
}
}
async updateSession(
request: Request,
payload: UserSession,
userAgent: string,
cookieOptions?: CookieOptions,
): Promise<string> {
const token = cookieService.extractToken(request);
if (!token) throw new Error("Session token not found");
const keys = await redis.keys(`session:*:${token}`);
if (!keys.length) throw new Error("Session not found or expired");
const sessionKey = keys[0];
if (!sessionKey) throw new Error("Session not found or expired");
const sessionData: SessionData = { ...payload, userAgent };
await redis.set(sessionKey, JSON.stringify(sessionData));
await redis.expire(sessionKey, jwt.expiration as number);
return cookieService.generateCookie(
token,
jwt.expiration as number,
cookieOptions,
);
}
async refreshSession(
request: Request,
cookieOptions?: CookieOptions,
): Promise<string | null> {
const token = cookieService.extractToken(request);
if (!token) return null;
const keys = await redis.keys(`session:*:${token}`);
if (!keys.length) return null;
const sessionKey = keys[0];
if (!sessionKey) return null;
await redis.expire(sessionKey, jwt.expiration as number);
return cookieService.generateCookie(
token,
jwt.expiration as number,
cookieOptions,
);
}
async verifySession(token: string): Promise<UserSession> {
const keys = await redis.keys(`session:*:${token}`);
if (!keys.length) throw new Error("Session not found or expired");
return jwtService.verify(token);
}
async decodeSession(token: string): Promise<UserSession> {
return jwtService.decode(token);
}
async invalidateSession(request: Request): Promise<void> {
const token = cookieService.extractToken(request);
if (!token) return;
await this.invalidateSessionByToken(token);
}
async invalidateSessionByToken(token: string): Promise<void> {
const keys = await redis.keys(`session:*:${token}`);
if (!keys.length) return;
const sessionKey = keys[0];
if (!sessionKey) return;
await redis.del(sessionKey);
}
async invalidateSessionById(sessionId: string): Promise<boolean> {
const keys = await redis.keys(`session:*:${sessionId}`);
if (!keys.length) return false;
const sessionKey = keys[0];
if (!sessionKey) return false;
await redis.del(sessionKey);
return true;
}
async invalidateAllSessionsForUser(userId: string): Promise<number> {
const keys = await redis.keys(`session:${userId}:*`);
if (keys.length === 0) return 0;
for (const key of keys) {
await redis.del(key);
}
return keys.length;
}
async getActiveSessionsForUser(userId: string): Promise<string[]> {
const keys = await redis.keys(`session:${userId}:*`);
return keys.flatMap((key) => {
const token = key.split(":")[2];
return token ? [token] : [];
});
}
// Private helper methods
private getSessionKey(userId: string, token: string): string {
return `session:${userId}:${token}`;
}
}
const sessionManager = new SessionManager();
export { SessionManager, sessionManager };

View file

@ -0,0 +1,304 @@
import { echo } from "@atums/echo";
import { cassandraConfig as config } from "#environment/database";
import { noFileLog } from "#index";
import {
Client,
type DseClientOptions,
type QueryOptions,
auth,
} from "cassandra-driver";
import { environment } from "#environment/config";
import type { ConnectionOptions } from "#types/config";
class CassandraService {
private static instance: Client | null = null;
private static isConnecting = false;
private static connectionPromise: Promise<void> | null = null;
private constructor() {}
public static getClient(): Client {
if (!CassandraService.instance) {
throw new Error(
"Cassandra client is not initialized. Call connect() first.",
);
}
return CassandraService.instance;
}
public static isConnected(): boolean {
return (
CassandraService.instance !== null &&
CassandraService.instance.getState().getConnectedHosts().length > 0
);
}
private static buildClientOptions(
options: ConnectionOptions,
): DseClientOptions {
const { withKeyspace = true, timeout = 30000 } = options;
const authProvider = config.authEnabled
? new auth.PlainTextAuthProvider(config.username, config.password)
: undefined;
const clientOptions: DseClientOptions = {
contactPoints: config.contactPoints,
localDataCenter: config.datacenter,
protocolOptions: {
port: config.port,
},
socketOptions: {
connectTimeout: timeout,
readTimeout: timeout,
},
};
if (authProvider) {
clientOptions.authProvider = authProvider;
}
if (withKeyspace && config.keyspace) {
clientOptions.keyspace = config.keyspace;
}
return clientOptions;
}
public static async connect(options: ConnectionOptions = {}): Promise<void> {
if (
CassandraService.instance &&
CassandraService.instance.getState().getConnectedHosts().length > 0
) {
return;
}
if (CassandraService.isConnecting && CassandraService.connectionPromise) {
return CassandraService.connectionPromise;
}
CassandraService.isConnecting = true;
try {
CassandraService.connectionPromise =
CassandraService.performConnection(options);
await CassandraService.connectionPromise;
} finally {
CassandraService.isConnecting = false;
CassandraService.connectionPromise = null;
}
}
private static async performConnection(
options: ConnectionOptions,
): Promise<void> {
const clientOptions = CassandraService.buildClientOptions(options);
if (options.logging !== false) {
noFileLog.info({
message: "Connecting to Cassandra...",
contactPoints: config.contactPoints,
datacenter: config.datacenter,
keyspace: clientOptions.keyspace || "none",
authEnabled: config.authEnabled,
});
}
const client = new Client(clientOptions);
try {
await client.connect();
const hosts = client.getState().getConnectedHosts();
const hostCount = hosts.length;
if (options.logging !== false) {
noFileLog.info(
`Connected to Cassandra successfully. Active hosts: ${hostCount}`,
);
}
CassandraService.instance = client;
if (options.logging !== false) {
client.on(
"log",
(level: string, className: string, message: string) => {
if (level === "error") {
echo.error(`Cassandra ${className}: ${message}`);
} else if (level === "warning") {
echo.warn(`Cassandra ${className}: ${message}`);
}
},
);
}
} catch (error) {
echo.error({ message: "Failed to connect to Cassandra:", error });
await client.shutdown().catch(() => {});
throw error;
}
}
public static async createKeyspaceIfNotExists(): Promise<void> {
if (!config.keyspace) {
throw new Error("No keyspace configured");
}
const client = CassandraService.getClient();
const query = `
CREATE KEYSPACE IF NOT EXISTS ${config.keyspace}
WITH REPLICATION = {
'class': 'SimpleStrategy',
'replication_factor': 1
}
`;
try {
await client.execute(query);
noFileLog.debug(`Keyspace '${config.keyspace}' ensured to exist`);
} catch (error) {
echo.error({
message: `Failed to create keyspace '${config.keyspace}':`,
error,
});
throw error;
}
}
public static async execute(
query: string,
params?: unknown[],
options?: QueryOptions,
): Promise<unknown> {
const client = CassandraService.getClient();
try {
const result = await client.execute(query, params, options);
return result;
} catch (error) {
echo.error({
message: "Cassandra query failed:",
query: query.substring(0, 100) + (query.length > 100 ? "..." : ""),
error,
});
throw error;
}
}
public static async shutdown(disableLogging = false): Promise<void> {
if (CassandraService.instance) {
try {
await CassandraService.instance.shutdown();
if (!disableLogging) {
noFileLog.info("Cassandra client shut down gracefully");
}
} catch (error) {
echo.error({ message: "Error during Cassandra shutdown:", error });
} finally {
CassandraService.instance = null;
}
}
}
public static getHealthStatus(): {
connected: boolean;
hosts: number;
} {
if (!CassandraService.instance) {
return { connected: false, hosts: 0 };
}
const hosts = CassandraService.instance.getState().getConnectedHosts();
return {
connected: hosts.length > 0,
hosts: hosts.length,
};
}
public static async dropEverything(): Promise<void> {
if (!config.keyspace) {
throw new Error("No keyspace configured");
}
if (!environment.development)
throw new Error(
"Drop operation is only allowed in development environment",
);
const client = CassandraService.getClient();
try {
const tablesQuery = `
SELECT table_name FROM system_schema.tables
WHERE keyspace_name = ?
`;
const tablesResult = await client.execute(tablesQuery, [config.keyspace]);
const tableNames = tablesResult.rows.map((row) => {
const tableRow = row as unknown as { table_name: string };
return tableRow.table_name;
});
if (tableNames.length > 0) {
noFileLog.warn(
`About to drop keyspace '${config.keyspace}' containing tables: ${tableNames.join(", ")}`,
);
} else {
noFileLog.info(
`Keyspace '${config.keyspace}' is empty or doesn't exist`,
);
}
const dropQuery = `DROP KEYSPACE IF EXISTS ${config.keyspace}`;
await client.execute(dropQuery);
noFileLog.info(`Keyspace '${config.keyspace}' dropped successfully`);
} catch (error) {
echo.error({
message: `Failed to drop keyspace '${config.keyspace}':`,
error,
});
throw error;
}
if (CassandraService.instance) {
try {
await CassandraService.shutdown(true);
} catch (shutdownError) {
noFileLog.warn({
message: "Error during shutdown after drop:",
error: shutdownError,
});
}
}
CassandraService.instance = null;
CassandraService.isConnecting = false;
CassandraService.connectionPromise = null;
noFileLog.info("Cassandra client state reset after dropping keyspace");
}
public static async resetDatabase(): Promise<void> {
if (!environment.development)
throw new Error(
"Reset operation is only allowed in development environment",
);
noFileLog.info("Starting database reset...");
await CassandraService.dropEverything();
await CassandraService.connect({ withKeyspace: false, logging: true });
await CassandraService.createKeyspaceIfNotExists();
await CassandraService.shutdown(true);
noFileLog.info(
"Database reset complete. Restart your application to run migrations.",
);
}
}
export { CassandraService as cassandra };

View file

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

View file

@ -0,0 +1,183 @@
import { readFile, readdir } from "node:fs/promises";
import { resolve } from "node:path";
import { echo } from "@atums/echo";
import { environment } from "#environment/config";
import { migrationsPath } from "#environment/constants";
import { noFileLog } from "#index";
import { cassandra } from "#lib/database";
import type { SqlMigration } from "#types/config";
class MigrationRunner {
private migrations: SqlMigration[] = [];
async loadMigrations(): Promise<void> {
try {
const upPath = resolve(migrationsPath, "up");
const downPath = resolve(migrationsPath, "down");
const upFiles = await readdir(upPath);
const sqlFiles = upFiles.filter((file) => file.endsWith(".sql")).sort();
for (const sqlFile of sqlFiles) {
try {
const baseName = sqlFile.replace(".sql", "");
const parts = baseName.split("_");
const id = parts[0];
const nameParts = parts.slice(1);
const name = nameParts.join("_") || "migration";
if (!id || id.trim() === "") {
noFileLog.debug(
`Skipping migration file with invalid ID: ${sqlFile}`,
);
continue;
}
const upSql = await readFile(resolve(upPath, sqlFile), "utf-8");
let downSql: string | undefined;
try {
downSql = await readFile(resolve(downPath, sqlFile), "utf-8");
} catch {
// down is optional
}
this.migrations.push({
id,
name,
upSql: upSql.trim(),
...(downSql && { downSql: downSql.trim() }),
});
} catch (error) {
echo.error({
message: `Failed to load migration ${sqlFile}:`,
error,
});
}
}
noFileLog.debug(`Loaded ${this.migrations.length} migrations`);
} catch (error) {
noFileLog.debug({
message: "No migrations directory found or error reading:",
error,
});
}
}
private async createMigrationsTable(): Promise<void> {
const query = `
CREATE TABLE IF NOT EXISTS schema_migrations (
id TEXT PRIMARY KEY,
name TEXT,
executed_at TIMESTAMP,
checksum TEXT
)
`;
await cassandra.execute(query);
noFileLog.debug("Schema migrations table ready");
}
private async getExecutedMigrations(): Promise<Set<string>> {
try {
const result = (await cassandra.execute(
"SELECT id FROM schema_migrations",
)) as { rows: Array<{ id: string }> };
return new Set(result.rows.map((row) => row.id));
} catch (error) {
noFileLog.debug({
message: "Could not fetch executed migrations:",
error,
});
return new Set();
}
}
private async markMigrationExecuted(migration: SqlMigration): Promise<void> {
const query = `
INSERT INTO schema_migrations (id, name, executed_at, checksum)
VALUES (?, ?, ?, ?)
`;
const checksum = this.generateChecksum(migration.upSql);
await cassandra.execute(query, [
migration.id,
migration.name,
new Date(),
checksum,
]);
}
private generateChecksum(input: string): string {
let hash = 0;
for (let i = 0; i < input.length; i++) {
const char = input.charCodeAt(i);
hash = (hash << 5) - hash + char;
hash = hash & hash;
}
return hash.toString(16);
}
private async executeSql(sql: string): Promise<void> {
const statements = sql
.split(";")
.map((stmt) => stmt.trim())
.filter((stmt) => stmt.length > 0);
for (const statement of statements) {
if (statement.trim()) {
await cassandra.execute(statement);
}
}
}
async runMigrations(): Promise<void> {
if (this.migrations.length === 0) {
noFileLog.debug("No migrations to run");
return;
}
await this.createMigrationsTable();
const executedMigrations = await this.getExecutedMigrations();
const pendingMigrations = this.migrations.filter(
(migration) => !executedMigrations.has(migration.id),
);
if (pendingMigrations.length === 0) {
noFileLog.debug("All migrations are up to date");
return;
}
noFileLog.debug(
`Running ${pendingMigrations.length} pending migrations...`,
);
for (const migration of pendingMigrations) {
try {
noFileLog.debug(
`Running migration: ${migration.id} - ${migration.name}`,
);
await this.executeSql(migration.upSql);
await this.markMigrationExecuted(migration);
noFileLog.debug(`Migration ${migration.id} completed`);
} catch (error) {
echo.error({
message: `Failed to run migration ${migration.id}:`,
error,
});
throw error;
}
}
noFileLog.debug("All migrations completed successfully");
}
async initialize(): Promise<void> {
await cassandra.connect({
withKeyspace: false,
logging: environment.development,
});
await cassandra.createKeyspaceIfNotExists();
await cassandra.shutdown(!environment.development);
await cassandra.connect({ withKeyspace: true });
await this.loadMigrations();
await this.runMigrations();
}
}
export const migrationRunner = new MigrationRunner();

View file

@ -0,0 +1,16 @@
import Pika from "pika-id";
const pika = new Pika([
"user",
{
prefix: "user",
description: "User ID",
},
"session",
{
prefix: "sess",
description: "Session ID",
},
]);
export { pika };

2
src/lib/utils/index.ts Normal file
View file

@ -0,0 +1,2 @@
export * from "./idGenerator";
export * from "./jwt";

35
src/lib/utils/jwt.ts Normal file
View file

@ -0,0 +1,35 @@
function getExpirationInSeconds(expiration: string): number {
const match = expiration.match(/^(\d+)([smhdwy])$/);
if (!match) throw new Error("Invalid expiresIn format in jwt config");
const [, value, unit] = match;
const num = Number(value);
switch (unit) {
case "s":
return num;
case "m":
return num * 60;
case "h":
return num * 3600;
case "d":
return num * 86400;
case "w":
return num * 604800; // 7 days
case "y":
return num * 31536000; // 365 days
default:
throw new Error("Invalid time unit in expiresIn");
}
}
function formatSecondsToTimeString(seconds: number): string {
if (seconds < 60) return `${seconds}s`;
if (seconds < 3600) return `${Math.floor(seconds / 60)}m`;
if (seconds < 86400) return `${Math.floor(seconds / 3600)}h`;
if (seconds < 604800) return `${Math.floor(seconds / 86400)}d`;
if (seconds < 31536000) return `${Math.floor(seconds / 604800)}w`;
return `${Math.floor(seconds / 31536000)}y`;
}
export { getExpirationInSeconds, formatSecondsToTimeString };

View file

@ -0,0 +1,18 @@
import { emailRestrictions } from "#environment/constants";
import type { validationResult } from "#types/lib";
function isValidEmail(rawEmail: string): validationResult {
const email = rawEmail.trim();
if (!email) {
return { valid: false, error: "Email is required" };
}
if (!emailRestrictions.regex.test(email)) {
return { valid: false, error: "Invalid email address" };
}
return { valid: true };
}
export { emailRestrictions, isValidEmail };

View file

@ -0,0 +1,4 @@
export * from "./name";
export * from "./password";
export * from "./email";
export * from "./jwt";

81
src/lib/validation/jwt.ts Normal file
View file

@ -0,0 +1,81 @@
import type { JWTConfig } from "#types/config";
import type { validationResult } from "#types/lib";
function isValidSecret(secret: string): boolean {
if (!secret || secret.trim().length === 0) return false;
return secret.length >= 32;
}
function isValidExpiration(expiration: string): boolean {
if (!expiration || expiration.trim().length === 0) return false;
const timeFormatRegex = /^(\d+)([smhdwy])$/;
return timeFormatRegex.test(expiration.toLowerCase());
}
function isValidIssuer(issuer: string): boolean {
if (!issuer || issuer.trim().length === 0) return false;
const issuerRegex = /^[a-zA-Z0-9]([a-zA-Z0-9-_.])*[a-zA-Z0-9]$/;
return issuer.length <= 255 && issuerRegex.test(issuer);
}
function isValidAlgorithm(algorithm: string): boolean {
const supportedAlgorithms = [
"HS256",
"HS384",
"HS512",
"RS256",
"RS384",
"RS512",
"ES256",
"ES384",
"ES512",
"PS256",
"PS384",
"PS512",
];
return supportedAlgorithms.includes(algorithm);
}
function validateJWTConfig(config: JWTConfig): validationResult {
const errors: string[] = [];
if (!isValidSecret(config.secret)) {
errors.push("Invalid JWT secret: Must be at least 32 characters long");
}
const expirationStr =
typeof config.expiration === "number"
? `${config.expiration}s`
: config.expiration;
if (!isValidExpiration(expirationStr)) {
errors.push(
"Invalid JWT expiration: Must be in format like '1h', '30m', '7d', '1y'",
);
}
if (!isValidIssuer(config.issuer)) {
errors.push(
"Invalid JWT issuer: Must be a valid identifier (domain, URL, or app name)",
);
}
if (!isValidAlgorithm(config.algorithm)) {
errors.push(
`Invalid JWT algorithm: ${config.algorithm}. Must be one of: HS256, HS384, HS512, RS256, RS384, RS512, ES256, ES384, ES512, PS256, PS384, PS512`,
);
}
return {
valid: errors.length === 0,
...(errors.length > 0 && { error: errors.join("; ") }),
};
}
export {
isValidSecret,
isValidExpiration,
isValidIssuer,
isValidAlgorithm,
validateJWTConfig,
};

View file

@ -0,0 +1,81 @@
import {
displayNameRestrictions,
forbiddenDisplayNamePatterns,
nameRestrictions,
} from "#environment/constants";
import type { validationResult } from "#types/lib";
function isValidUsername(rawUsername: string): validationResult {
if (typeof rawUsername !== "string") {
return { valid: false, error: "Username must be a string" };
}
const username = rawUsername.trim().normalize("NFC");
if (!username) return { valid: false, error: "Username is required" };
if (username.length < nameRestrictions.length.min)
return { valid: false, error: "Username is too short" };
if (username.length > nameRestrictions.length.max)
return { valid: false, error: "Username is too long" };
if (!nameRestrictions.regex.test(username))
return { valid: false, error: "Username contains invalid characters" };
if (/^[._-]|[._-]$/.test(username))
return {
valid: false,
error: "Username can't start or end with special characters",
};
return { valid: true, username };
}
function isValidDisplayName(rawDisplayName: string): validationResult {
if (typeof rawDisplayName !== "string") {
return { valid: false, error: "Display name must be a string" };
}
const displayName = rawDisplayName.normalize("NFC");
if (!displayName) {
return { valid: false, error: "Display name is required" };
}
if (displayName.length < displayNameRestrictions.length.min) {
return { valid: false, error: "Display name is too short" };
}
if (displayName.length > displayNameRestrictions.length.max) {
return { valid: false, error: "Display name is too long" };
}
for (const pattern of forbiddenDisplayNamePatterns) {
if (pattern.test(displayName)) {
return {
valid: false,
error: "Display name contains invalid characters or patterns",
};
}
}
if (!displayNameRestrictions.regex.test(displayName)) {
return {
valid: false,
error: "Display name contains invalid characters",
};
}
if (displayName.trim().length === 0) {
return {
valid: false,
error: "Display name cannot be only whitespace",
};
}
return { valid: true, name: displayName };
}
export { isValidUsername, isValidDisplayName };

View file

@ -0,0 +1,38 @@
import { passwordRestrictions } from "#environment/constants";
import type { validationResult } from "#types/lib";
function isValidPassword(rawPassword: string): validationResult {
if (typeof rawPassword !== "string") {
return { valid: false, error: "Password must be a string" };
}
if (!rawPassword) {
return { valid: false, error: "Password is required" };
}
if (rawPassword.length < passwordRestrictions.length.min) {
return {
valid: false,
error: `Password must be at least ${passwordRestrictions.length.min} characters`,
};
}
if (rawPassword.length > passwordRestrictions.length.max) {
return {
valid: false,
error: `Password must be at most ${passwordRestrictions.length.max} characters`,
};
}
if (!passwordRestrictions.regex.test(rawPassword)) {
return {
valid: false,
error:
"Password must contain at least one uppercase, one lowercase, one digit, and one special character",
};
}
return { valid: true };
}
export { passwordRestrictions, isValidPassword };

30
src/routes/health.ts Normal file
View file

@ -0,0 +1,30 @@
import { redis } from "bun";
import { cassandra } from "#lib/database";
import type { ExtendedRequest, RouteDef } from "#types/server";
const routeDef: RouteDef = {
method: "GET",
accepts: "*/*",
returns: "application/json",
};
async function handler(request: ExtendedRequest): Promise<Response> {
const cassandraHealth = cassandra.getHealthStatus();
const redisHealth = await redis
.connect()
.then(() => "healthy")
.catch(() => "unhealthy");
return Response.json({
status: "healthy",
timestamp: new Date().toISOString(),
requestTime: `${(performance.now() - request.startPerf).toFixed(2)}ms`,
services: {
cassandra: cassandraHealth,
redis: redisHealth,
},
});
}
export { handler, routeDef };

24
src/routes/index.ts Normal file
View file

@ -0,0 +1,24 @@
import type { ExtendedRequest, RouteDef } from "#types/server";
const routeDef: RouteDef = {
method: "GET",
accepts: "*/*",
returns: "application/json",
};
async function handler(request: ExtendedRequest): Promise<Response> {
const endPerf: number = Date.now();
const perf: number = endPerf - request.startPerf;
const { query, params } = request;
const response: Record<string, unknown> = {
perf,
query,
params,
};
return Response.json(response);
}
export { handler, routeDef };

163
src/routes/user/[id].ts Normal file
View file

@ -0,0 +1,163 @@
import { echo } from "@atums/echo";
import { sessionManager } from "#lib/auth";
import { cassandra } from "#lib/database";
import type {
ExtendedRequest,
RouteDef,
UserInfoResponse,
UserResponse,
UserRow,
} from "#types/server";
const routeDef: RouteDef = {
method: "GET",
accepts: "*/*",
returns: "application/json",
};
async function handler(request: ExtendedRequest): Promise<Response> {
try {
const { id: identifier } = request.params;
const session = await sessionManager.getSession(request);
let userQuery: string;
let queryParams: string[];
let targetUser: UserRow | null = null;
if (!identifier) {
if (!session) {
const response: UserInfoResponse = {
code: 401,
success: false,
error: "Not authenticated",
};
return Response.json(response, { status: 401 });
}
userQuery = `
SELECT id, username, display_name, email, is_verified, created_at, updated_at
FROM users WHERE id = ? LIMIT 1
`;
queryParams = [session.id];
} else {
const isLikelyId = identifier.startsWith("user_");
if (isLikelyId) {
userQuery = `
SELECT id, username, display_name, email, is_verified, created_at, updated_at
FROM users WHERE id = ? LIMIT 1
`;
queryParams = [identifier];
} else {
userQuery = `
SELECT id, username, display_name, email, is_verified, created_at, updated_at
FROM users WHERE username = ? LIMIT 1
`;
queryParams = [identifier];
}
}
const userResult = (await cassandra.execute(userQuery, queryParams)) as {
rows: UserRow[];
};
if (!userResult?.rows || !Array.isArray(userResult.rows)) {
const response: UserInfoResponse = {
code: 500,
success: false,
error: "Database query failed",
};
return Response.json(response, { status: 500 });
}
if (userResult.rows.length === 0) {
if (identifier?.startsWith("user_")) {
const usernameQuery = `
SELECT id, username, display_name, email, is_verified, created_at, updated_at
FROM users WHERE username = ? LIMIT 1
`;
const usernameResult = (await cassandra.execute(usernameQuery, [
identifier,
])) as {
rows: UserRow[];
};
if (usernameResult.rows.length > 0) {
targetUser = usernameResult.rows[0] || null;
}
}
if (!targetUser) {
const response: UserInfoResponse = {
code: 404,
success: false,
error: identifier ? "User not found" : "User not found",
};
return Response.json(response, { status: 404 });
}
} else {
targetUser = userResult.rows[0] || null;
}
if (!targetUser) {
const response: UserInfoResponse = {
code: 404,
success: false,
error: "User not found",
};
return Response.json(response, { status: 404 });
}
const isOwnProfile = session?.id === targetUser.id;
let responseUser: UserResponse;
if (isOwnProfile) {
responseUser = {
id: targetUser.id,
username: targetUser.username,
displayName: targetUser.display_name,
email: targetUser.email,
isVerified: targetUser.is_verified,
createdAt: targetUser.created_at.toISOString(),
};
} else {
responseUser = {
id: targetUser.id,
username: targetUser.username,
displayName: targetUser.display_name,
email: "",
isVerified: targetUser.is_verified,
createdAt: targetUser.created_at.toISOString(),
};
}
const response: UserInfoResponse = {
code: 200,
success: true,
message: isOwnProfile
? "User information retrieved successfully"
: "Public user information retrieved successfully",
user: responseUser,
};
return Response.json(response, { status: 200 });
} catch (error) {
echo.error({
message: "Error retrieving user information",
error,
});
const response: UserInfoResponse = {
code: 500,
success: false,
error: "Internal server error",
};
return Response.json(response, { status: 500 });
}
}
export { handler, routeDef };

178
src/routes/user/login.ts Normal file
View file

@ -0,0 +1,178 @@
import { echo } from "@atums/echo";
import { sessionManager } from "#lib/auth";
import { cassandra } from "#lib/database";
import { isValidEmail, isValidUsername } from "#lib/validation";
import type {
ExtendedRequest,
LoginRequest,
LoginResponse,
RouteDef,
UserRow,
} from "#types/server";
const routeDef: RouteDef = {
method: "POST",
accepts: "application/json",
returns: "application/json",
needsBody: "json",
};
async function handler(
request: ExtendedRequest,
requestBody: unknown,
): Promise<Response> {
try {
const { identifier, password } = requestBody as LoginRequest;
const { force } = request.query;
if (force !== "true" && force !== "1") {
const existingSession = await sessionManager.getSession(request);
if (existingSession) {
const response: LoginResponse = {
code: 409,
success: false,
error: "User already logged in",
};
return Response.json(response, { status: 409 });
}
}
if (!identifier || !password) {
const response: LoginResponse = {
code: 400,
success: false,
error:
"Missing required fields: identifier (username or email), password",
};
return Response.json(response, { status: 400 });
}
const isEmail = isValidEmail(identifier).valid;
const isUsername = isValidUsername(identifier).valid;
if (!isEmail && !isUsername) {
const response: LoginResponse = {
code: 400,
success: false,
error: "Invalid identifier format - must be a valid username or email",
};
return Response.json(response, { status: 400 });
}
let userQuery: string;
let queryParams: string[];
if (isEmail) {
userQuery = `
SELECT id, username, display_name, email, password, is_verified, created_at, updated_at
FROM users WHERE email = ? LIMIT 1
`;
queryParams = [identifier.trim().toLowerCase()];
} else {
userQuery = `
SELECT id, username, display_name, email, password, is_verified, created_at, updated_at
FROM users WHERE username = ? LIMIT 1
`;
queryParams = [identifier.trim()];
}
const userResult = (await cassandra.execute(userQuery, queryParams)) as {
rows: UserRow[];
};
if (!userResult?.rows || !Array.isArray(userResult.rows)) {
const response: LoginResponse = {
code: 500,
success: false,
error: "Database query failed",
};
return Response.json(response, { status: 500 });
}
if (userResult.rows.length === 0) {
const response: LoginResponse = {
code: 401,
success: false,
error: "Invalid credentials",
};
return Response.json(response, { status: 401 });
}
const user = userResult.rows[0];
if (!user) {
const response: LoginResponse = {
code: 401,
success: false,
error: "Invalid credentials",
};
return Response.json(response, { status: 401 });
}
const isPasswordValid = await Bun.password.verify(password, user.password);
if (!isPasswordValid) {
const response: LoginResponse = {
code: 401,
success: false,
error: "Invalid credentials",
};
return Response.json(response, { status: 401 });
}
const userAgent = request.headers.get("User-Agent") || "Unknown";
const sessionPayload = {
id: user.id,
username: user.username,
email: user.email,
isVerified: user.is_verified,
displayName: user.display_name,
createdAt: user.created_at.toISOString(),
updatedAt: user.updated_at.toISOString(),
};
const sessionCookie = await sessionManager.createSession(
sessionPayload,
userAgent,
);
const responseUser: LoginResponse["user"] = {
id: user.id,
username: user.username,
displayName: user.display_name,
email: user.email,
isVerified: user.is_verified,
createdAt: user.created_at.toISOString(),
};
const response: LoginResponse = {
code: 200,
success: true,
message: "Login successful",
user: responseUser,
};
return Response.json(response, {
status: 200,
headers: {
"Set-Cookie": sessionCookie,
},
});
} catch (error) {
echo.error({
message: "Error during user login",
error,
});
const response: LoginResponse = {
code: 500,
success: false,
error: "Internal server error",
};
return Response.json(response, { status: 500 });
}
}
export { handler, routeDef };

57
src/routes/user/logout.ts Normal file
View file

@ -0,0 +1,57 @@
import { echo } from "@atums/echo";
import { cookieService, sessionManager } from "#lib/auth";
import type { BaseResponse, ExtendedRequest, RouteDef } from "#types/server";
interface LogoutResponse extends BaseResponse {}
const routeDef: RouteDef = {
method: ["POST", "DELETE"],
accepts: "*/*",
returns: "application/json",
};
async function handler(request: ExtendedRequest): Promise<Response> {
try {
const session = await sessionManager.getSession(request);
if (!session) {
const response: LogoutResponse = {
code: 401,
success: false,
error: "Not authenticated",
};
return Response.json(response, { status: 401 });
}
await sessionManager.invalidateSession(request);
const clearCookie = cookieService.clearCookie();
const response: LogoutResponse = {
code: 200,
success: true,
message: "Logged out successfully",
};
return Response.json(response, {
status: 200,
headers: {
"Set-Cookie": clearCookie,
},
});
} catch (error) {
echo.error({
message: "Error during user logout",
error,
});
const response: LogoutResponse = {
code: 500,
success: false,
error: "Internal server error",
};
return Response.json(response, { status: 500 });
}
}
export { handler, routeDef };

169
src/routes/user/register.ts Normal file
View file

@ -0,0 +1,169 @@
import { cassandra } from "#lib/database";
import { pika } from "#lib/utils";
import {
isValidDisplayName,
isValidEmail,
isValidPassword,
isValidUsername,
} from "#lib/validation";
import type {
ExtendedRequest,
RegisterRequest,
RegisterResponse,
RouteDef,
} from "#types/server";
const routeDef: RouteDef = {
method: "POST",
accepts: "application/json",
returns: "application/json",
needsBody: "json",
};
async function handler(
_request: ExtendedRequest,
requestBody: unknown,
): Promise<Response> {
try {
const { username, displayName, email, password } =
requestBody as RegisterRequest;
if (!username || !email || !password) {
const response: RegisterResponse = {
code: 400,
success: false,
error: "Missing required fields: username, email, password",
};
return Response.json(response, { status: 400 });
}
const usernameValidation = isValidUsername(username);
if (!usernameValidation.valid || !usernameValidation.username) {
const response: RegisterResponse = {
code: 400,
success: false,
error: usernameValidation.error || "Invalid username",
};
return Response.json(response, { status: 400 });
}
let validatedDisplayName: string | null = null;
if (displayName?.trim()) {
const displayNameValidation = isValidDisplayName(displayName);
if (!displayNameValidation.valid) {
const response: RegisterResponse = {
code: 400,
success: false,
error: displayNameValidation.error || "Invalid display name",
};
return Response.json(response, { status: 400 });
}
validatedDisplayName = displayNameValidation.name || null;
}
const emailValidation = isValidEmail(email);
if (!emailValidation.valid) {
const response: RegisterResponse = {
code: 400,
success: false,
error: emailValidation.error || "Invalid email",
};
return Response.json(response, { status: 400 });
}
const passwordValidation = isValidPassword(password);
if (!passwordValidation.valid) {
const response: RegisterResponse = {
code: 400,
success: false,
error: passwordValidation.error || "Invalid password",
};
return Response.json(response, { status: 400 });
}
const existingUsernameQuery =
"SELECT id FROM users WHERE username = ? LIMIT 1";
const existingUsernameResult = (await cassandra.execute(
existingUsernameQuery,
[usernameValidation.username],
)) as { rows: Array<{ id: string }> };
if (existingUsernameResult.rows.length > 0) {
const response: RegisterResponse = {
code: 409,
success: false,
error: "Username already exists",
};
return Response.json(response, { status: 409 });
}
const existingEmailQuery = "SELECT id FROM users WHERE email = ? LIMIT 1";
const existingEmailResult = (await cassandra.execute(existingEmailQuery, [
email.trim().toLowerCase(),
])) as { rows: Array<{ id: string }> };
if (existingEmailResult.rows.length > 0) {
const response: RegisterResponse = {
code: 409,
success: false,
error: "Email already exists",
};
return Response.json(response, { status: 409 });
}
const userId = pika.gen("user");
const hashedPassword = await Bun.password.hash(password, {
algorithm: "argon2id",
memoryCost: 4096,
timeCost: 3,
});
const now = new Date();
const insertUserQuery = `
INSERT INTO users (
id, username, display_name, email, password,
is_verified, created_at, updated_at
) VALUES (?, ?, ?, ?, ?, ?, ?, ?)
`;
await cassandra.execute(insertUserQuery, [
userId,
usernameValidation.username,
validatedDisplayName,
email.trim().toLowerCase(),
hashedPassword,
false,
now,
now,
]);
const responseUser: RegisterResponse["user"] = {
id: userId,
username: usernameValidation.username,
displayName: validatedDisplayName,
email: email.trim().toLowerCase(),
isVerified: false,
createdAt: now.toISOString(),
};
const response: RegisterResponse = {
code: 201,
success: true,
message: "User registered successfully",
user: responseUser,
};
return Response.json(response, { status: 201 });
} catch {
const response: RegisterResponse = {
code: 500,
success: false,
error: "Internal server error",
};
return Response.json(response, { status: 500 });
}
}
export { handler, routeDef };

View file

@ -0,0 +1,321 @@
import { echo } from "@atums/echo";
import { sessionManager } from "#lib/auth";
import { cassandra } from "#lib/database";
import {
isValidDisplayName,
isValidEmail,
isValidUsername,
} from "#lib/validation";
import type {
ExtendedRequest,
RouteDef,
UpdateInfoRequest,
UpdateInfoResponse,
UserResponse,
UserRow,
} from "#types/server";
const routeDef: RouteDef = {
method: ["PUT", "PATCH"],
accepts: "application/json",
returns: "application/json",
needsBody: "json",
};
async function handler(
request: ExtendedRequest,
requestBody: unknown,
): Promise<Response> {
try {
const session = await sessionManager.getSession(request);
if (!session) {
const response: UpdateInfoResponse = {
code: 401,
success: false,
error: "Not authenticated",
};
return Response.json(response, { status: 401 });
}
const { username, displayName, email } = requestBody as UpdateInfoRequest;
if (
username === undefined &&
displayName === undefined &&
email === undefined
) {
const response: UpdateInfoResponse = {
code: 400,
success: false,
error:
"At least one field must be provided (username, displayName, email)",
};
return Response.json(response, { status: 400 });
}
const currentUserQuery = `
SELECT id, username, display_name, email, is_verified, created_at, updated_at
FROM users WHERE id = ? LIMIT 1
`;
const currentUserResult = (await cassandra.execute(currentUserQuery, [
session.id,
])) as { rows: UserRow[] };
if (!currentUserResult?.rows || currentUserResult.rows.length === 0) {
await sessionManager.invalidateSession(request);
const response: UpdateInfoResponse = {
code: 404,
success: false,
error: "User not found",
};
return Response.json(response, { status: 404 });
}
const currentUser = currentUserResult.rows[0];
if (!currentUser) {
const response: UpdateInfoResponse = {
code: 404,
success: false,
error: "User not found",
};
return Response.json(response, { status: 404 });
}
const updates: {
username?: string;
displayName?: string | null;
email?: string;
} = {};
if (username !== undefined) {
const usernameValidation = isValidUsername(username);
if (!usernameValidation.valid || !usernameValidation.username) {
const response: UpdateInfoResponse = {
code: 400,
success: false,
error: usernameValidation.error || "Invalid username",
};
return Response.json(response, { status: 400 });
}
if (usernameValidation.username !== currentUser.username) {
const existingUsernameQuery =
"SELECT id FROM users WHERE username = ? LIMIT 1";
const existingUsernameResult = (await cassandra.execute(
existingUsernameQuery,
[usernameValidation.username],
)) as { rows: Array<{ id: string }> };
if (
existingUsernameResult.rows.length > 0 &&
existingUsernameResult.rows[0]?.id !== session.id
) {
const response: UpdateInfoResponse = {
code: 409,
success: false,
error: "Username already exists",
};
return Response.json(response, { status: 409 });
}
updates.username = usernameValidation.username;
}
}
if (displayName !== undefined) {
if (displayName === null || displayName.trim() === "") {
updates.displayName = null;
} else {
const displayNameValidation = isValidDisplayName(displayName);
if (!displayNameValidation.valid) {
const response: UpdateInfoResponse = {
code: 400,
success: false,
error: displayNameValidation.error || "Invalid display name",
};
return Response.json(response, { status: 400 });
}
updates.displayName = displayNameValidation.name || null;
}
}
if (email !== undefined) {
const emailValidation = isValidEmail(email);
if (!emailValidation.valid) {
const response: UpdateInfoResponse = {
code: 400,
success: false,
error: emailValidation.error || "Invalid email",
};
return Response.json(response, { status: 400 });
}
const normalizedEmail = email.trim().toLowerCase();
if (normalizedEmail !== currentUser.email) {
const existingEmailQuery =
"SELECT id FROM users WHERE email = ? LIMIT 1";
const existingEmailResult = (await cassandra.execute(
existingEmailQuery,
[normalizedEmail],
)) as { rows: Array<{ id: string }> };
if (
existingEmailResult.rows.length > 0 &&
existingEmailResult.rows[0]?.id !== session.id
) {
const response: UpdateInfoResponse = {
code: 409,
success: false,
error: "Email already exists",
};
return Response.json(response, { status: 409 });
}
updates.email = normalizedEmail;
}
}
if (Object.keys(updates).length === 0) {
const response: UpdateInfoResponse = {
code: 200,
success: true,
message: "No changes required",
user: {
id: currentUser.id,
username: currentUser.username,
displayName: currentUser.display_name,
email: currentUser.email,
isVerified: currentUser.is_verified,
createdAt: currentUser.created_at.toISOString(),
},
};
return Response.json(response, { status: 200 });
}
const updateFields: string[] = [];
const updateValues: unknown[] = [];
if (updates.username !== undefined) {
updateFields.push("username = ?");
updateValues.push(updates.username);
}
if (updates.displayName !== undefined) {
updateFields.push("display_name = ?");
updateValues.push(updates.displayName);
}
if (updates.email !== undefined) {
updateFields.push("email = ?");
updateValues.push(updates.email);
updateFields.push("is_verified = ?");
updateValues.push(false);
}
updateFields.push("updated_at = ?");
updateValues.push(new Date());
updateValues.push(session.id);
const updateQuery = `
UPDATE users
SET ${updateFields.join(", ")}
WHERE id = ?
`;
await cassandra.execute(updateQuery, updateValues);
const updatedUserResult = (await cassandra.execute(currentUserQuery, [
session.id,
])) as { rows: UserRow[] };
const updatedUser = updatedUserResult.rows[0];
if (!updatedUser) {
const response: UpdateInfoResponse = {
code: 500,
success: false,
error: "Failed to fetch updated user data",
};
return Response.json(response, { status: 500 });
}
if (Object.keys(updates).length > 0) {
const userAgent = request.headers.get("User-Agent") || "Unknown";
const updatedSessionPayload = {
id: updatedUser.id,
username: updatedUser.username,
email: updatedUser.email,
isVerified: updatedUser.is_verified,
displayName: updatedUser.display_name,
createdAt: updatedUser.created_at.toISOString(),
updatedAt: updatedUser.updated_at.toISOString(),
};
const sessionCookie = await sessionManager.updateSession(
request,
updatedSessionPayload,
userAgent,
);
const responseUser: UserResponse = {
id: updatedUser.id,
username: updatedUser.username,
displayName: updatedUser.display_name,
email: updatedUser.email,
isVerified: updatedUser.is_verified,
createdAt: updatedUser.created_at.toISOString(),
};
const response: UpdateInfoResponse = {
code: 200,
success: true,
message: "User information updated successfully",
user: responseUser,
};
return Response.json(response, {
status: 200,
headers: {
"Set-Cookie": sessionCookie,
},
});
}
const responseUser: UserResponse = {
id: updatedUser.id,
username: updatedUser.username,
displayName: updatedUser.display_name,
email: updatedUser.email,
isVerified: updatedUser.is_verified,
createdAt: updatedUser.created_at.toISOString(),
};
const response: UpdateInfoResponse = {
code: 200,
success: true,
message: "User information updated successfully",
user: responseUser,
};
return Response.json(response, { status: 200 });
} catch (error) {
echo.error({
message: "Error updating user information",
error,
});
const response: UpdateInfoResponse = {
code: 500,
success: false,
error: "Internal server error",
};
return Response.json(response, { status: 500 });
}
}
export { handler, routeDef };

View file

@ -0,0 +1,215 @@
import { echo } from "@atums/echo";
import { sessionManager } from "#lib/auth";
import { cassandra } from "#lib/database";
import { isValidPassword } from "#lib/validation";
import type {
ExtendedRequest,
RouteDef,
UpdatePasswordRequest,
UpdatePasswordResponse,
UserRow,
} from "#types/server";
const routeDef: RouteDef = {
method: ["PUT", "PATCH"],
accepts: "application/json",
returns: "application/json",
needsBody: "json",
};
async function handler(
request: ExtendedRequest,
requestBody: unknown,
): Promise<Response> {
try {
const session = await sessionManager.getSession(request);
if (!session) {
const response: UpdatePasswordResponse = {
code: 401,
success: false,
error: "Not authenticated",
};
return Response.json(response, { status: 401 });
}
const { currentPassword, newPassword, logoutAllSessions } =
requestBody as UpdatePasswordRequest;
if (!currentPassword || !newPassword) {
const response: UpdatePasswordResponse = {
code: 400,
success: false,
error: "Both currentPassword and newPassword are required",
};
return Response.json(response, { status: 400 });
}
const passwordValidation = isValidPassword(newPassword);
if (!passwordValidation.valid) {
const response: UpdatePasswordResponse = {
code: 400,
success: false,
error: passwordValidation.error || "Invalid new password",
};
return Response.json(response, { status: 400 });
}
if (currentPassword === newPassword) {
const response: UpdatePasswordResponse = {
code: 400,
success: false,
error: "New password must be different from current password",
};
return Response.json(response, { status: 400 });
}
const userQuery = `
SELECT id, username, email, password, is_verified, created_at, updated_at
FROM users WHERE id = ? LIMIT 1
`;
const userResult = (await cassandra.execute(userQuery, [session.id])) as {
rows: UserRow[];
};
if (!userResult?.rows || userResult.rows.length === 0) {
await sessionManager.invalidateSession(request);
const response: UpdatePasswordResponse = {
code: 404,
success: false,
error: "User not found",
};
return Response.json(response, { status: 404 });
}
const user = userResult.rows[0];
if (!user) {
const response: UpdatePasswordResponse = {
code: 404,
success: false,
error: "User not found",
};
return Response.json(response, { status: 404 });
}
const isCurrentPasswordValid = await Bun.password.verify(
currentPassword,
user.password,
);
if (!isCurrentPasswordValid) {
const response: UpdatePasswordResponse = {
code: 401,
success: false,
error: "Current password is incorrect",
};
return Response.json(response, { status: 401 });
}
const hashedNewPassword = await Bun.password.hash(newPassword, {
algorithm: "argon2id",
memoryCost: 4096,
timeCost: 3,
});
const updateQuery = `
UPDATE users
SET password = ?, updated_at = ?
WHERE id = ?
`;
await cassandra.execute(updateQuery, [
hashedNewPassword,
new Date(),
session.id,
]);
if (logoutAllSessions === true) {
const invalidatedCount =
await sessionManager.invalidateAllSessionsForUser(session.id);
const response: UpdatePasswordResponse = {
code: 200,
success: true,
message: `Password updated successfully. Logged out from ${invalidatedCount} sessions.`,
loggedOutSessions: invalidatedCount,
};
return Response.json(response, {
status: 200,
headers: {
"Content-Type": "application/json",
"Set-Cookie": "session=; Path=/; Max-Age=0; HttpOnly",
},
});
}
const allSessions = await sessionManager.getActiveSessionsForUser(
session.id,
);
const currentToken = request.headers
.get("Cookie")
?.match(/session=([^;]+)/)?.[1];
let invalidatedCount = 0;
if (currentToken) {
for (const token of allSessions) {
if (token !== currentToken) {
await sessionManager.invalidateSessionByToken(token);
invalidatedCount++;
}
}
}
const userAgent = request.headers.get("User-Agent") || "Unknown";
const updatedSessionPayload = {
id: user.id,
username: user.username,
email: user.email,
isVerified: user.is_verified,
displayName: user.display_name,
createdAt: user.created_at.toISOString(),
updatedAt: new Date().toISOString(),
};
const sessionCookie = await sessionManager.updateSession(
request,
updatedSessionPayload,
userAgent,
);
const response: UpdatePasswordResponse = {
code: 200,
success: true,
message:
invalidatedCount > 0
? `Password updated successfully. Logged out from ${invalidatedCount} other sessions.`
: "Password updated successfully.",
loggedOutSessions: invalidatedCount,
};
return Response.json(response, {
status: 200,
headers: {
"Content-Type": "application/json",
"Set-Cookie": sessionCookie,
},
});
} catch (error) {
echo.error({
message: "Error updating user password",
error,
});
const response: UpdatePasswordResponse = {
code: 500,
success: false,
error: "Internal server error",
};
return Response.json(response, { status: 500 });
}
}
export { handler, routeDef };

302
src/server.ts Normal file
View file

@ -0,0 +1,302 @@
import { resolve } from "node:path";
import { type Echo, echo } from "@atums/echo";
import { environment } from "#environment/config";
import { reqLoggerIgnores } from "#environment/constants/server";
import { noFileLog } from "#index";
import { webSocketHandler } from "#websocket";
import {
type BunFile,
FileSystemRouter,
type MatchedRoute,
type Server,
} from "bun";
import type { ExtendedRequest, RouteModule } from "#types/server";
class ServerHandler {
private router: FileSystemRouter;
constructor(
private port: number,
private host: string,
) {
this.router = new FileSystemRouter({
style: "nextjs",
dir: resolve("src", "routes"),
fileExtensions: [".ts"],
origin: `http://${this.host}:${this.port}`,
});
}
public initialize(): void {
const server: Server = Bun.serve({
port: this.port,
hostname: this.host,
fetch: this.handleRequest.bind(this),
websocket: {
open: webSocketHandler.handleOpen.bind(webSocketHandler),
message: webSocketHandler.handleMessage.bind(webSocketHandler),
close: webSocketHandler.handleClose.bind(webSocketHandler),
},
});
noFileLog.info(
`Server running at http://${server.hostname}:${server.port}`,
);
this.logRoutes(noFileLog);
}
private logRoutes(echo: Echo): void {
echo.info("Available routes:");
const sortedRoutes: [string, string][] = Object.entries(
this.router.routes,
).sort(([pathA]: [string, string], [pathB]: [string, string]) =>
pathA.localeCompare(pathB),
);
for (const [path, filePath] of sortedRoutes) {
echo.info(`Route: ${path}, File: ${filePath}`);
}
}
private async serveStaticFile(
request: ExtendedRequest,
pathname: string,
ip: string,
): Promise<Response> {
let filePath: string;
let response: Response;
try {
filePath = resolve(`.${pathname}`);
const file: BunFile = Bun.file(filePath);
if (await file.exists()) {
const fileContent: ArrayBuffer = await file.arrayBuffer();
const contentType: string = file.type ?? "application/octet-stream";
response = new Response(fileContent, {
headers: { "Content-Type": contentType },
});
} else {
echo.warn(`File not found: ${filePath}`);
response = new Response("Not Found", { status: 404 });
}
} catch (error) {
echo.error({
message: `Error serving static file: ${pathname}`,
error: error as Error,
});
response = new Response("Internal Server Error", { status: 500 });
}
this.logRequest(request, response, ip);
return response;
}
private logRequest(
request: ExtendedRequest,
response: Response,
ip: string | undefined,
): void {
const pathname = new URL(request.url).pathname;
const { ignoredStartsWith, ignoredPaths } = reqLoggerIgnores;
if (
ignoredStartsWith.some((prefix) => pathname.startsWith(prefix)) ||
ignoredPaths.includes(pathname)
) {
return;
}
echo.custom(`${request.method}`, `${response.status}`, [
pathname,
`${(performance.now() - request.startPerf).toFixed(2)}ms`,
ip || "unknown",
]);
}
private async handleRequest(
request: Request,
server: Server,
): Promise<Response> {
const extendedRequest: ExtendedRequest = request as ExtendedRequest;
extendedRequest.startPerf = performance.now();
const headers = request.headers;
let ip = server.requestIP(request)?.address;
let response: Response;
if (!ip || ip.startsWith("172.") || ip === "127.0.0.1") {
ip =
headers.get("CF-Connecting-IP")?.trim() ||
headers.get("X-Real-IP")?.trim() ||
headers.get("X-Forwarded-For")?.split(",")[0]?.trim() ||
"unknown";
}
const pathname: string = new URL(request.url).pathname;
const baseDir = resolve("custom");
const customPath = resolve(baseDir, pathname.slice(1));
if (!customPath.startsWith(baseDir)) {
response = new Response("Forbidden", { status: 403 });
this.logRequest(extendedRequest, response, ip);
return response;
}
const customFile = Bun.file(customPath);
if (await customFile.exists()) {
const content = await customFile.arrayBuffer();
const type: string = customFile.type ?? "application/octet-stream";
response = new Response(content, {
headers: { "Content-Type": type },
});
this.logRequest(extendedRequest, response, ip);
return response;
}
if (pathname.startsWith("/public")) {
return await this.serveStaticFile(extendedRequest, pathname, ip);
}
const match: MatchedRoute | null = this.router.match(request);
let requestBody: unknown = {};
if (match) {
const { filePath, params, query } = match;
try {
const routeModule: RouteModule = await import(filePath);
const contentType: string | null = request.headers.get("Content-Type");
const actualContentType: string | null = contentType
? (contentType.split(";")[0]?.trim() ?? null)
: null;
if (
routeModule.routeDef.needsBody === "json" &&
actualContentType === "application/json"
) {
try {
requestBody = await request.json();
} catch {
requestBody = {};
}
} else if (
routeModule.routeDef.needsBody === "multipart" &&
actualContentType === "multipart/form-data"
) {
try {
requestBody = await request.formData();
} catch {
requestBody = {};
}
}
if (
(Array.isArray(routeModule.routeDef.method) &&
!routeModule.routeDef.method.includes(request.method)) ||
(!Array.isArray(routeModule.routeDef.method) &&
routeModule.routeDef.method !== request.method)
) {
response = Response.json(
{
success: false,
code: 405,
error: `Method ${request.method} Not Allowed, expected ${
Array.isArray(routeModule.routeDef.method)
? routeModule.routeDef.method.join(", ")
: routeModule.routeDef.method
}`,
},
{ status: 405 },
);
} else {
const expectedContentType: string | string[] | null =
routeModule.routeDef.accepts;
let matchesAccepts: boolean;
if (Array.isArray(expectedContentType)) {
matchesAccepts =
expectedContentType.includes("*/*") ||
expectedContentType.includes(actualContentType || "");
} else {
matchesAccepts =
expectedContentType === "*/*" ||
actualContentType === expectedContentType;
}
if (!matchesAccepts) {
response = Response.json(
{
success: false,
code: 406,
error: `Content-Type ${actualContentType} Not Acceptable, expected ${
Array.isArray(expectedContentType)
? expectedContentType.join(", ")
: expectedContentType
}`,
},
{ status: 406 },
);
} else {
extendedRequest.params = params;
extendedRequest.query = query;
response = await routeModule.handler(
extendedRequest,
requestBody,
server,
);
if (routeModule.routeDef.returns !== "*/*") {
response.headers.set(
"Content-Type",
routeModule.routeDef.returns,
);
}
}
}
} catch (error: unknown) {
echo.error({
message: `Error handling route ${request.url}`,
error: error,
});
response = Response.json(
{
success: false,
code: 500,
error: "Internal Server Error",
},
{ status: 500 },
);
}
} else {
response = Response.json(
{
success: false,
code: 404,
error: "Not Found",
},
{ status: 404 },
);
}
this.logRequest(extendedRequest, response, ip);
return response;
}
}
const serverHandler: ServerHandler = new ServerHandler(
environment.port,
environment.host,
);
export { serverHandler };

30
src/websocket.ts Normal file
View file

@ -0,0 +1,30 @@
import { echo } from "@atums/echo";
import type { ServerWebSocket } from "bun";
class WebSocketHandler {
public handleMessage(ws: ServerWebSocket, message: string): void {
echo.info(`WebSocket received: ${message}`);
try {
ws.send(`You said: ${message}`);
} catch (error) {
echo.error({ message: "WebSocket send error", error });
}
}
public handleOpen(ws: ServerWebSocket): void {
echo.info("WebSocket connection opened.");
try {
ws.send("Welcome to the WebSocket server!");
} catch (error) {
echo.error({ message: "WebSocket send error", error });
}
}
public handleClose(_ws: ServerWebSocket, code: number, reason: string): void {
echo.warn(`WebSocket closed with code ${code}, reason: ${reason}`);
}
}
const webSocketHandler: WebSocketHandler = new WebSocketHandler();
export { webSocketHandler, WebSocketHandler };