Incorporate some basic access control based off of GitHub username

This ensures that only those in the `grants` table can access specific pages.

For now this only supports wildcard grants, `*`, but in the future I expect to
restrict queries based on the type of grants made to the given user.
This commit is contained in:
R. Tyler Croy 2018-09-12 08:40:43 -07:00
parent c310e3dd74
commit 997fb0610e
No known key found for this signature in database
GPG Key ID: 1426C7DC3F51E16F
14 changed files with 169 additions and 15 deletions

View File

@ -44,6 +44,8 @@ migrate: depends
@sleep 1
$(COMPOSE) run --rm node \
/usr/local/bin/node $(SEQUELIZE) db:migrate
$(COMPOSE) run --rm node \
/usr/local/bin/node $(SEQUELIZE) db:seed:all
watch: migrate
# Running with docker-compose since our tests require a database to be

View File

@ -18,10 +18,12 @@ module.exports = {
},
createdAt: {
allowNull: false,
defaultValue: Sequelize.literal('NOW()'),
type: Sequelize.DATE
},
updatedAt: {
allowNull: false,
defaultValue: Sequelize.literal('NOW()'),
type: Sequelize.DATE
}
});

View File

@ -0,0 +1,34 @@
'use strict';
module.exports = {
up: (queryInterface, Sequelize) => {
return queryInterface.createTable('grants', {
id: {
allowNull: false,
autoIncrement: true,
primaryKey: true,
type: Sequelize.INTEGER
},
name: {
type: Sequelize.STRING,
index: true,
},
type: {
type: Sequelize.STRING
},
createdAt: {
allowNull: false,
defaultValue: Sequelize.literal('NOW()'),
type: Sequelize.DATE
},
updatedAt: {
allowNull: false,
defaultValue: Sequelize.literal('NOW()'),
type: Sequelize.DATE
}
});
},
down: (queryInterface, Sequelize) => {
return queryInterface.dropTable('grants');
}
};

View File

@ -0,0 +1,20 @@
'use strict';
module.exports = {
up: (queryInterface, Sequelize) => {
return queryInterface.bulkInsert('grants', [
{
name: 'rtyler',
type: '*',
},
{
name: 'daniel-beck',
type: '*',
},
]);
},
down: (queryInterface, Sequelize) => {
return queryInterface.bulkDelete('grants', null, {});
}
};

View File

@ -18,13 +18,17 @@ export default (app) => {
}, req.query);
app.service('events')
.find({ query: query })
.find({
query: query,
// propogate our user object down
user: (req as any).user,
})
.then(result =>
res.render('dashboard', {
events: result,
user: (req as any).user,
query: req.query,
})
);
}))
.catch(err => res.render('notauthorized'));
});
};

37
src/hooks/authorize.ts Normal file
View File

@ -0,0 +1,37 @@
import authentication from '@feathersjs/authentication';
import { Forbidden } from '@feathersjs/errors';
import { SKIP } from '@feathersjs/feathers';
import logger from '../logger';
export default () => {
return async context => {
context = await authentication.hooks.authenticate(['jwt'])(context);
if (context == SKIP) {
return SKIP;
}
const name : string = context.params.user.github.profile.username;
const type : string = context.params.query.type;
return context.app.service('grants').find({
query: {
name: name,
$or : [
{
type: type,
},
{
type: '*',
},
],
},
})
.then((records) => {
if (records.length === 0) {
throw new Forbidden('Not allowed, sorry buddy');
}
return context;
});
};
};

12
src/models/grant.ts Normal file
View File

@ -0,0 +1,12 @@
'use strict';
export default (sequelize, DataTypes) => {
const grant = sequelize.define('grants', {
name: DataTypes.STRING,
type: DataTypes.STRING
}, {});
grant.associate = function(models) {
// associations can be defined here
};
return grant;
};

View File

@ -3,10 +3,10 @@
* the form of files to clients
*/
import authentication from '@feathersjs/authentication';
import { Application, HooksObject, Params, SKIP } from '@feathersjs/feathers';
import { QueryTypes } from 'sequelize';
import authorize from '../hooks/authorize';
import logger from '../logger';
import db from '../models';
import Event from '../models/event';
@ -23,7 +23,7 @@ export const bulkHooks : HooksObject = {
}
return context;
},
authentication.hooks.authenticate(['jwt']),
authorize(),
],
},
after: {},

View File

@ -3,11 +3,11 @@
* Jenkins instances and storing it in the database
*/
import authentication from '@feathersjs/authentication';
import { Application, HooksObject } from '@feathersjs/feathers';
import service from 'feathers-sequelize';
import { DataTypes } from 'sequelize';
import authorize from '../hooks/authorize';
import logger from '../logger';
import db from '../models';
import Event from '../models/event';
@ -17,10 +17,10 @@ export const eventsHooks : HooksObject = {
all: [
],
find: [
authentication.hooks.authenticate(['jwt']),
authorize(),
],
get: [
authentication.hooks.authenticate(['jwt']),
authorize(),
],
create: [
/*
@ -28,13 +28,13 @@ export const eventsHooks : HooksObject = {
*/
],
update: [
authentication.hooks.authenticate(['jwt']),
authorize(),
],
patch: [
authentication.hooks.authenticate(['jwt']),
authorize(),
],
remove: [
authentication.hooks.authenticate(['jwt']),
authorize(),
],
},
after: {},

25
src/services/grants.ts Normal file
View File

@ -0,0 +1,25 @@
import authentication from '@feathersjs/authentication';
import { HooksObject } from '@feathersjs/feathers';
import service from 'feathers-sequelize';
import { DataTypes } from 'sequelize';
import db from '../models';
import Grant from '../models/grant';
export const grantHooks : HooksObject = {
before: {
all: [
authentication.hooks.authenticate(['jwt']),
],
},
after: {
},
error: {
},
};
export default (app) => {
const Model : any = Grant(db.sequelize, db.sequelize.Sequelize);
app.use('/grants', service({ Model }));
app.service('grants').hooks(grantHooks);
}

View File

@ -1,9 +1,11 @@
import events from './events';
import bulk from './bulk';
import events from './events';
import grants from './grants';
import users from './users';
export default (app) => {
app.configure(bulk);
app.configure(events);
app.configure(grants);
app.configure(users);
};

View File

@ -6,7 +6,9 @@ import authentication from '@feathersjs/authentication';
import { HooksObject } from '@feathersjs/feathers';
import service from 'feathers-sequelize';
import { DataTypes } from 'sequelize';
import { NotAuthenticated, Forbidden } from '@feathersjs/errors';
import authorize from '../hooks/authorize';
import db from '../models';
import User from '../models/user';
@ -14,6 +16,11 @@ export const usersHooks : HooksObject = {
before: {
all: [
authentication.hooks.authenticate(['jwt']),
(context) => {
if (context.params.provider == 'rest') {
throw new NotAuthenticated('This route is not allowed to be publicly accessed');
}
},
],
},
after: {

View File

@ -1,9 +1,6 @@
doctype html
html(lang="en")
script(src="https://code.jquery.com/jquery-3.3.1.slim.min.js" integrity="sha384-q8i/X+965DzO0rT7abK41JStQIAqVgRVzpbzo5smXKp4YfRvH+8abtTE1Pi6jizo" crossorigin="anonymous")
script(src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.3/umd/popper.min.js" integrity="sha384-ZMP7rVo3mIykV+2+9J3UJ46jBk0WLaUAdn689aCwoqbBJiSnjAK/l8WvCWPIPm49" crossorigin="anonymous")
link(rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.1.3/css/bootstrap.min.css" integrity="sha384-MCw98/SFnGE8fJT3GXwEOngsV7Zt27NXFoaoApmYm81iuXoPkFOJwJ8ERdknLPMO" crossorigin="anonymous")
script(src="https://stackpath.bootstrapcdn.com/bootstrap/4.1.3/js/bootstrap.min.js" integrity="sha384-ChfqqxuZUCnJSK3+MXmPNIyE6ZbWh2IMqE241rYiqJxyMiZ6OW/JmZQ5stwEULTy" crossorigin="anonymous")
head
title Uplink Dashboard
body.bg-dark

12
views/notauthorized.pug Normal file
View File

@ -0,0 +1,12 @@
doctype html
html(lang="en")
link(rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.1.3/css/bootstrap.min.css" integrity="sha384-MCw98/SFnGE8fJT3GXwEOngsV7Zt27NXFoaoApmYm81iuXoPkFOJwJ8ERdknLPMO" crossorigin="anonymous")
head
title Uplink Dashboard
body.bg-dark
br/
.container.text-light.text-center
h2.
You're not authorized to see this page.
// vim: ft=haml