@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.
- 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,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 {
|
35
|
+
const { username } = context.request.body || {}
|
35
36
|
|
36
|
-
const
|
37
|
-
|
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?
|
52
|
+
context.redirect(`/auth/signin?username=${username}`)
|
42
53
|
} else {
|
43
|
-
context.redirect(`/auth/signup?
|
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
|
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: '
|
259
|
-
elementScript: '/auth/
|
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,
|
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
|
-
|
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,
|
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
|
-
|
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',
|
132
|
+
'/auth/signin-webauthn',
|
133
|
+
createWebAuthnMiddleware('webauthn-login'),
|
72
134
|
async (context, next) => {
|
73
|
-
const { domain, user } = context.
|
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
|
-
/*
|
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
|
-
: '
|
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
|
-
: '
|
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
|
-
: '
|
119
|
-
|
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
|
38
|
+
const { user: updater } = context.state
|
39
|
+
const invitationRepository = getRepository(Invitation)
|
36
40
|
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
+
var user = await getRepository(User).findOne({
|
42
|
+
where: {
|
43
|
+
email: ILike(email),
|
44
|
+
status: UserStatus.ACTIVATED
|
45
|
+
}
|
41
46
|
})
|
42
47
|
|
43
|
-
|
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
|
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
|
-
|
60
|
-
updater: context.state.user
|
75
|
+
updater: updater
|
61
76
|
})
|
62
77
|
}
|
63
78
|
}
|