start of the 50th remake of a file host doomed to never be finished

This commit is contained in:
creations 2025-03-02 17:36:45 -05:00
commit 46c05ca3a9
Signed by: creations
GPG key ID: 8F553AA4320FC711
33 changed files with 2155 additions and 0 deletions

View file

@ -0,0 +1,161 @@
import {
isValidEmail,
isValidPassword,
isValidUsername,
} from "@config/sql/users";
import { password as bunPassword, type ReservedSQL, sql } from "bun";
import { logger } from "@/helpers/logger";
import { sessionManager } from "@/helpers/sessions";
const routeDef: RouteDef = {
method: "POST",
accepts: "application/json",
returns: "application/json",
needsBody: "json",
};
async function handler(
request: ExtendedRequest,
requestBody: unknown,
): Promise<Response> {
if (request.session) {
if ((request.session as ApiUserSession).is_api) {
return Response.json(
{
success: false,
code: 403,
error: "You cannot log in while using an authorization token",
},
{ status: 403 },
);
}
return Response.json(
{
success: false,
code: 403,
error: "Already logged in",
},
{ status: 403 },
);
}
const { username, email, password } = requestBody as {
username: string;
email: string;
password: string;
};
if (!password || (!username && !email)) {
return Response.json(
{
success: false,
code: 400,
error: "Expected username or email, and password",
},
{ status: 400 },
);
}
const errors: string[] = [];
const validations: UserValidation[] = [
{ check: isValidUsername(username), field: "Username" },
{ check: isValidEmail(email), field: "Email" },
{ check: isValidPassword(password), field: "Password" },
];
validations.forEach(({ check }: UserValidation): void => {
if (!check.valid && check.error) {
errors.push(check.error);
}
});
if (errors.length > 0) {
return Response.json(
{
success: false,
code: 400,
errors,
},
{ status: 400 },
);
}
const reservation: ReservedSQL = await sql.reserve();
let user: User | null = null;
try {
user = await reservation`
SELECT * FROM users
WHERE (username = ${username} OR email = ${email})
LIMIT 1;
`.then((rows: User[]): User | null => rows[0]);
if (!user) {
await bunPassword.verify("fake", await bunPassword.hash("fake"));
return Response.json(
{
success: false,
code: 401,
error: "Invalid username, email, or password",
},
{ status: 401 },
);
}
const passwordMatch: boolean = await bunPassword.verify(
password,
user.password,
);
if (!passwordMatch) {
return Response.json(
{
success: false,
code: 401,
error: "Invalid username, email, or password",
},
{ status: 401 },
);
}
} catch (error) {
logger.error(["Error logging in", error as Error]);
return Response.json(
{
success: false,
code: 500,
error: "An error occurred while logging in",
},
{ status: 500 },
);
} finally {
if (reservation) reservation.release();
}
const sessionCookie: string = await sessionManager.createSession(
{
id: user.id,
username: user.username,
email: user.email,
email_verified: user.email_verified,
roles: user.roles,
avatar: user.avatar,
timezone: user.timezone,
authorization_token: user.authorization_token,
},
request.headers.get("User-Agent") || "",
);
return Response.json(
{
success: true,
code: 200,
},
{ status: 200, headers: { "Set-Cookie": sessionCookie } },
);
}
export { handler, routeDef };

View file

@ -0,0 +1,50 @@
import { sessionManager } from "@/helpers/sessions";
const routeDef: RouteDef = {
method: "POST",
accepts: "*/*",
returns: "application/json",
};
async function handler(request: ExtendedRequest): Promise<Response> {
if (!request.session) {
return Response.json(
{
success: false,
code: 403,
error: "You are not logged in",
},
{ status: 403 },
);
}
if ((request.session as ApiUserSession).is_api) {
return Response.json(
{
success: false,
code: 403,
error: "You cannot logout while using an authorization token",
},
{ status: 403 },
);
}
sessionManager.invalidateSession(request);
return Response.json(
{
success: true,
code: 200,
message: "Successfully logged out",
},
{
status: 200,
headers: {
"Set-Cookie":
"session=; Path=/; Max-Age=0; HttpOnly; Secure; SameSite=Strict",
},
},
);
}
export { handler, routeDef };

View file

@ -0,0 +1,197 @@
import { getSetting } from "@config/sql/settings";
import {
isValidEmail,
isValidInvite,
isValidPassword,
isValidUsername,
} from "@config/sql/users";
import { password as bunPassword, type ReservedSQL, sql } from "bun";
import type { UUID } from "crypto";
import { logger } from "@/helpers/logger";
import { sessionManager } from "@/helpers/sessions";
const routeDef: RouteDef = {
method: "POST",
accepts: "application/json",
returns: "application/json",
needsBody: "json",
};
async function handler(
request: ExtendedRequest,
requestBody: unknown,
): Promise<Response> {
const { username, email, password, invite } = requestBody as {
username: string;
email: string;
password: string;
invite?: string;
};
if (!username || !email || !password) {
return Response.json(
{
success: false,
code: 400,
error: "Expected username, email, and password",
},
{ status: 400 },
);
}
const errors: string[] = [];
const validations: UserValidation[] = [
{ check: isValidUsername(username), field: "Username" },
{ check: isValidEmail(email), field: "Email" },
{ check: isValidPassword(password), field: "Password" },
];
validations.forEach(({ check }: UserValidation): void => {
if (!check.valid && check.error) {
errors.push(check.error);
}
});
const reservation: ReservedSQL = await sql.reserve();
let firstUser: boolean = false;
let invitedBy: UUID | null = null;
let roles: string[] = [];
try {
const registrationEnabled: boolean =
(await getSetting("registrationEnabled", reservation)) === "true";
const invitationsEnabled: boolean =
(await getSetting("invitationsEnabled", reservation)) === "true";
firstUser =
Number(
(await reservation`SELECT COUNT(*) AS count FROM users;`)[0]
?.count,
) === 0;
if (!firstUser && invite) {
const inviteValidation: { valid: boolean; error?: string } =
isValidInvite(invite);
if (!inviteValidation.valid && inviteValidation.error) {
errors.push(inviteValidation.error);
}
}
if (
(!firstUser && !registrationEnabled && !invite) ||
(!firstUser && invite && !invitationsEnabled)
) {
errors.push("Registration is disabled");
}
roles.push("user");
if (firstUser) {
roles.push("admin");
}
const { usernameExists, emailExists } = await reservation`
SELECT
EXISTS(SELECT 1 FROM users WHERE LOWER(username) = LOWER(${username})) AS usernameExists,
EXISTS(SELECT 1 FROM users WHERE LOWER(email) = LOWER(${email})) AS emailExists;
`;
if (usernameExists) errors.push("Username already exists");
if (emailExists) errors.push("Email already exists");
if (invite) {
invitedBy = (
await reservation`SELECT user_id FROM invites WHERE invite = ${invite};`
)[0]?.id;
if (!invitedBy) errors.push("Invalid invite code");
}
} catch (error) {
errors.push("An error occurred while checking for existing users");
logger.error(["Error checking for existing users:", error as Error]);
}
if (errors.length > 0) {
return Response.json(
{
success: false,
code: 400,
errors,
},
{ status: 400 },
);
}
let user: User | null = null;
const hashedPassword: string = await bunPassword.hash(password, {
algorithm: "argon2id",
});
const defaultTimezone: string =
(await getSetting("default_timezone", reservation)) || "UTC";
try {
user = (
await reservation`
INSERT INTO users (username, email, password, invited_by, roles, timezone)
VALUES (${username}, ${email}, ${hashedPassword}, ${invitedBy}, ARRAY[${roles.join(",")}]::TEXT[], ${defaultTimezone})
RETURNING *;
`
)[0];
if (!user) {
logger.error("User was not created");
return Response.json(
{
success: false,
code: 500,
error: "An error occurred with the user registration",
},
{ status: 500 },
);
}
if (invitedBy) {
await reservation`DELETE FROM invites WHERE invite = ${invite};`;
}
} catch (error) {
logger.error([
"Error inserting user into the database:",
error as Error,
]);
return Response.json(
{
success: false,
code: 500,
error: "An error occurred while creating the user",
},
{ status: 500 },
);
} finally {
reservation.release();
}
const sessionCookie: string = await sessionManager.createSession(
{
id: user.id,
username: user.username,
email: user.email,
email_verified: user.email_verified,
roles: user.roles,
avatar: user.avatar,
timezone: user.timezone,
authorization_token: user.authorization_token,
},
request.headers.get("User-Agent") || "",
);
return Response.json(
{
success: true,
code: 201,
message: "User Registered",
id: user.id,
},
{ status: 201, headers: { "Set-Cookie": sessionCookie } },
);
}
export { handler, routeDef };

15
src/routes/api/index.ts Normal file
View file

@ -0,0 +1,15 @@
const routeDef: RouteDef = {
method: "GET",
accepts: "*/*",
returns: "application/json",
};
async function handler(): Promise<Response> {
// TODO: Put something useful here
return Response.json({
message: "Hello, World!",
});
}
export { handler, routeDef };

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

@ -0,0 +1,17 @@
import { renderEjsTemplate } from "@helpers/ejs";
const routeDef: RouteDef = {
method: "GET",
accepts: "*/*",
returns: "text/html",
};
async function handler(): Promise<Response> {
const ejsTemplateData: EjsTemplateData = {
title: "Hello, World!",
};
return await renderEjsTemplate("index", ejsTemplateData);
}
export { handler, routeDef };