expressa 2.0.12 → 2.0.14
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.eslintrc.json +1 -1
- package/auth/index.js +26 -0
- package/controllers/users.js +52 -52
- package/index.js +1 -1
- package/modules/access_keys/access_keys.js +33 -0
- package/package.json +2 -2
- package/test/0-install.js +1 -1
- package/test/test.js +14 -0
- package/test/testutils.js +48 -0
- package/util.js +2 -0
package/.eslintrc.json
CHANGED
package/auth/index.js
CHANGED
|
@@ -17,6 +17,32 @@ exports.isValidPassword = function (password, hashedPassword) {
|
|
|
17
17
|
exports.doLogin = handler.doLogin
|
|
18
18
|
|
|
19
19
|
exports.middleware = async function authMiddleware(req, res, next) {
|
|
20
|
+
const key = req.query['access_key'] || req.headers['x-access-key']
|
|
21
|
+
if (key) {
|
|
22
|
+
if (typeof key == 'string' && req.db['access_keys']) {
|
|
23
|
+
const accessKeys = await req.db['access_keys'].find({ key: { $eq: key } }, 0, 1)
|
|
24
|
+
if (accessKeys && accessKeys.length > 0) {
|
|
25
|
+
const accessKey = accessKeys[0]
|
|
26
|
+
// check if access key is still valid
|
|
27
|
+
if (accessKey.expires_at >= new Date().toISOString()) {
|
|
28
|
+
let user
|
|
29
|
+
try {
|
|
30
|
+
user = await req.db[accessKey.user_collection].get(accessKey.user_id)
|
|
31
|
+
} catch (e) {
|
|
32
|
+
req.uerror = 'user no longer exists'
|
|
33
|
+
}
|
|
34
|
+
if (user && !req.uerror) {
|
|
35
|
+
req.uid = user._id
|
|
36
|
+
req.ucollection = 'users'
|
|
37
|
+
req.user = user
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
} else {
|
|
42
|
+
console.error('Non-string access key sent')
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
20
46
|
req.query = req.query || {}
|
|
21
47
|
const token = req.query['token'] || req.headers['x-access-token']
|
|
22
48
|
delete req.query['token']
|
package/controllers/users.js
CHANGED
|
@@ -1,52 +1,52 @@
|
|
|
1
|
-
const auth = require('../auth')
|
|
2
|
-
const util = require('../util')
|
|
3
|
-
const collectionsApi = require('./collections')
|
|
4
|
-
const permissions = require('../middleware/permissions')
|
|
5
|
-
|
|
6
|
-
exports.login = async (req, collection) => {
|
|
7
|
-
const password = req.body.password
|
|
8
|
-
|
|
9
|
-
if (typeof req.body.email !== 'string') {
|
|
10
|
-
throw new util.ApiError(400, 'email must be a string')
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
req.body.email = req.body.email.toLowerCase()
|
|
14
|
-
|
|
15
|
-
// check if user exists
|
|
16
|
-
const result = await req.db[collection].find({
|
|
17
|
-
email: req.body.email
|
|
18
|
-
})
|
|
19
|
-
if (result.length === 0) {
|
|
20
|
-
throw new util.ApiError(400, 'No ' + collection + ' found with this email.')
|
|
21
|
-
}
|
|
22
|
-
const user = result[0]
|
|
23
|
-
if (!auth.isValidPassword(password, user.password)) {
|
|
24
|
-
throw new util.ApiError(401, 'Incorrect password')
|
|
25
|
-
}
|
|
26
|
-
const jwt_options = req.settings.jwt_expires_in ? { expiresIn: req.settings.jwt_expires_in } : {}
|
|
27
|
-
const payload = auth.doLogin({
|
|
28
|
-
id: user._id,
|
|
29
|
-
collection,
|
|
30
|
-
timestamp: user.meta.password_last_updated_at,
|
|
31
|
-
jwt_secret: req.getSetting('jwt_secret'),
|
|
32
|
-
jwt_options
|
|
33
|
-
})
|
|
34
|
-
req.uid = user._id
|
|
35
|
-
req.ucollection = collection
|
|
36
|
-
req.user = user
|
|
37
|
-
await permissions.addRolePermissionsAsync(req)
|
|
38
|
-
payload.canUseAdmin = req.hasPermission('login to admin')
|
|
39
|
-
return payload
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
exports.register = function (req, collection) {
|
|
43
|
-
req.url = `/${collection}`
|
|
44
|
-
req.params.collection = collection
|
|
45
|
-
return collectionsApi.insert(req)
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
exports.getMe = function (req, collection) {
|
|
49
|
-
req.params.collection = collection
|
|
50
|
-
req.params.id = req.uid
|
|
51
|
-
return collectionsApi.getById(req)
|
|
52
|
-
}
|
|
1
|
+
const auth = require('../auth')
|
|
2
|
+
const util = require('../util')
|
|
3
|
+
const collectionsApi = require('./collections')
|
|
4
|
+
const permissions = require('../middleware/permissions')
|
|
5
|
+
|
|
6
|
+
exports.login = async (req, collection) => {
|
|
7
|
+
const password = req.body.password
|
|
8
|
+
|
|
9
|
+
if (typeof req.body.email !== 'string') {
|
|
10
|
+
throw new util.ApiError(400, 'email must be a string')
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
req.body.email = req.body.email.toLowerCase()
|
|
14
|
+
|
|
15
|
+
// check if user exists
|
|
16
|
+
const result = await req.db[collection].find({
|
|
17
|
+
email: req.body.email
|
|
18
|
+
})
|
|
19
|
+
if (result.length === 0) {
|
|
20
|
+
throw new util.ApiError(400, 'No ' + collection + ' found with this email.')
|
|
21
|
+
}
|
|
22
|
+
const user = result[0]
|
|
23
|
+
if (!auth.isValidPassword(password, user.password)) {
|
|
24
|
+
throw new util.ApiError(401, 'Incorrect password')
|
|
25
|
+
}
|
|
26
|
+
const jwt_options = req.settings.jwt_expires_in ? { expiresIn: req.settings.jwt_expires_in } : {}
|
|
27
|
+
const payload = auth.doLogin({
|
|
28
|
+
id: user._id,
|
|
29
|
+
collection,
|
|
30
|
+
timestamp: user.meta.password_last_updated_at,
|
|
31
|
+
jwt_secret: req.getSetting('jwt_secret'),
|
|
32
|
+
jwt_options
|
|
33
|
+
})
|
|
34
|
+
req.uid = user._id
|
|
35
|
+
req.ucollection = collection
|
|
36
|
+
req.user = user
|
|
37
|
+
await permissions.addRolePermissionsAsync(req)
|
|
38
|
+
payload.canUseAdmin = req.hasPermission('login to admin')
|
|
39
|
+
return payload
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
exports.register = function (req, collection) {
|
|
43
|
+
req.url = `/${collection}`
|
|
44
|
+
req.params.collection = collection
|
|
45
|
+
return collectionsApi.insert(req)
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
exports.getMe = function (req, collection) {
|
|
49
|
+
req.params.collection = collection
|
|
50
|
+
req.params.id = req.uid
|
|
51
|
+
return collectionsApi.getById(req)
|
|
52
|
+
}
|
package/index.js
CHANGED
|
@@ -183,7 +183,7 @@ module.exports.api = function (settings) {
|
|
|
183
183
|
(async function setup() {
|
|
184
184
|
await initCollections(router.db, router)
|
|
185
185
|
|
|
186
|
-
const modules = ['collections', 'core', 'logging', 'permissions']
|
|
186
|
+
const modules = ['collections', 'core', 'logging', 'permissions', 'access_keys']
|
|
187
187
|
router.modules = {}
|
|
188
188
|
for (const module of modules) {
|
|
189
189
|
router.modules[module] = require(`./modules/${module}/${module}`)
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
exports.collections = function () {
|
|
2
|
+
const access_keys = {
|
|
3
|
+
_id: 'access_keys',
|
|
4
|
+
schema: {
|
|
5
|
+
type: 'object',
|
|
6
|
+
additionalProperties: false,
|
|
7
|
+
properties: {
|
|
8
|
+
user_collection: {
|
|
9
|
+
type: 'string'
|
|
10
|
+
},
|
|
11
|
+
user_id: {
|
|
12
|
+
type: 'string'
|
|
13
|
+
},
|
|
14
|
+
key: {
|
|
15
|
+
type: 'string',
|
|
16
|
+
description: 'randomly generated secret'
|
|
17
|
+
},
|
|
18
|
+
expires_at: {
|
|
19
|
+
type: 'string'
|
|
20
|
+
},
|
|
21
|
+
},
|
|
22
|
+
required: [
|
|
23
|
+
'user_id',
|
|
24
|
+
'key',
|
|
25
|
+
'expires_at',
|
|
26
|
+
]
|
|
27
|
+
},
|
|
28
|
+
storage: 'file',
|
|
29
|
+
documentsHaveOwners: true
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
return [access_keys]
|
|
33
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "expressa",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.14",
|
|
4
4
|
"description": "API framework using JSON schemas",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"repository": "https://github.com/thomas4019/expressa",
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
"jfs": "^0.3.0",
|
|
18
18
|
"jsonwebtoken": "^9.0.0",
|
|
19
19
|
"mongo-query": "^0.5.7",
|
|
20
|
-
"mongo-query-to-postgres-jsonb": "^0.2.
|
|
20
|
+
"mongo-query-to-postgres-jsonb": "^0.2.17",
|
|
21
21
|
"mongo-querystring": "4.1.1",
|
|
22
22
|
"mongodb": "^3.5.7",
|
|
23
23
|
"on-finished": "^2.3.0",
|
package/test/0-install.js
CHANGED
|
@@ -21,7 +21,7 @@ describe('install flow', function () {
|
|
|
21
21
|
await request(app)
|
|
22
22
|
.post('/install')
|
|
23
23
|
.send({
|
|
24
|
-
modules: ['collections', 'core', 'logging', 'permissions'],
|
|
24
|
+
modules: ['collections', 'core', 'logging', 'permissions', 'access_keys'],
|
|
25
25
|
settings: {
|
|
26
26
|
jwt_secret: 'testing 123',
|
|
27
27
|
jwt_expire_on_password_change: true,
|
package/test/test.js
CHANGED
|
@@ -54,6 +54,20 @@ describe('General Tests:', () => {
|
|
|
54
54
|
.expect(200)
|
|
55
55
|
})
|
|
56
56
|
|
|
57
|
+
it('returns collections using a key', async function () {
|
|
58
|
+
const badKey = '123'
|
|
59
|
+
await request(app)
|
|
60
|
+
.get('/collection')
|
|
61
|
+
.set('x-access-key', badKey)
|
|
62
|
+
.expect(401)
|
|
63
|
+
|
|
64
|
+
const key = await testutils.getAccessKeyForUserWithPermissions(api, ['collection: view'])
|
|
65
|
+
await request(app)
|
|
66
|
+
.get('/collection')
|
|
67
|
+
.set('x-access-key', key)
|
|
68
|
+
.expect(200)
|
|
69
|
+
})
|
|
70
|
+
|
|
57
71
|
it('Sets headers', async function () {
|
|
58
72
|
const token = await testutils.getUserWithPermissions(api, ['collection: view'])
|
|
59
73
|
const res = await request(app)
|
package/test/testutils.js
CHANGED
|
@@ -81,6 +81,54 @@ exports.getUserWithPermissions = async function(api, permissions) {
|
|
|
81
81
|
user._id = loginRes.body.uid
|
|
82
82
|
return loginRes.body.token
|
|
83
83
|
}
|
|
84
|
+
|
|
85
|
+
exports.getAccessKeyForUserWithPermissions = async function(api, permissions) {
|
|
86
|
+
const service = request(exports.app)
|
|
87
|
+
if (typeof permissions === 'string') {
|
|
88
|
+
permissions = [permissions]
|
|
89
|
+
}
|
|
90
|
+
permissions = permissions || []
|
|
91
|
+
const permissionsMap = {}
|
|
92
|
+
permissions.forEach(function (permission) {
|
|
93
|
+
permissionsMap[permission] = true
|
|
94
|
+
})
|
|
95
|
+
|
|
96
|
+
const randId = randomstring.generate(12)
|
|
97
|
+
const roleName = 'role' + randId
|
|
98
|
+
const role = {
|
|
99
|
+
_id: roleName,
|
|
100
|
+
permissions: permissionsMap
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
const roleRes = await service.post('/role')
|
|
104
|
+
.set('x-access-token', tokens.admin)
|
|
105
|
+
.send(role)
|
|
106
|
+
.expect(200)
|
|
107
|
+
|
|
108
|
+
const user = {
|
|
109
|
+
email: 'test' + randId + '@example.com',
|
|
110
|
+
password: '123',
|
|
111
|
+
}
|
|
112
|
+
const registerRes = await service.post('/users/register')
|
|
113
|
+
.send(user)
|
|
114
|
+
.expect(200)
|
|
115
|
+
|
|
116
|
+
const updateRes = await service.post(`/users/${registerRes.body.id}/update`)
|
|
117
|
+
.send({ $push: { roles: roleName } })
|
|
118
|
+
.set('x-access-token', tokens.admin)
|
|
119
|
+
.expect(200)
|
|
120
|
+
|
|
121
|
+
let accessKey = randomstring.generate(8)
|
|
122
|
+
await exports.api.db.access_keys.create({
|
|
123
|
+
user_collection: 'users',
|
|
124
|
+
user_id: registerRes.body.id,
|
|
125
|
+
key: accessKey,
|
|
126
|
+
expires_at: '2032-01-01T00:00:00'
|
|
127
|
+
})
|
|
128
|
+
|
|
129
|
+
return accessKey
|
|
130
|
+
}
|
|
131
|
+
|
|
84
132
|
exports.clone = util.clone
|
|
85
133
|
exports.sleep = function sleep(ms) {
|
|
86
134
|
return new Promise(resolve => setTimeout(resolve, ms))
|
package/util.js
CHANGED
|
@@ -75,6 +75,8 @@ exports.getSchemaProperty = function(collection, path) {
|
|
|
75
75
|
for (const part of parts) {
|
|
76
76
|
if (obj.properties && obj.properties[part]) {
|
|
77
77
|
obj = obj.properties[part]
|
|
78
|
+
} else if (obj.items && obj.items.properties && obj.items.properties[part]) {
|
|
79
|
+
obj = obj.items.properties[part]
|
|
78
80
|
} else {
|
|
79
81
|
return
|
|
80
82
|
}
|