@things-factory/auth-base 8.0.0-alpha.8 → 8.0.0-beta.1

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 (118) hide show
  1. package/client/actions/auth.ts +5 -4
  2. package/client/index.ts +1 -0
  3. package/client/verify-webauthn.ts +86 -0
  4. package/dist-client/actions/auth.d.ts +5 -4
  5. package/dist-client/actions/auth.js.map +1 -1
  6. package/dist-client/index.d.ts +1 -0
  7. package/dist-client/index.js +1 -0
  8. package/dist-client/index.js.map +1 -1
  9. package/dist-client/tsconfig.tsbuildinfo +1 -1
  10. package/dist-client/verify-webauthn.d.ts +13 -0
  11. package/dist-client/verify-webauthn.js +72 -0
  12. package/dist-client/verify-webauthn.js.map +1 -0
  13. package/dist-server/constants/error-code.d.ts +2 -0
  14. package/dist-server/constants/error-code.js +3 -1
  15. package/dist-server/constants/error-code.js.map +1 -1
  16. package/dist-server/controllers/change-pwd.js +2 -2
  17. package/dist-server/controllers/change-pwd.js.map +1 -1
  18. package/dist-server/controllers/delete-user.js +13 -12
  19. package/dist-server/controllers/delete-user.js.map +1 -1
  20. package/dist-server/controllers/invitation.d.ts +2 -1
  21. package/dist-server/controllers/invitation.js +30 -5
  22. package/dist-server/controllers/invitation.js.map +1 -1
  23. package/dist-server/controllers/profile.d.ts +4 -3
  24. package/dist-server/controllers/profile.js +20 -2
  25. package/dist-server/controllers/profile.js.map +1 -1
  26. package/dist-server/controllers/signin.d.ts +4 -1
  27. package/dist-server/controllers/signin.js +17 -1
  28. package/dist-server/controllers/signin.js.map +1 -1
  29. package/dist-server/controllers/signup.js +13 -4
  30. package/dist-server/controllers/signup.js.map +1 -1
  31. package/dist-server/controllers/unlock-user.js +1 -0
  32. package/dist-server/controllers/unlock-user.js.map +1 -1
  33. package/dist-server/controllers/verification.js +1 -0
  34. package/dist-server/controllers/verification.js.map +1 -1
  35. package/dist-server/index.d.ts +1 -0
  36. package/dist-server/index.js +1 -0
  37. package/dist-server/index.js.map +1 -1
  38. package/dist-server/middlewares/signin-middleware.js +3 -3
  39. package/dist-server/middlewares/signin-middleware.js.map +1 -1
  40. package/dist-server/middlewares/webauthn-middleware.js.map +1 -1
  41. package/dist-server/migrations/1548206416130-SeedUser.js +2 -1
  42. package/dist-server/migrations/1548206416130-SeedUser.js.map +1 -1
  43. package/dist-server/router/auth-checkin-router.js +8 -2
  44. package/dist-server/router/auth-checkin-router.js.map +1 -1
  45. package/dist-server/router/auth-private-process-router.js +12 -7
  46. package/dist-server/router/auth-private-process-router.js.map +1 -1
  47. package/dist-server/router/auth-public-process-router.js +20 -9
  48. package/dist-server/router/auth-public-process-router.js.map +1 -1
  49. package/dist-server/router/auth-signin-router.js +3 -3
  50. package/dist-server/router/auth-signin-router.js.map +1 -1
  51. package/dist-server/router/webauthn-router.js +51 -1
  52. package/dist-server/router/webauthn-router.js.map +1 -1
  53. package/dist-server/service/appliance/appliance.js +4 -1
  54. package/dist-server/service/appliance/appliance.js.map +1 -1
  55. package/dist-server/service/application/application.js +11 -3
  56. package/dist-server/service/application/application.js.map +1 -1
  57. package/dist-server/service/invitation/invitation-mutation.d.ts +3 -2
  58. package/dist-server/service/invitation/invitation-mutation.js +20 -8
  59. package/dist-server/service/invitation/invitation-mutation.js.map +1 -1
  60. package/dist-server/service/user/user-mutation.d.ts +10 -9
  61. package/dist-server/service/user/user-mutation.js +135 -61
  62. package/dist-server/service/user/user-mutation.js.map +1 -1
  63. package/dist-server/service/user/user-types.d.ts +1 -0
  64. package/dist-server/service/user/user-types.js +4 -0
  65. package/dist-server/service/user/user-types.js.map +1 -1
  66. package/dist-server/service/user/user.d.ts +1 -0
  67. package/dist-server/service/user/user.js +57 -17
  68. package/dist-server/service/user/user.js.map +1 -1
  69. package/dist-server/service/verification-token/verification-token.js +7 -2
  70. package/dist-server/service/verification-token/verification-token.js.map +1 -1
  71. package/dist-server/templates/account-unlock-email.d.ts +2 -1
  72. package/dist-server/templates/account-unlock-email.js +1 -1
  73. package/dist-server/templates/account-unlock-email.js.map +1 -1
  74. package/dist-server/templates/invitation-email.d.ts +2 -1
  75. package/dist-server/templates/invitation-email.js +1 -1
  76. package/dist-server/templates/invitation-email.js.map +1 -1
  77. package/dist-server/templates/verification-email.d.ts +2 -1
  78. package/dist-server/templates/verification-email.js +1 -1
  79. package/dist-server/templates/verification-email.js.map +1 -1
  80. package/dist-server/tsconfig.tsbuildinfo +1 -1
  81. package/dist-server/utils/check-user-has-role.d.ts +12 -0
  82. package/dist-server/utils/check-user-has-role.js +28 -0
  83. package/dist-server/utils/check-user-has-role.js.map +1 -0
  84. package/package.json +6 -6
  85. package/server/constants/error-code.ts +2 -0
  86. package/server/controllers/change-pwd.ts +3 -2
  87. package/server/controllers/delete-user.ts +16 -13
  88. package/server/controllers/invitation.ts +36 -5
  89. package/server/controllers/profile.ts +29 -2
  90. package/server/controllers/signin.ts +21 -2
  91. package/server/controllers/signup.ts +16 -4
  92. package/server/controllers/unlock-user.ts +1 -0
  93. package/server/controllers/verification.ts +1 -0
  94. package/server/index.ts +1 -0
  95. package/server/middlewares/signin-middleware.ts +3 -3
  96. package/server/middlewares/webauthn-middleware.ts +7 -8
  97. package/server/migrations/1548206416130-SeedUser.ts +2 -1
  98. package/server/router/auth-checkin-router.ts +11 -5
  99. package/server/router/auth-private-process-router.ts +14 -7
  100. package/server/router/auth-public-process-router.ts +22 -10
  101. package/server/router/auth-signin-router.ts +3 -3
  102. package/server/router/webauthn-router.ts +71 -9
  103. package/server/service/appliance/appliance.ts +4 -1
  104. package/server/service/application/application.ts +12 -3
  105. package/server/service/invitation/invitation-mutation.ts +24 -9
  106. package/server/service/user/user-mutation.ts +152 -62
  107. package/server/service/user/user-types.ts +3 -0
  108. package/server/service/user/user.ts +74 -18
  109. package/server/service/verification-token/verification-token.ts +9 -3
  110. package/server/templates/account-unlock-email.ts +1 -1
  111. package/server/templates/invitation-email.ts +1 -1
  112. package/server/templates/verification-email.ts +1 -1
  113. package/server/utils/check-user-has-role.ts +29 -0
  114. package/translations/en.json +5 -1
  115. package/translations/ja.json +5 -1
  116. package/translations/ko.json +6 -3
  117. package/translations/ms.json +5 -1
  118. package/translations/zh.json +5 -1
@@ -1,4 +1,5 @@
1
1
  import Router from 'koa-router'
2
+ import { ILike } from 'typeorm'
2
3
 
3
4
  import { config } from '@things-factory/env'
4
5
  import { getRepository, getSiteRootPath } from '@things-factory/shell'
@@ -31,16 +32,26 @@ export const authPublicProcessRouter = new Router({
31
32
  })
32
33
 
33
34
  authPublicProcessRouter.post('/join', async (context, next) => {
34
- const { email } = context.request.body || {}
35
+ const { username } = context.request.body || {}
35
36
 
36
- const user: User = await getRepository(User).findOneBy({
37
- email
37
+ const repository = getRepository(User)
38
+
39
+ var user = await repository.findOne({
40
+ where: { username },
41
+ relations: ['domains']
38
42
  })
39
43
 
44
+ if (!user && /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(username)) {
45
+ user = await repository.findOne({
46
+ where: { email: ILike(username) },
47
+ relations: ['domains']
48
+ })
49
+ }
50
+
40
51
  if (user) {
41
- context.redirect(`/auth/signin?email=${email}`)
52
+ context.redirect(`/auth/signin?username=${username}`)
42
53
  } else {
43
- context.redirect(`/auth/signup?email=${email}`)
54
+ context.redirect(`/auth/signup?username=${username}`)
44
55
  }
45
56
  })
46
57
 
@@ -200,10 +211,9 @@ authPublicProcessRouter.post('/forgot-password', async (context, next) => {
200
211
 
201
212
  authPublicProcessRouter.post('/reset-password', async (context, next) => {
202
213
  const { header, t } = context
214
+ const { password, token } = context.request.body
203
215
 
204
216
  try {
205
- const { password, token } = context.request.body
206
-
207
217
  if (!(token && password)) {
208
218
  let message = t('error.token or password is invalid')
209
219
 
@@ -232,7 +242,7 @@ authPublicProcessRouter.post('/reset-password', async (context, next) => {
232
242
 
233
243
  await resetPassword(token, password, context)
234
244
 
235
- var message = t('text.password reset succeed')
245
+ var message = t('text.password changed successfully')
236
246
  context.body = message
237
247
 
238
248
  clearAccessTokenCookie(context)
@@ -255,10 +265,12 @@ authPublicProcessRouter.post('/reset-password', async (context, next) => {
255
265
 
256
266
  if (accepts(header.accept, ['text/html', '*/*'])) {
257
267
  await context.render('auth-page', {
258
- pageElement: 'auth-result',
259
- elementScript: '/auth/result.js',
268
+ pageElement: 'reset-password',
269
+ elementScript: '/auth/reset-password.js',
260
270
  data: {
271
+ token,
261
272
  message: e.message,
273
+ passwordRule,
262
274
  disableUserSignupProcess,
263
275
  disableUserFavoredLanguage,
264
276
  languages
@@ -19,13 +19,13 @@ const SSOLinks = Object.values(SSOConfig)
19
19
  export const authSigninRouter = new Router()
20
20
 
21
21
  authSigninRouter.get('/auth/signin', async (context, next) => {
22
- const { redirect_to, email } = context.query
22
+ const { redirect_to, username } = context.query
23
23
 
24
24
  await context.render('auth-page', {
25
25
  pageElement: 'auth-signin',
26
26
  elementScript: '/auth/signin.js',
27
27
  data: {
28
- email,
28
+ username,
29
29
  redirectTo: redirect_to,
30
30
  ssoLinks: SSOLinks,
31
31
  disableUserSignupProcess,
@@ -37,7 +37,7 @@ authSigninRouter.get('/auth/signin', async (context, next) => {
37
37
 
38
38
  authSigninRouter.post('/auth/signin', signinMiddleware, async (context, next) => {
39
39
  const { request, t } = context
40
- const { token, user, domain } = context.state
40
+ const { token, domain } = context.state
41
41
  const { body: reqBody, header } = request
42
42
 
43
43
  if (!accepts(header.accept, ['text/html', '*/*'])) {
@@ -5,17 +5,75 @@ import { appPackage } from '@things-factory/env'
5
5
  import { generateRegistrationOptions, generateAuthenticationOptions } from '@simplewebauthn/server'
6
6
 
7
7
  import { WebAuthCredential } from '../service/web-auth-credential/web-auth-credential'
8
- import {
9
- PublicKeyCredentialCreationOptionsJSON,
10
- } from '@simplewebauthn/server/script/deps'
8
+ import { PublicKeyCredentialCreationOptionsJSON } from '@simplewebauthn/server/script/deps'
11
9
  import { setAccessTokenCookie } from '../utils/access-token-cookie'
12
- import { createWebAuthnMiddleware } from '../middlewares/webauthn-middleware';
10
+ import { createWebAuthnMiddleware } from '../middlewares/webauthn-middleware'
13
11
 
14
12
  export const webAuthnGlobalPublicRouter = new Router()
15
13
  export const webAuthnGlobalPrivateRouter = new Router()
16
14
 
17
15
  const { name: rpName } = appPackage as any
18
16
 
17
+ // Generate authentication challenge for the currently logged-in user
18
+ webAuthnGlobalPrivateRouter.get('/auth/verify-webauthn/challenge', async (context, next) => {
19
+ const { user } = context.state
20
+ const rpID = context.hostname
21
+
22
+ if (!user) {
23
+ context.status = 401
24
+ context.body = { error: 'User not authenticated' }
25
+ return
26
+ }
27
+
28
+ const webAuthCredentials = await getRepository(WebAuthCredential).find({
29
+ where: { user: { id: user.id } }
30
+ })
31
+
32
+ if (webAuthCredentials.length === 0) {
33
+ context.status = 400
34
+ context.body = { error: 'No biometric credentials registered for this user' }
35
+ return
36
+ }
37
+
38
+ const options = await generateAuthenticationOptions({
39
+ rpID,
40
+ userVerification: 'preferred',
41
+ allowCredentials: webAuthCredentials.map(credential => ({
42
+ id: credential.credentialId,
43
+ type: 'public-key'
44
+ }))
45
+ })
46
+
47
+ context.session.challenge = options.challenge
48
+ context.body = options
49
+ })
50
+
51
+ // Verify biometric authentication
52
+ webAuthnGlobalPrivateRouter.post(
53
+ '/auth/verify-webauthn',
54
+ /* reuse webauthn-login as webauthn-verify strategy */
55
+ createWebAuthnMiddleware('webauthn-login'),
56
+ async (context, next) => {
57
+ const { user } = context.state
58
+ const { request } = context
59
+ const { body: reqBody } = request
60
+
61
+ if (!user) {
62
+ context.status = 401
63
+ context.body = { verified: false, message: 'User not authenticated' }
64
+ return
65
+ }
66
+
67
+ context.body = {
68
+ verified: true,
69
+ message: 'Biometric authentication successful'
70
+ }
71
+
72
+ await next()
73
+ }
74
+ )
75
+
76
+ // Generate registration challenge for the currently logged-in user
19
77
  webAuthnGlobalPrivateRouter.get('/auth/register-webauthn/challenge', async (context, next) => {
20
78
  const { user } = context.state
21
79
  const rpID = context.hostname
@@ -53,8 +111,10 @@ webAuthnGlobalPrivateRouter.get('/auth/register-webauthn/challenge', async (cont
53
111
  context.body = options
54
112
  })
55
113
 
56
- webAuthnGlobalPrivateRouter.post('/auth/verify-registration', createWebAuthnMiddleware('webauthn-register'));
114
+ // Verify registration
115
+ webAuthnGlobalPrivateRouter.post('/auth/verify-registration', createWebAuthnMiddleware('webauthn-register'))
57
116
 
117
+ // Generate sign-in challenge
58
118
  webAuthnGlobalPublicRouter.get('/auth/signin-webauthn/challenge', async (context, next) => {
59
119
  const rpID = context.hostname
60
120
 
@@ -67,10 +127,12 @@ webAuthnGlobalPublicRouter.get('/auth/signin-webauthn/challenge', async (context
67
127
  context.body = options
68
128
  })
69
129
 
130
+ // Sign in with biometric authentication
70
131
  webAuthnGlobalPublicRouter.post(
71
- '/auth/signin-webauthn', createWebAuthnMiddleware('webauthn-login'),
132
+ '/auth/signin-webauthn',
133
+ createWebAuthnMiddleware('webauthn-login'),
72
134
  async (context, next) => {
73
- const { domain, user } = context. state
135
+ const { domain, user } = context.state
74
136
  const { request } = context
75
137
  const { body: reqBody } = request
76
138
 
@@ -79,9 +141,9 @@ webAuthnGlobalPublicRouter.post(
79
141
 
80
142
  var redirectURL = `/auth/checkin${domain ? '/' + domain.subdomain : ''}?redirect_to=${encodeURIComponent(reqBody.redirectTo || '/')}`
81
143
 
82
- /* 2단계 인터렉션 때문에 브라우저에서 fetch(...) 진행될 것이므로, redirect(3xx) 응답으로 처리할 없다. 따라서, 데이타로 redirectURL를 응답한다. */
144
+ /* Due to the two-step interaction, it will be processed by fetch(...) in the browser, so it cannot be handled with a redirect(3xx) response. Therefore, respond with redirectURL as data. */
83
145
  context.body = { redirectURL, verified: true }
84
146
 
85
- await next();
147
+ await next()
86
148
  }
87
149
  )
@@ -73,7 +73,10 @@ export class Appliance {
73
73
  ? 'longtext'
74
74
  : DATABASE_TYPE == 'oracle'
75
75
  ? 'clob'
76
- : 'varchar'
76
+ : DATABASE_TYPE == 'mssql'
77
+ ? 'nvarchar'
78
+ : 'varchar',
79
+ length: DATABASE_TYPE == 'mssql' ? 'MAX' : undefined
77
80
  })
78
81
  @Field({ nullable: true })
79
82
  @Directive('@privilege(category: "security", privilege: "query", domainOwnerGranted: true)')
@@ -99,7 +99,10 @@ export class Application {
99
99
  ? 'longtext'
100
100
  : DATABASE_TYPE == 'oracle'
101
101
  ? 'clob'
102
- : 'varchar'
102
+ : DATABASE_TYPE == 'mssql'
103
+ ? 'nvarchar'
104
+ : 'varchar',
105
+ length: DATABASE_TYPE == 'mssql' ? 'MAX' : undefined
103
106
  })
104
107
  @Field({ nullable: true })
105
108
  @Directive('@privilege(category: "security", privilege: "query", domainOwnerGranted: true)')
@@ -115,8 +118,14 @@ export class Application {
115
118
  ? 'enum'
116
119
  : DATABASE_TYPE == 'oracle'
117
120
  ? 'varchar2'
118
- : 'smallint',
119
- enum: ApplicationType,
121
+ : DATABASE_TYPE == 'mssql'
122
+ ? 'nvarchar'
123
+ : 'varchar',
124
+ enum:
125
+ DATABASE_TYPE == 'postgres' || DATABASE_TYPE == 'mysql' || DATABASE_TYPE == 'mariadb'
126
+ ? ApplicationType
127
+ : undefined,
128
+ length: DATABASE_TYPE == 'postgres' || DATABASE_TYPE == 'mysql' || DATABASE_TYPE == 'mariadb' ? undefined : 32,
120
129
  default: ApplicationType.OTHERS
121
130
  })
122
131
  @Field()
@@ -1,8 +1,11 @@
1
+ import { ILike } from 'typeorm'
2
+
1
3
  import { Arg, Ctx, Mutation, Resolver } from 'type-graphql'
2
4
  import { GraphQLEmailAddress } from 'graphql-scalars'
3
5
 
4
6
  import { getRepository } from '@things-factory/shell'
5
7
 
8
+ import { User, UserStatus } from '../../service/user/user'
6
9
  import { sendInvitationEmail } from '../../controllers/invitation'
7
10
  import { Invitation } from './invitation'
8
11
 
@@ -32,32 +35,44 @@ export class InvitationMutation {
32
35
  @Arg('type') type: string,
33
36
  @Ctx() context: ResolverContext
34
37
  ) {
35
- const repository = getRepository(Invitation)
38
+ const { user: updater } = context.state
39
+ const invitationRepository = getRepository(Invitation)
36
40
 
37
- const oldone = await repository.findOneBy({
38
- email,
39
- type,
40
- reference
41
+ var user = await getRepository(User).findOne({
42
+ where: {
43
+ email: ILike(email),
44
+ status: UserStatus.ACTIVATED
45
+ }
41
46
  })
42
47
 
43
- // TODO send invitation
48
+ if (!user) {
49
+ throw new Error(`user not found: ${email}`)
50
+ }
51
+
44
52
  await sendInvitationEmail({
45
53
  invitation: {
46
54
  email,
47
55
  reference,
48
56
  type
49
57
  },
58
+ user,
50
59
  context
51
60
  })
52
61
 
62
+ const oldone = await invitationRepository.findOneBy({
63
+ email,
64
+ type,
65
+ reference
66
+ })
67
+
53
68
  // update or create
54
- return await repository.save({
69
+ return await invitationRepository.save({
70
+ creator: updater,
55
71
  ...oldone, // take only id from oldone for update
56
72
  email,
57
73
  reference,
58
74
  type,
59
- creator: context.state.user,
60
- updater: context.state.user
75
+ updater: updater
61
76
  })
62
77
  }
63
78
  }