node-user-accounts-boilerplate-nahid/setup.js
"use strict";
const audit = require('./helper/audit');
const access = require('./helper/access');
const bootstrap = require('./helper/bootstrap');
const cookieParser = require('cookie-parser');
const expressSession = require('express-session');
const json = require('body-parser')
.json();
const parseFilters = require('./filters/parse');
const passport = require('passport');
const CollectionSessionStore = require('./session/CollectionSessionStore');
const MemorySessionStore = require('./session/MemorySessionStore');
const passwordField = require('./fields/password');
const defaultFields = require('./fields/defaultFields');
/**
* Library entry point
*
* @param {Express} app result of express()
* @param {Config} config configuration
*/
function setup(app, config)
{
// PASSPORT BOILERPLATE
app.use(bootstrap(app, config));
app.use(cookieParser());
app.use(expressSession({
secret: Math.round(Date.now() / 1000 / 3600 / 24)
.toString(16),
resave: true,
saveUninitialized: false,
store: config.sessions ? new CollectionSessionStore(config.sessions, config) : new MemorySessionStore(config),
}));
app.use(passport.initialize());
app.use(passport.session());
config.users = config.users || config.collection;
// User serialisation
passport.serializeUser(function (user, done)
{
done(null, user.id || 'none');
});
passport.deserializeUser(function (user, done)
{
done(null, config.users.lookup[user] || {});
});
// API endpoint
config.prefix = config.prefix || '/api/accounts';
config.fields = config.fields || {};
for (let field in defaultFields)
{
if (!config.fields[field])
{
config.fields[field] = defaultFields[field];
}
}
// setup APIs
setupAuthAPI(app, config);
setupAccountsAPI(app, config);
}
function setupAuthAPI(app, config)
{
const prefix = config.prefix;
// login/registration
const methods = [];
for (let auth of config.auth || [])
{
methods.push(auth.description);
auth.install(app, `${prefix}/${auth.method}`, passport);
if (auth.description.usesPassword && !config.fields.password)
{
config.fields.password = passwordField;
}
}
app.get(`${prefix}/methods.json`, (req, res) =>
{
res.json(methods);
});
const fields = summariseFields(config.fields);
app.get(`${prefix}/fields.json`, (req, res) =>
{
res.json(fields);
});
// self read
app.get(`${prefix}/current.json`, (req, res) =>
{
if (req.user && req.user.id)
{
res.json(summariseUserRecord(req.user, config.fields));
}
else
{
res.json(false);
}
});
// self update
app.put(`${prefix}/current.json`, access.LOGGEDIN, json, async(req, res) =>
{
let user = req.user;
if (req.body)
{
try
{
await updateUserRecord(user, req.body || {}, user, config);
res.resolve(config.users.updateRecord(user), 'Done', audit.ACCOUNT_CHANGE);
}
catch (e)
{
res.error(e.message, `${audit.ACCOUNT_CHANGE}_FAILURE`)
}
}
});
// logout
app.all(`${prefix}/logout.json`, (req, res) =>
{
res.success('Logged out', audit.LOGOUT);
req.logout();
req.session.destroy();
});
}
function setupAccountsAPI(app, config)
{
// ACCOUNT MANAGEMENT API
const prefix = config.prefix;
const collection = config.users;
// discover
const USER_DATA_ACCESS = access.ROLE_ONE_OF(config.administratorRoles || {});
app.all(`${prefix}/search.json`, USER_DATA_ACCESS, (req, res) =>
{
let query = parseFilters(collection.searchMeta, req.query);
res.resolve(collection.searchRecords(query), false, audit.ACCOUNT_SEARCH, JSON.stringify(query));
});
app.post(`${prefix}/:user.json`, USER_DATA_ACCESS, json, async(req, res) =>
{
try
{
let auth = config.auth.filter(auth => auth.method === req.body.type)[0];
let user = auth.findUser(req.body.value);
if (user)
{
throw new Error('user already exists');
}
let profile = await auth.createProfileFromCredential(req.body.value, req.body);
user = auth.createUserFromProfile(profile);
user = await config.users.createRecord(user);
res.success({
success: 'Created!',
user
}, audit.ACCOUNT_CREATE + audit.SUCCESS);
}
catch (e)
{
res.audit(audit.ACCOUNT_CREATE + audit.FAILURE, e.message, JSON.stringify(req.body));
res.error('Account creation failed')
}
});
// read
app.get(`${prefix}/:user.json`, USER_DATA_ACCESS, (req, res) =>
{
let query = {};
query[collection.primaryKey] = req.params.user;
collection.readRecord(query)
.then((user) =>
{
res.json(summariseUserRecord(user, config.fields, {
credentials: true
}));
res.audit(audit.ACCOUNT_READ + audit.SUCCESS, JSON.stringify(query));
}, res.reject(audit.ACCOUNT_READ + audit.FAILURE), JSON.stringify(query));
});
// update
app.put(`${prefix}/:user.json`, USER_DATA_ACCESS, json, (req, res) =>
{
let query = {};
query[collection.primaryKey] = req.params.user;
collection.readRecord(query)
.then(async(record) =>
{
let params = Object.keys(req.body);
params = JSON.stringify(Object.assign({
params
}, query));
try
{
await updateUserRecord(record, req.body, req.user, config);
res.resolve(collection.updateRecord(record), 'Done', audit.ACCOUNT_UPDATE, params);
}
catch (e)
{
res.error(e.message, audit.ACCOUNT_UPDATE + audit.FAILURE, params);
}
}, res.reject(audit.ACCOUNT_UPDATE + audit.FAILURE, JSON.stringify(query)));
});
// delete
app.delete(`${prefix}/:user.json`, USER_DATA_ACCESS, (req, res) =>
{
let query = {};
query[collection.primaryKey] = req.params.user;
if (req.params.user !== req.user.id)
{
res.resolve(collection.deleteRecord(query), 'User deleted', audit.ACCOUNT_DELETE, JSON.stringify(req.params));
}
else
{
res.error("Can't delete yourself", audit.ACCOUNT_DELETE + audit.FAILURE);
}
});
}
function summariseUserRecord(user, fields, addiionalToInclude = {})
{
const output = {};
for (let field in user)
{
const meta = fields[field];
if (meta)
{
if (meta.mask)
{
output[field] = true;
}
else if (meta.view)
{
output[field] = meta.view(user[field]);
}
else
{
output[field] = user[field];
}
}
else if (addiionalToInclude[field])
{
output[field] = user[field];
}
}
output.roles = user.roles;
return output;
}
async function updateUserRecord(user, update, loginUser, config)
{
for (let field in update)
{
const meta = config.fields[field];
if (meta)
{
const value = update[field];
if (meta.self === false && loginUser.id === user.id)
{
throw new Error(`Can't update your own ${field}`);
}
await meta.assign(user, field, value, meta, loginUser, config);
continue;
}
else
{
throw new Error(`${field} not editable`);
}
}
}
function summariseFields(inputFields)
{
let fields = Object.keys(inputFields)
.map(field =>
{
return {
name: field,
order: inputFields[field].order || 10,
type: inputFields[field].type || 'string',
self: inputFields[field].self !== undefined ? inputFields[field].self : true,
admin: inputFields[field].admin !== undefined ? inputFields[field].admin : true,
enabled: inputFields[field].enabled !== undefined ? inputFields[field].enabled : true,
};
});
fields.sort(function (a, b)
{
if (a.order !== b.order)
{
return a.order - b.order;
}
else
{
if (a.name < b.name)
{
return -1
}
else
{
return 1;
}
}
});
return fields;
}
module.exports = setup;