expressa 2.0.6 → 2.0.8

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/auth/index.js CHANGED
@@ -1,47 +1,50 @@
1
- const bcrypt = require('bcryptjs')
2
- const handler = require('./jwt')
3
- const util = require('../util')
4
-
5
- exports.createHash = function (password) {
6
- const salt = bcrypt.genSaltSync(10)
7
- return bcrypt.hashSync(password, salt)
8
- }
9
-
10
- exports.isValidPassword = function (password, hashedPassword) {
11
- return bcrypt.compareSync(password, hashedPassword)
12
- }
13
-
14
- exports.doLogin = handler.doLogin
15
-
16
- exports.middleware = async function authMiddleware(req, res, next) {
17
- req.query = req.query || {}
18
- const token = req.query['token'] || req.headers['x-access-token']
19
- delete req.query['token']
20
- delete req.headers['x-access-token']
21
- let payload
22
- try {
23
- payload = await handler.isLoggedIn(token, req.getSetting('jwt_secret'))
24
- }
25
- catch(e) {
26
- req.uerror = e.message
27
- }
28
- if (payload) {
29
- let user
30
- try {
31
- user = await req.db[payload.collection].get(payload._id)
32
- } catch (e) {
33
- req.uerror = 'user no longer exists'
34
- }
35
- if (user) {
36
- if (req.getSetting('jwt_expire_on_password_change') && user.meta.password_last_updated_at !== payload.timestamp) {
37
- req.uerror = 'jwt expired'
38
- }
39
- if (!req.uerror) {
40
- req.uid = payload._id
41
- req.ucollection = payload.collection
42
- req.user = user
43
- }
44
- }
45
- }
46
- next()
47
- }
1
+ const bcrypt = require('bcryptjs')
2
+ const handler = require('./jwt')
3
+
4
+ exports.isHashed = function (string) {
5
+ return string && string.length === 60 && string[0] === '$'
6
+ }
7
+
8
+ exports.createHash = function (password) {
9
+ const salt = bcrypt.genSaltSync(10)
10
+ return bcrypt.hashSync(password, salt)
11
+ }
12
+
13
+ exports.isValidPassword = function (password, hashedPassword) {
14
+ return bcrypt.compareSync(password, hashedPassword)
15
+ }
16
+
17
+ exports.doLogin = handler.doLogin
18
+
19
+ exports.middleware = async function authMiddleware(req, res, next) {
20
+ req.query = req.query || {}
21
+ const token = req.query['token'] || req.headers['x-access-token']
22
+ delete req.query['token']
23
+ delete req.headers['x-access-token']
24
+ let payload
25
+ try {
26
+ payload = await handler.isLoggedIn(token, req.getSetting('jwt_secret'))
27
+ }
28
+ catch(e) {
29
+ req.uerror = e.message
30
+ }
31
+ if (payload) {
32
+ let user
33
+ try {
34
+ user = await req.db[payload.collection].get(payload._id)
35
+ } catch (e) {
36
+ req.uerror = 'user no longer exists'
37
+ }
38
+ if (user) {
39
+ if (req.getSetting('jwt_expire_on_password_change') && user.meta.password_last_updated_at !== payload.timestamp) {
40
+ req.uerror = 'jwt expired'
41
+ }
42
+ if (!req.uerror) {
43
+ req.uid = payload._id
44
+ req.ucollection = payload.collection
45
+ req.user = user
46
+ }
47
+ }
48
+ }
49
+ next()
50
+ }
package/db/file.js CHANGED
@@ -5,8 +5,6 @@ Store.prototype.allAsync = promisify(Store.prototype.all)
5
5
  Store.prototype.getAsync = promisify(Store.prototype.get)
6
6
  Store.prototype.saveAsync = promisify(Store.prototype.save)
7
7
 
8
- const sift = require('sift')
9
-
10
8
  const util = require('../util')
11
9
 
12
10
  module.exports = function (settings, collection) {
@@ -25,7 +23,7 @@ module.exports = function (settings, collection) {
25
23
  const arr = Object.keys(data).map(function (id) {
26
24
  return data[id]
27
25
  })
28
- let matches = sift(query || {}, arr)
26
+ let matches = util.mongoSearch(arr, query)
29
27
  if (orderby) {
30
28
  matches = util.orderBy(matches, orderby)
31
29
  }
@@ -96,7 +94,7 @@ module.exports = function (settings, collection) {
96
94
  updateWithQuery: async function (query, update, options) {
97
95
  const data = await store.allAsync()
98
96
  const arr = Object.keys(data).map((id) => ({ _id: id, ...data[id]}) )
99
- const matches = sift(query || {}, arr)
97
+ const matches = util.mongoSearch(arr, query)
100
98
  const promises = matches.map((doc) => {
101
99
  util.mongoUpdate(doc, update)
102
100
  return store.saveAsync(doc._id, doc)
package/db/memory.js CHANGED
@@ -1,6 +1,4 @@
1
1
  /* eslint no-unused-vars: ["error", { "args": "none" }] */
2
- const sift = require('sift')
3
-
4
2
  const util = require('../util')
5
3
 
6
4
  module.exports = function (settings, collection) {
@@ -14,7 +12,7 @@ module.exports = function (settings, collection) {
14
12
  },
15
13
  find: async function (query, offset, limit, orderby, fields) {
16
14
  const arr = Object.values(store)
17
- let matches = sift(query || {}, arr)
15
+ let matches = util.mongoSearch(arr, query)
18
16
  if (orderby) {
19
17
  matches = util.orderBy(matches, orderby)
20
18
  }
@@ -61,7 +59,7 @@ module.exports = function (settings, collection) {
61
59
  // https://docs.mongodb.com/manual/reference/method/db.collection.updateMany/
62
60
  updateWithQuery: async function (query, update, options) {
63
61
  const arr = Object.keys(store).map((id) => ({ _id: id, ...store[id]}) )
64
- const matches = sift(query || {}, arr)
62
+ const matches = util.mongoSearch(arr, query)
65
63
  matches.forEach((doc) => {
66
64
  util.mongoUpdate(doc, update)
67
65
  store[doc._id] = doc
package/db/postgres.js CHANGED
@@ -1,100 +1,93 @@
1
- const mongoToPostgres = require('mongo-query-to-postgres-jsonb')
2
- const util = require('../util')
3
-
4
- module.exports = function (settings, collectionId, collection) {
5
- const pool = util.getPgPool(settings.postgresql_uri)
6
-
7
- return {
8
- init: async function () {
9
- if (collection.plainStringIds) {
10
- await pool.query('CREATE TABLE IF NOT EXISTS ' + collectionId + ' (id text primary key, data jsonb)')
11
- } else {
12
- await pool.query('CREATE TABLE IF NOT EXISTS ' + collectionId + ' (id uuid primary key, data jsonb)')
13
- }
14
- },
15
- all: async function () {
16
- return this.find({})
17
- },
18
- find: async function (rawQuery, offset, limit, orderby, fields) {
19
- const arrayFields = util.getArrayPaths('', collection.schema)
20
- const pgQuery = mongoToPostgres('data', rawQuery || {}, arrayFields)
21
- const select = fields ? mongoToPostgres.convertSelect('data', fields, arrayFields) : '*'
22
- let query = 'SELECT ' + select + ' FROM ' + collectionId + (pgQuery ? ' WHERE ' + pgQuery : '')
23
- if (typeof orderby !== 'undefined') {
24
- query += ' ORDER BY '
25
- query += orderby.map((ordering) => {
26
- return mongoToPostgres.convertDotNotation('data', ordering[0]) + (ordering[1] > 0 ? ' ASC' : ' DESC')
27
- }).join(', ')
28
- }
29
- if (typeof offset !== 'undefined') {
30
- query += ' OFFSET ' + offset
31
- }
32
- if (typeof limit !== 'undefined') {
33
- query += ' LIMIT ' + limit
34
- }
35
- const result = await pool.query(query)
36
- return result.rows.map((row) => row.data)
37
- },
38
- count: async function(rawQuery, offset, limit) {
39
- const arrayFields = util.getArrayPaths('', collection.schema)
40
- const pgQuery = mongoToPostgres('data', rawQuery || {}, arrayFields)
41
- let query = 'SELECT COUNT(*) FROM ' + collectionId + (pgQuery ? ' WHERE ' + pgQuery : '')
42
- if (typeof offset !== 'undefined') {
43
- query += ' OFFSET ' + offset
44
- }
45
- if (typeof limit !== 'undefined') {
46
- query += ' LIMIT ' + limit
47
- }
48
- const result = await pool.query(query)
49
- return parseInt(result.rows[0].count)
50
- },
51
- get: async function (id, fields) {
52
- const arrayFields = util.getArrayPaths('', collection.schema)
53
- const select = fields ? mongoToPostgres.convertSelect('data', fields, arrayFields) : '*'
54
- const result = await pool.query(`SELECT ${select} FROM ${collectionId} WHERE id = $1`, [id])
55
- if (result.rowCount === 0) {
56
- throw new util.ApiError(404, 'document not found')
57
- }
58
- return result.rows[0].data
59
- },
60
- create: async function (data) {
61
- util.addIdIfMissing(data)
62
- try {
63
- await pool.query('INSERT INTO ' + collectionId + ' (id, data) VALUES ($1, $2)', [data._id, data])
64
- } catch (e) {
65
- if (e.message.includes('duplicate key')) {
66
- throw new util.ApiError(409, 'document already exists')
67
- }
68
- throw e
69
- }
70
- return data._id
71
- },
72
- update: async function (id, data) {
73
- if (typeof data._id === 'undefined') {
74
- data._id = id
75
- }
76
- const result = await pool.query('UPDATE ' + collectionId + ' SET data=$2,id=$3 WHERE id=$1', [id, data, data._id])
77
- if (result.rowCount === 0) {
78
- throw new util.ApiError(404, 'document not found')
79
- }
80
- return data
81
- },
82
- // eslint-disable-next-line no-unused-vars
83
- updateWithQuery: async function (query, update, options) {
84
- const arrayFields = util.getArrayPaths('', collection.schema)
85
- const pgQuery = mongoToPostgres('data', query || {}, arrayFields)
86
- const updateSql = mongoToPostgres.convertUpdate('data', update,false)
87
- const result = await pool.query('UPDATE ' + collectionId + ' SET data=' + updateSql + ' WHERE ' + pgQuery)
88
- return {
89
- matchedCount: result.rowCount
90
- }
91
- },
92
- delete: async function (id) {
93
- const result = await pool.query('DELETE FROM ' + collectionId + ' WHERE id=$1', [id])
94
- if (result.rowCount === 0) {
95
- throw new util.ApiError(404, 'document not found')
96
- }
97
- return 'OK'
98
- }
99
- }
100
- }
1
+ const util = require('../util')
2
+
3
+ module.exports = function (settings, collectionId, collection) {
4
+ const pool = util.getPgPool(settings.postgresql_uri)
5
+
6
+ return {
7
+ init: async function () {
8
+ if (collection.plainStringIds) {
9
+ await pool.query('CREATE TABLE IF NOT EXISTS ' + collectionId + ' (id text primary key, data jsonb)')
10
+ } else {
11
+ await pool.query('CREATE TABLE IF NOT EXISTS ' + collectionId + ' (id uuid primary key, data jsonb)')
12
+ }
13
+ },
14
+ all: async function () {
15
+ return this.find({})
16
+ },
17
+ find: async function (rawQuery, offset, limit, orderby, fields) {
18
+ const pgSelect = util.mongoToPostgresSelect(collectionId, fields)
19
+ const pgWhere = util.mongoToPostgresWhere(collectionId, rawQuery)
20
+ let query = 'SELECT ' + pgSelect + ' FROM ' + collectionId + (pgWhere ? ' WHERE ' + pgWhere : '')
21
+ if (typeof orderby !== 'undefined') {
22
+ query += ' ORDER BY '
23
+ query += util.mongoToPostgresOrderBy(collectionId, orderby)
24
+ }
25
+ if (typeof offset !== 'undefined') {
26
+ query += ' OFFSET ' + offset
27
+ }
28
+ if (typeof limit !== 'undefined') {
29
+ query += ' LIMIT ' + limit
30
+ }
31
+ const result = await pool.query(query)
32
+ return result.rows.map((row) => row.data)
33
+ },
34
+ count: async function(rawQuery, offset, limit) {
35
+ const pgWhere = util.mongoToPostgresWhere(collectionId, rawQuery)
36
+ let query = 'SELECT COUNT(*) FROM ' + collectionId + (pgWhere ? ' WHERE ' + pgWhere : '')
37
+ if (typeof offset !== 'undefined') {
38
+ query += ' OFFSET ' + offset
39
+ }
40
+ if (typeof limit !== 'undefined') {
41
+ query += ' LIMIT ' + limit
42
+ }
43
+ const result = await pool.query(query)
44
+ return parseInt(result.rows[0].count)
45
+ },
46
+ get: async function (id, fields) {
47
+ const pgSelect = util.mongoToPostgresSelect(collectionId, fields)
48
+ const result = await pool.query(`SELECT ${pgSelect} FROM ${collectionId} WHERE id = $1`, [id])
49
+ if (result.rowCount === 0) {
50
+ throw new util.ApiError(404, 'document not found')
51
+ }
52
+ return result.rows[0].data
53
+ },
54
+ create: async function (data) {
55
+ util.addIdIfMissing(data)
56
+ try {
57
+ await pool.query('INSERT INTO ' + collectionId + ' (id, data) VALUES ($1, $2)', [data._id, data])
58
+ } catch (e) {
59
+ if (e.message.includes('duplicate key')) {
60
+ throw new util.ApiError(409, 'document already exists')
61
+ }
62
+ throw e
63
+ }
64
+ return data._id
65
+ },
66
+ update: async function (id, data) {
67
+ if (typeof data._id === 'undefined') {
68
+ data._id = id
69
+ }
70
+ const result = await pool.query('UPDATE ' + collectionId + ' SET data=$2,id=$3 WHERE id=$1', [id, data, data._id])
71
+ if (result.rowCount === 0) {
72
+ throw new util.ApiError(404, 'document not found')
73
+ }
74
+ return data
75
+ },
76
+ // eslint-disable-next-line no-unused-vars
77
+ updateWithQuery: async function (query, update, options) {
78
+ const pgWhere = util.mongoToPostgresWhere(collectionId, query)
79
+ const updateSql = util.mongoToPostgresUpdate(collectionId, update)
80
+ const result = await pool.query('UPDATE ' + collectionId + ' SET data=' + updateSql + ' WHERE ' + pgWhere)
81
+ return {
82
+ matchedCount: result.rowCount
83
+ }
84
+ },
85
+ delete: async function (id) {
86
+ const result = await pool.query('DELETE FROM ' + collectionId + ' WHERE id=$1', [id])
87
+ if (result.rowCount === 0) {
88
+ throw new util.ApiError(404, 'document not found')
89
+ }
90
+ return 'OK'
91
+ }
92
+ }
93
+ }
package/index.js CHANGED
@@ -113,8 +113,6 @@ module.exports.api = function (settings) {
113
113
  get pgpool() {
114
114
  return util.getPgPool(router.settings.postgresql_uri)
115
115
  },
116
- doLogin: auth.doLogin,
117
- createHash: auth.createHash,
118
116
  ...util,
119
117
  }
120
118
  router.eventListeners = {}
@@ -13,7 +13,7 @@ module.exports = async function(api) {
13
13
  data.password = oldData.password // preserve password if not explicitly set
14
14
  }
15
15
  }
16
- if (data.password && data.password.length !== 60 && data.password[0] !== '$') {
16
+ if (!auth.isHashed(data.password)) {
17
17
  debug('hashing and replacing password in the user document.')
18
18
  data.password = auth.createHash(data.password)
19
19
  data.meta.password_last_updated_at = new Date().toISOString()
@@ -18,13 +18,13 @@ module.exports = async function (api) {
18
18
  const collections = await api.db.collection.all()
19
19
  collections.forEach((collection) => {
20
20
  const schema = addImplicitFields(collection.schema)
21
- util.addSchemaValidator(collection._id, schema)
21
+ util.addSchema(collection._id, schema)
22
22
  })
23
23
 
24
24
  // Load new and update validators as necessary
25
25
  api.addCollectionListener('changed', 'collection', function updateSchemaValidators (req, collection, data) {
26
26
  const schema = addImplicitFields(data.schema)
27
- util.addSchemaValidator(data._id, schema)
27
+ util.addSchema(data._id, schema)
28
28
  })
29
29
 
30
30
  api.addCollectionListener('get', ['collection', 'schemas'], function ensureIdAdded (req, collection, data) {
@@ -59,7 +59,7 @@
59
59
  "friendly-errors-webpack-plugin": "1.7.0",
60
60
  "html-webpack-plugin": "^4.0.0",
61
61
  "mini-css-extract-plugin": "0.4.1",
62
- "node-notifier": "5.2.1",
62
+ "node-notifier": "8.0.1",
63
63
  "node-sass": "^6.0.1",
64
64
  "optimize-css-assets-webpack-plugin": "5.0.0",
65
65
  "ora": "3.0.0",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "expressa",
3
- "version": "2.0.6",
3
+ "version": "2.0.8",
4
4
  "description": "API framework using JSON schemas",
5
5
  "main": "index.js",
6
6
  "repository": "https://github.com/thomas4019/expressa",
package/util.js CHANGED
@@ -1,4 +1,3 @@
1
- const randomstring = require('randomstring')
2
1
  const {v4} = require('uuid')
3
2
  const debug = require('debug')('expressa')
4
3
  const crypto = require('crypto')
@@ -6,6 +5,9 @@ const pg = require('pg')
6
5
  const pgPools = {}
7
6
  const dot = require('dot-object')
8
7
  const mongoQuery = require('mongo-query')
8
+ const mongoToPostgres = require('mongo-query-to-postgres-jsonb')
9
+ const sift = require('sift')
10
+ const auth = require('./auth/index')
9
11
  const Ajv = require('ajv')
10
12
  const ajv = new Ajv({
11
13
  allErrors: true,
@@ -31,9 +33,11 @@ ajv.addKeyword({
31
33
  const formatKey = ajv.getKeyword('format')
32
34
  formatKey.type = formatKey.type.concat(['array', 'boolean', 'object'])
33
35
 
36
+ const schemas = {}
34
37
  const schemaValidators = {}
35
38
 
36
- exports.addSchemaValidator = function(collection, schema) {
39
+ exports.addSchema = function(collection, schema) {
40
+ schemas[collection] = schema
37
41
  schemaValidators[collection] = ajv.compile(schema)
38
42
  }
39
43
 
@@ -139,6 +143,27 @@ function _exclude(obj, source) {
139
143
  return data
140
144
  }
141
145
 
146
+ exports.mongoToPostgresSelect = function(collection, fields) {
147
+ const arrayFields = exports.getArrayPaths('', schemas[collection])
148
+ return fields ? mongoToPostgres.convertSelect('data', fields, arrayFields) : '*'
149
+ }
150
+
151
+ exports.mongoToPostgresUpdate = function(collection, query) {
152
+ return mongoToPostgres.convertUpdate('data', query,false)
153
+ }
154
+
155
+ exports.mongoToPostgresWhere = function(collection, query) {
156
+ const arrayFields = exports.getArrayPaths('', schemas[collection])
157
+ return mongoToPostgres('data', query || {}, arrayFields)
158
+ }
159
+
160
+ exports.mongoToPostgresOrderBy = function(collection, orderby) {
161
+ const normalizedOrderBy = exports.normalizeOrderBy(orderby)
162
+ return normalizedOrderBy.map((ordering) => {
163
+ return mongoToPostgres.convertDotNotation('data', ordering[0]) + (ordering[1] > 0 ? ' ASC' : ' DESC')
164
+ }).join(', ')
165
+ }
166
+
142
167
  exports.mongoProject = function(record, projection) {
143
168
  if (!projection || Object.keys(projection).length < 1) {
144
169
  return record
@@ -152,6 +177,10 @@ exports.mongoProject = function(record, projection) {
152
177
  return result
153
178
  }
154
179
 
180
+ exports.mongoSearch = function(docs, query) {
181
+ return sift(query || {}, docs)
182
+ }
183
+
155
184
  exports.mongoUpdate = function(doc, update) {
156
185
  return mongoQuery(doc, {}, update)
157
186
  }
@@ -166,6 +195,7 @@ exports.normalizeOrderBy = function(orderby) {
166
195
  return [ordering[0], 1] // add 1 (default to ascending sort)
167
196
  }
168
197
  }
198
+ // if reaches here, is already normalized
169
199
  return ordering
170
200
  })
171
201
  } else if (typeof orderby === 'object') {
@@ -423,3 +453,7 @@ exports.createPagePagination = function createPagePagination (pageData, page, pa
423
453
  exports.getDatabaseOffset = function getDatabaseOffset(page, itemsPerPage) {
424
454
  return (page - 1) * itemsPerPage
425
455
  }
456
+
457
+ exports.createHash = auth.createHash
458
+ exports.isHashed = auth.isHashed
459
+ exports.doLogin = auth.doLogin