@ditojs/server 2.0.4 → 2.1.0

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.
Files changed (54) hide show
  1. package/package.json +11 -13
  2. package/src/app/Application.js +228 -212
  3. package/src/app/Validator.js +53 -43
  4. package/src/cli/console.js +6 -4
  5. package/src/cli/db/createMigration.js +59 -30
  6. package/src/cli/db/migrate.js +6 -4
  7. package/src/cli/db/reset.js +8 -5
  8. package/src/cli/db/rollback.js +6 -4
  9. package/src/cli/db/seed.js +2 -1
  10. package/src/cli/index.js +1 -1
  11. package/src/controllers/AdminController.js +100 -84
  12. package/src/controllers/CollectionController.js +37 -30
  13. package/src/controllers/Controller.js +83 -43
  14. package/src/controllers/ControllerAction.js +27 -15
  15. package/src/controllers/ModelController.js +4 -1
  16. package/src/controllers/RelationController.js +19 -21
  17. package/src/controllers/UsersController.js +3 -4
  18. package/src/decorators/parameters.js +3 -1
  19. package/src/decorators/scope.js +1 -1
  20. package/src/errors/ControllerError.js +2 -1
  21. package/src/errors/DatabaseError.js +20 -11
  22. package/src/graph/DitoGraphProcessor.js +48 -40
  23. package/src/graph/expression.js +6 -8
  24. package/src/graph/graph.js +20 -11
  25. package/src/lib/EventEmitter.js +12 -12
  26. package/src/middleware/handleConnectMiddleware.js +16 -10
  27. package/src/middleware/handleError.js +6 -5
  28. package/src/middleware/handleSession.js +78 -0
  29. package/src/middleware/handleUser.js +2 -2
  30. package/src/middleware/index.js +2 -0
  31. package/src/middleware/logRequests.js +3 -3
  32. package/src/middleware/setupRequestStorage.js +14 -0
  33. package/src/mixins/AssetMixin.js +62 -58
  34. package/src/mixins/SessionMixin.js +13 -10
  35. package/src/mixins/TimeStampedMixin.js +33 -29
  36. package/src/mixins/UserMixin.js +130 -116
  37. package/src/models/Model.js +245 -194
  38. package/src/models/definitions/filters.js +14 -13
  39. package/src/query/QueryBuilder.js +252 -195
  40. package/src/query/QueryFilters.js +3 -3
  41. package/src/query/QueryParameters.js +2 -2
  42. package/src/query/Registry.js +8 -10
  43. package/src/schema/keywords/_validate.js +10 -8
  44. package/src/schema/properties.test.js +247 -206
  45. package/src/schema/relations.js +42 -20
  46. package/src/schema/relations.test.js +36 -19
  47. package/src/services/Service.js +8 -14
  48. package/src/storage/S3Storage.js +5 -3
  49. package/src/storage/Storage.js +16 -14
  50. package/src/utils/function.js +7 -4
  51. package/src/utils/function.test.js +30 -6
  52. package/src/utils/object.test.js +5 -1
  53. package/types/index.d.ts +244 -257
  54. package/src/app/SessionStore.js +0 -31
@@ -4,130 +4,143 @@ import { Strategy as LocalStrategy } from 'passport-local'
4
4
  import { mixin, asArray } from '@ditojs/utils'
5
5
  import { AuthenticationError } from '../errors/index.js'
6
6
 
7
- export const UserMixin = mixin(Model => class extends Model {
8
- static options = {
9
- usernameProperty: 'username',
10
- passwordProperty: 'password',
11
- // This option can be used to specify (eager) scopes to be applied when
12
- // the user is deserialized from the session.
13
- sessionScope: undefined
14
- }
7
+ export const UserMixin = mixin(
8
+ Model =>
9
+ class extends Model {
10
+ static options = {
11
+ usernameProperty: 'username',
12
+ passwordProperty: 'password',
13
+ // This option can be used to specify (eager) scopes to be applied when
14
+ // the user is deserialized from the session.
15
+ sessionScope: undefined
16
+ }
15
17
 
16
- static get properties() {
17
- const {
18
- usernameProperty,
19
- passwordProperty
20
- } = this.definition.options
21
- return {
22
- [usernameProperty]: {
23
- type: 'string',
24
- required: true
25
- },
26
-
27
- // `password` isn't stored, but this is required for validation:
28
- [passwordProperty]: {
29
- type: 'string',
30
- computed: true
31
- },
32
-
33
- hash: {
34
- type: 'string',
35
- hidden: true
36
- },
37
-
38
- lastLogin: {
39
- type: 'timestamp',
40
- nullable: true
18
+ static get properties() {
19
+ const {
20
+ usernameProperty,
21
+ passwordProperty
22
+ } = this.definition.options
23
+ return {
24
+ [usernameProperty]: {
25
+ type: 'string',
26
+ required: true
27
+ },
28
+
29
+ // `password` isn't stored, but this is required for validation:
30
+ [passwordProperty]: {
31
+ type: 'string',
32
+ computed: true
33
+ },
34
+
35
+ hash: {
36
+ type: 'string',
37
+ hidden: true
38
+ },
39
+
40
+ lastLogin: {
41
+ type: 'timestamp',
42
+ nullable: true
43
+ }
44
+ }
41
45
  }
42
- }
43
- }
44
46
 
45
- get password() {
46
- // Nice try ;)
47
- return undefined
48
- }
47
+ get password() {
48
+ // Nice try ;)
49
+ return undefined
50
+ }
49
51
 
50
- set password(password) {
51
- this.hash = bcrypt.hashSync(password, bcrypt.genSaltSync(10))
52
- }
52
+ set password(password) {
53
+ this.hash = bcrypt.hashSync(password, bcrypt.genSaltSync(10))
54
+ }
53
55
 
54
- async $verifyPassword(password) {
55
- return bcrypt.compare(password, this.hash)
56
- }
56
+ async $verifyPassword(password) {
57
+ return bcrypt.compare(password, this.hash)
58
+ }
57
59
 
58
- $hasRole(...roles) {
59
- // Support an optional `roles` array on the model that can contain roles.
60
- return this.roles?.find(role => roles.includes(role)) || false
61
- }
60
+ $hasRole(...roles) {
61
+ // Support an optional `roles` array on the model that can contain roles
62
+ return this.roles?.find(role => roles.includes(role)) || false
63
+ }
62
64
 
63
- $hasOwner(owner) {
64
- return this.$is(owner)
65
- }
65
+ $hasOwner(owner) {
66
+ return this.$is(owner)
67
+ }
66
68
 
67
- $isLoggedIn(ctx) {
68
- return this.$is(ctx.state.user)
69
- }
69
+ $isLoggedIn(ctx) {
70
+ return this.$is(ctx.state.user)
71
+ }
70
72
 
71
- static setup() {
72
- userClasses[this.name] = this
73
- const {
74
- usernameProperty,
75
- passwordProperty
76
- } = this.definition.options
77
- passport.use(this.name,
78
- new LocalStrategy(
79
- {
80
- usernameField: usernameProperty,
81
- passwordField: passwordProperty,
82
- // Wee need the `req` object, so we can get the active database
83
- // transaction through `req.ctx.transaction`:
84
- passReqToCallback: true
85
- },
86
- async (req, username, password, done) => {
87
- try {
88
- const user = await this.sessionQuery(req.ctx.transaction)
89
- .findOne(usernameProperty, username)
90
- const res = user && await user.$verifyPassword(password)
91
- ? user
92
- : null
93
- done(null, res)
94
- } catch (err) {
95
- done(err)
96
- }
97
- }
98
- )
99
- )
100
- }
73
+ static setup() {
74
+ userClasses[this.name] = this
75
+ const {
76
+ usernameProperty,
77
+ passwordProperty
78
+ } = this.definition.options
79
+ passport.use(
80
+ this.name,
81
+ new LocalStrategy(
82
+ {
83
+ usernameField: usernameProperty,
84
+ passwordField: passwordProperty,
85
+ // Wee need the `req` object, so we can get the active database
86
+ // transaction through `req.ctx.transaction`:
87
+ passReqToCallback: true
88
+ },
89
+ async (req, username, password, done) => {
90
+ try {
91
+ const user = await this.sessionQuery(
92
+ req.ctx.transaction
93
+ ).findOne(usernameProperty, username)
94
+ const res =
95
+ user && (await user.$verifyPassword(password))
96
+ ? user
97
+ : null
98
+ done(null, res)
99
+ } catch (err) {
100
+ done(err)
101
+ }
102
+ }
103
+ )
104
+ )
105
+ }
101
106
 
102
- static async login(ctx, options) {
103
- // Unfortunately koa-passport isn't promisified yet, so do some wrapping:
104
- return new Promise((resolve, reject) => {
105
- // Use a custom callback to handle authentication, see:
106
- // http://www.passportjs.org/docs/downloads/html/#custom-callback
107
- passport.authenticate(this.name, async (err, user, message, status) => {
108
- if (err) {
109
- reject(err)
110
- } else if (user) {
111
- try {
112
- await ctx.login(user, options)
113
- resolve(user)
114
- } catch (err) {
115
- reject(err)
116
- }
117
- } else {
118
- reject(new AuthenticationError(
119
- message || 'Password or username is incorrect', status))
120
- }
121
- })(ctx)
122
- })
123
- }
107
+ static async login(ctx, options) {
108
+ // Unfortunately koa-passport isn't promisified yet so do some wrapping:
109
+ return new Promise((resolve, reject) => {
110
+ // Use a custom callback to handle authentication, see:
111
+ // http://www.passportjs.org/docs/downloads/html/#custom-callback
112
+ passport.authenticate(
113
+ this.name,
114
+ async (err, user, message, status) => {
115
+ if (err) {
116
+ reject(err)
117
+ } else if (user) {
118
+ try {
119
+ await ctx.login(user, options)
120
+ resolve(user)
121
+ } catch (err) {
122
+ reject(err)
123
+ }
124
+ } else {
125
+ reject(
126
+ new AuthenticationError(
127
+ message || 'Password or username is incorrect',
128
+ status
129
+ )
130
+ )
131
+ }
132
+ }
133
+ )(ctx)
134
+ })
135
+ }
124
136
 
125
- static sessionQuery(trx) {
126
- return this.query(trx).withScope(
127
- ...asArray(this.definition.options.sessionScope)
128
- )
129
- }
130
- })
137
+ static sessionQuery(trx) {
138
+ return this.query(trx).withScope(
139
+ ...asArray(this.definition.options.sessionScope)
140
+ )
141
+ }
142
+ }
143
+ )
131
144
 
132
145
  const userClasses = {}
133
146
 
@@ -137,9 +150,10 @@ passport.serializeUser((req, user, done) => {
137
150
  // To support multiple user model classes, use both the model class name and
138
151
  // id as identifier.
139
152
  const modelName = user?.constructor.name
140
- const identifier = modelName && userClasses[modelName]
141
- ? `${modelName}-${user.id}`
142
- : null
153
+ const identifier =
154
+ modelName && userClasses[modelName]
155
+ ? `${modelName}-${user.id}`
156
+ : null
143
157
  done(null, identifier)
144
158
  })
145
159