Commit 62f4082d by Ali Arshad

Merge branch 'user-registration' into 'master'

User-Profile

See merge request jibc/jibc-meteor-api!3
parents 536ccf81 6752cdc3
/* tslint:disable:no-require-imports */ /* tslint:disable:no-require-imports */
// tslint:disable-next-line:import-name import s3 from 'aws-sdk/clients/s3';
import S3 from 'aws-sdk/clients/s3';
import { FilesCollection } from 'meteor/ostrio:files'; import { FilesCollection } from 'meteor/ostrio:files';
import '../env'; import '../env';
...@@ -10,9 +9,7 @@ let s3Conf; ...@@ -10,9 +9,7 @@ let s3Conf;
let sendToStorage; let sendToStorage;
let interceptDownload; let interceptDownload;
let fileDir = './'; let fileDir = './';
const bound = Meteor.bindEnvironment((callback) => { const bound = Meteor.bindEnvironment(callback => callback());
return callback();
});
if (Meteor.isServer) { if (Meteor.isServer) {
const fs = require('fs'); const fs = require('fs');
...@@ -22,7 +19,7 @@ if (Meteor.isServer) { ...@@ -22,7 +19,7 @@ if (Meteor.isServer) {
fileDir = os.tmpdir(); fileDir = os.tmpdir();
s3Conf = JSON.parse(process.env.S3); s3Conf = JSON.parse(process.env.S3);
s3Client = new S3({ s3Client = new s3({
secretAccessKey: s3Conf.secret, secretAccessKey: s3Conf.secret,
accessKeyId: s3Conf.key, accessKeyId: s3Conf.key,
region: s3Conf.region, region: s3Conf.region,
...@@ -45,11 +42,14 @@ if (Meteor.isServer) { ...@@ -45,11 +42,14 @@ if (Meteor.isServer) {
(error) => { (error) => {
bound(() => { bound(() => {
if (error) { if (error) {
// console.error(error); // tslint:disable-next-line:no-console
console.error(error);
} else { } else {
files.update({ _id: fileRef._id }, { $set: { uploadedToS3: true } }); filesCollection.update({ _id: fileRef._id }, { $set: { uploadedToS3: true } });
if (fs.existsSync(fileRef.path)) { if (fs.existsSync(fileRef.path)) {
fs.unlink(fileRef.path); fs.unlink(fileRef.path, () => {
// file deleted.
});
} }
} }
}); });
...@@ -86,7 +86,8 @@ if (Meteor.isServer) { ...@@ -86,7 +86,8 @@ if (Meteor.isServer) {
s3Client.getObject(opts, (error, data) => { s3Client.getObject(opts, (error, data) => {
if (error) { if (error) {
// console.error(error); // tslint:disable-next-line:no-console
console.error(error);
if (!http.response.finished) { if (!http.response.finished) {
http.response.end(); http.response.end();
} }
...@@ -125,7 +126,7 @@ const onAfterUpload = (fileRef) => { ...@@ -125,7 +126,7 @@ const onAfterUpload = (fileRef) => {
} }
}; };
export const files = new FilesCollection({ export const filesCollection = new FilesCollection({
onBeforeUpload, onBeforeUpload,
onAfterUpload, onAfterUpload,
interceptDownload, interceptDownload,
...@@ -135,7 +136,7 @@ export const files = new FilesCollection({ ...@@ -135,7 +136,7 @@ export const files = new FilesCollection({
}); });
if (Meteor.isServer) { if (Meteor.isServer) {
files.denyClient(); filesCollection.denyClient();
} }
if (Meteor.isClient) { if (Meteor.isClient) {
...@@ -143,5 +144,5 @@ if (Meteor.isClient) { ...@@ -143,5 +144,5 @@ if (Meteor.isClient) {
} }
if (Meteor.isServer) { if (Meteor.isServer) {
Meteor.publish('files.images.all', () => files.collection.find({})); Meteor.publish('files.images.all', () => filesCollection.collection.find({}));
} }
import { MongoObservable } from 'meteor-rxjs'; import { MongoObservable } from 'meteor-rxjs';
import { SettingModel } from '../models/setting.model';
export const settingsCollection = new MongoObservable.Collection('setting'); export const settingsCollection = new MongoObservable.Collection<SettingModel>('setting');
...@@ -5,8 +5,27 @@ export const PERMISSIONS = { ...@@ -5,8 +5,27 @@ export const PERMISSIONS = {
CAN_UPDATE_USER: 'CAN_UPDATE_USER', CAN_UPDATE_USER: 'CAN_UPDATE_USER',
CAN_SEE_ALL_USERS: 'CAN_SEE_ALL_USERS', CAN_SEE_ALL_USERS: 'CAN_SEE_ALL_USERS',
CAN_ACCESS_DASHBOARD_PAGE: 'CAN_ACCESS_DASHBOARD_PAGE', CAN_ACCESS_DASHBOARD_PAGE: 'CAN_ACCESS_DASHBOARD_PAGE',
CAN_UPDATE_SETTINGS: 'CAN_UPDATE_SETTINGS',
CAN_ACCESS_SETTINGS: 'CAN_ACCESS_SETTINGS',
CAN_ACCESS_EMAIL_TEMPLATES: 'CAN_ACCESS_EMAIL_TEMPLATES',
CAN_UPDATE_ALL_USERS: 'CAN_UPDATE_ALL_USERS',
}; };
export const CONFIG = { export const CONFIG = {
SiteName: 'JIBC', SiteName: 'JIBC',
}; };
export const ERR = {
EMAIL_EXISTS: 'Email address already in use.',
EMAIL_NOT_EXISTS: 'Email address doesn\'t exist.',
ID_MISSING: 'UserId is missing',
EMAIL_MISSING: 'Email is missing',
DATA_MISSING: 'Required fields are missing',
FORBIDDEN: 'Not Enough Permissions',
};
export const E_CODE = {
UNPROCESSABLE_ENTITY: 422,
SERVER_ERROR: 500,
FORBIDDEN: 403,
};
...@@ -12,11 +12,4 @@ Meteor.startup(() => { ...@@ -12,11 +12,4 @@ Meteor.startup(() => {
return user && user.profile.role return user && user.profile.role
&& UtilsService.hasPermissionOfUser(user, PERMISSIONS.CAN_LOGIN); && UtilsService.hasPermissionOfUser(user, PERMISSIONS.CAN_LOGIN);
}); });
// Changing url of reset password
Accounts.emailTemplates.resetPassword.text = (user, url) => {
const token = url.substring(url.lastIndexOf('/') + 1, url.length);
return Meteor.absoluteUrl(`auth/reset-password/${token}`);
};
}); });
import { Meteor } from 'meteor/meteor'; import { Meteor } from 'meteor/meteor';
import { EmailService } from '../services/email.service'; import { EmailService } from '../services/email.service';
import { E_CODE, ERR } from '../config';
Meteor.methods({ Meteor.methods({
sendForgotPasswordEmail(email: string): boolean { sendForgotPasswordEmail(email: string): boolean {
...@@ -8,6 +9,6 @@ Meteor.methods({ ...@@ -8,6 +9,6 @@ Meteor.methods({
EmailService.sendForgotPasswordEmail(user, email); EmailService.sendForgotPasswordEmail(user, email);
return true; return true;
} }
throw new Meteor.Error(422, 'Email address doesn\'t exist.'); throw new Meteor.Error(E_CODE.UNPROCESSABLE_ENTITY, ERR.EMAIL_NOT_EXISTS);
}, },
}); });
import { Meteor } from 'meteor/meteor'; import { Meteor } from 'meteor/meteor';
import { first } from 'rxjs/operators'; import { first } from 'rxjs/operators';
import { rolesCollection } from '../collections/role.collection'; import { rolesCollection } from '../collections/role.collection';
import { PERMISSIONS } from '../config'; import { E_CODE, PERMISSIONS } from '../config';
import { RoleModel } from '../models/role.model'; import { RoleModel } from '../models/role.model';
import { UtilsService } from '../services/utils.service'; import { UtilsService } from '../services/utils.service';
...@@ -14,7 +14,7 @@ Meteor.methods({ ...@@ -14,7 +14,7 @@ Meteor.methods({
.toPromise(); .toPromise();
} }
} catch (e) { } catch (e) {
throw new Meteor.Error('Unable to add.', JSON.stringify(e)); throw new Meteor.Error(E_CODE.SERVER_ERROR, JSON.stringify(e));
} }
}, },
}); });
import { Meteor } from 'meteor/meteor';
import { E_CODE, ERR, PERMISSIONS } from '../config';
import { UtilsService } from '../services/utils.service';
import { settingsCollection } from '../collections/setting.collections';
Meteor.methods({
updateSettings(setting: any): void {
if (UtilsService.hasPermission(PERMISSIONS.CAN_UPDATE_SETTINGS)) {
Object.keys(setting)
.forEach(key => settingsCollection.update({ Key: key }, { $set: { Value: setting[key] } }));
} else {
throw new Meteor.Error(E_CODE.FORBIDDEN, ERR.FORBIDDEN);
}
},
updateEmailTemplates(setting: any): void {
if (UtilsService.hasPermission(PERMISSIONS.CAN_UPDATE_SETTINGS)) {
settingsCollection.update(setting.id, {
$set: {
Value: {
SUBJECT: setting.subject,
CONTENT: setting.content,
},
},
});
} else {
throw new Meteor.Error(E_CODE.FORBIDDEN, ERR.FORBIDDEN);
}
},
});
import { Meteor } from 'meteor/meteor'; import { Meteor } from 'meteor/meteor';
import { rolesCollection } from '../collections/role.collection'; import { rolesCollection } from '../collections/role.collection';
import { PERMISSIONS } from '../config'; import { ERR, E_CODE, PERMISSIONS } from '../config';
import { UtilsService } from '../services/utils.service'; import { UtilsService } from '../services/utils.service';
import { QueryModel } from '../models/query.model';
import { EmailService } from '../services/email.service'; import { EmailService } from '../services/email.service';
Meteor.methods({ Meteor.methods({
usersGetCount(query: QueryModel): any {
return {
recordsFiltered: Meteor.users.find(query.query).count(),
recordsTotal: Meteor.users.find().count(),
};
},
registerUser(user: any): string { registerUser(user: any): string {
const defaultRole = rolesCollection.findOne({ title: user.role }); const defaultRole = rolesCollection.findOne({ title: user.role });
if (!Accounts.findUserByEmail(user.email)) { if (!Accounts.findUserByEmail(user.email)) {
const userObj = { const userObj = {
email: user.email, email: user.email,
...@@ -28,26 +20,76 @@ Meteor.methods({ ...@@ -28,26 +20,76 @@ Meteor.methods({
}, },
}; };
const userId = Accounts.createUser(userObj); const userId = Accounts.createUser(userObj);
EmailService.sendVerificationEmail(user, userId); const usr = Accounts.findUserByUsername(user.username);
EmailService.sendVerificationEmail(usr, userId);
return userId; return userId;
} }
throw new Meteor.Error(422, 'Email address already in use.'); throw new Meteor.Error(E_CODE.UNPROCESSABLE_ENTITY, ERR.EMAIL_EXISTS);
},
verifyEmailAddress(user: any): any {
if (Accounts.findUserByEmail(user.email)) {
const usr = Accounts.findUserByEmail(user.email);
EmailService.sendVerificationEmail(usr, user._id, user.email);
return true;
}
throw new Meteor.Error(E_CODE.UNPROCESSABLE_ENTITY, ERR.EMAIL_EXISTS);
}, },
updateUser(user: any): any { updateUser(user: any): any {
if (!UtilsService.hasPermission(PERMISSIONS.CAN_UPDATE_OWN_USER)) { if (!user._id) {
throw new Meteor.Error(403, 'Forbidden.'); throw new Meteor.Error(E_CODE.UNPROCESSABLE_ENTITY, ERR.ID_MISSING);
} }
if (user._id) { if (UtilsService.hasPermission(PERMISSIONS.CAN_UPDATE_ALL_USERS) ||
if (user.password) { (
Accounts.setPassword(user._id, user.password); UtilsService.hasPermission(PERMISSIONS.CAN_UPDATE_OWN_USER)
} && user._id === Meteor.user()._id
)) {
const updateObj = { $set: { profile: user.profile } }; const updateObj = { $set: { profile: user.profile } };
return Meteor.users.update(user._id, updateObj); return Meteor.users.update(user._id, updateObj);
} }
throw new Meteor.Error(E_CODE.FORBIDDEN, ERR.FORBIDDEN);
},
addEmailAddress(user: any): any {
if (!user._id || !user.email) {
throw new Meteor.Error(E_CODE.UNPROCESSABLE_ENTITY, ERR.DATA_MISSING);
}
if (Accounts.findUserByEmail(user.email)) {
throw new Meteor.Error(E_CODE.UNPROCESSABLE_ENTITY, ERR.EMAIL_EXISTS);
}
if (UtilsService.hasPermission(PERMISSIONS.CAN_UPDATE_ALL_USERS) ||
(
UtilsService.hasPermission(PERMISSIONS.CAN_UPDATE_OWN_USER)
&& user._id === Meteor.user()._id
)) {
Accounts.addEmail(user._id, user.email);
return true;
}
throw new Meteor.Error(E_CODE.FORBIDDEN, ERR.FORBIDDEN);
},
removeEmailAddress(user: any): any {
if (!user._id || !user.email) {
throw new Meteor.Error(E_CODE.UNPROCESSABLE_ENTITY, ERR.DATA_MISSING);
}
if (UtilsService.hasPermission(PERMISSIONS.CAN_UPDATE_ALL_USERS) ||
(
UtilsService.hasPermission(PERMISSIONS.CAN_UPDATE_OWN_USER)
&& user._id === Meteor.user()._id
)) {
Accounts.removeEmail(user._id, user.email);
return true;
}
throw new Meteor.Error(E_CODE.FORBIDDEN, ERR.FORBIDDEN);
}, },
checkUserByEmail(email: string): any { checkUserByEmail(email: string): any {
......
...@@ -18,7 +18,8 @@ Migrations.add({ ...@@ -18,7 +18,8 @@ Migrations.add({
}); });
rolesCollection.insert({ rolesCollection.insert({
title: 'User', title: 'User',
permissions: [PERMISSIONS.CAN_LOGIN, PERMISSIONS.CAN_UPDATE_OWN_USER], permissions: [PERMISSIONS.CAN_LOGIN, PERMISSIONS.CAN_UPDATE_OWN_USER,
PERMISSIONS.CAN_ACCESS_DASHBOARD_PAGE],
description: '', description: '',
}); });
rolesCollection.insert({ rolesCollection.insert({
...@@ -35,18 +36,27 @@ Migrations.add({ ...@@ -35,18 +36,27 @@ Migrations.add({
// @ts-ignore // @ts-ignore
Migrations.add({ Migrations.add({
version: 2, version: 2,
name: 'Adding Admin User', name: 'Adding Admin & User',
up() { up() {
if (!Meteor.users.find({}).fetch().length) { if (!Meteor.users.find({}).fetch().length) {
Accounts.createUser({ Accounts.createUser({
username: 'admin', username: 'admin',
password: 'admin', password: 'Admin123',
email: 'ali.arshad@vqode.com', email: 'ali.arshad@vqode.com',
profile: { profile: {
name: 'Admin User', name: 'Admin User',
role: rolesCollection.findOne({ title: 'Admin' })._id, role: rolesCollection.findOne({ title: 'Admin' })._id,
}, },
}); });
Accounts.createUser({
username: 'lead',
password: 'Qweasdzxc1',
email: 'lead@mailinator.com',
profile: {
name: 'Lead',
role: rolesCollection.findOne({ title: 'User' })._id,
},
});
} }
}, },
}); });
...@@ -56,17 +66,28 @@ Migrations.add({ ...@@ -56,17 +66,28 @@ Migrations.add({
version: 3, version: 3,
name: 'Adding Email Templates', name: 'Adding Email Templates',
up() { up() {
if (settingsCollection.find({ REGISTER_HEADING: { $exists: true } }).fetch().length === 0) { if (settingsCollection.find({ Key: 'REGISTER' }).fetch().length === 0) {
settingsCollection.insert({ REGISTER_HEADING: 'Welcome to $platform' }); settingsCollection.insert({
} Key: 'REGISTER',
if (settingsCollection.find({ REGISTER_EMAIL_BODY: { $exists: true } }).fetch().length === 0) { Value: {
settingsCollection.insert({ REGISTER_EMAIL_BODY: 'Hi $name! You are our $role now. Please click on the link <a href="$link" target="_blank">$link</a> to verify your account.' }); SUBJECT: 'Welcome to $platform',
} CONTENT: 'Hi $name! You are our $role now. Please click on the link <a href="$link" target="_blank">$link</a> to verify your account.',
if (settingsCollection.find({ RESET_HEADING: { $exists: true } }).fetch().length === 0) { },
settingsCollection.insert({ RESET_HEADING: 'Reset Password for $platform' }); Description: 'Register Email Template',
IsEmail: true,
});
} }
if (settingsCollection.find({ RESET_EMAIL_BODY: { $exists: true } }).fetch().length === 0) {
settingsCollection.insert({ RESET_EMAIL_BODY: 'Hi $name! Don\'t worry. Reset your password by clicking on the following link <a href="$link" target="_blank">$link</a>.' }); if (settingsCollection.find({ Key: 'RESET' }).fetch().length === 0) {
settingsCollection.insert({
Key: 'RESET',
Value: {
SUBJECT: 'Reset Password for $platform',
CONTENT: 'Hi $name! Don\'t worry. Reset your password by clicking on the following link <a href="$link" target="_blank">$link</a>.',
},
Description: 'Reset Password Email Template',
IsEmail: true,
});
} }
}, },
}); });
export interface SettingModel {
_id?: string;
Key?: string;
Value?: any;
Description?: string;
IsEmail?: boolean;
}
import { settingsCollection } from '../collections/setting.collections';
import { UtilsService } from '../services/utils.service';
import { E_CODE, ERR, PERMISSIONS } from '../config';
import { Meteor } from 'meteor/meteor';
Meteor.publish('settings', (filter: any = {}) => {
if (UtilsService.hasPermission(PERMISSIONS.CAN_ACCESS_SETTINGS)) {
if (filter.IsEmail && !UtilsService.hasPermission(PERMISSIONS.CAN_ACCESS_EMAIL_TEMPLATES)) {
throw new Meteor.Error(E_CODE.FORBIDDEN, ERR.FORBIDDEN);
}
return settingsCollection.find(filter);
}
throw new Meteor.Error(E_CODE.FORBIDDEN, ERR.FORBIDDEN);
});
...@@ -2,7 +2,7 @@ import { Meteor } from 'meteor/meteor'; ...@@ -2,7 +2,7 @@ import { Meteor } from 'meteor/meteor';
// tslint:disable-next-line:ban-ts-ignore // tslint:disable-next-line:ban-ts-ignore
// @ts-ignore // @ts-ignore
import { publishComposite } from 'meteor/reywood:publish-composite'; import { publishComposite } from 'meteor/reywood:publish-composite';
import { PERMISSIONS } from '../config'; import { E_CODE, ERR, PERMISSIONS } from '../config';
import { QueryModel } from '../models/query.model'; import { QueryModel } from '../models/query.model';
import { UtilsService } from '../services/utils.service'; import { UtilsService } from '../services/utils.service';
import { rolesCollection } from '../collections/role.collection'; import { rolesCollection } from '../collections/role.collection';
...@@ -12,7 +12,7 @@ publishComposite('usersList', (filters = {}) => { ...@@ -12,7 +12,7 @@ publishComposite('usersList', (filters = {}) => {
const queryFilters = { ...new QueryModel(), ...filters }; const queryFilters = { ...new QueryModel(), ...filters };
if (!UtilsService.hasPermission([PERMISSIONS.CAN_SEE_ALL_USERS])) { if (!UtilsService.hasPermission([PERMISSIONS.CAN_SEE_ALL_USERS])) {
throw new Meteor.Error(403, 'Not enough permissions to get all users'); throw new Meteor.Error(E_CODE.FORBIDDEN, ERR.FORBIDDEN);
} }
return { return {
......
import { CONFIG } from '../config'; import { CONFIG } from '../config';
import { settingsCollection } from '../collections/setting.collections'; import { settingsCollection } from '../collections/setting.collections';
import { rolesCollection } from '../collections/role.collection';
export class EmailService { export class EmailService {
static sendVerificationEmail(user: any, userId: string): void { static sendVerificationEmail(user: any, userId: string, email = false): void {
Accounts.emailTemplates.siteName = CONFIG.SiteName; Accounts.emailTemplates.siteName = CONFIG.SiteName;
const mail = email ? email : user.emails[0].address;
const defaultRole = rolesCollection.findOne(user.profile.role);
// Will uncomment it later with domain email // Will uncomment it later with domain email
// Accounts.emailTemplates.from = CONFIG.DomainEmail; // Accounts.emailTemplates.from = CONFIG.DomainEmail;
Accounts.emailTemplates.verifyEmail = { Accounts.emailTemplates.verifyEmail = {
subject() { subject() {
const content: any = settingsCollection.findOne({ REGISTER_HEADING: { $exists: true } }); const content: any = settingsCollection.findOne({ Key: 'REGISTER' });
return content ? content.REGISTER_HEADING.replace('$platform', CONFIG.SiteName) : 'Welcome';
if (content) {
return EmailService.formattedContent(
content.Value.SUBJECT, user.profile.name, defaultRole.title, mail);
}
return 'Welcome';
}, },
html(usr, url) { html(usr, url) {
const token = url.substr(url.lastIndexOf('/') + 1); const token = url.substr(url.lastIndexOf('/') + 1);
const link = `${process.env.appUrl}#/auth/verify-email/${token}`; const link = `${process.env.appUrl}#/auth/verify-email/${token}`;
const content: any = settingsCollection.findOne({ REGISTER_EMAIL_BODY: { $exists: true } }); const content: any = settingsCollection.findOne({ Key: 'REGISTER' });
return content.REGISTER_EMAIL_BODY.replace(/\$name/g, user.name)
.replace(/\$role/g, user.role) return EmailService.formattedContent(
.replace(/\$link/g, link); content.Value.CONTENT, user.profile.name, defaultRole.title, mail, link);
}, },
}; };
Accounts.sendVerificationEmail(userId, user.email); Accounts.sendVerificationEmail(userId, mail);
} }
static sendForgotPasswordEmail(user: Meteor.User, email: string): void { static sendForgotPasswordEmail(user: Meteor.User, email: string): void {
Accounts.emailTemplates.siteName = CONFIG.SiteName; Accounts.emailTemplates.siteName = CONFIG.SiteName;
const mail = email ? email : user.emails[0].address;
const defaultRole = rolesCollection.findOne(user.profile.role);
// Will uncomment it later with domain email // Will uncomment it later with domain email
// Accounts.emailTemplates.from = CONFIG.DomainEmail; // Accounts.emailTemplates.from = CONFIG.DomainEmail;
Accounts.emailTemplates.resetPassword = { Accounts.emailTemplates.resetPassword = {
subject() { subject() {
const content: any = settingsCollection.findOne({ RESET_HEADING: { $exists: true } }); const content: any = settingsCollection.findOne({ Key: 'RESET' });
return content ? content.RESET_HEADING.replace('$platform', CONFIG.SiteName) : 'Welcome';
if (content) {
return EmailService.formattedContent(
content.Value.SUBJECT, user.profile.name, defaultRole.title, mail);
}
return 'Reset Password';
}, },
html(usr, url) { html(usr, url) {
const token = url.substr(url.lastIndexOf('/') + 1); const token = url.substr(url.lastIndexOf('/') + 1);
const link = `${process.env.appUrl}#/auth/reset-password/${token}`; const link = `${process.env.appUrl}#/auth/reset-password/${token}`;
const content: any = settingsCollection.findOne({ RESET_EMAIL_BODY: { $exists: true } }); const content: any = settingsCollection.findOne({ Key: 'RESET' });
return content.RESET_EMAIL_BODY.replace(/\$name/g, user.profile.name)
.replace(/\$link/g, link); return EmailService.formattedContent(
content.Value.CONTENT, user.profile.name, defaultRole.title, mail, link);
}, },
}; };
Accounts.sendResetPasswordEmail(user._id, email); Accounts.sendResetPasswordEmail(user._id, email);
} }
static formattedContent(content: string, name: string, role: string, email, link = null): string {
const str = content.replace(/\$name/g, name)
.replace(/\$role/g, role)
.replace(/\$platform/g, CONFIG.SiteName)
.replace(/\$email/g, email);
return link ? str.replace(/\$link/g, link) : str;
}
} }
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment