Skip to content

Commit

Permalink
WIP - Add jwt authentication
Browse files Browse the repository at this point in the history
  • Loading branch information
javierbrea committed Aug 25, 2018
1 parent 3ddb8f3 commit 2074ad0
Show file tree
Hide file tree
Showing 18 changed files with 279 additions and 144 deletions.
16 changes: 7 additions & 9 deletions lib/Api.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,13 @@

const users = require('./api/users')

const Api = function (service, commands) {
return {
openapis: [].concat(
users.openapi()
),
operations: Object.assign({},
users.Operations(service, commands)
)
const Api = (service, commands) => ({
openapis: [].concat(
users.openapi()
),
operations: {
...users.Operations(service, commands)
}
}
})

module.exports = Api
5 changes: 2 additions & 3 deletions lib/Client.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
'use strict'

const Client = function (service) {
return {}
}
const Client = service => ({
})

module.exports = Client
10 changes: 5 additions & 5 deletions lib/Commands.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
'use strict'

const user = require('./commands/user')
const refreshToken = require('./commands/refreshToken')

const Commands = function (service, models, client) {
return {
user: user.Commands(service, models, client)
}
}
const Commands = (service, models, client) => ({
user: user.Commands(service, models, client),
refreshToken: refreshToken.Commands(service, models, client)
})

module.exports = Commands
16 changes: 7 additions & 9 deletions lib/Database.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,19 +17,17 @@ const CONNECTION_OPTIONS = {
socketTimeoutMS: 45000
}

const Database = function (service) {
const connectMongoose = function (db) {
const Database = service => {
const connectMongoose = db => {
mongoose.Promise = Promise
return mongoose.connect(db, CONNECTION_OPTIONS)
}

const connect = function () {
return service.config.get('db')
.then(db => service.tracer.info(templates.connectingDb({db}))
.then(() => connectMongoose(db))
.then(() => service.tracer.info(templates.connectedDb({db})))
)
}
const connect = () => service.config.get('db')
.then(db => service.tracer.info(templates.connectingDb({db}))
.then(() => connectMongoose(db))
.then(() => service.tracer.info(templates.connectedDb({db})))
)

return {
connect
Expand Down
10 changes: 5 additions & 5 deletions lib/Models.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
'use strict'

const user = require('./models/user')
const refreshToken = require('./models/refreshToken')

const Models = function (service) {
return {
User: user.Model(service)
}
}
const Models = service => ({
User: user.Model(service),
RefreshToken: refreshToken.Model(service)
})

module.exports = Models
67 changes: 39 additions & 28 deletions lib/Security.js
Original file line number Diff line number Diff line change
@@ -1,41 +1,52 @@
'use strict'

const Security = function (service, commands) {
const Security = (service, commands) => {
const getUserByRefreshToken = refreshToken => commands.refreshToken.getUser(refreshToken)
.then(userData => Promise.resolve({
userData
}))

const getUserByCredentials = userCredentials => {
return commands.user.get({
email: userCredentials.userName,
password: userCredentials.password
}).then(userData => commands.refreshToken.add(userData)
.then(refreshToken => Promise.resolve({
userData: {
_id: userData._id,
name: userData.name,
email: userData.email,
role: userData.role
},
refreshToken: refreshToken.token
})))
}

const authenticateHandler = body => {
const action = body.refreshToken ? getUserByRefreshToken(body.refreshToken) : getUserByCredentials(body)
return action.catch(() => /* TODO, trace error */ Promise.reject(new service.errors.Unauthorized()))
}

const revokeAuth = userData => {
console.log(userData)
// Check if user is allowed to remove an existant refresh token
// return checkUserPermissionToManageApiKeys(userData)
return true
}

const revokeHandler = body => commands.refreshToken.remove(body.refreshToken)

return {
methods: {
jwt: {
secret: 'thisIsNotaRealTokenSecretPleaseReplaceIt',
expiresIn: 180,
authenticate: {
handler: (userCredentials) => {
// Check if user has right credentials, or refresh token. Returns user data (with new refresh token if not provided)
if (userCredentials.refreshToken) {
return getUserDataFromRefreshToken(userCredentials.refreshToken)
} else {
return checkUserData({
name: userCredentials.userName,
password: userCredentials.password
}).then((userData) => {
return createNewRefreshToken(userData)
.then((refreshToken) => {
return Promise.resolve({
userData: userData,
refreshToken: refreshToken
})
})
})
}
}
handler: authenticateHandler
},
revoke: {
auth: (userData) => {
// Check if user is allowed to remove an existant refresh token
return checkUserPermissionToManageApiKeys(userData)
},
handler: (refreshToken) => {
// Remove existant refresh token
return removeRefreshToken(refreshToken)
}
auth: revokeAuth,
handler: revokeHandler
}
}
}
Expand Down
34 changes: 13 additions & 21 deletions lib/api/users.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,29 +2,21 @@

const definition = require('./users.json')

const Operations = function (service, commands) {
return {
getUsers: {
handler: () => {
return commands.user.getAll()
}
},
addUser: {
handler: (params, body, res) => {
return commands.user.add(body)
.then((user) => {
res.status(201)
res.header('location', `/api/users/${user._id}`)
return Promise.resolve()
})
}
}
const Operations = (service, commands) => ({
getUsers: {
handler: () => commands.user.getAll()
},
addUser: {
handler: (params, body, res) => commands.user.add(body)
.then((user) => {
res.status(201)
res.header('location', `/api/users/${user._id}`)
return Promise.resolve()
})
}
}
})

const openapi = function () {
return [definition]
}
const openapi = () => [definition]

module.exports = {
Operations,
Expand Down
39 changes: 28 additions & 11 deletions lib/api/users.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,10 +27,6 @@
"type": "string",
"enum": ["admin", "service", "plugin"]
},
"__v": {
"description": "Version key",
"type": "string"
},
"createdAt": {
"description": "Creation date timestamp",
"type": "string"
Expand All @@ -46,20 +42,41 @@
"name": "Foo user",
"email": "foo-email@foo-domain.com",
"role": "admin",
"__v": 0,
"createdAt": "2018-07-28T17:13:08.718Z",
"updatedAt": "2018-07-28T17:13:09.730Z"
}
},
"NewUser": {
"allOf": [
{
"$ref": "#/components/schemas/User"
"description": "User data",
"type": "object",
"properties": {
"name": {
"description": "Name of the user",
"type": "string"
},
"email": {
"description": "Email of the user",
"type": "string",
"format": "email"
},
{
"required": ["name", "role"]
"password": {
"description": "User password",
"type": "string"
},
"role": {
"description": "Role assigned to the user",
"type": "string",
"enum": ["admin", "service", "plugin"]
}
]
},
"required": ["name", "email", "password", "role"],
"additionalProperties": false,
"example": {
"name": "Foo user",
"email": "foo-email@foo-domain.com",
"role": "admin",
"password": "foopass"
}
},
"Users": {
"type": "array",
Expand Down
39 changes: 39 additions & 0 deletions lib/commands/refreshToken.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
'use strict'

const randToken = require('rand-token')

const templates = require('../templates')
const utils = require('../utils')

const Commands = (service, models, client) => {
const add = userData => {
const token = new models.RefreshToken({
token: randToken.generate(32),
_user: userData._id
})
return token.save()
.catch(error => utils.transformValidationErrors(error, service))
.then(() => service.tracer.debug(templates.refreshTokenAdded(userData)))
.then(() => Promise.resolve(token))
}

const remove = token => models.RefreshToken.deleteOne({token})

const getUser = token => models.RefreshToken.find({token})
.then(tokenData => {
if (tokenData) {
return models.User.findById(tokenData._user)
}
return Promise.reject(new service.errors.NotFound(templates.refreshTokenNotFound()))
})

return {
add,
remove,
getUser
}
}

module.exports = {
Commands
}
34 changes: 21 additions & 13 deletions lib/commands/user.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,27 +3,35 @@
const templates = require('../templates')
const utils = require('../utils')

const Commands = function (service, models, client) {
const add = function (userData) {
const PUBLIC_FIELDS = 'name email role updatedAt createdAt'

const Commands = (service, models, client) => {
const ensureUser = user => {
if (!user) {
return Promise.reject(new service.errors.NotFound(templates.userNotFound()))
}
return Promise.resolve(user)
}

const add = userData => {
const user = new models.User(userData)
return user.save()
.catch((error) => {
return utils.transformValidationErrors(error, service)
})
.then(() => service.tracer.debug(templates.userAdded({
name: user.name,
id: user._id
})))
.catch(error => utils.transformValidationErrors(error, service))
.then(() => service.tracer.debug(templates.userAdded(user)))
.then(() => Promise.resolve(user))
}

const getAll = function () {
return models.User.find()
}
const getAll = () => models.User.find({}, PUBLIC_FIELDS)

const getById = _id => models.User.findById(_id).then(ensureUser)

const get = (filter = {}) => models.User.findOne(filter, PUBLIC_FIELDS).then(ensureUser)

return {
add,
getAll
getAll,
getById,
get
}
}

Expand Down
12 changes: 6 additions & 6 deletions lib/controller.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,12 @@ const serviceConfig = () => ({
})

const initService = (service) => {
const database = new lib.Database(service)
const models = new lib.Models(service)
const client = new lib.Client(service)
const commands = new lib.Commands(service, models, client)
const security = new lib.Security(service, commands)
const api = new lib.Api(service, commands)
const database = lib.Database(service)
const models = lib.Models(service)
const client = lib.Client(service)
const commands = lib.Commands(service, models, client)
const security = lib.Security(service, commands)
const api = lib.Api(service, commands)

const extendOpenApis = () => Promise.map(api.openapis, openapi => service.server.extendOpenApi(openapi))

Expand Down
Loading

0 comments on commit 2074ad0

Please sign in to comment.