Skip to content
This repository has been archived by the owner on Aug 11, 2021. It is now read-only.

Commit

Permalink
for client credentials flow, let's store an email on the client which…
Browse files Browse the repository at this point in the history
… gets copied to the token when it's provisioned
  • Loading branch information
Benjamin Coe committed Mar 29, 2016
1 parent b6bde75 commit f2821ea
Show file tree
Hide file tree
Showing 12 changed files with 289 additions and 4 deletions.
14 changes: 14 additions & 0 deletions LICENSE.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
Copyright (c) 2016, Contributors

Permission to use, copy, modify, and/or distribute this software
for any purpose with or without fee is hereby granted, provided
that the above copyright notice and this permission notice
appear in all copies.

THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES
OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE
LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES
OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
20 changes: 19 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,25 @@
# oauth2-server-pg

[![Build Status](https://travis-ci.org/npm/oauth2-server-pg.svg)](https://travis-ci.org/npm/oauth2-server-pg)
[![Coverage Status](https://coveralls.io/repos/github/npm/oauth2-server-pg/badge.svg?branch=master)](https://coveralls.io/r/npm/oauth2-server-pg?branch=master)
[![Coverage Status](https://coveralls.io/repos/github/npm/oauth2-server-pg/badge.svg?branch=tokens)](https://coveralls.io/github/npm/oauth2-server-pg?branch=tokens)
[![NPM version](https://img.shields.io/npm/v/oauth2-server-pg.svg)](https://www.npmjs.com/package/oauth2-server-pg)

A PostgreSQL OAuth 2.0 Server.

## Usage

**starting server:**

```sh
./bin/oauth2-server-pg server --help
```

**generating a client:**

```sh
./bin/oauth2-server-pg generate-client --help
```

## License

ISC
23 changes: 23 additions & 0 deletions bin/oauth2-server-pg.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
#!/usr/bin/env node

require('yargs')
.usage('$0 <cmd> [options]')
.demand(1)
.command('start', 'start the oauth2 server', function (yargs) {
return yargs
.option('port', {
alias: 'p',
describe: 'port to run server on',
default: 9999
})
.option('model', {
alias: 'm',
describe: 'oauth grant model to use',
default: './client-credentials'
})
}, function (argv) {
require('../lib/server')(argv)
})
.help()
.alias('help', 'h')
.argv
87 changes: 87 additions & 0 deletions lib/client-credentials.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
'use strict'

const Client = require('../lib/client')
const Token = require('../lib/token')
const Promise = require('bluebird')
const uuid = require('uuid')

module.exports = {
getAccessToken: function (bearerToken) {
var token = null

return Token.objects.get({
access_token: bearerToken
})
.then(function (_token) {
token = _token
return token.client
})
.then(function (client) {
return {
accessToken: token.access_token,
client: client,
accessTokenExpiresAt: token.access_expires,
refreshTokenExpiresAt: token.refresh_expires,
user: {
email: token.user_email
}
}
})
},

getClient: function (clientId, clientSecret) {
return Client.objects.get({
client_id: clientId,
client_secret: clientSecret
})
.then(function (client) {
return {
clientId: client.client_id,
clientSecret: client.client_secret,
grants: ['client_credentials']
}
})
},

getUserFromClient: function (client) {
return Client.objects.get({
client_id: client.clientId,
client_secret: client.clientSecret
}).then(function (client) {
return {
email: client.email
}
})
},

saveToken: function (token, client, user) {
return Token.objects.create({
client: Client.objects.get({
client_id: client.clientId,
client_secret: client.clientSecret
}),
user_email: user.email
}).then((token) => {
return {
accessToken: token.access_token,
refreshToken: token.refresh_token,
accessTokenExpiresAt: token.access_expires,
refreshTokenExpiresAt: token.refresh_expires,
client: client,
user: user
}
})
},

validateScope: function (user, client, scope) {
// we don't have any scopes that we
// enforce yet. We will probably want
// to eventually store this as a field on
// tokens.
return true
},

generateAccessToken: function () {
return Promise.resolve(uuid.v4())
}
}
2 changes: 2 additions & 0 deletions lib/client.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ function Client (opts) {
this.id = opts.id
this.client_id = opts.client_id
this.client_secret = opts.client_secret
this.email = opts.email
this.name = opts.name
this.homepage = opts.homepage
this.description = opts.homepage
Expand All @@ -22,6 +23,7 @@ Client.objects = orm(Client, {
client_id: orm.joi.string().default(uuid.v4, 'uuid v4'),
client_secret: orm.joi.string().default(uuid.v4, 'uuid v4'),
name: orm.joi.string(),
email: orm.joi.string().regex(/@/),
homepage: orm.joi.string(),
description: orm.joi.string(),
callback: orm.joi.string(),
Expand Down
2 changes: 2 additions & 0 deletions lib/connection.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
'use strict'

const Promise = require('bluebird')
const orm = require('ormnomnom')
const pg = require('pg')
Expand Down
31 changes: 31 additions & 0 deletions lib/server.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
'use strict'

var bodyParser = require('body-parser')
var express = require('express')
var OAuthServer = require('@npmcorp/express-oauth-server')

module.exports = function (opts, cb) {
var app = express()

opts = opts || {}

app.oauth = new OAuthServer({
model: require(opts.model || './client-credentials')
})
app.use(bodyParser.json())
app.use(bodyParser.urlencoded({extended: false}))
app.post('/oauth/token', app.oauth.token())

app.get('/ping', app.oauth.authenticate(), function (req, res) {
if (res.statusCode === 200) {
res.send('pong')
} else {
res.send('unauthorized')
}
})

var server = app.listen(opts.port || 9999, function () {
console.info('server listening on ', opts.port)
return cb(null, server)
})
}
1 change: 1 addition & 0 deletions migrations/20160324224401-create-client.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ exports.up = function (db, callback) {
client_secret: { type: type.STRING },
name: { type: type.STRING },
homepage: { type: type.STRING },
email: { type: type.STRING },
description: { type: type.STRING },
callback: { type: type.STRING },
created: { type: type.DATE_TIME },
Expand Down
8 changes: 6 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@
"name": "oauth2-server-pg",
"version": "1.0.0",
"description": "PostgreSQL and Express powered OAuth 2.0 server",
"main": "index.js",
"main": "./lib/server.js",
"bin": "./bin/oauth2-server-pg.js",
"scripts": {
"pretest": "standard",
"test": "nyc mocha --timeout=5000 test/*.js",
Expand All @@ -25,8 +26,9 @@
},
"homepage": "https://github.com/npm/oauth2-server-pg#readme",
"dependencies": {
"@npmcorp/express-oauth-server": "^1.0.1",
"body-parser": "^1.15.0",
"moment": "^2.12.0",
"node-oauth2-server": "^2.4.0",
"ormnomnom": "^2.3.0",
"pg": "^4.5.1",
"uuid": "^2.0.1",
Expand All @@ -38,6 +40,8 @@
"db-migrate": "^0.9.23",
"mocha": "^2.4.5",
"nyc": "^6.1.1",
"request": "^2.69.0",
"simple-oauth2": "^0.5.1",
"standard": "^6.0.8"
},
"nyc": {
Expand Down
2 changes: 1 addition & 1 deletion test/client.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ const Token = require('../lib/token')

require('chai').should()

describe('Client', function () {
describe('Client Model', function () {
before(function (done) {
helper.resetDb(done)
})
Expand Down
98 changes: 98 additions & 0 deletions test/server.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
/* global describe, it, before, after */

const helper = require('./test-helper')
const Client = require('../lib/client')
const Token = require('../lib/token')
const request = require('request')

require('chai').should()

console.info = function () {}

describe('OAuth2 Server', function () {
var server = null

before(function (done) {
helper.startServer(9999, function (err, _server) {
if (err) return done(err)
server = _server
helper.resetDb(done)
})
})

describe('oauth', function () {
var client = null

before(function (done) {
Client.objects.create({
name: 'foo security',
email: 'foo@example.com'
}).then((_client) => {
client = _client
return done()
})
})

it('allows token to be generated if client exists', function (done) {
var credentials = {
clientID: client.client_id,
clientSecret: client.client_secret,
site: 'http://localhost:9999'
}
var oauth2 = require('simple-oauth2')(credentials)

oauth2.client.getToken({}, function saveToken (err, result) {
if (err) return done(err)
var token = oauth2.accessToken.create(result)
token.token.access_token.should.match(/[a-z0-9]{8}-[a-z0-9]{4}-[a-z0-9]{4}-[a-z0-9]{4}-[a-z0-9]{12}/)
token.token.refresh_token.should.match(/[a-z0-9]{8}-[a-z0-9]{4}-[a-z0-9]{4}-[a-z0-9]{4}-[a-z0-9]{12}/)
token.expired().should.equal(false)
return done()
})
})

it('responds with error if client does not exist', function (done) {
var credentials = {
clientID: 'fake-id',
clientSecret: 'fake-secret',
site: 'http://localhost:9999'
}
var oauth2 = require('simple-oauth2')(credentials)

oauth2.client.getToken({}, function saveToken (err, result) {
if (err) return done(err)
result.error_description.should.equal('Client not found')
return done()
})
})

it('allows protected resources to be accessed with token', function (done) {
Token.objects.get({})
.then((token) => {
request.get('http://localhost:9999/ping', {
auth: {
bearer: token.access_token
}
}, function (err, res, body) {
if (err) return done(err)
res.statusCode.should.equal(200)
body.should.equal('pong')
return done()
})
})
})

it('protects a resource', function (done) {
request.get('http://localhost:9999/ping', function (err, res, body) {
if (err) return done(err)
res.statusCode.should.equal(401)
res.body.should.equal('unauthorized')
return done()
})
})
})

after(function () {
server.close()
})
})
5 changes: 5 additions & 0 deletions test/test-helper.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
const exec = require('child_process').exec
const pg = require('pg')
const Promise = require('bluebird')
const Server = require('../lib/server')
var helper = {}

helper.resetDb = function (cb) {
Expand Down Expand Up @@ -41,4 +42,8 @@ function dropTable (conn, name) {
return deferred.promise
}

helper.startServer = function (port, cb) {
Server({port: port}, cb)
}

module.exports = helper

0 comments on commit f2821ea

Please sign in to comment.