Commit e4606dc6 authored by Jarrod's avatar Jarrod 💬

Core tweakies

parent 7037ef8d
......@@ -26,11 +26,16 @@ module.exports = {
}
},
fn: async function (inputs) {
const payload = Object.assign({}, inputs)
payload.user = this.req.me
fn: async function (inputs, exits) {
const payload = {
zone: inputs.zone,
relId: inputs.id,
comment: inputs.comment,
user: this.req.me.id
}
let comment = await Comment.create(payload).fetch()
comment.user = User.pickCore(this.req.me)
// All done.
return exits.created({ comment });
......
......@@ -16,6 +16,12 @@ module.exports = {
description: '"Zone" of the comment, ie "blog"'
},
relId: {
type: 'string',
required: true,
description: 'ID of the thing this comment is related to'
},
comment: {
type: 'string',
required: true,
......
module.exports = {
friendlyName: 'Get a list of user accounts',
description: '',
inputs: {
},
fn: async function(inputs) {
const users = await User.find({ })
.select(['id', 'username', 'emailVerified', 'isAdmin', 'createdAt', 'lastSeen'])
.sort(['username'])
return { users }
}
}
\ No newline at end of file
......@@ -3,9 +3,7 @@
*/
const Pluggable = require('./pluggable')
const userMiddleware = require('./middleware/user')
const permissionsMiddleware = require('./middleware/permissions')
const cacheMiddleware = require('./middleware/cache')
const loadUserMiddleware = require('./middleware/user')
module.exports = new Pluggable({
apiBase: '',
......@@ -19,6 +17,12 @@ module.exports = new Pluggable({
cache: 30,
permission: 'admin'
},
// Admin Routes
'GET /admin/users': {
action: 'admin/list-users',
permission: 'admin'
},
},
async initialize () {
......@@ -51,18 +55,14 @@ module.exports = new Pluggable({
skipAssets: true,
fn: async function (req, res, next) {
res.set('X-Powered-By', 'Ahoy!')
userMiddleware(req, res, () => {
permissionsMiddleware(req, res, next)
})
// Add `req.me` to the request if available (JWT/Session cookie(not really session cookie though...))
loadUserMiddleware(req, res, next)
}
},
'GET /*': {
skipAssets: false,
fn: cacheMiddleware
}
},
// TODO: Maybe remove this for now. Without it we can add the old express app "under" this version
// so that endpoints can be overridden in sails land and will take pref. over the old version :)
after: {
'/*': {
fn: (req, res, next) => {
......
......@@ -2,28 +2,39 @@ const mcache = require('memory-cache');
module.exports = (req, res, next) => {
const logLevel = sails.hooks.ahoy.logLevel || 'debug'
// let routeConfig = req.options // sails.config.routes[ req.method + ' ' + req.route.path ];
let duration = req.options && req.options.cache ? req.options.cache : null;
let routeConfig = sails.config.routes[ req.method + ' ' + req.path ];
let duration = routeConfig ? routeConfig.cache : null;
if (!duration) {
sails.log.warn('Cache middleware called but no cache time specified', req.options)
return next();
}
let key = '__ahoy_cache__' + req.method + '__'+ req.originalUrl || req.url
let cachedBody = mcache.get(key)
if (cachedBody) {
let cached = mcache.get(key)
if (cached) {
sails.log[logLevel](' - Sending cached response');
sails.log.warn(`Sending cached response BUT it will quite likely have the wrong Content-Type header`)
res.set('X-AppCache-Hit', 'HIT');
res.send(cachedBody)
res.set('Content-Type', cached.contentType);
res.status(cached.statusCode)
res.send(cached.body)
return
} else {
sails.log[logLevel](' - Generating and caching response');
res.sendResponse = res.send
sails.lastRes = res
res.send = (body) => {
mcache.put(key, body, duration * 1000);
// This stops the occasional explosion
if (res.headersSent || !body) { return res }
mcache.put(key, {
body,
statusCode: res.statusCode,
contentType: res.get('Content-Type')
}, duration * 1000);
res.set('X-AppCache-Hit', 'MISS');
res.sendResponse(body)
return res
}
next()
}
......
module.exports = function (req, res, next) {
const logLevel = 'debug'
sails.log[logLevel]('Checking permission for ' + req.method + ' ' + req.path);
sails.log[logLevel]('Checking permission for ' + req.method + ' ' + req.route.path);
sails.log.warn('Skipping permissions check!')
return next()
let routeConfig = sails.config.routes[ req.method + ' ' + req.path ];
let permission = routeConfig ? routeConfig.permission : null;
let routeConfig = req.options // sails.config.routes[ req.method + ' ' + req.route.path ];
if (!routeConfig) { return next() }
let permission = routeConfig.permission;
if ((routeConfig.auth || permission) && !req.me) {
sails.log[logLevel](' ❌ User is not authenticated - bailing on request');
res.set('X-Token-Update', '');
return res.unauthorized();
}
......@@ -19,12 +20,17 @@ module.exports = function (req, res, next) {
sails.log[logLevel](' - Permission is required: ', permission);
// TODO: This should be handled a bit more betterly.
// So the user model has permissions[] and you could have 'blog' and something something other permission
if (permission !== 'admin') { permission = 'admin' }
if (req.me && User.hasPermission(req.me, User.PERMISSIONS[permission])) {
const isType = `is${permission.charAt(0).toUpperCase()}${permission.substr(1)}`
if (req.me[isType]) {
// if (req.me && User.hasPermission(req.me, User.PERMISSIONS[permission])) {
sails.log[logLevel](' ✓ User has required permission ' + permission);
return next();
}
sails.log[logLevel](' ❌ Permission does not exist for this user - bailing on request');
return res.forbidden();
return res.forbidden(`You don't have permission to access this resource`);
}
......@@ -4,33 +4,44 @@ module.exports = async function (req, res, next) {
const logLevel = sails.hooks.ahoy.logLevel || 'info'
sails.log[logLevel](`Running ahoy user middleware`)
// Shim the old `req.user` usage so it works, but logs a stack trace to the console
// TODO: Remove theeeees
function shimUserProperty () {
Object.defineProperty(req, 'user', {
get () {
sails.log.warn((new Error(`Deprecated usage of "req.user" seen`)).stack)
return req.me
}
})
}
if (req.user) {
sails.log.warn(`Using user from upstream (old express app)`)
req.me = req.user
shimUserProperty()
return next()
}
shimUserProperty()
let token;
try {
token = await jwt.fromRequest(req.headers, {
algorithm: config.jwt.algorithm || 'HS256',
algorithm: sails.config.custom.jwt.algorithm || 'HS256',
issuer: null,
acceptedKeys: config.jwt.acceptedKeys
acceptedKeys: sails.config.custom.jwt.acceptedKeys
});
} catch (err) {
// console.log('Unable to load JWT from request heaers', err.stack)
// console.log('Unable to load JWT from request headers', err.stack)
}
if (token && token.data && token.data.user && token.data.user.id) {
try {
let user = await db.user.find({ where: { id: token.data.user.id } });
let user = await User.findOne({ id: token.data.user.id })
req.me = user
} catch (err) {
sails.log.error('Ahoy user middleware: Unable to load user from JWT token', err.stack)
}
}
// Shim the old `req.user` usage so it works, but logs a stack trace to the console
Object.defineProperty(req, 'user', {
get () {
sails.log.warn((new Error(`Deprecated usage of "req.me" seen`)).stack)
return req.me
}
})
next()
}
......@@ -13,15 +13,16 @@ module.exports = {
// ╩ ╩╚═╩╩ ╩╩ ╩ ╩ ╚╝ ╚═╝╚═╝
username: {
type: 'string',
columnType: 'citext',
required: true,
maxLength: 20,
description: 'A users username',
maxLength: 30,
example: 'twiggy'
},
emailAddress: {
type: 'string',
columnType: 'string',
columnType: 'citext',
columnName: 'email',
required: true,
unique: true,
......@@ -39,26 +40,30 @@ module.exports = {
},
isAdmin: {
columnName: 'is_admin',
type: 'boolean',
description: 'Whether this user is a "super admin" with extra permissions, etc.',
defaultsTo: false
},
emailVerified: {
columnName: 'email_verified',
type: 'boolean',
description: 'Whether a user has verified ownership of their email address.',
defaultsTo: false
},
permissions: {
type: 'json',
type: 'ref',
columnType: 'text[]',
defaultsTo: []
},
lastSeen: {
type: 'number',
description: 'A JS timestamp (epoch ms) representing the moment at which this user most recently interacted with the backend while logged in (or 0 if they have not interacted with the backend at all yet).',
example: 1502844074211
type: 'ref',
columnType: 'timestamptz',
defaultsTo: null,
description: 'A timestamp representing the moment at which this user most recently interacted with the backend while logged in (or null if they have not interacted with the backend at all yet).',
},
/*
......@@ -130,5 +135,9 @@ without necessarily having a billing card.`
customToJSON () {
return _.omit(this, ['password']);
},
pickCore (user) {
return _.pick(user, ['id', 'username', 'avatar'])
}
};
......@@ -2,6 +2,9 @@ const path = require('path')
const _ = require('lodash')
const includeAll = require('include-all');
const permissionsMiddleware = require('./middleware/permissions')
const cacheMiddleware = require('./middleware/cache')
module.exports = class SailsPluggable {
constructor (opt = {}) {
this.opt = opt
......@@ -55,6 +58,7 @@ module.exports = class SailsPluggable {
// Reload the Sails ORM
sails.hooks.orm.reload((err) => {
if (err) { sails.log.error(`Unable to reload sails ORM`, err.stack) }
sails.emit('ahoy:ready')
})
}
}
......@@ -69,6 +73,14 @@ module.exports = class SailsPluggable {
this.log(` - Registering action ${key}`)
sails.registerAction(require(path.join(this.opt.dir, `actions`, config.action)), key)
if (config.auth || config.permission) {
sails.registerActionMiddleware(permissionsMiddleware, key)
}
if (config.cache) {
sails.registerActionMiddleware(cacheMiddleware, key)
}
if (this.opt.apiBase) {
reqPath = reqPath.replace(/\//, `/${this.opt.apiBase}/`)
}
......@@ -129,6 +141,14 @@ module.exports = class SailsPluggable {
return
}
// Hold up the Sails bootstrap until Ahoy is ready
if (sails.config.bootstrap) {
const bootstrap = sails.config.bootstrap
sails.config.bootstrap = function (done) {
sails.after('ahoy:ready', () => { bootstrap(done) })
}
}
// Find other Ahoy hooks
_.each(sails.hooks, (hook, name) => {
if (hook.ahoy) {
......
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