@things-factory/auth-base 8.0.0-alpha.2 → 8.0.0-alpha.22
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/index.d.ts +1 -0
- package/dist-server/index.js +1 -0
- package/dist-server/index.js.map +1 -1
- 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/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/index.ts +1 -0
- package/server/middlewares/webauthn-middleware.ts +7 -8
- package/server/router/webauthn-router.ts +71 -9
- package/server/utils/check-user-has-role.ts +29 -0
@@ -0,0 +1,12 @@
|
|
1
|
+
import { Domain } from '@things-factory/shell';
|
2
|
+
import { User } from '../service/user/user';
|
3
|
+
/**
|
4
|
+
* @description 사용자가 특정 도메인 또는 상위 도메인에서 특정 역할을 가지고 있는지 확인합니다.
|
5
|
+
*
|
6
|
+
* @param roleId 확인할 역할의 ID
|
7
|
+
* @param domain 역할을 확인할 도메인
|
8
|
+
* @param user 역할을 확인할 사용자
|
9
|
+
*
|
10
|
+
* @returns 사용자가 도메인 또는 상위 도메인에서 역할을 가지고 있는지 여부를 나타내는 boolean을 반환하는 Promise
|
11
|
+
*/
|
12
|
+
export declare function checkUserHasRole(roleId: string, domain: Domain, user: User): Promise<Boolean>;
|
@@ -0,0 +1,28 @@
|
|
1
|
+
"use strict";
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
3
|
+
exports.checkUserHasRole = checkUserHasRole;
|
4
|
+
const shell_1 = require("@things-factory/shell");
|
5
|
+
const user_1 = require("../service/user/user");
|
6
|
+
/**
|
7
|
+
* @description 사용자가 특정 도메인 또는 상위 도메인에서 특정 역할을 가지고 있는지 확인합니다.
|
8
|
+
*
|
9
|
+
* @param roleId 확인할 역할의 ID
|
10
|
+
* @param domain 역할을 확인할 도메인
|
11
|
+
* @param user 역할을 확인할 사용자
|
12
|
+
*
|
13
|
+
* @returns 사용자가 도메인 또는 상위 도메인에서 역할을 가지고 있는지 여부를 나타내는 boolean을 반환하는 Promise
|
14
|
+
*/
|
15
|
+
async function checkUserHasRole(roleId, domain, user) {
|
16
|
+
if (!roleId) {
|
17
|
+
return true;
|
18
|
+
}
|
19
|
+
const me = await (0, shell_1.getRepository)(user_1.User).findOne({
|
20
|
+
where: { id: user.id },
|
21
|
+
relations: ['roles']
|
22
|
+
});
|
23
|
+
return me.roles
|
24
|
+
.filter(role => role.domainId === domain.id || (domain.parentId && role.domainId === domain.parentId))
|
25
|
+
.map(role => role.id)
|
26
|
+
.includes(roleId);
|
27
|
+
}
|
28
|
+
//# sourceMappingURL=check-user-has-role.js.map
|
@@ -0,0 +1 @@
|
|
1
|
+
{"version":3,"file":"check-user-has-role.js","sourceRoot":"","sources":["../../server/utils/check-user-has-role.ts"],"names":[],"mappings":";;AAcA,4CAcC;AA5BD,iDAA6D;AAE7D,+CAA2C;AAG3C;;;;;;;;GAQG;AACI,KAAK,UAAU,gBAAgB,CAAC,MAAc,EAAE,MAAc,EAAE,IAAU;IAC/E,IAAI,CAAC,MAAM,EAAE,CAAC;QACZ,OAAO,IAAI,CAAA;IACb,CAAC;IAED,MAAM,EAAE,GAAG,MAAM,IAAA,qBAAa,EAAC,WAAI,CAAC,CAAC,OAAO,CAAC;QAC3C,KAAK,EAAE,EAAE,EAAE,EAAE,IAAI,CAAC,EAAE,EAAE;QACtB,SAAS,EAAE,CAAC,OAAO,CAAC;KACrB,CAAC,CAAA;IAEF,OAAO,EAAE,CAAC,KAAK;SACZ,MAAM,CAAC,IAAI,CAAC,EAAE,CAAC,IAAI,CAAC,QAAQ,KAAK,MAAM,CAAC,EAAE,IAAI,CAAC,MAAM,CAAC,QAAQ,IAAI,IAAI,CAAC,QAAQ,KAAK,MAAM,CAAC,QAAQ,CAAC,CAAC;SACrG,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC,IAAI,CAAC,EAAE,CAAC;SACpB,QAAQ,CAAC,MAAM,CAAC,CAAA;AACrB,CAAC","sourcesContent":["import { Domain, getRepository } from '@things-factory/shell'\n\nimport { User } from '../service/user/user'\nimport { Role } from 'service'\n\n/**\n * @description 사용자가 특정 도메인 또는 상위 도메인에서 특정 역할을 가지고 있는지 확인합니다.\n *\n * @param roleId 확인할 역할의 ID\n * @param domain 역할을 확인할 도메인\n * @param user 역할을 확인할 사용자\n *\n * @returns 사용자가 도메인 또는 상위 도메인에서 역할을 가지고 있는지 여부를 나타내는 boolean을 반환하는 Promise\n */\nexport async function checkUserHasRole(roleId: string, domain: Domain, user: User): Promise<Boolean> {\n if (!roleId) {\n return true\n }\n\n const me = await getRepository(User).findOne({\n where: { id: user.id },\n relations: ['roles']\n })\n\n return me.roles\n .filter(role => role.domainId === domain.id || (domain.parentId && role.domainId === domain.parentId))\n .map(role => role.id)\n .includes(roleId)\n}\n"]}
|
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.22",
|
4
4
|
"main": "dist-server/index.js",
|
5
5
|
"browser": "dist-client/index.js",
|
6
6
|
"things-factory": true,
|
@@ -32,10 +32,10 @@
|
|
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.
|
36
|
-
"@things-factory/env": "^8.0.0-alpha.
|
37
|
-
"@things-factory/shell": "^8.0.0-alpha.
|
38
|
-
"@things-factory/utils": "^8.0.0-alpha.
|
35
|
+
"@things-factory/email-base": "^8.0.0-alpha.22",
|
36
|
+
"@things-factory/env": "^8.0.0-alpha.8",
|
37
|
+
"@things-factory/shell": "^8.0.0-alpha.22",
|
38
|
+
"@things-factory/utils": "^8.0.0-alpha.14",
|
39
39
|
"@types/webappsec-credential-management": "^0.6.8",
|
40
40
|
"jsonwebtoken": "^9.0.0",
|
41
41
|
"koa-passport": "^6.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": "27cd29ef5f60c8fc8a177dd52eb0f4979b463991"
|
50
50
|
}
|
package/server/index.ts
CHANGED
@@ -17,6 +17,7 @@ export * from './utils/check-user-belongs-domain'
|
|
17
17
|
export * from './utils/access-token-cookie'
|
18
18
|
export * from './utils/encrypt-state'
|
19
19
|
export * from './utils/check-permission'
|
20
|
+
export * from './utils/check-user-has-role'
|
20
21
|
|
21
22
|
export * from './errors'
|
22
23
|
|
@@ -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
|
)
|
@@ -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
|
+
}
|