diff --git a/.github/workflows/pull-request-merge-check.yml b/.github/workflows/pull-request-merge-check.yml index 40be67e8..6b0f64f4 100644 --- a/.github/workflows/pull-request-merge-check.yml +++ b/.github/workflows/pull-request-merge-check.yml @@ -29,6 +29,8 @@ jobs: steps: - name: Checkout uses: actions/checkout@v4 + with: + token: ${{ secrets.PAT }} - name: Gradle Wrapper Validation uses: gradle/actions/wrapper-validation@v4 @@ -55,6 +57,8 @@ jobs: steps: - name: Checkout uses: actions/checkout@v4 + with: + token: ${{ secrets.PAT }} - name: Set up JDK 21 uses: actions/setup-java@v4 @@ -101,6 +105,7 @@ jobs: uses: actions/checkout@v4 with: ref: ${{ github.head_ref }} + token: '${{ secrets.PAT }}' - name: Set up JDK 21 uses: actions/setup-java@v4 diff --git a/.gitignore b/.gitignore index 39e4144d..9f79d3cb 100644 --- a/.gitignore +++ b/.gitignore @@ -48,3 +48,4 @@ out/ /hideout-core/files/ /hideout-core/.kotlin/sessions/ /hideout-mastodon/.kotlin/sessions/ +/http-client.private.env.json diff --git a/hideout-core/build.gradle.kts b/hideout-core/build.gradle.kts index 8e67e1c4..10054038 100644 --- a/hideout-core/build.gradle.kts +++ b/hideout-core/build.gradle.kts @@ -74,6 +74,7 @@ val os = org.gradle.nativeplatform.platform.internal dependencies { developmentOnly(libs.h2db) + developmentOnly("org.springframework.boot:spring-boot-devtools") detektPlugins(libs.detekt.formatting) implementation(libs.bundles.exposed) @@ -152,6 +153,12 @@ configurations.matching { it.name == "detekt" }.all { } } +//tasks{ +// bootRun { +// sourceResources(sourceSets.main.get()) +// } +//} + tasks.withType { exclude("**/generated/**") doFirst { diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/config/SecurityConfig.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/config/SecurityConfig.kt index 25f9690d..2eccad56 100644 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/config/SecurityConfig.kt +++ b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/config/SecurityConfig.kt @@ -23,6 +23,7 @@ import com.nimbusds.jose.jwk.source.JWKSource import com.nimbusds.jose.proc.SecurityContext import dev.usbharu.hideout.core.infrastructure.springframework.oauth2.HideoutUserDetails import dev.usbharu.hideout.util.RsaUtil +import org.slf4j.LoggerFactory import org.springframework.boot.context.properties.ConfigurationProperties import org.springframework.context.annotation.Bean import org.springframework.context.annotation.Configuration @@ -50,6 +51,10 @@ import org.springframework.security.oauth2.server.authorization.token.JwtEncodin import org.springframework.security.oauth2.server.authorization.token.OAuth2TokenCustomizer import org.springframework.security.web.SecurityFilterChain import org.springframework.security.web.authentication.LoginUrlAuthenticationEntryPoint +import java.security.KeyPairGenerator +import java.security.interfaces.RSAPrivateKey +import java.security.interfaces.RSAPublicKey +import java.util.* @Configuration @EnableWebSecurity(debug = false) @@ -126,17 +131,54 @@ class SecurityConfig { } @Bean - fun loadJwkSource(jwkConfig: JwkConfig): JWKSource { - val rsaKey = RSAKey.Builder(RsaUtil.decodeRsaPublicKey(jwkConfig.publicKey)) - .privateKey(RsaUtil.decodeRsaPrivateKey(jwkConfig.privateKey)).keyID(jwkConfig.keyId).build() + fun loadJwkSource(jwkConfig: JwkConfig, applicationConfig: ApplicationConfig): JWKSource { + if (jwkConfig.keyId == null) { + logger.error("hideout.security.jwt.keyId is null.") + } + if (jwkConfig.publicKey == null) { + logger.error("hideout.security.jwt.publicKey is null.") + } + if (jwkConfig.privateKey == null) { + logger.error("hideout.security.jwt.privateKey is null.") + } + if (jwkConfig.keyId == null || jwkConfig.publicKey == null || jwkConfig.privateKey == null) { + val keyPairGenerator = KeyPairGenerator.getInstance("RSA") + keyPairGenerator.initialize(applicationConfig.keySize) + val generateKeyPair = keyPairGenerator.generateKeyPair() + + jwkConfig.keyId = UUID.randomUUID().toString() + jwkConfig.publicKey = RsaUtil.encodeRsaPublicKey(generateKeyPair.public as RSAPublicKey) + jwkConfig.privateKey = RsaUtil.encodeRsaPrivateKey(generateKeyPair.private as RSAPrivateKey) + logger.error( + """ + |============== + |============== + | + |**Write the following settings in application.yml** + | + |hideout: + | security: + | jwt: + | keyId: ${jwkConfig.keyId} + | publicKey: ${jwkConfig.publicKey} + | privateKey: ${jwkConfig.privateKey} + | + |============== + |============== + """.trimMargin() + ) + } + + val rsaKey = RSAKey.Builder(RsaUtil.decodeRsaPublicKey(jwkConfig.publicKey!!)) + .privateKey(RsaUtil.decodeRsaPrivateKey(jwkConfig.privateKey!!)).keyID(jwkConfig.keyId).build() return ImmutableJWKSet(JWKSet(rsaKey)) } @ConfigurationProperties("hideout.security.jwt") data class JwkConfig( - val keyId: String, - val publicKey: String, - val privateKey: String, + var keyId: String?, + var publicKey: String?, + var privateKey: String?, ) @Bean @@ -195,4 +237,8 @@ class SecurityConfig { return roleHierarchyImpl } + + companion object { + private val logger = LoggerFactory.getLogger(SecurityConfig::class.java) + } } diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/util/RsaUtil.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/util/RsaUtil.kt index 56b20ac3..4af71576 100644 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/util/RsaUtil.kt +++ b/hideout-core/src/main/kotlin/dev/usbharu/hideout/util/RsaUtil.kt @@ -44,4 +44,12 @@ object RsaUtil { } fun decodeRsaPrivateKey(encoded: String): RSAPrivateKey = decodeRsaPrivateKey(Base64Util.decode(encoded)) + + fun encodeRsaPublicKey(publicKey: RSAPublicKey): String { + return Base64Util.encode(publicKey.encoded) + } + + fun encodeRsaPrivateKey(privateKey: RSAPrivateKey): String { + return Base64Util.encode(privateKey.encoded) + } } diff --git a/hideout-core/src/main/resources/application-dev.yml b/hideout-core/src/main/resources/application-dev.yml new file mode 100644 index 00000000..a6d4118b --- /dev/null +++ b/hideout-core/src/main/resources/application-dev.yml @@ -0,0 +1,42 @@ +hideout: + url: "http://localhost:8081" + private: true + debug: + trace-query-exception: true + trace-query-call: true +spring: + devtools: + livereload: + enabled: true + restart: + enabled: true + data: + mongodb: + auto-index-creation: true + host: localhost + port: 27017 + database: hideout + datasource: + driver-class-name: org.postgresql.Driver + url: "jdbc:postgresql:hideout" + username: "postgres" + password: "password" + servlet: + multipart: + max-file-size: 40MB + max-request-size: 40MB + threads: + virtual: + enabled: true + messages: + basename: messages.hideout-web-messages + thymeleaf: + cache: false +server: + tomcat: + basedir: tomcat + accesslog: + enabled: true + max-http-form-post-size: 40MB + max-swallow-size: 40MB + port: 8081 \ No newline at end of file diff --git a/hideout-core/src/main/resources/application.yml b/hideout-core/src/main/resources/application.yml index 371565d4..81d62b8a 100644 --- a/hideout-core/src/main/resources/application.yml +++ b/hideout-core/src/main/resources/application.yml @@ -1,23 +1,7 @@ #file: noinspection SpellCheckingInspection,SpellCheckingInspection,SpellCheckingInspection,SpellCheckingInspection,SpellCheckingInspection,SpellCheckingInspection,SpellCheckingInspection,SpellCheckingInspection,SpellCheckingInspection,SpellCheckingInspection,SpellCheckingInspection,SpellCheckingInspection,SpellCheckingInspection,SpellCheckingInspection,SpellCheckingInspection,SpellCheckingInspection,SpellCheckingInspection,SpellCheckingInspection,SpellCheckingInspection,SpellCheckingInspection,SpellCheckingInspection,SpellCheckingInspection,SpellCheckingInspection,SpellCheckingInspection,SpellCheckingInspection,SpellCheckingInspection,SpellCheckingInspection,SpellCheckingInspection,SpellCheckingInspection,SpellCheckingInspection,SpellCheckingInspection,SpellCheckingInspection,SpellCheckingInspection,SpellCheckingInspection,SpellCheckingInspection,SpellCheckingInspection,SpellCheckingInspection,SpellCheckingInspection,SpellCheckingInspection,SpellCheckingInspection,SpellCheckingInspection,SpellCheckingInspection,SpellCheckingInspection,SpellCheckingInspection,SpellCheckingInspection,SpellCheckingInspection,SpellCheckingInspection,SpellCheckingInspection,SpellCheckingInspection,SpellCheckingInspection,SpellCheckingInspection,SpellCheckingInspection,SpellCheckingInspection,SpellCheckingInspection,SpellCheckingInspection,SpellCheckingInspection,SpellCheckingInspection,SpellCheckingInspection,SpellCheckingInspection,SpellCheckingInspection,SpellCheckingInspection,SpellCheckingInspection,SpellCheckingInspection,SpellCheckingInspection,SpellCheckingInspection,SpellCheckingInspection,SpellCheckingInspection,SpellCheckingInspection,SpellCheckingInspection,SpellCheckingInspection,SpellCheckingInspection,SpellCheckingInspection,SpellCheckingInspection,SpellCheckingInspection,SpellCheckingInspection,SpellCheckingInspection,SpellCheckingInspection,SpellCheckingInspection,SpellCheckingInspection,SpellCheckingInspection,SpellCheckingInspection,SpellCheckingInspection hideout: url: "https://test-hideout-dev.usbharu.dev" - use-mongodb: true - owl: - producer: - standalone: embedded - security: - jwt: - generate: true - key-id: a - private-key: "MIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAoIBAQC7VJTUt9Us8cKjMzEfYyjiWA4R4/M2bS1GB4t7NXp98C3SC6dVMvDuictGeurT8jNbvJZHtCSuYEvuNMoSfm76oqFvAp8Gy0iz5sxjZmSnXyCdPEovGhLa0VzMaQ8s+CLOyS56YyCFGeJZqgtzJ6GR3eqoYSW9b9UMvkBpZODSctWSNGj3P7jRFDO5VoTwCQAWbFnOjDfH5Ulgp2PKSQnSJP3AJLQNFNe7br1XbrhV//eO+t51mIpGSDCUv3E0DDFcWDTH9cXDTTlRZVEiR2BwpZOOkE/Z0/BVnhZYL71oZV34bKfWjQIt6V/isSMahdsAASACp4ZTGtwiVuNd9tybAgMBAAECggEBAKTmjaS6tkK8BlPXClTQ2vpz/N6uxDeS35mXpqasqskVlaAidgg/sWqpjXDbXr93otIMLlWsM+X0CqMDgSXKejLS2jx4GDjI1ZTXg++0AMJ8sJ74pWzVDOfmCEQ/7wXs3+cbnXhKriO8Z036q92Qc1+N87SI38nkGa0ABH9CN83HmQqt4fB7UdHzuIRe/me2PGhIq5ZBzj6h3BpoPGzEP+x3l9YmK8t/1cN0pqI+dQwYdgfGjackLu/2qH80MCF7IyQaseZUOJyKrCLtSD/Iixv/hzDEUPfOCjFDgTpzf3cwta8+oE4wHCo1iI1/4TlPkwmXx4qSXtmw4aQPz7IDQvECgYEA8KNThCO2gsC2I9PQDM/8Cw0O983WCDY+oi+7JPiNAJwv5DYBqEZB1QYdj06YD16XlC/HAZMsMku1na2TN0driwenQQWzoev3g2S7gRDoS/FCJSI3jJ+kjgtaA7Qmzlgk1TxODN+G1H91HW7t0l7VnL27IWyYo2qRRK3jzxqUiPUCgYEAx0oQs2reBQGMVZnApD1jeq7n4MvNLcPvt8b/eU9iUv6Y4Mj0Suo/AU8lYZXm8ubbqAlwz2VSVunD2tOplHyMUrtCtObAfVDUAhCndKaA9gApgfb3xw1IKbuQ1u4IF1FJl3VtumfQn//LiH1B3rXhcdyo3/vIttEk48RakUKClU8CgYEAzV7W3COOlDDcQd935DdtKBFRAPRPAlspQUnzMi5eSHMD/ISLDY5IiQHbIH83D4bvXq0X7qQoSBSNP7Dvv3HYuqMhf0DaegrlBuJllFVVq9qPVRnKxt1Il2HgxOBvbhOT+9in1BzA+YJ99UzC85O0Qz06A+CmtHEy4aZ2kj5hHjECgYEAmNS4+A8Fkss8Js1RieK2LniBxMgmYml3pfVLKGnzmng7H2+cwPLhPIzIuwytXywh2bzbsYEfYx3EoEVgMEpPhoarQnYPukrJO4gwE2o5Te6T5mJSZGlQJQj9q4ZB2Dfzet6INsK0oG8XVGXSpQvQh3RUYekCZQkBBFcpqWpbIEsCgYAnM3DQf3FJoSnXaMhrVBIovic5l0xFkEHskAjFTevO86Fsz1C2aSeRKSqGFoOQ0tmJzBEs1R6KqnHInicDTQrKhArgLXX4v3CddjfTRJkFWDbE/CkvKZNOrcf1nhaGCPspRJj2KUkj1Fhl9Cncdn/RsYEONbwQSjIfMPkvxF+8HQ==" - public-key: "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAu1SU1LfVLPHCozMxH2Mo4lgOEePzNm0tRgeLezV6ffAt0gunVTLw7onLRnrq0/IzW7yWR7QkrmBL7jTKEn5u+qKhbwKfBstIs+bMY2Zkp18gnTxKLxoS2tFczGkPLPgizskuemMghRniWaoLcyehkd3qqGElvW/VDL5AaWTg0nLVkjRo9z+40RQzuVaE8AkAFmxZzow3x+VJYKdjykkJ0iT9wCS0DRTXu269V264Vf/3jvredZiKRkgwlL9xNAwxXFg0x/XFw005UWVRIkdgcKWTjpBP2dPwVZ4WWC+9aGVd+Gyn1o0CLelf4rEjGoXbAAEgAqeGUxrcIlbjXfbcmwIDAQAB" private: true - debug: - trace-query-exception: true - trace-query-call: true - - - spring: data: @@ -26,12 +10,6 @@ spring: host: localhost port: 27017 database: hideout - jmx: - enabled: false - jackson: - serialization: - WRITE_DATES_AS_TIMESTAMPS: false - default-property-inclusion: always datasource: driver-class-name: org.postgresql.Driver url: "jdbc:postgresql:hideout" @@ -41,9 +19,6 @@ spring: multipart: max-file-size: 40MB max-request-size: 40MB - h2: - console: - enabled: true threads: virtual: enabled: true diff --git a/http-client.env.json b/http-client.env.json new file mode 100644 index 00000000..c1b28a1f --- /dev/null +++ b/http-client.env.json @@ -0,0 +1,19 @@ +{ + "dev": { + "name": "value", + "Security": { + "Auth": { + "auth-id": { + "Type": "OAuth2", + "Grant Type": "Authorization Code", + "Client ID": "{{client_id}}", + "Client Secret": "{{client_secret}}", + "Redirect URL": "https://example.com", + "Token URL": "http://{{host}}/oauth/token", + "Auth URL": "http://{{host}}/oauth/authorize", + "Scope": "write read" + } + } + } + } +} \ No newline at end of file diff --git a/mastodon-api.http b/mastodon-api.http new file mode 100644 index 00000000..608850f0 --- /dev/null +++ b/mastodon-api.http @@ -0,0 +1,14 @@ +POST http://{{host}}/api/v1/apps +Accept: application/json +Content-Type: application/json + +{ + "client_name": "test-client", + "redirect_uris": "https://example.com", + "scopes": "write read" +} + +### + +GET http://{{host}}/api/v1/accounts/verify_credentials +Authorization: Bearer {{$auth.token("auth-id")}} \ No newline at end of file