old-ieeeucsd-org/src/Database.ts
2022-06-28 01:57:37 -07:00

208 lines
No EOL
6.3 KiB
TypeScript

import {Schema, Document, LeanDocument} from "mongoose";
import * as mongoose from "mongoose";
import {promisify} from "util";
import {randomBytes, pbkdf2} from "crypto";
const pbkdf = promisify(pbkdf2);
const User = mongoose.model("User", new Schema({
display: String,
email: {
public: Boolean,
text: String,
},
picture: String,
password: {
hash: Buffer,
salt: Buffer
},
bio: {type: String, default: ""},
isOfficer: {type: Boolean, default: false},
title: {type: String, default: "Member"},
uuid: String
}));
export interface DBUser extends Document {
display: string;
email: string;
picture: string;
password: HashSalt
isOfficer: boolean;
title: string;
uuid: string;
}
interface HashSalt {
hash: Buffer;
salt: Buffer;
}
export default class UserDatabase {
// How many iterations pbkdf2 will iterate the password hashing algorithm
private static PW_ITERATIONS = 100000;
// The length of the salt that should be used with every password hash
private static SALT_LENGTH = 64;
// How many bytes will be used to generate a UUID of length 2 times this variable
private static UUID_LENGTH = 32;
/**
* Makes a new UserDatabase
* @param url the mongodb database url to connect to
*/
constructor(url: string) {
mongoose.connect(url);
mongoose.set("returnOriginal", false);
}
// Functions to edit current users
/**
* Changes a user's display name on the website
* @param uuid the UUID of the user
* @param newName the user's new name
*/
public async setName(uuid: string, newName: string) {
await User.findOneAndUpdate({uuid: uuid}, {name: newName}).exec();
}
/**
* Gets the display name of a user with a given UUID
* @param uuid the UUID of the user
* @returns the user's display name
*/
public async getName(uuid: string): Promise<string> {
return (await User.findOne({uuid: uuid}).lean().exec() as DBUser).display;
}
/**
* Updates a user's display email
* @param uuid the UUID of the user
* @param newEmail the user's new display email
*/
public async setEmail(uuid: string, newEmail: string) {
await User.findOneAndUpdate({uuid: uuid}, {email: newEmail}).exec();
}
/**
* Gets the email of a user with a given UUID
* @param uuid the UUID of the user
* @returns the user's email
*/
public async getEmail(uuid: string): Promise<string> {
return (await User.findOne({uuid: uuid}).lean().exec() as DBUser).email;
}
/**
* Updates a user's password
* @param uuid the UUID of the user
* @param newPassword the user's new password
*/
public async setPassword(uuid: string, newPassword: string) {
let hashsalt = await UserDatabase.hash(newPassword);
await User.findOneAndUpdate({uuid: uuid}, {password: hashsalt}).exec();
}
/**
* Gets the password of a user with a given UUID
* @param uuid the UUID of the user
* @returns the user's password
*/
public async getPasswordHash(uuid: string): Promise<Buffer> {
return (await User.findOne({uuid: uuid}).lean().exec() as DBUser).password.hash;
}
/**
* Updates a member's position
* @param uuid the UUID of the user
* @param newTitle the user's new officer title/position
*/
public async setRank(uuid: string, newTitle: string, isOfficer: boolean) {
await User.findOneAndUpdate({uuid: uuid}, {title: newTitle, isOfficer: isOfficer}).exec();
}
/**
* Gets the title of a user with a given UUID
* @param uuid the UUID of the user
* @returns the user's title
*/
public async getRank(uuid: string): Promise<string> {
return (await User.findOne({uuid: uuid}).lean().exec() as DBUser).title;
}
/**
* Gets whether of a user with a given UUID is an officer
* @param uuid the UUID of the user
* @returns true if the user is an officer
*/
public async isOfficer(uuid: string): Promise<boolean> {
return (await User.findOne({uuid: uuid}).lean().exec() as DBUser).isOfficer;
}
/**
* Sets the user's new profile picture
* @param uuid the UUID of the user
* @param newPic the link to the new URL of the profile picture
*/
public async setPicture(uuid: string, newPic: string) {
await User.findOneAndUpdate({uuid: uuid}, {picture: newPic}).exec();
}
/**
* Gets the profile picture of a user with a given UUID
* @param uuid the UUID of the user
* @returns the user's profile picture
*/
public async getPicture(uuid: string): Promise<string> {
return (await User.findOne({uuid: uuid}).lean().exec() as DBUser).picture;
}
public async getUser(uuid: string): Promise<DBUser> {
return User.findOne({uuid: uuid}).exec() as Promise<DBUser>;
}
// User creation and deletion functions
/**
* Deletes a user with the given UUID
* @param uuid the UUID of the user
*/
public async deleteUser(uuid: string) {
await User.findOneAndDelete({uuid: uuid}).exec();
}
/**
* Makes a new user and adds it to the database
* @param display the user's display name
* @param email the user's display email
* @param picture the user's picture
* @param password the user's plaintext password
*/
public async makeNewUser(display: string, email: string, picture: string, password: string, title?: string, isOfficer?: boolean): Promise<void> {
let encrypted = await UserDatabase.hash(password);
await (new User({
display: display,
email: email,
picture: picture,
password: encrypted,
uuid: UserDatabase.genUUID(),
isOfficer: !!isOfficer,
title: title ? title : "Member"
})).save();
}
// Utility functions
/**
* Hashes a password using a salt of 64 random bytes and pbkdf2 with 100000 iterations
* @param password the password as a string that should be hashed
* @returns the hashed password
*/
public static async hash(password: string): Promise<HashSalt> {
let salt = randomBytes(UserDatabase.SALT_LENGTH);
let pass = Buffer.from(String.prototype.normalize(password));
return {
hash: await pbkdf(pass, salt, UserDatabase.PW_ITERATIONS, 512, "sha512"),
salt: salt
};
}
/**
* Generates a UUID for a user or anything else
* @returns a random hex string of length 2 * UUID_LENGTH
*/
public static genUUID(): string {
return Array.from(randomBytes(UserDatabase.UUID_LENGTH))
.map(val=>(val.toString(16).padStart(2,"0"))).join("");
}
}