@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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@things-factory/auth-base",
3
- "version": "8.0.0-alpha.18",
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.18",
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.18",
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": "983da8f678924a0545762f012a7408faafd71226"
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
- 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
  )