633 lines
18 KiB
TypeScript
633 lines
18 KiB
TypeScript
import mongoose, {Document} from "mongoose";
|
|
import validator from "validator";
|
|
import crypto from "crypto";
|
|
import bcrypt from "bcrypt";
|
|
import jwt from "jsonwebtoken";
|
|
import env from "../enviroment/env";
|
|
|
|
const userSchema = new mongoose.Schema({
|
|
|
|
name:
|
|
{
|
|
type:String,
|
|
},
|
|
email: {
|
|
type: String,
|
|
required: true,
|
|
trim: true,
|
|
unique: true,
|
|
lowercase: true,
|
|
validate(value: any): any {
|
|
if (!validator.isEmail(value)) {
|
|
throw new Error("Email is invalid");
|
|
}
|
|
}
|
|
},
|
|
password: {
|
|
type: String,
|
|
trim: true,
|
|
required: true,
|
|
validate(value: any): any {
|
|
if (value.length < 6) {
|
|
throw new Error("Password Length Not Sufficent");
|
|
}
|
|
}
|
|
},
|
|
tokens:[{
|
|
token: {
|
|
type: String,
|
|
required: true
|
|
},
|
|
ipAddress: {
|
|
type: String,
|
|
required: true,
|
|
},
|
|
time: {
|
|
type: Number,
|
|
required: true
|
|
}
|
|
}],
|
|
tempTokens: [{
|
|
token: {
|
|
type: String,
|
|
required: true
|
|
},
|
|
ipAddress: {
|
|
type: String,
|
|
required: true,
|
|
},
|
|
time: {
|
|
type: Number,
|
|
required: true
|
|
}
|
|
}],
|
|
privateKey: {
|
|
type: String,
|
|
},
|
|
publicKey: {
|
|
type: String,
|
|
},
|
|
emailVerified: {
|
|
type: Boolean
|
|
},
|
|
emailToken: {
|
|
type: String,
|
|
},
|
|
passwordResetToken: {
|
|
type: String
|
|
},
|
|
googleDriveEnabled: {
|
|
type: Boolean
|
|
},
|
|
googleDriveData: {
|
|
|
|
id: {
|
|
type: String
|
|
},
|
|
key: {
|
|
type: String
|
|
},
|
|
iv: {
|
|
type: Buffer
|
|
},
|
|
token: {
|
|
type: String
|
|
}
|
|
// accessToken: {
|
|
// type: String
|
|
// },
|
|
// refreshToken: {
|
|
// type: String
|
|
// },
|
|
// tokenType: {
|
|
// type: String
|
|
// },
|
|
// expiryDate: {
|
|
// type: String
|
|
// }
|
|
},
|
|
s3Enabled: {
|
|
type: Boolean
|
|
},
|
|
s3Data: {
|
|
id: {
|
|
type: String
|
|
},
|
|
key: {
|
|
type: String
|
|
},
|
|
bucket: {
|
|
type: String
|
|
},
|
|
iv: {
|
|
type: Buffer
|
|
}
|
|
},
|
|
personalStorageCanceledDate: Number,
|
|
storageData: {
|
|
storageSize: Number,
|
|
storageLimit: Number,
|
|
failed: Boolean,
|
|
},
|
|
storageDataPersonal: {
|
|
storageSize: Number,
|
|
failed: Boolean,
|
|
},
|
|
storageDataGoogle: {
|
|
storageSize: Number,
|
|
storageLimit: Number,
|
|
failed: Boolean,
|
|
},
|
|
activeSubscription: Boolean,
|
|
planID: String,
|
|
passwordLastModified: Number,
|
|
lastSubscriptionCheckTime: Number,
|
|
lastSubscriptionStatus: Boolean
|
|
}, {
|
|
timestamps: true
|
|
})
|
|
|
|
export interface UserInterface extends Document {
|
|
_id: string,
|
|
name: string,
|
|
email: string,
|
|
password: string,
|
|
tokens: any[],
|
|
tempTokens: any[],
|
|
privateKey?: string,
|
|
publicKey?: string,
|
|
token?: string,
|
|
emailVerified?: boolean,
|
|
emailToken?: string,
|
|
passwordResetToken?: string,
|
|
googleDriveEnabled?: boolean,
|
|
googleDriveData?: {
|
|
id?: string,
|
|
key?: string,
|
|
iv?: Buffer,
|
|
// accessToken?: string,
|
|
// refreshToken?: string,
|
|
// tokenType?: string,
|
|
// expiryDate?: string,
|
|
token?: string,
|
|
},
|
|
s3Enabled?: boolean,
|
|
s3Data?: {
|
|
id?: string,
|
|
key?: string,
|
|
bucket?: string,
|
|
iv?: Buffer,
|
|
},
|
|
storageData?: {
|
|
storageSize?: number,
|
|
storageLimit?: number,
|
|
failed?: boolean,
|
|
},
|
|
storageDataPersonal?: {
|
|
storageSize?: number,
|
|
failed?: boolean,
|
|
},
|
|
storageDataGoogle?: {
|
|
storageSize?: number,
|
|
storageLimit?: number,
|
|
failed?: boolean,
|
|
},
|
|
activeSubscription?: boolean,
|
|
passwordLastModified?: number,
|
|
personalStorageCanceledDate?: number,
|
|
planID?: string,
|
|
lastSubscriptionCheckTime?: number,
|
|
lastSubscriptionStatus?: boolean,
|
|
|
|
getEncryptionKey: () => Buffer | undefined;
|
|
generateTempAuthToken: () => any;
|
|
// generateTempAuthTokenVideo: (cookie: string) => any;
|
|
encryptToken: (tempToken: any, key: any, publicKey: any) => any;
|
|
decryptToken: (encryptedToken: any, key: any, publicKey: any) => any;
|
|
findByCreds: (email: string, password: string) => UserInterface;
|
|
generateAuthToken: (ipAddress: string | undefined) => Promise<{accessToken: string, refreshToken: string}>
|
|
generateAuthTokenStreamVideo: (ipAddress: string | undefined) => Promise<string>
|
|
generateEncryptionKeys: () => Promise<void>;
|
|
changeEncryptionKey: (randomKey: Buffer) => void;
|
|
generateEmailVerifyToken: () => string;
|
|
generatePasswordResetToken: () => string;
|
|
encryptDriveIDandKey: (ID:string, key: string) => Promise<void>;
|
|
decryptDriveIDandKey: () => {clientID: string, clientKey: string};
|
|
encryptDriveTokenData: (token: Object) => void;
|
|
decryptDriveTokenData: () => any;
|
|
encryptS3Data: (id: string, key: string, bucket: string) => void;
|
|
decryptS3Data: () => {
|
|
id: string,
|
|
key: string,
|
|
bucket: string
|
|
}
|
|
}
|
|
|
|
const maxAgeAccess = 60 * 1000 * 20 + (1000 * 60);
|
|
const maxAgeRefresh = 60 * 1000 * 60 * 24 * 30 + (1000 * 60);
|
|
|
|
const maxAgeAccessStreamVideo = 60 * 1000 * 60 * 24;
|
|
|
|
userSchema.pre("save", async function(this: any, next: any) {
|
|
|
|
const user = this;
|
|
|
|
if (user.isModified("password")) {
|
|
user.password = await bcrypt.hash(user.password, 8);
|
|
}
|
|
|
|
next();
|
|
})
|
|
|
|
userSchema.statics.findByCreds = async(email: string, password: string) => {
|
|
|
|
const user = await User.findOne({email});
|
|
|
|
if (!user) {
|
|
|
|
throw new Error("User not found")
|
|
}
|
|
|
|
const isMatch = await bcrypt.compare(password, user.password);
|
|
|
|
if (!isMatch) {
|
|
console.log("incorrect password")
|
|
throw new Error("Incorrect password");
|
|
}
|
|
|
|
return user;
|
|
}
|
|
|
|
userSchema.methods.toJSON = function() {
|
|
|
|
const user = this;
|
|
|
|
const userObject = user.toObject();
|
|
|
|
delete userObject.password;
|
|
delete userObject.tokens;
|
|
delete userObject.tempTokens;
|
|
delete userObject.privateKey;
|
|
delete userObject.publicKey;
|
|
|
|
return userObject;
|
|
}
|
|
|
|
userSchema.methods.generateAuthTokenStreamVideo = async function(ipAddress: string | undefined) {
|
|
|
|
const iv = crypto.randomBytes(16);
|
|
|
|
const user = this;
|
|
|
|
const date = new Date();
|
|
const time = date.getTime();
|
|
|
|
let accessTokenStreamVideo = jwt.sign({_id:user._id.toString(), iv, time}, env.passwordAccess!, {expiresIn: maxAgeAccessStreamVideo.toString()});
|
|
|
|
const encryptionKey = user.getEncryptionKey();
|
|
|
|
const encryptedToken = user.encryptToken(accessTokenStreamVideo, encryptionKey, iv);
|
|
|
|
ipAddress = ipAddress ? ipAddress : "";
|
|
|
|
await User.updateOne({_id: user._id}, {$push: {"tempTokens": {token: encryptedToken, ipAddress, time}}});
|
|
|
|
return accessTokenStreamVideo;
|
|
}
|
|
|
|
userSchema.methods.generateAuthToken = async function(ipAddress: string | undefined) {
|
|
|
|
const iv = crypto.randomBytes(16);
|
|
|
|
const user = this;
|
|
|
|
const date = new Date();
|
|
const time = date.getTime();
|
|
|
|
const userObj = {_id: user._id, emailVerified: user.emailVerified || env.disableEmailVerification, email: user.email, s3Enabled: user.s3Enabled, googleDriveEnabled: user.googleDriveEnabled}
|
|
|
|
let accessToken = jwt.sign({user: userObj, iv}, env.passwordAccess!, {expiresIn: maxAgeAccess.toString()});
|
|
let refreshToken = jwt.sign({_id:user._id.toString(), iv, time}, env.passwordRefresh!, {expiresIn: maxAgeRefresh.toString()});
|
|
|
|
const encryptionKey = user.getEncryptionKey();
|
|
|
|
const encryptedToken = user.encryptToken(refreshToken, encryptionKey, iv);
|
|
|
|
//user.tokens = user.tokens.concat({token: encryptedToken});
|
|
|
|
ipAddress = ipAddress ? ipAddress : "";
|
|
|
|
await User.updateOne({_id: user._id}, {$push: {"tokens": {token: encryptedToken, ipAddress, time}}})
|
|
|
|
// console.log("saving user")
|
|
// console.log("user saved")
|
|
return {accessToken, refreshToken};
|
|
|
|
// const iv = crypto.randomBytes(16);
|
|
|
|
// const user = this;
|
|
// let token = jwt.sign({_id:user._id.toString(), iv}, env.passwordAccess!);
|
|
|
|
// const encryptionKey = user.getEncryptionKey();
|
|
|
|
// const encryptedToken = user.encryptToken(token, encryptionKey, iv);
|
|
|
|
// user.tokens = user.tokens.concat({token: encryptedToken});
|
|
|
|
// await user.save();
|
|
// return token;
|
|
}
|
|
|
|
userSchema.methods.encryptToken = function(token: string, key: string, iv: any) {
|
|
|
|
iv = Buffer.from(iv, "hex")
|
|
|
|
const TOKEN_CIPHER_KEY = crypto.createHash('sha256').update(key).digest();
|
|
const cipher = crypto.createCipheriv('aes-256-cbc', TOKEN_CIPHER_KEY, iv);
|
|
const encryptedText = cipher.update(token);
|
|
|
|
return Buffer.concat([encryptedText, cipher.final()]).toString("hex");;
|
|
}
|
|
|
|
userSchema.methods.decryptToken = function(encryptedToken: any, key: string, iv: any) {
|
|
|
|
encryptedToken = Buffer.from(encryptedToken, "hex");
|
|
iv = Buffer.from(iv, "hex")
|
|
|
|
const TOKEN_CIPHER_KEY = crypto.createHash('sha256').update(key).digest();
|
|
const decipher = crypto.createDecipheriv('aes-256-cbc', TOKEN_CIPHER_KEY, iv)
|
|
|
|
const tokenDecrypted = decipher.update(encryptedToken);
|
|
|
|
return Buffer.concat([tokenDecrypted, decipher.final()]).toString();
|
|
}
|
|
|
|
userSchema.methods.generateEncryptionKeys = async function() {
|
|
|
|
const user = this;
|
|
const userPassword = user.password;
|
|
const masterPassword = env.key!;
|
|
|
|
const randomKey = crypto.randomBytes(32);
|
|
|
|
const iv = crypto.randomBytes(16);
|
|
const USER_CIPHER_KEY = crypto.createHash('sha256').update(userPassword).digest();
|
|
const cipher = crypto.createCipheriv('aes-256-cbc', USER_CIPHER_KEY, iv);
|
|
let encryptedText = cipher.update(randomKey);
|
|
encryptedText = Buffer.concat([encryptedText, cipher.final()]);
|
|
|
|
const MASTER_CIPHER_KEY = crypto.createHash('sha256').update(masterPassword).digest();
|
|
const masterCipher = crypto.createCipheriv('aes-256-cbc', MASTER_CIPHER_KEY, iv);
|
|
const masterEncryptedText = masterCipher.update(encryptedText);
|
|
|
|
user.privateKey = Buffer.concat([masterEncryptedText, masterCipher.final()]).toString("hex");
|
|
user.publicKey = iv.toString("hex");
|
|
|
|
await user.save();
|
|
}
|
|
|
|
userSchema.methods.getEncryptionKey = function() {
|
|
|
|
try {
|
|
const user = this;
|
|
const userPassword = user.password;
|
|
const masterEncryptedText = user.privateKey;
|
|
const masterPassword = env.key!;
|
|
const iv = Buffer.from(user.publicKey, "hex");
|
|
|
|
const USER_CIPHER_KEY = crypto.createHash('sha256').update(userPassword).digest();
|
|
const MASTER_CIPHER_KEY = crypto.createHash('sha256').update(masterPassword).digest();
|
|
|
|
const unhexMasterText = Buffer.from(masterEncryptedText, "hex");
|
|
const masterDecipher = crypto.createDecipheriv('aes-256-cbc', MASTER_CIPHER_KEY, iv)
|
|
let masterDecrypted = masterDecipher.update(unhexMasterText);
|
|
masterDecrypted = Buffer.concat([masterDecrypted, masterDecipher.final()])
|
|
|
|
let decipher = crypto.createDecipheriv('aes-256-cbc', USER_CIPHER_KEY, iv);
|
|
let decrypted = decipher.update(masterDecrypted);
|
|
decrypted = Buffer.concat([decrypted, decipher.final()]);
|
|
|
|
return decrypted;
|
|
|
|
} catch (e) {
|
|
|
|
console.log("Get Encryption Key Error", e);
|
|
return undefined;
|
|
}
|
|
}
|
|
|
|
userSchema.methods.changeEncryptionKey = async function(randomKey: Buffer) {
|
|
|
|
const user = this;
|
|
const userPassword = user.password;
|
|
const masterPassword = env.key!;
|
|
|
|
const iv = crypto.randomBytes(16);
|
|
const USER_CIPHER_KEY = crypto.createHash('sha256').update(userPassword).digest();
|
|
const cipher = crypto.createCipheriv('aes-256-cbc', USER_CIPHER_KEY, iv);
|
|
let encryptedText = cipher.update(randomKey);
|
|
encryptedText = Buffer.concat([encryptedText, cipher.final()]);
|
|
|
|
const MASTER_CIPHER_KEY = crypto.createHash('sha256').update(masterPassword).digest();
|
|
const masterCipher = crypto.createCipheriv('aes-256-cbc', MASTER_CIPHER_KEY, iv);
|
|
const masterEncryptedText = masterCipher.update(encryptedText);
|
|
|
|
user.privateKey = Buffer.concat([masterEncryptedText, masterCipher.final()]).toString("hex");
|
|
user.publicKey = iv.toString("hex");
|
|
|
|
await user.save();
|
|
}
|
|
|
|
userSchema.methods.generateTempAuthToken = async function() {
|
|
|
|
const iv = crypto.randomBytes(16);
|
|
|
|
const user = this as UserInterface;
|
|
const token = jwt.sign({_id:user._id.toString(), iv}, env.passwordAccess!, {expiresIn: "3000ms"});
|
|
|
|
const encryptionKey = user.getEncryptionKey();
|
|
const encryptedToken = user.encryptToken(token, encryptionKey, iv);
|
|
|
|
user.tempTokens = user.tempTokens.concat({token: encryptedToken});
|
|
|
|
await user.save();
|
|
return token;
|
|
}
|
|
|
|
userSchema.methods.generateEmailVerifyToken = async function() {
|
|
|
|
const iv = crypto.randomBytes(16);
|
|
|
|
const user = this as UserInterface;
|
|
const token = jwt.sign({_id:user._id.toString(), iv}, env.passwordAccess!, {expiresIn: "1d"});
|
|
|
|
const encryptionKey = user.getEncryptionKey();
|
|
const encryptedToken = user.encryptToken(token, encryptionKey, iv);
|
|
|
|
user.emailToken = encryptedToken;
|
|
|
|
await user.save();
|
|
return token;
|
|
}
|
|
|
|
userSchema.methods.encryptDriveIDandKey = async function(ID: string, key: string) {
|
|
|
|
const iv = crypto.randomBytes(16);
|
|
|
|
const user = this as UserInterface;
|
|
|
|
const encryptedKey = user.getEncryptionKey();
|
|
|
|
const encryptedDriveID = user.encryptToken(ID, encryptedKey, iv);
|
|
const encryptedDriveKey = user.encryptToken(key, encryptedKey, iv);
|
|
|
|
if (!user.googleDriveData) user.googleDriveData = {};
|
|
|
|
user.googleDriveData!.id = encryptedDriveID;
|
|
user.googleDriveData!.key = encryptedDriveKey;
|
|
user.googleDriveData!.iv = iv;
|
|
|
|
await user.save();
|
|
}
|
|
|
|
userSchema.methods.decryptDriveIDandKey = async function() {
|
|
|
|
const user = this as UserInterface;
|
|
|
|
const iv = user.googleDriveData!.iv;
|
|
|
|
const encryptedKey = user.getEncryptionKey();
|
|
|
|
const encryptedDriveID = user.googleDriveData?.id;
|
|
const encryptedDriveKey = user.googleDriveData?.key;
|
|
|
|
const decryptedDriveID = user.decryptToken(encryptedDriveID, encryptedKey, iv)
|
|
const decryptedDriveKey = user.decryptToken(encryptedDriveKey, encryptedKey, iv);
|
|
|
|
return {
|
|
clientID: decryptedDriveID,
|
|
clientKey: decryptedDriveKey
|
|
}
|
|
}
|
|
|
|
userSchema.methods.encryptDriveTokenData = async function(token: Object) {
|
|
|
|
const user = this as UserInterface;
|
|
const iv = user.googleDriveData?.iv;
|
|
|
|
const tokenToString = JSON.stringify(token);
|
|
|
|
const encryptedKey = user.getEncryptionKey();
|
|
|
|
const encryptedDriveToken = user.encryptToken(tokenToString, encryptedKey, iv);;
|
|
|
|
if (!user.googleDriveData) user.googleDriveData = {};
|
|
|
|
user.googleDriveData.token = encryptedDriveToken;
|
|
user.googleDriveEnabled = true;
|
|
|
|
await user.save();
|
|
}
|
|
|
|
userSchema.methods.decryptDriveTokenData = async function() {
|
|
|
|
const user = this as UserInterface;
|
|
|
|
const iv = user.googleDriveData!.iv;
|
|
|
|
const encryptedKey = user.getEncryptionKey();
|
|
|
|
const encryptedToken = user.googleDriveData?.token;
|
|
|
|
const decryptedToken = user.decryptToken(encryptedToken, encryptedKey, iv);
|
|
|
|
const tokenToObj = JSON.parse(decryptedToken);
|
|
|
|
return tokenToObj;
|
|
}
|
|
|
|
userSchema.methods.encryptS3Data = async function(ID: string, key: string, bucket: string) {
|
|
|
|
const iv = crypto.randomBytes(16);
|
|
|
|
const user = this as UserInterface;
|
|
|
|
const encryptedKey = user.getEncryptionKey();
|
|
|
|
const encryptedS3ID = user.encryptToken(ID, encryptedKey, iv);
|
|
const encryptedS3Key = user.encryptToken(key, encryptedKey, iv);
|
|
const encryptedS3Bucket = user.encryptToken(bucket, encryptedKey, iv);
|
|
|
|
if (!user.s3Data) user.s3Data = {};
|
|
|
|
user.s3Data!.id = encryptedS3ID;
|
|
user.s3Data!.key = encryptedS3Key;
|
|
user.s3Data!.bucket = encryptedS3Bucket;
|
|
user.s3Data!.iv = iv;
|
|
user.s3Enabled = true;
|
|
|
|
await user.save();
|
|
}
|
|
|
|
userSchema.methods.decryptS3Data = async function() {
|
|
|
|
const user = this as UserInterface;
|
|
|
|
const iv = user.s3Data?.iv;
|
|
|
|
const encryptedKey = user.getEncryptionKey();
|
|
|
|
const encrytpedS3ID = user.s3Data?.id;
|
|
const encryptedS3Key = user.s3Data?.key;
|
|
const encryptedS3Bucket = user.s3Data?.bucket;
|
|
|
|
const decrytpedS3ID = user.decryptToken(encrytpedS3ID, encryptedKey, iv);
|
|
const decryptedS3Key = user.decryptToken(encryptedS3Key, encryptedKey, iv);
|
|
const decryptedS3Bucket = user.decryptToken(encryptedS3Bucket, encryptedKey, iv);
|
|
|
|
//console.log("decrypted keys", decrytpedS3ID, decryptedS3Key, decryptedS3Bucket);
|
|
|
|
return {
|
|
id: decrytpedS3ID,
|
|
key: decryptedS3Key,
|
|
bucket: decryptedS3Bucket,
|
|
}
|
|
}
|
|
|
|
userSchema.methods.generatePasswordResetToken = async function() {
|
|
|
|
console.log("generating reset password token")
|
|
const iv = crypto.randomBytes(16);
|
|
|
|
const user = this as UserInterface;
|
|
const token = jwt.sign({_id:user._id.toString(), iv}, env.passwordAccess!, {expiresIn: "1h"});
|
|
|
|
const encryptionKey = user.getEncryptionKey();
|
|
const encryptedToken = user.encryptToken(token, encryptionKey, iv);
|
|
|
|
user.passwordResetToken = encryptedToken;
|
|
|
|
await user.save();
|
|
return token;
|
|
}
|
|
|
|
userSchema.methods.generateTempAuthTokenVideo = async function(cookie: string) {
|
|
|
|
const iv = crypto.randomBytes(16);
|
|
|
|
const user = this;
|
|
const token = jwt.sign({_id:user._id.toString(), cookie, iv}, env.passwordAccess!, {expiresIn:"5h"});
|
|
|
|
const encryptionKey = user.getEncryptionKey();
|
|
const encryptedToken = user.encryptToken(token, encryptionKey, iv);
|
|
|
|
user.tempTokens = user.tempTokens.concat({token: encryptedToken});
|
|
|
|
await user.save();
|
|
return token;
|
|
}
|
|
|
|
const User = mongoose.model<UserInterface>("User", userSchema);
|
|
|
|
export default User;
|
|
module.exports = User; |