@things-factory/auth-base 8.0.0-alpha.18 → 8.0.0-alpha.19
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/index.ts +1 -0
- package/client/verify-webauthn.ts +86 -0
- 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/middlewares/webauthn-middleware.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/tsconfig.tsbuildinfo +1 -1
- package/package.json +4 -4
- package/server/middlewares/webauthn-middleware.ts +7 -8
- package/server/router/webauthn-router.ts +71 -9
package/package.json
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
{
|
2
2
|
"name": "@things-factory/auth-base",
|
3
|
-
"version": "8.0.0-alpha.
|
3
|
+
"version": "8.0.0-alpha.19",
|
4
4
|
"main": "dist-server/index.js",
|
5
5
|
"browser": "dist-client/index.js",
|
6
6
|
"things-factory": true,
|
@@ -32,9 +32,9 @@
|
|
32
32
|
"dependencies": {
|
33
33
|
"@simplewebauthn/browser": "^10.0.0",
|
34
34
|
"@simplewebauthn/server": "^10.0.0",
|
35
|
-
"@things-factory/email-base": "^8.0.0-alpha.
|
35
|
+
"@things-factory/email-base": "^8.0.0-alpha.19",
|
36
36
|
"@things-factory/env": "^8.0.0-alpha.8",
|
37
|
-
"@things-factory/shell": "^8.0.0-alpha.
|
37
|
+
"@things-factory/shell": "^8.0.0-alpha.19",
|
38
38
|
"@things-factory/utils": "^8.0.0-alpha.14",
|
39
39
|
"@types/webappsec-credential-management": "^0.6.8",
|
40
40
|
"jsonwebtoken": "^9.0.0",
|
@@ -46,5 +46,5 @@
|
|
46
46
|
"passport-jwt": "^4.0.0",
|
47
47
|
"passport-local": "^1.0.0"
|
48
48
|
},
|
49
|
-
"gitHead": "
|
49
|
+
"gitHead": "9519b20f754d4e3cf0d21cf65660af7a4f50c4e0"
|
50
50
|
}
|
@@ -3,7 +3,6 @@ import { Strategy as CustomStrategy } from 'passport-custom'
|
|
3
3
|
|
4
4
|
import { getRepository } from '@things-factory/shell'
|
5
5
|
|
6
|
-
import { User } from '../service/user/user'
|
7
6
|
import { AuthError } from '../errors/auth-error'
|
8
7
|
|
9
8
|
import { WebAuthCredential } from '../service/web-auth-credential/web-auth-credential'
|
@@ -57,23 +56,23 @@ passport.use(
|
|
57
56
|
const { body, session, origin, hostname } = context as any
|
58
57
|
|
59
58
|
const challenge = session.challenge
|
60
|
-
|
59
|
+
|
61
60
|
const assertionResponse = body as {
|
62
61
|
id: string
|
63
62
|
response: AuthenticatorAssertionResponse
|
64
63
|
}
|
65
|
-
|
64
|
+
|
66
65
|
const credential = await getRepository(WebAuthCredential).findOne({
|
67
66
|
where: {
|
68
67
|
credentialId: assertionResponse.id
|
69
68
|
},
|
70
69
|
relations: ['user']
|
71
70
|
})
|
72
|
-
|
71
|
+
|
73
72
|
if (!credential) {
|
74
73
|
return done(null, false)
|
75
74
|
}
|
76
|
-
|
75
|
+
|
77
76
|
const verification = await verifyAuthenticationResponse({
|
78
77
|
response: body,
|
79
78
|
expectedChallenge: challenge,
|
@@ -86,18 +85,18 @@ passport.use(
|
|
86
85
|
counter: credential.counter
|
87
86
|
}
|
88
87
|
})
|
89
|
-
|
88
|
+
|
90
89
|
if (verification.verified) {
|
91
90
|
const { authenticationInfo } = verification
|
92
91
|
credential.counter = authenticationInfo.newCounter
|
93
92
|
await getRepository(WebAuthCredential).save(credential)
|
94
|
-
|
93
|
+
|
95
94
|
const user = credential.user
|
96
95
|
return done(null, user)
|
97
96
|
} else {
|
98
97
|
return done(verification, false)
|
99
98
|
}
|
100
|
-
} catch(error) {
|
99
|
+
} catch (error) {
|
101
100
|
return done(error, false)
|
102
101
|
}
|
103
102
|
})
|
@@ -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
|
)
|