diff --git a/src/server/lib/accounts.ts b/src/server/lib/accounts.ts index 6e20ee3..c9f755f 100644 --- a/src/server/lib/accounts.ts +++ b/src/server/lib/accounts.ts @@ -27,6 +27,14 @@ export interface Account { } } +/** + * @description Create a new account. + * @param username New account's username + * @param pwd New account's password + * @param admin Whether or not the account should have administrative rights + * @returns A Promise which returns the new account's ID + */ + export function create(username:string,pwd:string,admin:boolean=false):Promise { return new Promise((resolve,reject) => { let accId = crypto.randomBytes(12).toString("hex") @@ -46,26 +54,52 @@ export function create(username:string,pwd:string,admin:boolean=false):Promise e.username == username) } +/** + * @description Gets an account from its ID. + * @param id The target account's ID + * @returns An Account, if it exists + */ export function getFromId(id:string) { return Accounts.find(e => e.id == id) } +/** + * @description Gets an account from an AuthToken. Equivalent to getFromId(auth.validate(token)). + * @param token A valid AuthToken + * @returns An Account, if the token is valid + */ export function getFromToken(token:string) { let accId = auth.validate(token) if (!accId) return return getFromId(accId) } +/** + * @description Deletes an account. + * @param id The target account's ID + */ export function deleteAccount(id:string) { Accounts.splice(Accounts.findIndex(e => e.id == id),1) return save() } export namespace password { + + /** + * @description Generates a hashed and salted version of an input password. + * @param password Target password. + * @param _salt Designated password salt. Use to validate a password. + */ + export function hash(password:string,_salt?:string) { let salt = _salt || crypto.randomBytes(12).toString('base64') let hash = crypto.createHash('sha256').update(`${salt}${password}`).digest('hex') @@ -75,6 +109,12 @@ export namespace password { hash:hash } } + + /** + * @description Sets an account's password. + * @param id The target account's ID + * @param password New password + */ export function set(id:string,password:string) { let acc = Accounts.find(e => e.id == id) @@ -84,6 +124,12 @@ export namespace password { return save() } + + /** + * @description Tests a password against an account. + * @param id The target account's ID + * @param password Password to check + */ export function check(id:string,password:string) { let acc = Accounts.find(e => e.id == id) if (!acc) return @@ -93,6 +139,12 @@ export namespace password { } export namespace files { + /** + * @description Adds a file to an account's file index + * @param accountId The target account's ID + * @param fileId The target file's ID + * @returns Promise that resolves after accounts.json finishes writing + */ export function index(accountId:string,fileId:string) { // maybe replace with a obj like // { x:true } @@ -105,6 +157,13 @@ export namespace files { return save() } + /** + * @description Removes a file from an account's file index + * @param accountId The target account's ID + * @param fileId The target file's ID + * @param noWrite Whether or not accounts.json should save + * @returns A Promise which resolves when accounts.json finishes writing, if `noWrite` is `false` + */ export function deindex(accountId:string,fileId:string, noWrite:boolean=false) { let acc = Accounts.find(e => e.id == accountId) if (!acc) return @@ -116,6 +175,10 @@ export namespace files { } } +/** + * @description Saves accounts.json + * @returns A promise which resolves when accounts.json finishes writing + */ export function save() { return writeFile(`${process.cwd()}/.data/accounts.json`,JSON.stringify(Accounts)) .catch((err) => console.error(err)) diff --git a/src/server/lib/errors.ts b/src/server/lib/errors.ts index 6e67f33..84c1cc5 100644 --- a/src/server/lib/errors.ts +++ b/src/server/lib/errors.ts @@ -3,6 +3,12 @@ import { readFile } from "fs/promises" let errorPage:string +/** + * @description Serves an error as a response to a request with an error page attached + * @param res Express response object + * @param code Error code + * @param reason Error reason + */ export default async function ServeError( res:Response, code:number, @@ -29,7 +35,12 @@ export default async function ServeError( .replace(/\$text/g,reason) ) } - +/** + * @description Redirects a user to another page. + * @param res Express response object + * @param url Target URL + * @deprecated Use `res.redirect` instead. + */ export function Redirect(res:Response,url:string) { res.status(302) res.header("Location",url) diff --git a/src/server/lib/files.ts b/src/server/lib/files.ts index d0be358..259761e 100644 --- a/src/server/lib/files.ts +++ b/src/server/lib/files.ts @@ -14,6 +14,11 @@ export let alphanum = Array.from("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRST export type FileVisibility = "public" | "anonymous" | "private" +/** + * @description Generates an alphanumeric string, used for files + * @param length Length of the ID + * @returns a random alphanumeric string + */ export function generateFileId(length:number=5) { let fid = "" for (let i = 0; i < length; i++) { @@ -40,7 +45,10 @@ export interface Configuration { accounts: { registrationEnabled: boolean, requiredForUpload: boolean - } + }, + + trustProxy: boolean, + forceSSL: boolean } export interface FilePointer { @@ -93,6 +101,12 @@ export default class Files { } + /** + * @description Uploads a new file + * @param settings Settings for your new upload + * @param fBuffer Buffer containing file content + * @returns Promise which resolves to the ID of the new file + */ uploadFile(settings:FileUploadSettings,fBuffer:Buffer):Promise { return new Promise(async (resolve,reject) => { if (!this.uploadChannel) { @@ -244,6 +258,12 @@ export default class Files { // fs + /** + * @description Writes a file to disk + * @param uploadId New file's ID + * @param file FilePointer representing the new file + * @returns Promise which resolves to the file's ID + */ writeFile(uploadId: string, file: FilePointer):Promise { return new Promise((resolve, reject) => { @@ -264,8 +284,12 @@ export default class Files { }) } - // todo: move read code here - + /** + * @description Read a file + * @param uploadId Target file's ID + * @param range Byte range to get + * @returns A `Readable` containing the file's contents + */ readFileStream(uploadId: string, range?: {start:number, end:number}):Promise { return new Promise(async (resolve,reject) => { if (!this.uploadChannel) { @@ -401,6 +425,11 @@ export default class Files { }) } + /** + * @description Deletes a file + * @param uploadId Target file's ID + * @param noWrite Whether or not the change should be written to disk. Enable for bulk deletes + */ unlink(uploadId:string, noWrite: boolean = false):Promise { return new Promise(async (resolve,reject) => { let tmp = this.files[uploadId]; @@ -431,6 +460,11 @@ export default class Files { }) } + /** + * @description Get a file's FilePointer + * @param uploadId Target file's ID + * @returns FilePointer for the file + */ getFilePointer(uploadId:string):FilePointer { return this.files[uploadId] } diff --git a/src/server/lib/mail.ts b/src/server/lib/mail.ts index 4fdbb3e..bd9ce07 100644 --- a/src/server/lib/mail.ts +++ b/src/server/lib/mail.ts @@ -19,6 +19,13 @@ transport = // lazy but +/** + * @description Sends an email + * @param to Target email address + * @param subject Email subject + * @param content Email content + * @returns Promise which resolves to the output from nodemailer.transport.sendMail + */ export function sendMail(to: string, subject: string, content: string) { return new Promise((resolve,reject) => { transport.sendMail({ diff --git a/src/server/lib/middleware.ts b/src/server/lib/middleware.ts index 9a1a542..2d312ad 100644 --- a/src/server/lib/middleware.ts +++ b/src/server/lib/middleware.ts @@ -3,11 +3,17 @@ import express, { type RequestHandler } from "express" import ServeError from "../lib/errors"; import * as auth from "./auth"; +/** + * @description Middleware which adds an account, if any, to res.locals.acc + */ export const getAccount: RequestHandler = function(req, res, next) { res.locals.acc = Accounts.getFromToken(auth.tokenFor(req)) next() } +/** + * @description Middleware which blocks requests which do not have res.locals.acc set + */ export const requiresAccount: RequestHandler = function(_req, res, next) { if (!res.locals.acc) { ServeError(res, 401, "not logged in") @@ -16,6 +22,9 @@ export const requiresAccount: RequestHandler = function(_req, res, next) { next() } +/** + * @description Middleware which blocks requests that have res.locals.acc.admin set to a falsy value + */ export const requiresAdmin: RequestHandler = function(_req, res, next) { if (!res.locals.acc.admin) { ServeError(res, 403, "you are not an administrator") @@ -25,10 +34,10 @@ export const requiresAdmin: RequestHandler = function(_req, res, next) { } /** - * @description Blocks requests based on the permissions which a token has. Does not apply to routes being accessed with a token of type `User` - * @param tokenPermissions Permissions which your route requires. - * @returns Express middleware - */ + * @description Blocks requests based on the permissions which a token has. Does not apply to routes being accessed with a token of type `User` + * @param tokenPermissions Permissions which your route requires. + * @returns Express middleware + */ export const requiresPermissions = function(...tokenPermissions: auth.TokenPermission[]): RequestHandler { return function(req, res, next) { diff --git a/src/server/lib/ratelimit.ts b/src/server/lib/ratelimit.ts index a53533a..94d9d32 100644 --- a/src/server/lib/ratelimit.ts +++ b/src/server/lib/ratelimit.ts @@ -2,14 +2,19 @@ import { RequestHandler } from "express" import { type Account } from "./accounts" import ServeError from "./errors" -interface ratelimitSettings { +interface RatelimitSettings { requests: number per: number } -export function accountRatelimit( settings: ratelimitSettings ): RequestHandler { +/** + * @description Ratelimits a route based on res.locals.acc + * @param settings Ratelimit settings + * @returns Express middleware + */ +export function accountRatelimit( settings: RatelimitSettings ): RequestHandler { let activeLimits: { [ key: string ]: { requests: number,