@things-factory/auth-base 8.0.0-alpha.8 → 8.0.0-beta.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/client/actions/auth.ts +5 -4
- package/client/index.ts +1 -0
- package/client/verify-webauthn.ts +86 -0
- package/dist-client/actions/auth.d.ts +5 -4
- package/dist-client/actions/auth.js.map +1 -1
- package/dist-client/index.d.ts +1 -0
- package/dist-client/index.js +1 -0
- package/dist-client/index.js.map +1 -1
- package/dist-client/tsconfig.tsbuildinfo +1 -1
- package/dist-client/verify-webauthn.d.ts +13 -0
- package/dist-client/verify-webauthn.js +72 -0
- package/dist-client/verify-webauthn.js.map +1 -0
- package/dist-server/constants/error-code.d.ts +2 -0
- package/dist-server/constants/error-code.js +3 -1
- package/dist-server/constants/error-code.js.map +1 -1
- package/dist-server/controllers/change-pwd.js +2 -2
- package/dist-server/controllers/change-pwd.js.map +1 -1
- package/dist-server/controllers/delete-user.js +13 -12
- package/dist-server/controllers/delete-user.js.map +1 -1
- package/dist-server/controllers/invitation.d.ts +2 -1
- package/dist-server/controllers/invitation.js +30 -5
- package/dist-server/controllers/invitation.js.map +1 -1
- package/dist-server/controllers/profile.d.ts +4 -3
- package/dist-server/controllers/profile.js +20 -2
- package/dist-server/controllers/profile.js.map +1 -1
- package/dist-server/controllers/signin.d.ts +4 -1
- package/dist-server/controllers/signin.js +17 -1
- package/dist-server/controllers/signin.js.map +1 -1
- package/dist-server/controllers/signup.js +13 -4
- package/dist-server/controllers/signup.js.map +1 -1
- package/dist-server/controllers/unlock-user.js +1 -0
- package/dist-server/controllers/unlock-user.js.map +1 -1
- package/dist-server/controllers/verification.js +1 -0
- package/dist-server/controllers/verification.js.map +1 -1
- package/dist-server/index.d.ts +1 -0
- package/dist-server/index.js +1 -0
- package/dist-server/index.js.map +1 -1
- package/dist-server/middlewares/signin-middleware.js +3 -3
- package/dist-server/middlewares/signin-middleware.js.map +1 -1
- package/dist-server/middlewares/webauthn-middleware.js.map +1 -1
- package/dist-server/migrations/1548206416130-SeedUser.js +2 -1
- package/dist-server/migrations/1548206416130-SeedUser.js.map +1 -1
- package/dist-server/router/auth-checkin-router.js +8 -2
- package/dist-server/router/auth-checkin-router.js.map +1 -1
- package/dist-server/router/auth-private-process-router.js +12 -7
- package/dist-server/router/auth-private-process-router.js.map +1 -1
- package/dist-server/router/auth-public-process-router.js +20 -9
- package/dist-server/router/auth-public-process-router.js.map +1 -1
- package/dist-server/router/auth-signin-router.js +3 -3
- package/dist-server/router/auth-signin-router.js.map +1 -1
- package/dist-server/router/webauthn-router.js +51 -1
- package/dist-server/router/webauthn-router.js.map +1 -1
- package/dist-server/service/appliance/appliance.js +4 -1
- package/dist-server/service/appliance/appliance.js.map +1 -1
- package/dist-server/service/application/application.js +11 -3
- package/dist-server/service/application/application.js.map +1 -1
- package/dist-server/service/invitation/invitation-mutation.d.ts +3 -2
- package/dist-server/service/invitation/invitation-mutation.js +20 -8
- package/dist-server/service/invitation/invitation-mutation.js.map +1 -1
- package/dist-server/service/user/user-mutation.d.ts +10 -9
- package/dist-server/service/user/user-mutation.js +135 -61
- package/dist-server/service/user/user-mutation.js.map +1 -1
- package/dist-server/service/user/user-types.d.ts +1 -0
- package/dist-server/service/user/user-types.js +4 -0
- package/dist-server/service/user/user-types.js.map +1 -1
- package/dist-server/service/user/user.d.ts +1 -0
- package/dist-server/service/user/user.js +57 -17
- package/dist-server/service/user/user.js.map +1 -1
- package/dist-server/service/verification-token/verification-token.js +7 -2
- package/dist-server/service/verification-token/verification-token.js.map +1 -1
- package/dist-server/templates/account-unlock-email.d.ts +2 -1
- package/dist-server/templates/account-unlock-email.js +1 -1
- package/dist-server/templates/account-unlock-email.js.map +1 -1
- package/dist-server/templates/invitation-email.d.ts +2 -1
- package/dist-server/templates/invitation-email.js +1 -1
- package/dist-server/templates/invitation-email.js.map +1 -1
- package/dist-server/templates/verification-email.d.ts +2 -1
- package/dist-server/templates/verification-email.js +1 -1
- package/dist-server/templates/verification-email.js.map +1 -1
- package/dist-server/tsconfig.tsbuildinfo +1 -1
- package/dist-server/utils/check-user-has-role.d.ts +12 -0
- package/dist-server/utils/check-user-has-role.js +28 -0
- package/dist-server/utils/check-user-has-role.js.map +1 -0
- package/package.json +6 -6
- package/server/constants/error-code.ts +2 -0
- package/server/controllers/change-pwd.ts +3 -2
- package/server/controllers/delete-user.ts +16 -13
- package/server/controllers/invitation.ts +36 -5
- package/server/controllers/profile.ts +29 -2
- package/server/controllers/signin.ts +21 -2
- package/server/controllers/signup.ts +16 -4
- package/server/controllers/unlock-user.ts +1 -0
- package/server/controllers/verification.ts +1 -0
- package/server/index.ts +1 -0
- package/server/middlewares/signin-middleware.ts +3 -3
- package/server/middlewares/webauthn-middleware.ts +7 -8
- package/server/migrations/1548206416130-SeedUser.ts +2 -1
- package/server/router/auth-checkin-router.ts +11 -5
- package/server/router/auth-private-process-router.ts +14 -7
- package/server/router/auth-public-process-router.ts +22 -10
- package/server/router/auth-signin-router.ts +3 -3
- package/server/router/webauthn-router.ts +71 -9
- package/server/service/appliance/appliance.ts +4 -1
- package/server/service/application/application.ts +12 -3
- package/server/service/invitation/invitation-mutation.ts +24 -9
- package/server/service/user/user-mutation.ts +152 -62
- package/server/service/user/user-types.ts +3 -0
- package/server/service/user/user.ts +74 -18
- package/server/service/verification-token/verification-token.ts +9 -3
- package/server/templates/account-unlock-email.ts +1 -1
- package/server/templates/invitation-email.ts +1 -1
- package/server/templates/verification-email.ts +1 -1
- package/server/utils/check-user-has-role.ts +29 -0
- package/translations/en.json +5 -1
- package/translations/ja.json +5 -1
- package/translations/ko.json +6 -3
- package/translations/ms.json +5 -1
- package/translations/zh.json +5 -1
@@ -1,6 +1,6 @@
|
|
1
1
|
import { Arg, Ctx, Directive, Mutation, Resolver } from 'type-graphql'
|
2
2
|
import { GraphQLEmailAddress } from 'graphql-scalars'
|
3
|
-
import { ILike, In, SelectQueryBuilder } from 'typeorm'
|
3
|
+
import { ILike, In, SelectQueryBuilder, EntityManager } from 'typeorm'
|
4
4
|
|
5
5
|
import { config } from '@things-factory/env'
|
6
6
|
import { Domain, getRepository, ObjectRef } from '@things-factory/shell'
|
@@ -10,6 +10,7 @@ import { buildDomainUsersQueryBuilder } from '../../utils/get-domain-users'
|
|
10
10
|
import { Role } from '../role/role'
|
11
11
|
import { User, UserStatus } from './user'
|
12
12
|
import { NewUser, UserPatch } from './user-types'
|
13
|
+
import { USERNAME_ALREADY_EXISTS, EMAIL_ALREADY_EXISTS } from '../../constants/error-code'
|
13
14
|
|
14
15
|
@Resolver(User)
|
15
16
|
export class UserMutation {
|
@@ -17,19 +18,29 @@ export class UserMutation {
|
|
17
18
|
@Directive('@transaction')
|
18
19
|
@Mutation(returns => User, { description: 'To create new user' })
|
19
20
|
async createUser(@Arg('user') user: NewUser, @Ctx() context: ResolverContext) {
|
20
|
-
const { domain } = context.state
|
21
|
+
const { domain, tx } = context.state
|
21
22
|
const { defaultPassword } = config.get('password')
|
22
|
-
const { email } = user
|
23
|
+
const { username, email } = user
|
24
|
+
const userRepository = getRepository(User, tx)
|
23
25
|
|
26
|
+
user.username = username.trim()
|
24
27
|
user.email = email.trim()
|
25
28
|
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
+
if (await userRepository.findOne({ where: { username: user.username } })) {
|
30
|
+
throw new Error(context.t(USERNAME_ALREADY_EXISTS))
|
31
|
+
}
|
32
|
+
|
33
|
+
if (await userRepository.findOne({ where: { email: ILike(user.email) } })) {
|
34
|
+
throw new Error(context.t(EMAIL_ALREADY_EXISTS))
|
29
35
|
}
|
30
36
|
|
31
37
|
if (!user.password && !defaultPassword) {
|
32
|
-
throw new Error(
|
38
|
+
throw new Error('initial password or default password should be supported.')
|
39
|
+
}
|
40
|
+
|
41
|
+
// TODO username은 다음 패턴을 따라야 한다. pattern="^[A-Za-z0-9]*$"
|
42
|
+
if (!/^[A-Za-z0-9]*$/.test(user.username)) {
|
43
|
+
throw new Error(context.t('error.invalid x', { x: context.t('field.username') }))
|
33
44
|
}
|
34
45
|
|
35
46
|
// consider if validation password rule is required
|
@@ -38,14 +49,14 @@ export class UserMutation {
|
|
38
49
|
|
39
50
|
const salt = User.generateSalt()
|
40
51
|
|
41
|
-
return await
|
52
|
+
return await userRepository.save({
|
42
53
|
creator: context.state.user,
|
43
54
|
updater: context.state.user,
|
44
55
|
...user,
|
45
56
|
domains: [domain],
|
46
57
|
roles:
|
47
58
|
user.roles && user.roles.length
|
48
|
-
? await getRepository(Role).findBy({
|
59
|
+
? await getRepository(Role, tx).findBy({
|
49
60
|
id: In(user.roles.map(role => role.id)),
|
50
61
|
domain: { id: domain.id }
|
51
62
|
})
|
@@ -64,7 +75,7 @@ export class UserMutation {
|
|
64
75
|
@Arg('patch') patch: UserPatch,
|
65
76
|
@Ctx() context: ResolverContext
|
66
77
|
) {
|
67
|
-
const { domain, user: updater }: { domain: Domain; user: User } = context.state
|
78
|
+
const { domain, user: updater, tx }: { domain: Domain; user: User; tx?: EntityManager } = context.state
|
68
79
|
const qb: SelectQueryBuilder<User> = buildDomainUsersQueryBuilder(domain.id, 'USER')
|
69
80
|
const user: User = await qb
|
70
81
|
.andWhere('LOWER(USER.email) = :email', { email: email?.toLowerCase().trim() || '' })
|
@@ -73,14 +84,16 @@ export class UserMutation {
|
|
73
84
|
.getOne()
|
74
85
|
|
75
86
|
if (patch.roles) {
|
76
|
-
patch.roles = await getRepository(Role).find({
|
87
|
+
patch.roles = await getRepository(Role, tx).find({
|
88
|
+
where: { id: In(patch.roles.map((r: Partial<Role>) => r.id)) }
|
89
|
+
})
|
77
90
|
}
|
78
91
|
|
79
92
|
if (patch.status && patch.status === 'activated') {
|
80
93
|
user.status = UserStatus.ACTIVATED
|
81
94
|
}
|
82
95
|
|
83
|
-
return await getRepository(User).save({
|
96
|
+
return await getRepository(User, tx).save({
|
84
97
|
...user,
|
85
98
|
...patch,
|
86
99
|
updater
|
@@ -92,7 +105,7 @@ export class UserMutation {
|
|
92
105
|
@Mutation(returns => [User], { description: 'To modify multiple users information' })
|
93
106
|
async updateMultipleUser(@Arg('patches', type => [UserPatch]) patches: UserPatch[], @Ctx() context: ResolverContext) {
|
94
107
|
const { domain, user, tx } = context.state
|
95
|
-
const userRepo =
|
108
|
+
const userRepo = getRepository(User, tx)
|
96
109
|
|
97
110
|
let results = []
|
98
111
|
const _createRecords = patches.filter((patch: any) => patch.cuFlag.toUpperCase() === '+')
|
@@ -183,10 +196,10 @@ export class UserMutation {
|
|
183
196
|
@Directive('@privilege(category: "user", privilege: "mutation", domainOwnerGranted: true)')
|
184
197
|
@Directive('@transaction')
|
185
198
|
@Mutation(returns => Boolean, { description: 'To delete a user' })
|
186
|
-
async deleteUser(@Arg('
|
199
|
+
async deleteUser(@Arg('username') username: string, @Ctx() context: ResolverContext) {
|
187
200
|
const { tx } = context.state
|
188
201
|
|
189
|
-
await commonDeleteUser({
|
202
|
+
await commonDeleteUser({ username }, tx)
|
190
203
|
|
191
204
|
return true
|
192
205
|
}
|
@@ -194,25 +207,31 @@ export class UserMutation {
|
|
194
207
|
@Directive('@privilege(category: "user", privilege: "mutation", domainOwnerGranted: true)')
|
195
208
|
@Directive('@transaction')
|
196
209
|
@Mutation(returns => Boolean, { description: 'To delete some users' })
|
197
|
-
async deleteUsers(@Arg('
|
210
|
+
async deleteUsers(@Arg('usernames', type => [String]) usernames: string[], @Ctx() context: ResolverContext) {
|
198
211
|
const { tx } = context.state
|
199
|
-
await commonDeleteUsers({
|
212
|
+
await commonDeleteUsers({ usernames }, tx)
|
200
213
|
|
201
214
|
return true
|
202
215
|
}
|
203
216
|
|
204
217
|
@Directive('@transaction')
|
205
218
|
@Mutation(returns => Boolean, { description: 'To invite new user' })
|
206
|
-
async inviteUser(
|
207
|
-
|
208
|
-
|
209
|
-
|
210
|
-
|
211
|
-
|
212
|
-
where: { email: ILike(email) },
|
219
|
+
async inviteUser(@Arg('username') username: string, @Ctx() context: ResolverContext): Promise<boolean> {
|
220
|
+
const { domain, tx } = context.state
|
221
|
+
const userRepository = getRepository(User, tx)
|
222
|
+
|
223
|
+
var invitee: User = await userRepository.findOne({
|
224
|
+
where: { username },
|
213
225
|
relations: ['domains']
|
214
226
|
})
|
215
227
|
|
228
|
+
if (!invitee && /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(username)) {
|
229
|
+
invitee = await userRepository.findOne({
|
230
|
+
where: { email: ILike(username) },
|
231
|
+
relations: ['domains']
|
232
|
+
})
|
233
|
+
}
|
234
|
+
|
216
235
|
if (!invitee) {
|
217
236
|
throw new Error(context.t('error.failed to find x', { x: context.t('field.user') }))
|
218
237
|
}
|
@@ -221,8 +240,9 @@ export class UserMutation {
|
|
221
240
|
if (existingDomains.find((d: Domain) => d.id === domain.id)) {
|
222
241
|
throw new Error(context.t('error.x already exists in y', { x: context.t('field.user'), y: domain.name }))
|
223
242
|
}
|
243
|
+
|
224
244
|
invitee.domains = [...existingDomains, domain]
|
225
|
-
await
|
245
|
+
await userRepository.save(invitee)
|
226
246
|
|
227
247
|
return true
|
228
248
|
}
|
@@ -230,15 +250,22 @@ export class UserMutation {
|
|
230
250
|
@Directive('@transaction')
|
231
251
|
@Directive('@privilege(category: "user", privilege: "mutation", domainOwnerGranted: true)')
|
232
252
|
@Mutation(returns => Boolean, { description: 'To delete domain user' })
|
233
|
-
async deleteDomainUser(
|
234
|
-
@Arg('email', type => GraphQLEmailAddress) email: string,
|
235
|
-
@Ctx() context: ResolverContext
|
236
|
-
): Promise<boolean> {
|
253
|
+
async deleteDomainUser(@Arg('username') username: string, @Ctx() context: ResolverContext): Promise<boolean> {
|
237
254
|
const { tx, domain } = context.state
|
255
|
+
const userRepository = getRepository(User, tx)
|
256
|
+
|
257
|
+
var user: User = await userRepository.findOne({
|
258
|
+
where: { username },
|
259
|
+
relations: ['domains', 'roles']
|
260
|
+
})
|
261
|
+
|
262
|
+
if (!user && /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(username)) {
|
263
|
+
user = await userRepository.findOne({
|
264
|
+
where: { email: ILike(username) },
|
265
|
+
relations: ['domains', 'roles']
|
266
|
+
})
|
267
|
+
}
|
238
268
|
|
239
|
-
let user: User = await tx
|
240
|
-
.getRepository(User)
|
241
|
-
.findOne({ where: { email: ILike(email) }, relations: ['domains', 'roles', 'roles.domain'] })
|
242
269
|
if (!user) {
|
243
270
|
throw new Error(context.t('error.failed to find x', { x: context.t('field.user') }))
|
244
271
|
}
|
@@ -252,9 +279,9 @@ export class UserMutation {
|
|
252
279
|
user.domains.splice(targetDomainIdx, 1)
|
253
280
|
|
254
281
|
// Remove domain's roles that user has
|
255
|
-
user.roles = user.roles.filter((role: Role) => role.
|
282
|
+
user.roles = user.roles.filter((role: Role) => role.domainId !== domain.id)
|
256
283
|
|
257
|
-
await
|
284
|
+
await userRepository.save(user)
|
258
285
|
|
259
286
|
return true
|
260
287
|
}
|
@@ -262,16 +289,40 @@ export class UserMutation {
|
|
262
289
|
@Directive('@privilege(domainOwnerGranted: true, superUserGranted: true)')
|
263
290
|
@Directive('@transaction')
|
264
291
|
@Mutation(returns => Boolean, { description: 'To transfer owner of domain' })
|
265
|
-
async transferOwner(
|
266
|
-
|
267
|
-
|
268
|
-
|
269
|
-
|
270
|
-
|
271
|
-
|
292
|
+
async transferOwner(@Arg('username') username: string, @Ctx() context: ResolverContext): Promise<boolean> {
|
293
|
+
const { domain, tx } = context.state
|
294
|
+
const userRepository = getRepository(User, tx)
|
295
|
+
|
296
|
+
var user: User = await userRepository.findOne({
|
297
|
+
where: { username },
|
298
|
+
relations: ['domains']
|
272
299
|
})
|
300
|
+
|
301
|
+
if (!user && /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(username)) {
|
302
|
+
user = await userRepository.findOne({
|
303
|
+
where: { email: ILike(username) },
|
304
|
+
relations: ['domains']
|
305
|
+
})
|
306
|
+
}
|
307
|
+
|
308
|
+
if (!user) {
|
309
|
+
throw new Error(context.t('error.failed to find x', { x: context.t('field.user') }))
|
310
|
+
}
|
311
|
+
|
312
|
+
if (user.status !== UserStatus.ACTIVATED) {
|
313
|
+
throw new Error('Only activated users are eligible to receive admin privileges.')
|
314
|
+
}
|
315
|
+
|
316
|
+
if (user.domains.map((d: Domain) => d.id).indexOf(domain.id) < 0) {
|
317
|
+
throw new Error(`User is not belongs to current domain`)
|
318
|
+
}
|
319
|
+
|
320
|
+
if (user.roles.filter((r: Role) => r.domainId == domain.id).length == 0) {
|
321
|
+
throw new Error(`Only users with at least one role in this domain are eligible to receive admin privileges.`)
|
322
|
+
}
|
323
|
+
|
273
324
|
domain.owner = user.id
|
274
|
-
await getRepository(Domain).save(domain)
|
325
|
+
await getRepository(Domain, tx).save(domain)
|
275
326
|
|
276
327
|
return true
|
277
328
|
}
|
@@ -279,15 +330,24 @@ export class UserMutation {
|
|
279
330
|
@Directive('@privilege(category: "user", privilege: "mutation", domainOwnerGranted: true)')
|
280
331
|
@Directive('@transaction')
|
281
332
|
@Mutation(returns => Boolean, { description: 'To activate user' })
|
282
|
-
async activateUser(@Arg('
|
333
|
+
async activateUser(@Arg('username') username: string, @Ctx() context: ResolverContext): Promise<boolean> {
|
283
334
|
const { tx, domain } = context.state
|
335
|
+
const userRepository = getRepository(User, tx)
|
284
336
|
|
285
|
-
|
286
|
-
where: {
|
337
|
+
var targetUser: User = await userRepository.findOne({
|
338
|
+
where: { username },
|
287
339
|
relations: ['domains']
|
288
340
|
})
|
341
|
+
|
342
|
+
if (!targetUser && /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(username)) {
|
343
|
+
targetUser = await userRepository.findOne({
|
344
|
+
where: { email: ILike(username) },
|
345
|
+
relations: ['domains']
|
346
|
+
})
|
347
|
+
}
|
348
|
+
|
289
349
|
if (!targetUser) {
|
290
|
-
throw new Error('
|
350
|
+
throw new Error(context.t('error.failed to find x', { x: context.t('field.user') }))
|
291
351
|
}
|
292
352
|
|
293
353
|
if (!targetUser?.domains?.find((userDomain: Domain) => userDomain.id === domain.id)) {
|
@@ -297,7 +357,7 @@ export class UserMutation {
|
|
297
357
|
targetUser.failCount = 0
|
298
358
|
targetUser.status = UserStatus.ACTIVATED
|
299
359
|
|
300
|
-
await
|
360
|
+
await userRepository.save(targetUser)
|
301
361
|
|
302
362
|
return true
|
303
363
|
}
|
@@ -305,15 +365,24 @@ export class UserMutation {
|
|
305
365
|
@Directive('@privilege(category: "user", privilege: "mutation", domainOwnerGranted: true)')
|
306
366
|
@Directive('@transaction')
|
307
367
|
@Mutation(returns => Boolean, { description: 'To inactivate user' })
|
308
|
-
async inactivateUser(@Arg('
|
368
|
+
async inactivateUser(@Arg('username') username: string, @Ctx() context: ResolverContext): Promise<boolean> {
|
309
369
|
const { tx, domain } = context.state
|
370
|
+
const userRepository = getRepository(User, tx)
|
310
371
|
|
311
|
-
|
312
|
-
where: {
|
372
|
+
var targetUser: User = await userRepository.findOne({
|
373
|
+
where: { username },
|
313
374
|
relations: ['domains']
|
314
375
|
})
|
376
|
+
|
377
|
+
if (!targetUser && /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(username)) {
|
378
|
+
targetUser = await userRepository.findOne({
|
379
|
+
where: { email: ILike(username) },
|
380
|
+
relations: ['domains']
|
381
|
+
})
|
382
|
+
}
|
383
|
+
|
315
384
|
if (!targetUser) {
|
316
|
-
throw new Error('
|
385
|
+
throw new Error(context.t('error.failed to find x', { x: context.t('field.user') }))
|
317
386
|
}
|
318
387
|
|
319
388
|
if (!targetUser?.domains?.find((userDomain: Domain) => userDomain.id === domain.id)) {
|
@@ -326,7 +395,7 @@ export class UserMutation {
|
|
326
395
|
|
327
396
|
targetUser.status = UserStatus.INACTIVE
|
328
397
|
|
329
|
-
await
|
398
|
+
await userRepository.save(targetUser)
|
330
399
|
|
331
400
|
return true
|
332
401
|
}
|
@@ -334,7 +403,7 @@ export class UserMutation {
|
|
334
403
|
@Directive('@privilege(category: "user", privilege: "mutation", domainOwnerGranted: true)')
|
335
404
|
@Directive('@transaction')
|
336
405
|
@Mutation(returns => Boolean, { description: 'To reset password to default' })
|
337
|
-
async resetPasswordToDefault(@Arg('
|
406
|
+
async resetPasswordToDefault(@Arg('username') username: string, @Ctx() context: ResolverContext): Promise<boolean> {
|
338
407
|
const { tx, domain } = context.state
|
339
408
|
|
340
409
|
const { defaultPassword } = config.get('password')
|
@@ -342,12 +411,22 @@ export class UserMutation {
|
|
342
411
|
throw new Error('No default password found')
|
343
412
|
}
|
344
413
|
|
345
|
-
const
|
346
|
-
|
414
|
+
const userRepository = getRepository(User, tx)
|
415
|
+
|
416
|
+
var targetUser: User = await userRepository.findOne({
|
417
|
+
where: { username },
|
347
418
|
relations: ['domains']
|
348
419
|
})
|
420
|
+
|
421
|
+
if (!targetUser && /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(username)) {
|
422
|
+
targetUser = await userRepository.findOne({
|
423
|
+
where: { email: ILike(username) },
|
424
|
+
relations: ['domains']
|
425
|
+
})
|
426
|
+
}
|
427
|
+
|
349
428
|
if (!targetUser) {
|
350
|
-
throw new Error('
|
429
|
+
throw new Error(context.t('error.failed to find x', { x: context.t('field.user') }))
|
351
430
|
}
|
352
431
|
|
353
432
|
if (!targetUser?.domains?.find((userDomain: Domain) => userDomain.id === domain.id)) {
|
@@ -356,7 +435,8 @@ export class UserMutation {
|
|
356
435
|
|
357
436
|
targetUser.salt = User.generateSalt()
|
358
437
|
targetUser.password = User.encode(defaultPassword, targetUser.salt)
|
359
|
-
|
438
|
+
|
439
|
+
await userRepository.save(targetUser)
|
360
440
|
|
361
441
|
return true
|
362
442
|
}
|
@@ -365,18 +445,28 @@ export class UserMutation {
|
|
365
445
|
@Directive('@transaction')
|
366
446
|
@Mutation(returns => User, { description: 'To update roles for a user' })
|
367
447
|
async updateUserRoles(
|
368
|
-
@Arg('
|
448
|
+
@Arg('username') username: string,
|
369
449
|
@Arg('availableRoles', type => [ObjectRef]) availableRoles: ObjectRef[],
|
370
450
|
@Arg('selectedRoles', type => [ObjectRef]) selectedRoles: ObjectRef[],
|
371
451
|
@Ctx() context: ResolverContext
|
372
452
|
) {
|
373
453
|
const { domain, tx } = context.state
|
374
|
-
|
375
|
-
|
454
|
+
const userRepository = getRepository(User, tx)
|
455
|
+
|
456
|
+
var user: User = await userRepository.findOne({
|
457
|
+
where: { username },
|
376
458
|
relations: ['domains', 'roles']
|
377
459
|
})
|
460
|
+
|
461
|
+
if (!user && /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(username)) {
|
462
|
+
user = await userRepository.findOne({
|
463
|
+
where: { email: ILike(username) },
|
464
|
+
relations: ['domains', 'roles']
|
465
|
+
})
|
466
|
+
}
|
467
|
+
|
378
468
|
if (!user) {
|
379
|
-
throw new Error('
|
469
|
+
throw new Error(context.t('error.failed to find x', { x: context.t('field.user') }))
|
380
470
|
}
|
381
471
|
|
382
472
|
if (user.domains.map((d: Domain) => d.id).indexOf(domain.id) < 0) {
|
@@ -387,6 +477,6 @@ export class UserMutation {
|
|
387
477
|
user.roles = user.roles.filter((r: Role) => availableRoleIds.indexOf(r.id) < 0)
|
388
478
|
user.roles = user.roles.concat(selectedRoles as Role[])
|
389
479
|
|
390
|
-
return await
|
480
|
+
return await userRepository.save(user)
|
391
481
|
}
|
392
482
|
}
|
@@ -2,7 +2,20 @@ import crypto from 'crypto'
|
|
2
2
|
import jwt from 'jsonwebtoken'
|
3
3
|
import { Directive, Field, ID, ObjectType } from 'type-graphql'
|
4
4
|
import { GraphQLEmailAddress } from 'graphql-scalars'
|
5
|
-
import {
|
5
|
+
import {
|
6
|
+
Column,
|
7
|
+
CreateDateColumn,
|
8
|
+
Entity,
|
9
|
+
ILike,
|
10
|
+
Index,
|
11
|
+
JoinTable,
|
12
|
+
ManyToMany,
|
13
|
+
ManyToOne,
|
14
|
+
OneToMany,
|
15
|
+
PrimaryGeneratedColumn,
|
16
|
+
RelationId,
|
17
|
+
UpdateDateColumn
|
18
|
+
} from 'typeorm'
|
6
19
|
|
7
20
|
import { config } from '@things-factory/env'
|
8
21
|
import { Domain, getRepository } from '@things-factory/shell'
|
@@ -31,13 +44,23 @@ export enum UserStatus {
|
|
31
44
|
}
|
32
45
|
|
33
46
|
@Entity()
|
34
|
-
@Index('ix_user_0', (user: User) => [user.email], {
|
47
|
+
@Index('ix_user_0', (user: User) => [user.email], {
|
48
|
+
unique: true
|
49
|
+
})
|
50
|
+
@Index('ix_user_1', (user: User) => [user.username], {
|
51
|
+
unique: true,
|
52
|
+
where: '"username" IS NOT NULL'
|
53
|
+
})
|
35
54
|
@ObjectType()
|
36
55
|
export class User {
|
37
56
|
@PrimaryGeneratedColumn('uuid')
|
38
57
|
@Field(type => ID)
|
39
58
|
readonly id: string
|
40
59
|
|
60
|
+
@Column({ nullable: true })
|
61
|
+
@Field({ nullable: true })
|
62
|
+
username: string
|
63
|
+
|
41
64
|
@Column()
|
42
65
|
@Field({ nullable: true })
|
43
66
|
name: string
|
@@ -58,7 +81,15 @@ export class User {
|
|
58
81
|
@Directive('@privilege(category: "security", privilege: "query", domainOwnerGranted: true)')
|
59
82
|
@Column({
|
60
83
|
nullable: true,
|
61
|
-
type:
|
84
|
+
type:
|
85
|
+
DATABASE_TYPE == 'mysql' || DATABASE_TYPE == 'mariadb'
|
86
|
+
? 'longtext'
|
87
|
+
: DATABASE_TYPE == 'oracle'
|
88
|
+
? 'clob'
|
89
|
+
: DATABASE_TYPE == 'mssql'
|
90
|
+
? 'nvarchar'
|
91
|
+
: 'varchar',
|
92
|
+
length: DATABASE_TYPE == 'mssql' ? 'MAX' : undefined
|
62
93
|
})
|
63
94
|
password: string
|
64
95
|
|
@@ -89,8 +120,17 @@ export class User {
|
|
89
120
|
ssoId: string
|
90
121
|
|
91
122
|
@Column({
|
92
|
-
type:
|
93
|
-
|
123
|
+
type:
|
124
|
+
DATABASE_TYPE == 'postgres' || DATABASE_TYPE == 'mysql' || DATABASE_TYPE == 'mariadb'
|
125
|
+
? 'enum'
|
126
|
+
: DATABASE_TYPE == 'oracle'
|
127
|
+
? 'varchar2'
|
128
|
+
: DATABASE_TYPE == 'mssql'
|
129
|
+
? 'nvarchar'
|
130
|
+
: 'varchar',
|
131
|
+
enum:
|
132
|
+
DATABASE_TYPE == 'postgres' || DATABASE_TYPE == 'mysql' || DATABASE_TYPE == 'mariadb' ? UserStatus : undefined,
|
133
|
+
length: DATABASE_TYPE == 'postgres' || DATABASE_TYPE == 'mysql' || DATABASE_TYPE == 'mariadb' ? undefined : 32,
|
94
134
|
default: UserStatus.INACTIVE
|
95
135
|
})
|
96
136
|
@Field(type => String)
|
@@ -136,15 +176,10 @@ export class User {
|
|
136
176
|
|
137
177
|
/* signing for jsonwebtoken */
|
138
178
|
async sign(options?) {
|
139
|
-
var { expiresIn = sessionExpirySeconds
|
179
|
+
var { expiresIn = sessionExpirySeconds } = options || {}
|
140
180
|
|
141
181
|
var user = {
|
142
|
-
|
143
|
-
userType: this.userType,
|
144
|
-
status: this.status,
|
145
|
-
domain: {
|
146
|
-
subdomain
|
147
|
-
}
|
182
|
+
username: this.username || this.email
|
148
183
|
}
|
149
184
|
|
150
185
|
return await jwt.sign(user, SECRET, {
|
@@ -233,18 +268,39 @@ export class User {
|
|
233
268
|
}
|
234
269
|
|
235
270
|
static async checkAuth(decoded) {
|
236
|
-
|
271
|
+
// id 는 하위호환성을 위해 단기적으로 유지함
|
272
|
+
const { id, username } = decoded || {}
|
273
|
+
|
274
|
+
if (!id && !username) {
|
237
275
|
throw new AuthError({
|
238
276
|
errorCode: AuthError.ERROR_CODES.USER_NOT_FOUND
|
239
277
|
})
|
240
278
|
}
|
241
279
|
|
242
280
|
const repository = getRepository(User)
|
243
|
-
|
244
|
-
|
245
|
-
|
246
|
-
|
247
|
-
|
281
|
+
if (id) {
|
282
|
+
var user = await repository.findOne({
|
283
|
+
where: { id },
|
284
|
+
relations: ['domains', 'credentials'],
|
285
|
+
cache: true
|
286
|
+
})
|
287
|
+
} else {
|
288
|
+
var user = await repository.findOne({
|
289
|
+
where: { username },
|
290
|
+
relations: ['domains', 'credentials'],
|
291
|
+
cache: true
|
292
|
+
})
|
293
|
+
|
294
|
+
if (!user && /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(username)) {
|
295
|
+
user = await repository.findOne({
|
296
|
+
where: {
|
297
|
+
email: ILike(username)
|
298
|
+
},
|
299
|
+
relations: ['domains', 'credentials'],
|
300
|
+
cache: true
|
301
|
+
})
|
302
|
+
}
|
303
|
+
}
|
248
304
|
|
249
305
|
if (!user)
|
250
306
|
throw new AuthError({
|
@@ -30,9 +30,15 @@ export class VerificationToken {
|
|
30
30
|
DATABASE_TYPE == 'postgres' || DATABASE_TYPE == 'mysql' || DATABASE_TYPE == 'mariadb'
|
31
31
|
? 'enum'
|
32
32
|
: DATABASE_TYPE == 'oracle'
|
33
|
-
|
34
|
-
|
35
|
-
|
33
|
+
? 'varchar2'
|
34
|
+
: DATABASE_TYPE == 'mssql'
|
35
|
+
? 'nvarchar'
|
36
|
+
: 'varchar',
|
37
|
+
enum:
|
38
|
+
DATABASE_TYPE == 'postgres' || DATABASE_TYPE == 'mysql' || DATABASE_TYPE == 'mariadb'
|
39
|
+
? VerificationTokenType
|
40
|
+
: undefined,
|
41
|
+
length: DATABASE_TYPE == 'postgres' || DATABASE_TYPE == 'mysql' || DATABASE_TYPE == 'mariadb' ? undefined : 32,
|
36
42
|
default: VerificationTokenType.ACTIVATION
|
37
43
|
})
|
38
44
|
@Field()
|
@@ -0,0 +1,29 @@
|
|
1
|
+
import { Domain, getRepository } from '@things-factory/shell'
|
2
|
+
|
3
|
+
import { User } from '../service/user/user'
|
4
|
+
import { Role } from 'service'
|
5
|
+
|
6
|
+
/**
|
7
|
+
* @description 사용자가 특정 도메인 또는 상위 도메인에서 특정 역할을 가지고 있는지 확인합니다.
|
8
|
+
*
|
9
|
+
* @param roleId 확인할 역할의 ID
|
10
|
+
* @param domain 역할을 확인할 도메인
|
11
|
+
* @param user 역할을 확인할 사용자
|
12
|
+
*
|
13
|
+
* @returns 사용자가 도메인 또는 상위 도메인에서 역할을 가지고 있는지 여부를 나타내는 boolean을 반환하는 Promise
|
14
|
+
*/
|
15
|
+
export async function checkUserHasRole(roleId: string, domain: Domain, user: User): Promise<Boolean> {
|
16
|
+
if (!roleId) {
|
17
|
+
return true
|
18
|
+
}
|
19
|
+
|
20
|
+
const me = await getRepository(User).findOne({
|
21
|
+
where: { id: user.id },
|
22
|
+
relations: ['roles']
|
23
|
+
})
|
24
|
+
|
25
|
+
return me.roles
|
26
|
+
.filter(role => role.domainId === domain.id || (domain.parentId && role.domainId === domain.parentId))
|
27
|
+
.map(role => role.id)
|
28
|
+
.includes(roleId)
|
29
|
+
}
|