diff --git a/build.gradle.kts b/build.gradle.kts index bf3eafb9..c27ebaf6 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -93,6 +93,7 @@ dependencies { implementation("org.springframework.boot:spring-boot-starter-web") implementation("org.springframework.boot:spring-boot-starter-security") + implementation("org.springframework.boot:spring-boot-starter-thymeleaf") implementation("org.springframework.boot:spring-boot-starter-oauth2-authorization-server") implementation("org.springframework.boot:spring-boot-starter-oauth2-resource-server") implementation("jakarta.validation:jakarta.validation-api") diff --git a/src/main/kotlin/dev/usbharu/hideout/config/SecurityConfig.kt b/src/main/kotlin/dev/usbharu/hideout/config/SecurityConfig.kt index d4a8d4d1..95a2dc4c 100644 --- a/src/main/kotlin/dev/usbharu/hideout/config/SecurityConfig.kt +++ b/src/main/kotlin/dev/usbharu/hideout/config/SecurityConfig.kt @@ -72,6 +72,9 @@ class SecurityConfig { builder.pattern("/error"), builder.pattern("/nodeinfo/2.0") ).permitAll() + it.requestMatchers( + builder.pattern("/auth/**") + ).anonymous() it.requestMatchers(builder.pattern("/change-password")).authenticated() it.requestMatchers(builder.pattern("/api/v1/accounts/verify_credentials")) .hasAnyAuthority("SCOPE_read", "SCOPE_read:accounts") @@ -84,7 +87,6 @@ class SecurityConfig { .passwordManagement { } .formLogin(Customizer.withDefaults()) .csrf { - it.ignoringRequestMatchers(builder.pattern("/api/**")) it.ignoringRequestMatchers(builder.pattern("/users/*/inbox")) it.ignoringRequestMatchers(builder.pattern("/inbox")) it.ignoringRequestMatchers(PathRequest.toH2Console()) diff --git a/src/main/kotlin/dev/usbharu/hideout/controller/AuthController.kt b/src/main/kotlin/dev/usbharu/hideout/controller/AuthController.kt new file mode 100644 index 00000000..c91b1f34 --- /dev/null +++ b/src/main/kotlin/dev/usbharu/hideout/controller/AuthController.kt @@ -0,0 +1,10 @@ +package dev.usbharu.hideout.controller + +import org.springframework.stereotype.Controller +import org.springframework.web.bind.annotation.GetMapping + +@Controller +class AuthController { + @GetMapping("/auth/sign_up") + fun signUp(): String = "sign_up" +} diff --git a/src/main/kotlin/dev/usbharu/hideout/controller/mastodon/MastodonAccountApiController.kt b/src/main/kotlin/dev/usbharu/hideout/controller/mastodon/MastodonAccountApiController.kt index d6754a3d..290f2240 100644 --- a/src/main/kotlin/dev/usbharu/hideout/controller/mastodon/MastodonAccountApiController.kt +++ b/src/main/kotlin/dev/usbharu/hideout/controller/mastodon/MastodonAccountApiController.kt @@ -2,16 +2,23 @@ package dev.usbharu.hideout.controller.mastodon import dev.usbharu.hideout.controller.mastodon.generated.AccountApi import dev.usbharu.hideout.domain.mastodon.model.generated.CredentialAccount +import dev.usbharu.hideout.domain.model.hideout.dto.UserCreateDto import dev.usbharu.hideout.service.api.mastodon.AccountApiService +import dev.usbharu.hideout.service.core.Transaction import kotlinx.coroutines.runBlocking +import org.springframework.http.HttpHeaders import org.springframework.http.HttpStatus import org.springframework.http.ResponseEntity import org.springframework.security.core.context.SecurityContextHolder import org.springframework.security.oauth2.jwt.Jwt import org.springframework.stereotype.Controller +import java.net.URI @Controller -class MastodonAccountApiController(private val accountApiService: AccountApiService) : AccountApi { +class MastodonAccountApiController( + private val accountApiService: AccountApiService, + private val transaction: Transaction +) : AccountApi { override fun apiV1AccountsVerifyCredentialsGet(): ResponseEntity = runBlocking { val principal = SecurityContextHolder.getContext().getAuthentication().principal as Jwt @@ -20,4 +27,21 @@ class MastodonAccountApiController(private val accountApiService: AccountApiServ HttpStatus.OK ) } + + override fun apiV1AccountsPost( + username: String, + password: String, + email: String?, + agreement: Boolean?, + locale: Boolean?, + reason: String? + ): ResponseEntity = runBlocking { + transaction.transaction { + + accountApiService.registerAccount(UserCreateDto(username, username, "", password)) + } + val httpHeaders = HttpHeaders() + httpHeaders.location = URI("/users/$username") + ResponseEntity(Unit, httpHeaders, HttpStatus.FOUND) + } } diff --git a/src/main/kotlin/dev/usbharu/hideout/service/api/mastodon/AccountApiService.kt b/src/main/kotlin/dev/usbharu/hideout/service/api/mastodon/AccountApiService.kt index 45df785d..2eece508 100644 --- a/src/main/kotlin/dev/usbharu/hideout/service/api/mastodon/AccountApiService.kt +++ b/src/main/kotlin/dev/usbharu/hideout/service/api/mastodon/AccountApiService.kt @@ -4,23 +4,34 @@ import dev.usbharu.hideout.domain.mastodon.model.generated.Account import dev.usbharu.hideout.domain.mastodon.model.generated.CredentialAccount import dev.usbharu.hideout.domain.mastodon.model.generated.CredentialAccountSource import dev.usbharu.hideout.domain.mastodon.model.generated.Role +import dev.usbharu.hideout.domain.model.hideout.dto.UserCreateDto import dev.usbharu.hideout.service.core.Transaction import dev.usbharu.hideout.service.mastodon.AccountService +import dev.usbharu.hideout.service.user.UserService import org.springframework.stereotype.Service @Service interface AccountApiService { suspend fun verifyCredentials(userid: Long): CredentialAccount + suspend fun registerAccount(userCreateDto: UserCreateDto): Unit } @Service -class AccountApiServiceImpl(private val accountService: AccountService, private val transaction: Transaction) : +class AccountApiServiceImpl( + private val accountService: AccountService, + private val transaction: Transaction, + private val userService: UserService +) : AccountApiService { override suspend fun verifyCredentials(userid: Long): CredentialAccount = transaction.transaction { val account = accountService.findById(userid) from(account) } + override suspend fun registerAccount(userCreateDto: UserCreateDto) { + userService.createLocalUser(UserCreateDto(userCreateDto.name, userCreateDto.name, "", userCreateDto.password)) + } + private fun from(account: Account): CredentialAccount { return CredentialAccount( id = account.id, diff --git a/src/main/resources/openapi/mastodon.yaml b/src/main/resources/openapi/mastodon.yaml index 9b10b266..246a15a5 100644 --- a/src/main/resources/openapi/mastodon.yaml +++ b/src/main/resources/openapi/mastodon.yaml @@ -182,8 +182,57 @@ paths: schema: $ref: "#/components/schemas/CredentialAccount" + /api/v1/accounts: + post: + tags: + - account + security: + - OAuth2: + - "write:accounts" + requestBody: + required: true + content: + application/x-www-form-urlencoded: + schema: + $ref: "#/components/schemas/AccountsCreateRequest" + responses: + 200: + description: 成功 + components: schemas: + AccountsCreateRequest: + type: object + properties: + username: + type: string + email: + type: string + password: + type: string + agreement: + type: boolean + locale: + type: boolean + reason: + type: string + required: + - username + - password + + Token: + type: object + properties: + access_token: + type: string + token_type: + type: string + scope: + type: string + created_at: + type: integer + format: int64 + Account: type: object properties: diff --git a/src/main/resources/templates/sign_up.html b/src/main/resources/templates/sign_up.html new file mode 100644 index 00000000..079fdd7c --- /dev/null +++ b/src/main/resources/templates/sign_up.html @@ -0,0 +1,15 @@ + + + + + SignUp + + + +
+ + + +
+ + diff --git a/src/test/kotlin/dev/usbharu/hideout/service/ap/APReceiveFollowServiceImplTest.kt b/src/test/kotlin/dev/usbharu/hideout/service/ap/APReceiveFollowServiceImplTest.kt index 79c93816..9aebf1ee 100644 --- a/src/test/kotlin/dev/usbharu/hideout/service/ap/APReceiveFollowServiceImplTest.kt +++ b/src/test/kotlin/dev/usbharu/hideout/service/ap/APReceiveFollowServiceImplTest.kt @@ -36,7 +36,12 @@ class APReceiveFollowServiceImplTest { } val activityPubFollowService = APReceiveFollowServiceImpl( - jobQueueParentService, mock(), mock(), mock(), mock(), TestTransaction, + jobQueueParentService, + mock(), + mock(), + mock(), + mock(), + TestTransaction, objectMapper ) activityPubFollowService.receiveFollow(