Skip to content

Commit

Permalink
Merge pull request #88 from developmentseed/feature/client-permissions
Browse files Browse the repository at this point in the history
Feature/client permissions
  • Loading branch information
kamicut authored Aug 21, 2019
2 parents 7297a0c + 8df6290 commit 1d075d2
Show file tree
Hide file tree
Showing 8 changed files with 157 additions and 20 deletions.
10 changes: 5 additions & 5 deletions app/manage/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -40,9 +40,9 @@ function manageRouter (nextApp) {
/**
* List / Create / Delete clients
*/
router.get('/api/clients', can('clients:view'), getClients)
router.post('/api/clients', can('clients:create'), createClient)
router.delete('/api/clients/:id', can('clients:delete'), deleteClient)
router.get('/api/clients', can('clients'), getClients)
router.post('/api/clients', can('clients'), createClient)
router.delete('/api/clients/:id', can('client:delete'), deleteClient)

/**
* List / Create / Delete teams
Expand All @@ -59,11 +59,11 @@ function manageRouter (nextApp) {
/**
* Page renders
*/
router.get('/clients', can('clients:view'), (req, res) => {
router.get('/clients', can('clients'), (req, res) => {
return nextApp.render(req, res, '/clients')
})

router.get('/profile', can('clients:view'), (req, res) => {
router.get('/profile', can('clients'), (req, res) => {
return nextApp.render(req, res, '/profile')
})

Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
const db = require('../../db')

/**
* clients:view
* clients
*
* To access the clients API, requests need to be authenticated
* with a signed up user
Expand All @@ -10,12 +10,12 @@ const db = require('../../db')
* @param {Object} params request parameters
* @returns {boolean} can the request go through?
*/
async function viewClients (uid) {
async function clients (uid) {
let conn = await db()
const [user] = await conn('users').where('id', uid)
if (user) {
return true
}
}

module.exports = viewClients
module.exports = clients
18 changes: 18 additions & 0 deletions app/manage/permissions/delete-client.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
const db = require('../../db')

/**
* client:delete
*
* To delete a client, an authenticated user must own this client
*
*
* @param uid
* @returns {undefined}
*/
async function deleteClient (uid, { id }) {
let conn = await db()
const [client] = await conn('hydra_client').where('id', id)
return (client.owner === uid)
}

module.exports = deleteClient
3 changes: 2 additions & 1 deletion app/manage/permissions/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,8 @@ const teamPermissions = {
}

const clientPermissions = {
'clients:view': require('./view-clients')
'clients': require('./clients'),
'client:delete': require('./delete-client')
}

const permissions = mergeAll([
Expand Down
49 changes: 49 additions & 0 deletions app/tests/permissions/clients.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
const test = require('ava')
const db = require('../../db')
const path = require('path')
const hydra = require('../../lib/hydra')
const sinon = require('sinon')

const migrationsDirectory = path.join(__dirname, '..', '..', 'db', 'migrations')

let agent
test.before(async () => {
const conn = await db()
await conn.migrate.latest({ directory: migrationsDirectory })

// seed
await conn('users').insert({ id: 100 })

// stub hydra introspect
let introspectStub = sinon.stub(hydra, 'introspect')
introspectStub.withArgs('validToken').returns({
active: true,
sub: '100'
})
introspectStub.withArgs('invalidToken').returns({ active: false })

// stub hydra get clients
let getClientsStub = sinon.stub('hydra', 'getClients')
getClientsStub.returns([])

agent = require('supertest').agent(await require('../../index')())
})

test.after.always(async () => {
const conn = await db()
await conn.migrate.rollback({ directory: migrationsDirectory })
conn.destroy()
})

test('an authenticated user can view their clients', async t => {
let res = await agent.get('/api/clients')
.set('Authorization', `Bearer validToken`)

t.is(res.status, 200)
})

test('an unauthenticated user cannot view their clients', async t => {
let res = await agent.get('/api/clients')

t.is(res.status, 401)
})
61 changes: 61 additions & 0 deletions app/tests/permissions/delete-client.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
const test = require('ava')
const db = require('../../db')
const path = require('path')
const hydra = require('../../lib/hydra')
const sinon = require('sinon')

const migrationsDirectory = path.join(__dirname, '..', '..', 'db', 'migrations')

let agent
test.before(async () => {
const conn = await db()
await conn.migrate.latest({ directory: migrationsDirectory })
await conn.schema.createTable('hydra_client', t => {
// schema at https://github.com/ory/hydra/blob/master/client/manager_sql.go
t.string('id')
t.string('owner')
})

// seed
await conn('hydra_client').insert({ id: 999, owner: '100' })
await conn('hydra_client').insert({ id: 998, owner: '101' })

// stub hydra introspect
let introspectStub = sinon.stub(hydra, 'introspect')
introspectStub.withArgs('validToken').returns({
active: true,
sub: '100'
})
introspectStub.withArgs('differentUser').returns({
active: true,
sub: '101'
})
introspectStub.withArgs('invalidToken').returns({ active: false })

// stub hydra delete client
let deleteClientStub = sinon.stub(hydra, 'deleteClient')
deleteClientStub.returns(Promise.resolve(true))

agent = require('supertest').agent(await require('../../index')())
})

test.after.always(async () => {
const conn = await db()
await conn.schema.dropTable('hydra_client')
await conn.migrate.rollback({ directory: migrationsDirectory })
conn.destroy()
})

test('a user can delete a client they created', async t => {
let res = await agent.delete('/api/clients/999')
.set('Authorization', 'Bearer validToken')

t.is(res.status, 200)
})

test("a user can't delete a client they don't own", async t => {
let res = await agent.delete('/api/clients/998')
.set('Authorization', 'Bearer validToken')

t.is(res.status, 401)
})
22 changes: 11 additions & 11 deletions components/clients.js
Original file line number Diff line number Diff line change
Expand Up @@ -131,7 +131,7 @@ class Clients extends Component {
<div className='inner page clients'>
<div className='page__heading'>
<h2> ⚙️ OAuth2 settings</h2>
<p>Add an OAuth app to integrate with OSM/Hydra.</p>
<p>Add an OAuth app to integrate with the OSM Teams API</p>
</div>
<section className='clients__new'>
<h3>Add a new app</h3>
Expand All @@ -148,26 +148,26 @@ class Clients extends Component {
/>
<br />
<br />
<Button variant='submit' value='Add new app'>Add New App </Button>
<Button variant='submit' type='submit' value='Add new app'>Add New App </Button>
</form>
</section>
<section className='clients__list'>
{
this.state.newClient
? <section className='alert'>
<h3>Newly created client</h3>
<p>⚠️ Save this information, we won't show it again.</p>
{newClient(this.state.newClient)}
</section>
: <div />
}
<Card>
<h3>Your apps</h3>
{
clientSection
}
</Card>
</section>
{
this.state.newClient
? <section className='alert'>
<h3>Newly created client</h3>
<p>⚠️ Save this information, we won't show it again.</p>
{newClient(this.state.newClient)}
</section>
: <div />
}
<style jsx>
{`
.inner.clients {
Expand Down
8 changes: 8 additions & 0 deletions components/layout.js
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,13 @@ function Layout (props) {
}
}
.alert {
background: white;
padding: 2em;
margin-bottom: 1rem;
box-shadow: 2px 2px 0 ${theme.colors.warningColor};
}
/* Typography
========================================================================== */
Expand Down Expand Up @@ -163,6 +170,7 @@ function Layout (props) {
a.danger {
color: ${theme.colors.secondaryColor};
}
/* Forms
========================================================================== */
Expand Down

0 comments on commit 1d075d2

Please sign in to comment.