Moved contents of seasoned_api up to root folder
This commit is contained in:
41
src/user/token.js
Normal file
41
src/user/token.js
Normal file
@@ -0,0 +1,41 @@
|
||||
const User = require("./user");
|
||||
const jwt = require("jsonwebtoken");
|
||||
|
||||
class Token {
|
||||
constructor(user, admin = false, settings = null) {
|
||||
this.user = user;
|
||||
this.admin = admin;
|
||||
this.settings = settings;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate a new token.
|
||||
* @param {String} secret a cipher of the token
|
||||
* @returns {String}
|
||||
*/
|
||||
toString(secret) {
|
||||
const { user, admin, settings } = this;
|
||||
|
||||
let data = { username: user.username, settings };
|
||||
if (admin) data["admin"] = admin;
|
||||
|
||||
return jwt.sign(data, secret, { expiresIn: "90d" });
|
||||
}
|
||||
|
||||
/**
|
||||
* Decode a token.
|
||||
* @param {Token} jwtToken an encrypted token
|
||||
* @param {String} secret a cipher of the token
|
||||
* @returns {Token}
|
||||
*/
|
||||
static fromString(jwtToken, secret) {
|
||||
const token = jwt.verify(jwtToken, secret, { clockTolerance: 10000 });
|
||||
if (token.username == null) throw new Error("Malformed token");
|
||||
|
||||
const { username, admin, settings } = token;
|
||||
const user = new User(username);
|
||||
return new Token(user, admin, settings);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = Token;
|
||||
8
src/user/user.js
Normal file
8
src/user/user.js
Normal file
@@ -0,0 +1,8 @@
|
||||
class User {
|
||||
constructor(username, email=undefined) {
|
||||
this.username = username;
|
||||
this.email = email;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = User;
|
||||
256
src/user/userRepository.js
Normal file
256
src/user/userRepository.js
Normal file
@@ -0,0 +1,256 @@
|
||||
const assert = require("assert");
|
||||
const establishedDatabase = require("../database/database");
|
||||
|
||||
class UserRepository {
|
||||
constructor(database) {
|
||||
this.database = database || establishedDatabase;
|
||||
this.queries = {
|
||||
read: "select * from user where lower(user_name) = lower(?)",
|
||||
create: "insert into user (user_name) values (?)",
|
||||
change: "update user set password = ? where user_name = ?",
|
||||
retrieveHash: "select * from user where user_name = ?",
|
||||
getAdminStateByUser: "select admin from user where user_name = ?",
|
||||
link: "update settings set plex_userid = ? where user_name = ?",
|
||||
unlink: "update settings set plex_userid = null where user_name = ?",
|
||||
createSettings: "insert into settings (user_name) values (?)",
|
||||
updateSettings:
|
||||
"update settings set user_name = ?, dark_mode = ?, emoji = ?",
|
||||
getSettings: "select * from settings where user_name = ?"
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a user in a database.
|
||||
* @param {User} user the user you want to create
|
||||
* @returns {Promise}
|
||||
*/
|
||||
create(user) {
|
||||
return this.database
|
||||
.get(this.queries.read, user.username)
|
||||
.then(() => this.database.run(this.queries.create, user.username))
|
||||
.catch(error => {
|
||||
if (
|
||||
error.name === "AssertionError" ||
|
||||
error.message.endsWith("user_name")
|
||||
) {
|
||||
throw new Error("That username is already registered");
|
||||
}
|
||||
throw Error(error);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve a password from a database.
|
||||
* @param {User} user the user you want to retrieve the password
|
||||
* @returns {Promise}
|
||||
*/
|
||||
retrieveHash(user) {
|
||||
return this.database
|
||||
.get(this.queries.retrieveHash, user.username)
|
||||
.then(row => {
|
||||
assert(row, "The user does not exist.");
|
||||
return row.password;
|
||||
})
|
||||
.catch(err => {
|
||||
console.log(error);
|
||||
throw new Error("Unable to find your user.");
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Change a user's password in a database.
|
||||
* @param {User} user the user you want to create
|
||||
* @param {String} password the new password you want to change
|
||||
* @returns {Promise}
|
||||
*/
|
||||
changePassword(user, password) {
|
||||
return this.database.run(this.queries.change, [password, user.username]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Link plex userid with seasoned user
|
||||
* @param {String} username the user you want to lunk plex userid with
|
||||
* @param {Number} plexUserID plex unique id
|
||||
* @returns {Promsie}
|
||||
*/
|
||||
linkPlexUserId(username, plexUserID) {
|
||||
return new Promise((resolve, reject) => {
|
||||
this.database
|
||||
.run(this.queries.link, [plexUserID, username])
|
||||
.then(row => resolve(row))
|
||||
.catch(error => {
|
||||
// TODO log this unknown db error
|
||||
console.error("db error", error);
|
||||
|
||||
reject({
|
||||
status: 500,
|
||||
message:
|
||||
"An unexpected error occured while linking plex and seasoned accounts",
|
||||
source: "seasoned database"
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Unlink plex userid with seasoned user
|
||||
* @param {User} user the user you want to lunk plex userid with
|
||||
* @returns {Promsie}
|
||||
*/
|
||||
unlinkPlexUserId(username) {
|
||||
return new Promise((resolve, reject) => {
|
||||
this.database
|
||||
.run(this.queries.unlink, username)
|
||||
.then(row => resolve(row))
|
||||
.catch(error => {
|
||||
// TODO log this unknown db error
|
||||
console.log("db error", error);
|
||||
|
||||
reject({
|
||||
status: 500,
|
||||
message:
|
||||
"An unexpected error occured while unlinking plex and seasoned accounts",
|
||||
source: "seasoned database"
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the user has boolean flag set for admin in database
|
||||
* @param {User} user object
|
||||
* @returns {Promsie}
|
||||
*/
|
||||
checkAdmin(user) {
|
||||
return this.database
|
||||
.get(this.queries.getAdminStateByUser, user.username)
|
||||
.then(row => row.admin);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get settings for user matching string username
|
||||
* @param {String} username
|
||||
* @returns {Promsie}
|
||||
*/
|
||||
getSettings(username) {
|
||||
return new Promise((resolve, reject) => {
|
||||
this.database
|
||||
.get(this.queries.getSettings, username)
|
||||
.then(async row => {
|
||||
if (row == null) {
|
||||
console.debug(
|
||||
`settings do not exist for user: ${username}. Creating settings entry.`
|
||||
);
|
||||
|
||||
const userExistsWithUsername = await this.database.get(
|
||||
"select * from user where user_name is ?",
|
||||
username
|
||||
);
|
||||
if (userExistsWithUsername !== undefined) {
|
||||
try {
|
||||
resolve(this.dbCreateSettings(username));
|
||||
} catch (error) {
|
||||
reject(error);
|
||||
}
|
||||
} else {
|
||||
reject({
|
||||
status: 404,
|
||||
message: "User not found, no settings to get"
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
resolve(row);
|
||||
})
|
||||
.catch(error => {
|
||||
console.error(
|
||||
"Unexpected error occured while fetching settings for your account. Error:",
|
||||
error
|
||||
);
|
||||
reject({
|
||||
status: 500,
|
||||
message:
|
||||
"An unexpected error occured while fetching settings for your account",
|
||||
source: "seasoned database"
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Update settings values for user matching string username
|
||||
* @param {String} username
|
||||
* @param {String} dark_mode
|
||||
* @param {String} emoji
|
||||
* @returns {Promsie}
|
||||
*/
|
||||
updateSettings(username, dark_mode = undefined, emoji = undefined) {
|
||||
const settings = this.getSettings(username);
|
||||
dark_mode = dark_mode !== undefined ? dark_mode : settings.dark_mode;
|
||||
emoji = emoji !== undefined ? emoji : settings.emoji;
|
||||
|
||||
return this.dbUpdateSettings(username, dark_mode, emoji).catch(error => {
|
||||
if (error.status && error.message) {
|
||||
return error;
|
||||
}
|
||||
|
||||
return {
|
||||
status: 500,
|
||||
message:
|
||||
"An unexpected error occured while updating settings for your account"
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function for creating settings in the database
|
||||
* @param {String} username
|
||||
* @returns {Promsie}
|
||||
*/
|
||||
dbCreateSettings(username) {
|
||||
return this.database
|
||||
.run(this.queries.createSettings, username)
|
||||
.then(() => this.database.get(this.queries.getSettings, username))
|
||||
.catch(error =>
|
||||
rejectUnexpectedDatabaseError(
|
||||
"Unexpected error occured while creating settings",
|
||||
503,
|
||||
error
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function for updating settings in the database
|
||||
* @param {String} username
|
||||
* @returns {Promsie}
|
||||
*/
|
||||
dbUpdateSettings(username, dark_mode, emoji) {
|
||||
return new Promise((resolve, reject) =>
|
||||
this.database
|
||||
.run(this.queries.updateSettings, [username, dark_mode, emoji])
|
||||
.then(row => resolve(row))
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const rejectUnexpectedDatabaseError = (
|
||||
message,
|
||||
status,
|
||||
error,
|
||||
reject = null
|
||||
) => {
|
||||
console.error(error);
|
||||
const body = {
|
||||
status,
|
||||
message,
|
||||
source: "seasoned database"
|
||||
};
|
||||
|
||||
if (reject == null) {
|
||||
return new Promise((resolve, reject) => reject(body));
|
||||
}
|
||||
reject(body);
|
||||
};
|
||||
|
||||
module.exports = UserRepository;
|
||||
75
src/user/userSecurity.js
Normal file
75
src/user/userSecurity.js
Normal file
@@ -0,0 +1,75 @@
|
||||
const bcrypt = require("bcrypt");
|
||||
const UserRepository = require("./userRepository");
|
||||
|
||||
class UserSecurity {
|
||||
constructor(database) {
|
||||
this.userRepository = new UserRepository(database);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new user in PlanFlix.
|
||||
* @param {User} user the new user you want to create
|
||||
* @param {String} clearPassword a password of the user
|
||||
* @returns {Promise}
|
||||
*/
|
||||
createNewUser(user, clearPassword) {
|
||||
if (user.username.trim() === "") {
|
||||
throw new Error("The username is empty.");
|
||||
} else if (clearPassword.trim() === "") {
|
||||
throw new Error("The password is empty.");
|
||||
} else {
|
||||
return this.userRepository
|
||||
.create(user)
|
||||
.then(() => UserSecurity.hashPassword(clearPassword))
|
||||
.then(hash => this.userRepository.changePassword(user, hash));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Login into PlanFlix.
|
||||
* @param {User} user the user you want to login
|
||||
* @param {String} clearPassword the user's password
|
||||
* @returns {Promise}
|
||||
*/
|
||||
login(user, clearPassword) {
|
||||
return this.userRepository
|
||||
.retrieveHash(user)
|
||||
.then(hash => UserSecurity.compareHashes(hash, clearPassword))
|
||||
.catch(() => {
|
||||
throw new Error("Incorrect username or password.");
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Compare between a password and a hash password from database.
|
||||
* @param {String} hash the hash password from database
|
||||
* @param {String} clearPassword the user's password
|
||||
* @returns {Promise}
|
||||
*/
|
||||
static compareHashes(hash, clearPassword) {
|
||||
return new Promise((resolve, reject) => {
|
||||
bcrypt.compare(clearPassword, hash, (error, match) => {
|
||||
if (match) resolve(true);
|
||||
reject(false);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Hashes a password.
|
||||
* @param {String} clearPassword the user's password
|
||||
* @returns {Promise}
|
||||
*/
|
||||
static hashPassword(clearPassword) {
|
||||
return new Promise(resolve => {
|
||||
const saltRounds = 10;
|
||||
bcrypt.hash(clearPassword, saltRounds, (error, hash) => {
|
||||
if (error) reject(error);
|
||||
|
||||
resolve(hash);
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = UserSecurity;
|
||||
Reference in New Issue
Block a user