@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.
- package/package.json +11 -13
- package/src/app/Application.js +228 -212
- package/src/app/Validator.js +53 -43
- package/src/cli/console.js +6 -4
- package/src/cli/db/createMigration.js +59 -30
- package/src/cli/db/migrate.js +6 -4
- package/src/cli/db/reset.js +8 -5
- package/src/cli/db/rollback.js +6 -4
- package/src/cli/db/seed.js +2 -1
- package/src/cli/index.js +1 -1
- package/src/controllers/AdminController.js +100 -84
- package/src/controllers/CollectionController.js +37 -30
- package/src/controllers/Controller.js +83 -43
- package/src/controllers/ControllerAction.js +27 -15
- package/src/controllers/ModelController.js +4 -1
- package/src/controllers/RelationController.js +19 -21
- package/src/controllers/UsersController.js +3 -4
- package/src/decorators/parameters.js +3 -1
- package/src/decorators/scope.js +1 -1
- package/src/errors/ControllerError.js +2 -1
- package/src/errors/DatabaseError.js +20 -11
- package/src/graph/DitoGraphProcessor.js +48 -40
- package/src/graph/expression.js +6 -8
- package/src/graph/graph.js +20 -11
- package/src/lib/EventEmitter.js +12 -12
- package/src/middleware/handleConnectMiddleware.js +16 -10
- package/src/middleware/handleError.js +6 -5
- package/src/middleware/handleSession.js +78 -0
- package/src/middleware/handleUser.js +2 -2
- package/src/middleware/index.js +2 -0
- package/src/middleware/logRequests.js +3 -3
- package/src/middleware/setupRequestStorage.js +14 -0
- package/src/mixins/AssetMixin.js +62 -58
- package/src/mixins/SessionMixin.js +13 -10
- package/src/mixins/TimeStampedMixin.js +33 -29
- package/src/mixins/UserMixin.js +130 -116
- package/src/models/Model.js +245 -194
- package/src/models/definitions/filters.js +14 -13
- package/src/query/QueryBuilder.js +252 -195
- package/src/query/QueryFilters.js +3 -3
- package/src/query/QueryParameters.js +2 -2
- package/src/query/Registry.js +8 -10
- package/src/schema/keywords/_validate.js +10 -8
- package/src/schema/properties.test.js +247 -206
- package/src/schema/relations.js +42 -20
- package/src/schema/relations.test.js +36 -19
- package/src/services/Service.js +8 -14
- package/src/storage/S3Storage.js +5 -3
- package/src/storage/Storage.js +16 -14
- package/src/utils/function.js +7 -4
- package/src/utils/function.test.js +30 -6
- package/src/utils/object.test.js +5 -1
- package/types/index.d.ts +244 -257
- package/src/app/SessionStore.js +0 -31
package/src/mixins/UserMixin.js
CHANGED
|
@@ -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(
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
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
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
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
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
47
|
+
get password() {
|
|
48
|
+
// Nice try ;)
|
|
49
|
+
return undefined
|
|
50
|
+
}
|
|
49
51
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
52
|
+
set password(password) {
|
|
53
|
+
this.hash = bcrypt.hashSync(password, bcrypt.genSaltSync(10))
|
|
54
|
+
}
|
|
53
55
|
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
56
|
+
async $verifyPassword(password) {
|
|
57
|
+
return bcrypt.compare(password, this.hash)
|
|
58
|
+
}
|
|
57
59
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
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
|
-
|
|
64
|
-
|
|
65
|
-
|
|
65
|
+
$hasOwner(owner) {
|
|
66
|
+
return this.$is(owner)
|
|
67
|
+
}
|
|
66
68
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
69
|
+
$isLoggedIn(ctx) {
|
|
70
|
+
return this.$is(ctx.state.user)
|
|
71
|
+
}
|
|
70
72
|
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
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
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
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
|
-
|
|
126
|
-
|
|
127
|
-
|
|
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 =
|
|
141
|
-
|
|
142
|
-
|
|
153
|
+
const identifier =
|
|
154
|
+
modelName && userClasses[modelName]
|
|
155
|
+
? `${modelName}-${user.id}`
|
|
156
|
+
: null
|
|
143
157
|
done(null, identifier)
|
|
144
158
|
})
|
|
145
159
|
|