diff --git a/docs/mfa.md b/docs/mfa.md index e69de29b..97c51ee2 100644 --- a/docs/mfa.md +++ b/docs/mfa.md @@ -0,0 +1,12 @@ +# MFA + + +## Abstract + +In order to increase account security VAuthenticator allow to register MFA via email and soon SMS + +## How to + +During the account registration a step to validate the main mail, that is the user_name too, the main mail became a valid MFA channel. +It is possible to register multiple email via api. + diff --git a/local-environment/request.http b/local-environment/request.http index b30d975f..5f53e97f 100644 --- a/local-environment/request.http +++ b/local-environment/request.http @@ -22,7 +22,7 @@ Content-Type: application/json Authorization: Bearer {{auth_token}} { - "email": "xxx@email.com", + "email": "user5@email.com", "password": "secret!", "firstName": "Admin", "lastName": "", @@ -43,7 +43,7 @@ Content-Type: application/json Authorization: Bearer {{auth_token}} { - "email": "user2@email.com" + "email": "user4@email.com" } @@ -72,11 +72,11 @@ Authorization: Bearer {{auth_token}} POST {{host}}/api/mfa/enrollment Content-Type: application/json -Authorization: Bearer +Authorization: Bearer eyJraWQiOiJkYTFiNzAwMS05NjlkLTQyNzQtYTM5Yy0wYTI2ZjMzODg2NmIiLCJhbGciOiJSUzI1NiJ9.eyJzdWIiOiJ1c2VyNUBlbWFpbC5jb20iLCJhdWQiOiJ1c2VyLWludHJvc3BlY3Rpb24iLCJuYmYiOjE3MjIxOTI1NzcsInVzZXJfbmFtZSI6InVzZXI1QGVtYWlsLmNvbSIsInNjb3BlIjpbIm9wZW5pZCIsImVtYWlsIiwicHJvZmlsZSJdLCJpc3MiOiJodHRwOi8vbG9jYWwuYXBpLnZhdXRoZW50aWNhdG9yLmNvbTo5MDkwIiwiZXhwIjoxNzIyMTkyOTM3LCJpYXQiOjE3MjIxOTI1NzcsImp0aSI6ImYzMmI1NzFkLWJlODItNDAwNy1iMzZhLTA4MzQ4MThkYzdhYSIsImF1dGhvcml0aWVzIjpbIlJPTEVfVVNFUiIsIlZBVVRIRU5USUNBVE9SX0FETUlOIl19.THR5FIbXmqhgx-As3C73pRhP6e2zWdrH3bEM6jyo_-SjgSXm5qkrocKs0j1mUKJk2Dpiah7rwtVjxcpl13ELCrBAO9Hq-6Kn8lB1ZlOnTNtIC7StQ2q18GjVdWNBBuvO4nbd3cjfmub_DEaFGFv5wZVg1fdlqSKeEzz1bLQbR5VtBvvq6ktTA1U1axC8iO7DnfICdXzR6ZdklKpJ9POYAK0x-d3qeXd0PO8pByJwJPdl1d13WazwJGZVRAJkwV00Dnsrbtetw6s_S4SeCHr7xzFALLALvvJgqgz2OreUuB-iEIDw7iC7pAwerHN58cjTkFdtJUUCie0WIr4H8ltnfA { "mfaMethod": "EMAIL_MFA_METHOD", - "mfaChannel" : "xxx@email.com" + "mfaChannel" : "321@email.com" } @@ -84,9 +84,9 @@ Authorization: Bearer POST {{host}}/api/mfa/associate Content-Type: application/json -Authorization: Bearer +Authorization: Bearer eyJraWQiOiJkYTFiNzAwMS05NjlkLTQyNzQtYTM5Yy0wYTI2ZjMzODg2NmIiLCJhbGciOiJSUzI1NiJ9.eyJzdWIiOiJ1c2VyNUBlbWFpbC5jb20iLCJhdWQiOiJ1c2VyLWludHJvc3BlY3Rpb24iLCJuYmYiOjE3MjIxOTI1NzcsInVzZXJfbmFtZSI6InVzZXI1QGVtYWlsLmNvbSIsInNjb3BlIjpbIm9wZW5pZCIsImVtYWlsIiwicHJvZmlsZSJdLCJpc3MiOiJodHRwOi8vbG9jYWwuYXBpLnZhdXRoZW50aWNhdG9yLmNvbTo5MDkwIiwiZXhwIjoxNzIyMTkyOTM3LCJpYXQiOjE3MjIxOTI1NzcsImp0aSI6ImYzMmI1NzFkLWJlODItNDAwNy1iMzZhLTA4MzQ4MThkYzdhYSIsImF1dGhvcml0aWVzIjpbIlJPTEVfVVNFUiIsIlZBVVRIRU5USUNBVE9SX0FETUlOIl19.THR5FIbXmqhgx-As3C73pRhP6e2zWdrH3bEM6jyo_-SjgSXm5qkrocKs0j1mUKJk2Dpiah7rwtVjxcpl13ELCrBAO9Hq-6Kn8lB1ZlOnTNtIC7StQ2q18GjVdWNBBuvO4nbd3cjfmub_DEaFGFv5wZVg1fdlqSKeEzz1bLQbR5VtBvvq6ktTA1U1axC8iO7DnfICdXzR6ZdklKpJ9POYAK0x-d3qeXd0PO8pByJwJPdl1d13WazwJGZVRAJkwV00Dnsrbtetw6s_S4SeCHr7xzFALLALvvJgqgz2OreUuB-iEIDw7iC7pAwerHN58cjTkFdtJUUCie0WIr4H8ltnfA { - "ticket": "xxx", - "code" : "xxx" + "ticket": "f5380792-f1ca-4575-9192-6fa6b548afdd", + "code" : "853261" } \ No newline at end of file diff --git a/src/main/kotlin/com/vauthenticator/server/mfa/api/MfaMethodsEnrolmentEndPoint.kt b/src/main/kotlin/com/vauthenticator/server/mfa/api/MfaMethodsEnrolmentEndPoint.kt index b4313261..a736b2cf 100644 --- a/src/main/kotlin/com/vauthenticator/server/mfa/api/MfaMethodsEnrolmentEndPoint.kt +++ b/src/main/kotlin/com/vauthenticator/server/mfa/api/MfaMethodsEnrolmentEndPoint.kt @@ -8,9 +8,14 @@ import com.vauthenticator.server.mfa.domain.MfaMethod import com.vauthenticator.server.mfa.domain.MfaMethodsEnrollment import com.vauthenticator.server.mfa.domain.MfaMethodsEnrollmentAssociation import com.vauthenticator.server.mfa.repository.MfaAccountMethodsRepository +import com.vauthenticator.server.oauth2.clientapp.Scope +import com.vauthenticator.server.oauth2.clientapp.Scopes +import com.vauthenticator.server.role.PermissionValidator +import jakarta.servlet.http.HttpSession import org.springframework.http.ResponseEntity import org.springframework.http.ResponseEntity.ok import org.springframework.security.core.Authentication +import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationToken import org.springframework.web.bind.annotation.GetMapping import org.springframework.web.bind.annotation.PostMapping import org.springframework.web.bind.annotation.RequestBody @@ -22,7 +27,8 @@ class MfaEnrolmentAssociationEndPoint( private val mfaAccountMethodsRepository: MfaAccountMethodsRepository, private val mfaMethodsEnrollment: MfaMethodsEnrollment, private val accountRepository: AccountRepository, - private val mfaMethodsEnrolmentAssociation: MfaMethodsEnrollmentAssociation + private val mfaMethodsEnrolmentAssociation: MfaMethodsEnrollmentAssociation, + private val permissionValidator: PermissionValidator ) { @@ -47,9 +53,11 @@ class MfaEnrolmentAssociationEndPoint( @PostMapping("/api/mfa/enrollment") fun enrollMfa( - authentication: Authentication, + authentication: JwtAuthenticationToken, + httpSession: HttpSession, @RequestBody enrolling: MfaEnrollmentRequest ): ResponseEntity { + permissionValidator.validate(authentication, httpSession, Scopes.from(Scope.MFA_ENROLLMENT)) val ticketId = mfaMethodsEnrollment.enroll( authentication.name, enrolling.mfaMethod, @@ -62,9 +70,11 @@ class MfaEnrolmentAssociationEndPoint( @PostMapping("/api/mfa/associate") fun associateMfaEnrollment( + httpSession: HttpSession, + authentication: JwtAuthenticationToken, @RequestBody associationRequest: MfaEnrollmentAssociationRequest, - authentication: Authentication ) { + permissionValidator.validate(authentication, httpSession, Scopes.from(Scope.MFA_ENROLLMENT)) mfaMethodsEnrolmentAssociation.associate(associationRequest.ticket, associationRequest.code) } diff --git a/src/main/kotlin/com/vauthenticator/server/oauth2/clientapp/ClientApplication.kt b/src/main/kotlin/com/vauthenticator/server/oauth2/clientapp/ClientApplication.kt index ceab02fd..e6113e54 100644 --- a/src/main/kotlin/com/vauthenticator/server/oauth2/clientapp/ClientApplication.kt +++ b/src/main/kotlin/com/vauthenticator/server/oauth2/clientapp/ClientApplication.kt @@ -78,6 +78,7 @@ data class Scope(val content: String) { val KEY_EDITOR = Scope("admin:key-editor") val MFA_ALWAYS = Scope("mfa:always") + val MFA_ENROLLMENT = Scope("mfa:enrollment") val AVAILABLE_SCOPES = listOf( OPEN_ID, @@ -96,7 +97,8 @@ data class Scope(val content: String) { MAIL_TEMPLATE_READER, MAIL_TEMPLATE_WRITER, - MFA_ALWAYS + MFA_ALWAYS, + MFA_ENROLLMENT ) } diff --git a/src/test/kotlin/com/vauthenticator/server/mfa/api/MfaEnrolmentAssociationEndPointTest.kt b/src/test/kotlin/com/vauthenticator/server/mfa/api/MfaEnrolmentAssociationEndPointTest.kt index 362e7131..affe5d80 100644 --- a/src/test/kotlin/com/vauthenticator/server/mfa/api/MfaEnrolmentAssociationEndPointTest.kt +++ b/src/test/kotlin/com/vauthenticator/server/mfa/api/MfaEnrolmentAssociationEndPointTest.kt @@ -8,6 +8,7 @@ import com.vauthenticator.server.mfa.domain.MfaMethod.EMAIL_MFA_METHOD import com.vauthenticator.server.mfa.domain.MfaMethodsEnrollment import com.vauthenticator.server.mfa.domain.MfaMethodsEnrollmentAssociation import com.vauthenticator.server.mfa.repository.MfaAccountMethodsRepository +import com.vauthenticator.server.role.PermissionValidator import com.vauthenticator.server.support.AccountTestFixture import com.vauthenticator.server.support.MfaFixture.accountMfaAssociatedMfaMethods import com.vauthenticator.server.support.SecurityFixture.principalFor @@ -45,6 +46,9 @@ class MfaEnrolmentAssociationEndPointTest { @MockK private lateinit var mfaMethodsEnrolmentAssociation: MfaMethodsEnrollmentAssociation + @MockK + private lateinit var permissionValidator: PermissionValidator + @BeforeEach internal fun setUp() { mokMvc = MockMvcBuilders.standaloneSetup( @@ -53,7 +57,8 @@ class MfaEnrolmentAssociationEndPointTest { mfaAccountMethodsRepository, mfaMethodsEnrollment, accountRepository, - mfaMethodsEnrolmentAssociation + mfaMethodsEnrolmentAssociation, + permissionValidator ) ).build() }