gypsum
Version:
Simple and easy lightweight typescript server side framework on Node.js.
586 lines • 24.7 kB
JavaScript
"use strict";
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
return c > 3 && r && Object.defineProperty(target, key, r), r;
};
Object.defineProperty(exports, "__esModule", { value: true });
const path = require("path");
const fs = require("fs");
const MongoDB = require("mongodb");
const jwt = require("jsonwebtoken");
const crypt_1 = require("tools-box/crypt");
const string_1 = require("tools-box/string");
const unique_1 = require("tools-box/unique");
const Validall = require("validall");
const decorators_1 = require("../../decorators");
const models_1 = require("../../models");
const nodemailer_1 = require("nodemailer");
const state_1 = require("../../state");
const schema_1 = require("./schema");
const types_1 = require("../../types");
let Users = class Users extends models_1.MongoModel {
constructor(app) {
super(app);
if (state_1.State.auth.transporter) {
this.transporter = nodemailer_1.createTransport(state_1.State.auth.transporter);
}
else {
nodemailer_1.createTestAccount((err, account) => {
if (err)
throw err;
this.transporter = nodemailer_1.createTransport({
host: 'smtp.ethereal.email',
port: 587,
secure: false,
auth: {
user: account.user,
pass: account.pass
}
});
});
}
}
getRootUser() {
return new Promise((resolve, reject) => {
this.$logger.info('getting root user: ' + state_1.State.auth.rootUserEmail);
this.collection.findOne({ username: state_1.State.auth.rootUserEmail })
.then(doc => {
resolve(doc);
})
.catch(error => reject(error));
});
}
createRootUser() {
this.$logger.info('creating root user: ' + state_1.State.auth.rootUserEmail);
return new Promise((resolve, reject) => {
crypt_1.hash(state_1.State.auth.rootUserPassword)
.then(results => {
this.collection.insertOne({
email: state_1.State.auth.rootUserEmail,
password: results[0],
passwordSalt: results[1],
verified: true
})
.then(res => {
if (res && res.ops.length)
resolve(res.ops[0]);
else
reject('unable to create root user');
})
.catch(error => reject(error));
})
.catch(error => reject(error));
});
}
getSockets(ids) {
return new Promise((resolve, reject) => {
if (ids && ids.length) {
for (let i = 0; i < ids.length; i++)
if (typeof ids[i] === "string")
ids[i] = new MongoDB.ObjectID(ids[i]);
this.collection.find({ _id: { $in: ids } })
.project({ sockets: 1 })
.toArray()
.then(res => resolve(res
.map(entry => entry.sockets)
.reduce((a, b) => {
a.push(...(b.filter(entry => !!entry)));
return a;
}, [])))
.catch(err => reject(err));
}
else {
resolve([]);
}
});
}
pushToken(ctx) {
return new Promise((resolve, reject) => {
let responseData = ctx.response.data;
if (!responseData || !responseData._id)
return resolve();
responseData.token = jwt.sign({ id: responseData._id, date: Date.now(), type: 'auth' }, state_1.State.auth.tokenSecret);
resolve();
});
}
pushNotification(ctx, id) {
}
authenticate(ctx) {
return new Promise((resolve, reject) => {
this.$logger.info(`authenticating user for ${ctx.service.__name} service...`);
let token = ctx.getHeader('token') || ctx.body.token || ctx.query.token;
if (!token) {
return reject({
message: `[${this.name}]: user token is missing`,
code: types_1.RESPONSE_CODES.UNAUTHORIZED
});
}
let data;
try {
data = jwt.verify(token, state_1.State.auth.tokenSecret);
}
catch (err) {
return reject({
message: `[${this.name}]: invalid user token`,
code: types_1.RESPONSE_CODES.UNAUTHORIZED
});
}
if (!data || !data.id)
return reject({
message: `[${this.name}]: invalid user token`,
code: types_1.RESPONSE_CODES.UNAUTHORIZED
});
if (data.type !== 'verifyEmail' && data.type !== 'auth') {
return reject({
message: `[${this.name}]: fake token`,
code: types_1.RESPONSE_CODES.UNAUTHORIZED
});
}
if (data.type === 'auth' && (!data.date || ((Date.now() - data.date) > state_1.State.auth.tokenExpiry))) {
return reject({
message: `[${this.name}]: out dated token`,
code: types_1.RESPONSE_CODES.UNAUTHORIZED
});
}
ctx.set('tokenData', data);
this.collection.findOne({ _id: new MongoDB.ObjectID(data.id) })
.then(doc => {
if (!doc)
return reject({
message: `[${this.name}]: user no longer exists`,
code: types_1.RESPONSE_CODES.UNAUTHORIZED
});
if (data.type === "verifyEmail") {
ctx.user = doc;
return resolve();
}
let update = { $set: { lastVisit: Date.now() } };
if (ctx._socket && (doc.sockets.indexOf(ctx._socket.id) === -1))
update.$push = { sockets: ctx._socket.id };
this.collection.findOneAndUpdate({ _id: doc._id }, update, { returnOriginal: false })
.then((res) => {
ctx.user = res.value;
resolve();
})
.catch(err => reject({
message: `[${this.name}]: error updating user auth`,
original: err,
code: types_1.RESPONSE_CODES.UNKNOWN_ERROR
}));
})
.catch(err => reject({
message: `[${this.name}]: error finding user`,
original: err,
code: types_1.RESPONSE_CODES.UNKNOWN_ERROR
}));
});
}
verificationEmail(ctx) {
return new Promise((resolve, reject) => {
let user = ctx.user || ctx.response.data;
if (!user)
return reject({
message: 'user_not_provided',
code: types_1.RESPONSE_CODES.UNKNOWN_ERROR
});
fs.readFile(path.join(__dirname, '../templates/activation-email-template.html'), (err, template) => {
if (err)
return reject({
message: 'error_reading_activation_email_template',
original: err,
code: types_1.RESPONSE_CODES.UNKNOWN_ERROR
});
let token = jwt.sign({ id: user._id, date: Date.now(), type: 'verifyEmail' }, state_1.State.auth.tokenSecret);
let activationLink = `http${state_1.State.config.secure ? 's' : ''}://`;
activationLink += `${this.app.name}.${state_1.State.config.domain}/`;
activationLink += `${this.name}/activateUser?token=${token}`;
this.$logger.info('activation link:', activationLink);
let emailOptions = {
from: state_1.State.auth.supportEmail || `${state_1.State.config.server_name} Administration`,
to: user.email,
subject: `${state_1.State.config.server_name} Account Verification`,
html: string_1.compile(template.toString('utf-8'), { username: user.email.split('@')[0], activationLink })
};
this.transporter.sendMail(emailOptions, (sendEmailError, info) => {
if (sendEmailError)
return reject({
message: `error sending activation email`,
original: sendEmailError,
code: types_1.RESPONSE_CODES.UNKNOWN_ERROR
});
this.$logger.info('Message %s sent: %s', info.messageId, info.response);
resolve();
});
});
});
}
sendVerificationEmail(email, ctx) {
return new Promise((resolve, reject) => {
this.collection.findOne({ email: email })
.then(doc => {
if (doc) {
ctx.user = doc;
resolve({ data: true });
}
else {
reject({
message: 'email_not_found',
code: types_1.RESPONSE_CODES.UNAUTHORIZED
});
}
})
.catch(err => reject(err));
});
}
activateUser(ctx) {
return new Promise((resolve, reject) => {
let user = ctx.user;
let tokenData = ctx.get('tokenData');
if (tokenData.type !== "verifyEmail") {
return reject({
message: `[${this.name}]: fake token`,
code: types_1.RESPONSE_CODES.UNAUTHORIZED
});
}
if (!tokenData.date || ((Date.now() - tokenData.date) > state_1.State.auth.verificationEmailExpiry)) {
return reject({
message: `[${this.name}]: out dated token`,
code: types_1.RESPONSE_CODES.UNAUTHORIZED
});
}
this.collection
.updateOne({ _id: new MongoDB.ObjectID(user._id) }, { $set: { verified: true } })
.then(doc => {
fs.readFile(path.join(__dirname, '../templates/activation-page-template.html'), 'utf-8', (err, data) => {
if (err)
return reject({
message: `[${this.name}]: error reading activation page`,
original: err,
code: types_1.RESPONSE_CODES.UNKNOWN_ERROR
});
let message = 'Your account has been activated successfully';
let template = string_1.compile(data, { username: ctx.user.email.split('@')[0], message: message });
resolve({ data: template, type: 'html' });
});
})
.catch(error => reject({
message: `[${this.name}]: error activating account`,
original: error,
code: types_1.RESPONSE_CODES.UNKNOWN_ERROR
}));
});
}
forgetPassword(email) {
return new Promise((resolve, reject) => {
if (!email)
return reject({
message: 'email_is_required',
code: types_1.RESPONSE_CODES.BAD_REQUEST
});
if (!Validall.Is.email('email'))
return reject({
message: 'invalid_email',
code: types_1.RESPONSE_CODES.BAD_REQUEST
});
this.collection.findOne({ email: email })
.then(user => {
if (!user)
return reject({
message: 'email_is_not_registered',
code: types_1.RESPONSE_CODES.BAD_REQUEST
});
let newPassword = unique_1.Unique.Get();
let hashedPassword;
let passwordSalt;
crypt_1.hash(newPassword)
.then((result) => {
let hashedPassword = result[0];
let passwordSalt = result[1];
this.collection.findOneAndUpdate({ _id: user._id }, {
$set: {
password: hashedPassword,
passwordSalt: passwordSalt,
}
}, {
returnOriginal: false
})
.then(doc => {
fs.readFile(path.join(__dirname, '../templates/forget-password-email-template.html'), (err, template) => {
if (err)
return reject({
message: 'error reading forget password email template',
original: err,
code: types_1.RESPONSE_CODES.UNKNOWN_ERROR
});
let emailOptions = {
from: state_1.State.auth.supportEmail || `${state_1.State.config.server_name} Administration`,
to: user.email,
subject: `${state_1.State.config.server_name} Reset Password`,
html: string_1.compile(template.toString('utf-8'), { username: user.email.split('@')[0], password: newPassword })
};
this.transporter.sendMail(emailOptions, (sendEmailError, info) => {
if (sendEmailError)
return reject({
message: `error_sending_forget_password_email`,
original: sendEmailError,
code: types_1.RESPONSE_CODES.UNKNOWN_ERROR
});
this.$logger.info('Message %s sent: %s', info.messageId, info.response);
resolve(true);
});
});
})
.catch(err => reject({
message: 'error updaring user password',
original: err,
code: types_1.RESPONSE_CODES.UNKNOWN_ERROR
}));
})
.catch(err => reject({
message: 'error hashing new password',
original: err,
code: types_1.RESPONSE_CODES.UNKNOWN_ERROR
}));
})
.catch(err => reject({
message: 'error finding user',
original: err,
code: types_1.RESPONSE_CODES.UNKNOWN_ERROR
}));
});
}
verifyToken(token, ctx) {
return new Promise((resolve, reject) => {
if (ctx.user)
return resolve({ data: ctx.user });
reject({
message: 'invalid token',
code: types_1.RESPONSE_CODES.UNAUTHORIZED
});
});
}
signin(email, password, uuid, ctx) {
return new Promise((resolve, reject) => {
if (!email || !email.trim())
return reject({
message: 'email is required',
code: types_1.RESPONSE_CODES.BAD_REQUEST
});
if (!password || !password.trim())
return reject({
message: 'password is required',
code: types_1.RESPONSE_CODES.BAD_REQUEST
});
let query = {};
if (Validall.Is.email(email))
query.email = email;
else
return reject({
message: 'invalid email',
code: types_1.RESPONSE_CODES.BAD_REQUEST
});
this.collection
.findOne(query)
.then(doc => {
if (!doc || !Object.keys(doc).length)
return reject({
message: 'user is not found',
code: types_1.RESPONSE_CODES.UNAUTHORIZED
});
crypt_1.verify(password, doc.password, doc.passwordSalt)
.then((match) => {
if (match === true) {
let update = { $set: { lastVisit: Date.now() } };
if (uuid && doc.uuids.indexOf(uuid) === -1)
update.$push = { uuids: uuid };
this.updateById(doc._id, update)
.then(res => resolve(res));
}
else {
reject({
message: 'wrong password',
code: types_1.RESPONSE_CODES.UNAUTHORIZED
});
}
})
.catch(error => reject({
message: 'error verifying password',
original: error,
code: types_1.RESPONSE_CODES.UNKNOWN_ERROR
}));
})
.catch(error => reject({
message: `[${this.name}] - findOne: unknown error`,
original: error,
code: types_1.RESPONSE_CODES.UNKNOWN_ERROR
}));
});
}
signup(document, ctx) {
return new Promise((resolve, reject) => {
try {
let state = Validall(document, this.$get('schema'));
if (!state)
return reject({
message: Validall.error.message,
original: Validall.error,
code: types_1.RESPONSE_CODES.BAD_REQUEST
});
this.collection.count({ email: document.email })
.then(count => {
if (count)
return reject({
message: 'document with same email already exists!',
code: types_1.RESPONSE_CODES.BAD_REQUEST
});
crypt_1.hash(document.password)
.then(results => {
if (results && results.length) {
document.password = results[0];
document.passwordSalt = results[1];
this.insertOne(document)
.then(doc => {
resolve(doc);
})
.catch(error => reject(error));
}
else {
reject({
message: 'Error hashing password',
code: types_1.RESPONSE_CODES.UNKNOWN_ERROR
});
}
})
.catch(error => {
reject({
message: 'Error hashing password',
original: error,
code: types_1.RESPONSE_CODES.UNKNOWN_ERROR
});
});
})
.catch(err => reject({
message: 'error checking email existance!',
original: err,
code: types_1.RESPONSE_CODES.BAD_REQUEST
}));
}
catch (e) {
console.trace(e);
}
});
}
signout(uuid, ctx) {
return new Promise((resolve, reject) => {
let update = {};
if (ctx._socket)
update.$pop = { sockets: ctx._socket };
if (uuid)
update.$pop = { uuids: uuid };
if (!Object.keys(update).length)
return resolve(true);
this.collection.updateOne({ _id: ctx.user._id }, update)
.then(res => {
resolve({ data: true });
})
.catch(err => {
this.$logger.error('error signing user out:');
this.$logger.error(err);
reject({
message: 'error signing user out',
original: err,
code: types_1.RESPONSE_CODES.UNKNOWN_ERROR
});
});
});
}
};
__decorate([
decorators_1.HOOK()
], Users.prototype, "pushToken", null);
__decorate([
decorators_1.HOOK()
], Users.prototype, "pushNotification", null);
__decorate([
decorators_1.HOOK()
], Users.prototype, "authenticate", null);
__decorate([
decorators_1.HOOK({ private: true })
], Users.prototype, "verificationEmail", null);
__decorate([
decorators_1.SERVICE({
secure: false,
authorize: false,
args: ['body.email'],
method: 'post',
after: ['-filter', 'auth.users.verificationEmail']
})
], Users.prototype, "sendVerificationEmail", null);
__decorate([
decorators_1.SERVICE({
secure: true,
authorize: false,
after: ['-filter']
})
], Users.prototype, "activateUser", null);
__decorate([
decorators_1.SERVICE({
secure: false,
authorize: false,
method: 'post',
args: ['body.email'],
after: ['-filter']
})
], Users.prototype, "forgetPassword", null);
__decorate([
decorators_1.SERVICE({
secure: true,
authorize: false,
args: ['body.token'],
method: 'post',
after: [`auth.users.pushToken`]
})
], Users.prototype, "verifyToken", null);
__decorate([
decorators_1.SERVICE({
secure: false,
authorize: false,
args: ['body.email', 'body.password', 'body.uuid'],
method: 'post',
after: [`auth.users.pushToken`]
})
], Users.prototype, "signin", null);
__decorate([
decorators_1.SERVICE({
secure: false,
authorize: false,
args: ['body.document'],
method: 'post',
after: [`auth.users.pushToken`, `auth.users.verificationEmail`]
})
], Users.prototype, "signup", null);
__decorate([
decorators_1.SERVICE({
secure: true,
authorize: false,
args: ['body.uuid']
})
], Users.prototype, "signout", null);
Users = __decorate([
decorators_1.MODEL({
secure: true,
authorize: true,
schema: schema_1.usersSchema,
servicesOptions: {
FindById: { authorize: { match: '$params.id' } },
UpdateById: { authorize: { match: '$params.id' } }
},
domain: types_1.RESPONSE_DOMAINS.SELF,
after: ['filter:-password,passwordSalt,uuids,sockets,lastVisit'],
indexes: [{ name: 'email', options: { unique: true } }]
})
], Users);
exports.Users = Users;
//# sourceMappingURL=index.js.map