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

12
.editorconfig Normal file
View file

@ -0,0 +1,12 @@
# EditorConfig is awesome: https://EditorConfig.org
# top-most EditorConfig file
root = true
[*]
indent_style = tab
indent_size = 4
end_of_line = lf
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true

View file

@ -0,0 +1,24 @@
name: Code quality checks
on:
push:
pull_request:
jobs:
biome:
runs-on: docker
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Install Bun
run: |
curl -fsSL https://bun.sh/install | bash
export BUN_INSTALL="$HOME/.bun"
echo "$BUN_INSTALL/bin" >> $GITHUB_PATH
- name: Install Dependencies
run: bun install
- name: Run Biome with verbose output
run: bunx biome ci . --verbose

1
.gitattributes vendored Normal file
View file

@ -0,0 +1 @@
* text=auto eol=lf

4
.gitignore vendored Normal file
View file

@ -0,0 +1,4 @@
/node_modules
logs
/custom
.env

28
LICENSE Normal file
View file

@ -0,0 +1,28 @@
BSD 3-Clause License
Copyright (c) 2025, creations.works
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
3. Neither the name of the copyright holder nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

1
README.md Normal file
View file

@ -0,0 +1 @@
# void.backend

54
biome.json Normal file
View file

@ -0,0 +1,54 @@
{
"$schema": "https://biomejs.dev/schemas/1.9.4/schema.json",
"vcs": {
"enabled": true,
"clientKind": "git",
"useIgnoreFile": false
},
"files": {
"ignoreUnknown": true,
"ignore": ["dist"]
},
"formatter": {
"enabled": true,
"indentStyle": "tab",
"lineEnding": "lf"
},
"organizeImports": {
"enabled": true
},
"css": {
"formatter": {
"indentStyle": "tab",
"lineEnding": "lf"
}
},
"linter": {
"enabled": true,
"rules": {
"recommended": true,
"correctness": {
"noUnusedImports": "error",
"noUnusedVariables": "error"
},
"suspicious": {
"noConsole": "error"
},
"style": {
"useConst": "error",
"noVar": "error",
"useImportType": "error"
}
},
"ignore": ["types"]
},
"javascript": {
"formatter": {
"quoteStyle": "double",
"indentStyle": "tab",
"lineEnding": "lf",
"jsxQuoteStyle": "double",
"semicolons": "always"
}
}
}

84
bun.lock Normal file
View file

@ -0,0 +1,84 @@
{
"lockfileVersion": 1,
"workspaces": {
"": {
"name": "void.backend",
"dependencies": {
"@atums/echo": "latest",
"cassandra-driver": "latest",
"fast-jwt": "latest",
"pika-id": "latest",
},
"devDependencies": {
"@biomejs/biome": "latest",
"@types/bun": "latest",
},
},
},
"trustedDependencies": [
"@biomejs/biome",
],
"packages": {
"@atums/echo": ["@atums/echo@1.0.3", "", { "dependencies": { "date-fns-tz": "^3.2.0" } }, "sha512-WQ2d4oWTaE+6VeLIu2FepmZipdwUrM+SiiO5moHhSsP4P+MaQCjq5qp34nwB/vOHv2jd9UcBzy27iUziTffCjg=="],
"@biomejs/biome": ["@biomejs/biome@1.9.4", "", { "optionalDependencies": { "@biomejs/cli-darwin-arm64": "1.9.4", "@biomejs/cli-darwin-x64": "1.9.4", "@biomejs/cli-linux-arm64": "1.9.4", "@biomejs/cli-linux-arm64-musl": "1.9.4", "@biomejs/cli-linux-x64": "1.9.4", "@biomejs/cli-linux-x64-musl": "1.9.4", "@biomejs/cli-win32-arm64": "1.9.4", "@biomejs/cli-win32-x64": "1.9.4" }, "bin": { "biome": "bin/biome" } }, "sha512-1rkd7G70+o9KkTn5KLmDYXihGoTaIGO9PIIN2ZB7UJxFrWw04CZHPYiMRjYsaDvVV7hP1dYNRLxSANLaBFGpog=="],
"@biomejs/cli-darwin-arm64": ["@biomejs/cli-darwin-arm64@1.9.4", "", { "os": "darwin", "cpu": "arm64" }, "sha512-bFBsPWrNvkdKrNCYeAp+xo2HecOGPAy9WyNyB/jKnnedgzl4W4Hb9ZMzYNbf8dMCGmUdSavlYHiR01QaYR58cw=="],
"@biomejs/cli-darwin-x64": ["@biomejs/cli-darwin-x64@1.9.4", "", { "os": "darwin", "cpu": "x64" }, "sha512-ngYBh/+bEedqkSevPVhLP4QfVPCpb+4BBe2p7Xs32dBgs7rh9nY2AIYUL6BgLw1JVXV8GlpKmb/hNiuIxfPfZg=="],
"@biomejs/cli-linux-arm64": ["@biomejs/cli-linux-arm64@1.9.4", "", { "os": "linux", "cpu": "arm64" }, "sha512-fJIW0+LYujdjUgJJuwesP4EjIBl/N/TcOX3IvIHJQNsAqvV2CHIogsmA94BPG6jZATS4Hi+xv4SkBBQSt1N4/g=="],
"@biomejs/cli-linux-arm64-musl": ["@biomejs/cli-linux-arm64-musl@1.9.4", "", { "os": "linux", "cpu": "arm64" }, "sha512-v665Ct9WCRjGa8+kTr0CzApU0+XXtRgwmzIf1SeKSGAv+2scAlW6JR5PMFo6FzqqZ64Po79cKODKf3/AAmECqA=="],
"@biomejs/cli-linux-x64": ["@biomejs/cli-linux-x64@1.9.4", "", { "os": "linux", "cpu": "x64" }, "sha512-lRCJv/Vi3Vlwmbd6K+oQ0KhLHMAysN8lXoCI7XeHlxaajk06u7G+UsFSO01NAs5iYuWKmVZjmiOzJ0OJmGsMwg=="],
"@biomejs/cli-linux-x64-musl": ["@biomejs/cli-linux-x64-musl@1.9.4", "", { "os": "linux", "cpu": "x64" }, "sha512-gEhi/jSBhZ2m6wjV530Yy8+fNqG8PAinM3oV7CyO+6c3CEh16Eizm21uHVsyVBEB6RIM8JHIl6AGYCv6Q6Q9Tg=="],
"@biomejs/cli-win32-arm64": ["@biomejs/cli-win32-arm64@1.9.4", "", { "os": "win32", "cpu": "arm64" }, "sha512-tlbhLk+WXZmgwoIKwHIHEBZUwxml7bRJgk0X2sPyNR3S93cdRq6XulAZRQJ17FYGGzWne0fgrXBKpl7l4M87Hg=="],
"@biomejs/cli-win32-x64": ["@biomejs/cli-win32-x64@1.9.4", "", { "os": "win32", "cpu": "x64" }, "sha512-8Y5wMhVIPaWe6jw2H+KlEm4wP/f7EW3810ZLmDlrEEy5KvBsb9ECEfu/kMWD484ijfQ8+nIi0giMgu9g1UAuuA=="],
"@lukeed/ms": ["@lukeed/ms@2.0.2", "", {}, "sha512-9I2Zn6+NJLfaGoz9jN3lpwDgAYvfGeNYdbAIjJOqzs4Tpc+VU3Jqq4IofSUBKajiDS8k9fZIg18/z13mpk1bsA=="],
"@types/bun": ["@types/bun@1.2.15", "", { "dependencies": { "bun-types": "1.2.15" } }, "sha512-U1ljPdBEphF0nw1MIk0hI7kPg7dFdPyM7EenHsp6W5loNHl7zqy6JQf/RKCgnUn2KDzUpkBwHPnEJEjII594bA=="],
"@types/node": ["@types/node@18.19.111", "", { "dependencies": { "undici-types": "~5.26.4" } }, "sha512-90sGdgA+QLJr1F9X79tQuEut0gEYIfkX9pydI4XGRgvFo9g2JWswefI+WUSUHPYVBHYSEfTEqBxA5hQvAZB3Mw=="],
"adm-zip": ["adm-zip@0.5.16", "", {}, "sha512-TGw5yVi4saajsSEgz25grObGHEUaDrniwvA2qwSC060KfqGPdglhvPMA2lPIoxs3PQIItj2iag35fONcQqgUaQ=="],
"asn1.js": ["asn1.js@5.4.1", "", { "dependencies": { "bn.js": "^4.0.0", "inherits": "^2.0.1", "minimalistic-assert": "^1.0.0", "safer-buffer": "^2.1.0" } }, "sha512-+I//4cYPccV8LdmBLiX8CYvf9Sp3vQsrqu2QNXRcrbiWvcx/UdlFiqUJJzxRQxgsZmvhXhn4cSKeSmoFjVdupA=="],
"bn.js": ["bn.js@4.12.2", "", {}, "sha512-n4DSx829VRTRByMRGdjQ9iqsN0Bh4OolPsFnaZBLcbi8iXcB+kJ9s7EnRt4wILZNV3kPLHkRVfOc/HvhC3ovDw=="],
"bun-types": ["bun-types@1.2.15", "", { "dependencies": { "@types/node": "*" } }, "sha512-NarRIaS+iOaQU1JPfyKhZm4AsUOrwUOqRNHY0XxI8GI8jYxiLXLcdjYMG9UKS+fwWasc1uw1htV9AX24dD+p4w=="],
"cassandra-driver": ["cassandra-driver@4.8.0", "", { "dependencies": { "@types/node": "^18.11.18", "adm-zip": "~0.5.10", "long": "~5.2.3" } }, "sha512-HritfMGq9V7SuESeSodHvArs0mLuMk7uh+7hQK2lqdvXrvm50aWxb4RPxkK3mPDdsgHjJ427xNRFITMH2ei+Sw=="],
"date-fns": ["date-fns@4.1.0", "", {}, "sha512-Ukq0owbQXxa/U3EGtsdVBkR1w7KOQ5gIBqdH2hkvknzZPYvBxb/aa6E8L7tmjFtkwZBu3UXBbjIgPo/Ez4xaNg=="],
"date-fns-tz": ["date-fns-tz@3.2.0", "", { "peerDependencies": { "date-fns": "^3.0.0 || ^4.0.0" } }, "sha512-sg8HqoTEulcbbbVXeg84u5UnlsQa8GS5QXMqjjYIhS4abEVVKIUwe0/l/UhrZdKaL/W5eWZNlbTeEIiOXTcsBQ=="],
"ecdsa-sig-formatter": ["ecdsa-sig-formatter@1.0.11", "", { "dependencies": { "safe-buffer": "^5.0.1" } }, "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ=="],
"fast-jwt": ["fast-jwt@6.0.2", "", { "dependencies": { "@lukeed/ms": "^2.0.2", "asn1.js": "^5.4.1", "ecdsa-sig-formatter": "^1.0.11", "mnemonist": "^0.40.0" } }, "sha512-dTF4bhYnuXhZYQUaxsHKqAyA5y/L/kQc4fUu0wQ0BSA0dMfcNrcv0aqR2YnVi4f7e1OnzDVU7sDsNdzl1O5EVA=="],
"inherits": ["inherits@2.0.4", "", {}, "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="],
"long": ["long@5.2.5", "", {}, "sha512-e0r9YBBgNCq1D1o5Dp8FMH0N5hsFtXDBiVa0qoJPHpakvZkmDKPRoGffZJII/XsHvj9An9blm+cRJ01yQqU+Dw=="],
"minimalistic-assert": ["minimalistic-assert@1.0.1", "", {}, "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A=="],
"mnemonist": ["mnemonist@0.40.3", "", { "dependencies": { "obliterator": "^2.0.4" } }, "sha512-Vjyr90sJ23CKKH/qPAgUKicw/v6pRoamxIEDFOF8uSgFME7DqPRpHgRTejWVjkdGg5dXj0/NyxZHZ9bcjH+2uQ=="],
"obliterator": ["obliterator@2.0.5", "", {}, "sha512-42CPE9AhahZRsMNslczq0ctAEtqk8Eka26QofnqC346BZdHDySk3LWka23LI7ULIw11NmltpiLagIq8gBozxTw=="],
"pika-id": ["pika-id@1.1.3", "", {}, "sha512-+82ue4qBu3GipX0ulJOd7lBlNccJuXnt6zquhF6ekk4WiIO98fV54fkUU3NCienmvKrYu97Cqpk5T3jYOtJRVA=="],
"safe-buffer": ["safe-buffer@5.2.1", "", {}, "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ=="],
"safer-buffer": ["safer-buffer@2.1.2", "", {}, "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="],
"undici-types": ["undici-types@5.26.5", "", {}, "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA=="],
}
}

69
environment/config.ts Normal file
View file

@ -0,0 +1,69 @@
import { echo } from "@atums/echo";
import { validateJWTConfig } from "#lib/validation";
import { cassandraConfig, validateCassandraConfig } from "./database/cassandra";
import { jwt } from "./jwt";
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"),
};
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;
}
if (hasError) {
process.exit(1);
}
}
export { environment, verifyRequiredVariables };

View file

@ -0,0 +1,5 @@
import { resolve } from "node:path";
const migrationsPath = resolve("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);

28
environment/jwt.ts Normal file
View file

@ -0,0 +1,28 @@
import { getExpirationInSeconds } from "#lib/utils";
import { validateJWTConfig } from "#lib/validation";
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,
};
const validation = validateJWTConfig(configForValidation);
if (!validation.valid) {
throw new Error(`JWT Configuration Error: ${validation.error}`);
}
return configForValidation;
}
export const jwt = createJWTConfig();

39
logger.json Normal file
View file

@ -0,0 +1,39 @@
{
"directory": "logs",
"level": "debug",
"disableFile": false,
"rotate": true,
"maxFiles": 3,
"console": true,
"consoleColor": true,
"dateFormat": "yyyy-MM-dd HH:mm:ss.SSS",
"timezone": "local",
"silent": false,
"pattern": "{color:gray}{pretty-timestamp}{reset} {color:levelColor}[{level-name}]{reset} {color:gray}({reset}{file-name}:{color:blue}{line}{reset}:{color:blue}{column}{color:gray}){reset} {data}",
"levelColor": {
"debug": "blue",
"info": "green",
"warn": "yellow",
"error": "red",
"fatal": "red"
},
"customPattern": "{color:gray}{pretty-timestamp}{reset} {color:tagColor}[{tag}]{reset} {color:contextColor}({context}){reset} {data}",
"customColors": {
"GET": "green",
"POST": "blue",
"PUT": "yellow",
"DELETE": "red",
"PATCH": "cyan",
"HEAD": "magenta",
"OPTIONS": "white",
"TRACE": "gray"
},
"prettyPrint": true
}

25
package.json Normal file
View file

@ -0,0 +1,25 @@
{
"name": "void.backend",
"module": "src/index.ts",
"type": "module",
"scripts": {
"start": "bun run src/index.ts",
"dev": "bun run --hot src/index.ts --dev",
"lint": "bunx biome check",
"lint:fix": "bunx biome check --fix",
"cleanup": "rm -rf logs node_modules bun.lock"
},
"devDependencies": {
"@biomejs/biome": "latest",
"@types/bun": "latest"
},
"dependencies": {
"@atums/echo": "latest",
"cassandra-driver": "latest",
"fast-jwt": "latest",
"pika-id": "latest"
},
"trustedDependencies": [
"@biomejs/biome"
]
}

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 };

29
tsconfig.json Normal file
View file

@ -0,0 +1,29 @@
{
"compilerOptions": {
"baseUrl": "./",
"paths": {
"#*": ["src/*"],
"#environment/*": ["environment/*"],
"#types/*": ["types/*"]
},
"typeRoots": ["./node_modules/@types"],
"lib": ["ESNext", "DOM"],
"target": "ESNext",
"module": "ESNext",
"moduleDetection": "force",
"allowJs": false,
"moduleResolution": "bundler",
"allowImportingTsExtensions": false,
"verbatimModuleSyntax": true,
"noEmit": true,
"strict": true,
"skipLibCheck": true,
"noFallthroughCasesInSwitch": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"exactOptionalPropertyTypes": true,
"noUncheckedIndexedAccess": true,
"noPropertyAccessFromIndexSignature": false
},
"include": ["src", "environment"]
}

32
types/config/auth.ts Normal file
View file

@ -0,0 +1,32 @@
type JWTConfig = {
secret: string;
expiration: string | number;
issuer: string;
algorithm: string;
};
interface CookieOptions {
secure?: boolean;
httpOnly?: boolean;
sameSite?: "Strict" | "Lax" | "None";
path?: string;
domain?: string;
}
interface UserSession {
id: string;
username: string;
email: string;
isVerified: boolean;
displayName: string | null;
createdAt: string; // ISO date string
updatedAt?: string; // ISO date string, optional
iat?: number; // issued at (added by JWT libs)
exp?: number; // expiration (added by JWT libs)
}
interface SessionData extends UserSession {
userAgent: string;
}
export type { JWTConfig, CookieOptions, UserSession, SessionData };

25
types/config/database.ts Normal file
View file

@ -0,0 +1,25 @@
type CassandraConfig = {
host: string;
port: number;
keyspace: string;
username: string;
password: string;
datacenter: string;
contactPoints: string[];
authEnabled: boolean;
};
type SqlMigration = {
id: string;
name: string;
upSql: string;
downSql?: string | undefined;
};
type ConnectionOptions = {
withKeyspace?: boolean;
timeout?: number;
logging?: boolean;
};
export type { CassandraConfig, SqlMigration, ConnectionOptions };

View file

@ -0,0 +1,7 @@
type Environment = {
port: number;
host: string;
development: boolean;
};
export type { Environment };

3
types/config/index.ts Normal file
View file

@ -0,0 +1,3 @@
export * from "./environment";
export * from "./database";
export * from "./auth";

3
types/index.ts Normal file
View file

@ -0,0 +1,3 @@
export * from "./server";
export * from "./config";
export * from "./lib";

1
types/lib/index.ts Normal file
View file

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

13
types/lib/validation.ts Normal file
View file

@ -0,0 +1,13 @@
type genericValidation = {
length: { min: number; max: number };
regex: RegExp;
};
type validationResult = {
valid: boolean;
error?: string;
username?: string;
name?: string;
};
export type { genericValidation, validationResult };

3
types/server/index.ts Normal file
View file

@ -0,0 +1,3 @@
export * from "./server";
export * from "./routes";
export * from "./requests";

View file

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

View file

@ -0,0 +1,28 @@
interface BaseResponse {
code: number;
success: boolean;
error?: string;
message?: string;
}
interface UserResponse {
id: string;
username: string;
email: string;
isVerified: boolean;
createdAt: string;
displayName: string | null;
}
interface UserRow {
id: string;
username: string;
display_name: string | null;
email: string;
password: string;
is_verified: boolean;
created_at: Date;
updated_at: Date;
}
export type { BaseResponse, UserResponse, UserRow };

View file

@ -0,0 +1,6 @@
export * from "./base";
export * from "./responses";
export * from "./register";
export * from "./login";
export * from "./update";

View file

@ -0,0 +1,12 @@
import type { BaseResponse, UserResponse } from "./base";
interface LoginRequest {
identifier: string; // Username or email
password: string;
}
interface LoginResponse extends BaseResponse {
user?: UserResponse;
}
export type { LoginRequest, LoginResponse };

View file

@ -0,0 +1,7 @@
interface UpdatePasswordRequest {
currentPassword: string;
newPassword: string;
logoutAllSessions?: boolean; // defaults to false
}
export type { UpdatePasswordRequest };

View file

@ -0,0 +1,14 @@
import type { BaseResponse, UserResponse } from "./base";
interface RegisterRequest {
username: string;
displayName: string | null;
email: string;
password: string;
}
interface RegisterResponse extends BaseResponse {
user?: UserResponse;
}
export type { RegisterRequest, RegisterResponse };

View file

@ -0,0 +1,7 @@
import type { BaseResponse, UserResponse } from "./base";
interface UserInfoResponse extends BaseResponse {
user?: UserResponse;
}
export type { UserInfoResponse };

View file

@ -0,0 +1,2 @@
export * from "./info";
export * from "./password";

View file

@ -0,0 +1,13 @@
import type { BaseResponse, UserResponse } from "../base";
interface UpdateInfoRequest {
username?: string;
displayName?: string | null;
email?: string;
}
interface UpdateInfoResponse extends BaseResponse {
user?: UserResponse;
}
export type { UpdateInfoRequest, UpdateInfoResponse };

View file

@ -0,0 +1,14 @@
import type { BaseResponse, UserResponse } from "../base";
interface UpdatePasswordRequest {
currentPassword: string;
newPassword: string;
logoutAllSessions?: boolean; // defaults to false
}
interface UpdatePasswordResponse extends BaseResponse {
user?: UserResponse;
loggedOutSessions?: number;
}
export type { UpdatePasswordRequest, UpdatePasswordResponse };

20
types/server/routes.ts Normal file
View file

@ -0,0 +1,20 @@
import type { Server } from "bun";
import type { ExtendedRequest } from "./server";
type RouteDef = {
method: string | string[];
accepts: string | null | string[];
returns: string;
needsBody?: "multipart" | "json";
};
type RouteModule = {
handler: (
request: Request | ExtendedRequest,
requestBody: unknown,
server: Server,
) => Promise<Response> | Response;
routeDef: RouteDef;
};
export type { RouteDef, RouteModule };

10
types/server/server.ts Normal file
View file

@ -0,0 +1,10 @@
type Query = Record<string, string>;
type Params = Record<string, string>;
interface ExtendedRequest extends Request {
startPerf: number;
query: Query;
params: Params;
}
export type { ExtendedRequest, Query, Params };