diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..30042ad --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,54 @@ +Thank you for your interest in contributing to the Cloudeko Zenei Auth Service! We appreciate your willingness to help improve and enhance this project. + +**Setting Up Your Development Environment** + +1. **Prerequisites** + + * **Java 21:** Ensure you have Java 21 installed. You can download it from [https://openjdk.org/](https://openjdk.org/). + + * **Maven:** Install Maven. Check the installation instructions at [https://maven.apache.org/](https://maven.apache.org/). + +2. **Generate Development Keys** + + * **Execute the Script:** Run the script to create `dev-private-key.pem` and `dev-public-key.pem`. + + ```bash + ./generate-dev-keys.sh + ``` + +3. **Build and Run** + + * **Build:** + + ```bash + mvn clean install + ``` + + * **Run:** + + ```bash + mvn quarkus:dev + ``` + +**Making Contributions** + +1. **Fork the Repository** +2. **Create a Branch** +3. **Make Your Changes** +4. **Commit Your Changes** +5. **Push to Your Branch** +6. **Open a Pull Request** + +**Code Style** + +* Please follow the existing code style and formatting. + +**Additional Notes** + +* Ensure your code is well-documented. +* Write unit tests to accompany your changes. +* If you're introducing a new feature or making a significant change, consider opening an issue for discussion beforehand. + +**We look forward to your contributions!** + +Feel free to ask if you have any questions. \ No newline at end of file diff --git a/core/src/main/java/dev/cloudeko/zenei/application/web/resource/ExternalResource.java b/core/src/main/java/dev/cloudeko/zenei/application/web/resource/ExternalResource.java index c48f224..435640f 100644 --- a/core/src/main/java/dev/cloudeko/zenei/application/web/resource/ExternalResource.java +++ b/core/src/main/java/dev/cloudeko/zenei/application/web/resource/ExternalResource.java @@ -36,9 +36,9 @@ public Response login(@PathParam("provider") String provider) { final var uriBuilder = UriBuilder.fromUri(providerConfig.get().getAuthorizationEndpoint()) .queryParam("client_id", providerConfig.get().config().clientId()) - .queryParam("redirect_uri", providerConfig.get().config().redirectUri()) + .queryParam("redirect_uri", providerConfig.get().config().redirectUri().orElse("")) .queryParam("response_type", "code") - .queryParam("scope", providerConfig.get().config().scope()); + .queryParam("scope", providerConfig.get().config().scope().orElse("")); return Response.temporaryRedirect(uriBuilder.build()).build(); } diff --git a/core/src/main/java/dev/cloudeko/zenei/domain/feature/impl/LoginUserWithAuthorizationCodeImpl.java b/core/src/main/java/dev/cloudeko/zenei/domain/feature/impl/LoginUserWithAuthorizationCodeImpl.java index 632f8a2..bce0246 100644 --- a/core/src/main/java/dev/cloudeko/zenei/domain/feature/impl/LoginUserWithAuthorizationCodeImpl.java +++ b/core/src/main/java/dev/cloudeko/zenei/domain/feature/impl/LoginUserWithAuthorizationCodeImpl.java @@ -54,8 +54,11 @@ public Token handle(String provider, String code) { .baseUri(URI.create(externalProvider.getTokenEndpoint())) .build(LoginOAuthClient.class); - final var accessToken = client.getAccessToken(externalProvider.config().clientId(), - externalProvider.config().clientSecret(), code, null); + final var accessToken = client.getAccessToken("authorization_code", + externalProvider.config().clientId(), + externalProvider.config().clientSecret(), + code, + externalProvider.config().redirectUri().orElseThrow()); if (accessToken == null) { throw new IllegalArgumentException("Invalid authorization code"); diff --git a/core/src/main/resources/application-common.properties b/core/src/main/resources/application-common.properties new file mode 100644 index 0000000..1531551 --- /dev/null +++ b/core/src/main/resources/application-common.properties @@ -0,0 +1,10 @@ +# Application configuration +# Name of the Quarkus application. This can be used in various places, e.g., metrics. +quarkus.application.name=cloudeko-zenei-auth-service +# Path to a custom banner text file to be displayed on application startup. +quarkus.banner.path=zenei_banner.txt + +# Database configuration +# JDBC driver class for the database connection. +quarkus.datasource.jdbc.driver=org.postgresql.Driver +quarkus.hibernate-orm.database.generation=update \ No newline at end of file diff --git a/core/src/main/resources/application-dev.properties b/core/src/main/resources/application-dev.properties new file mode 100644 index 0000000..3dc2472 --- /dev/null +++ b/core/src/main/resources/application-dev.properties @@ -0,0 +1,22 @@ +# In 'dev' profile, drop and recreate the database schema on each startup. +quarkus.hibernate-orm.database.generation=drop-and-create + +# Mail configuration +# Use a mock mailer for development, not sending actual emails. +%dev.quarkus.mailer.mock=true + +# Zenei Configuration +# Type of database used by the Zenei application. +zenei.database.db-kind=postgresql +# Location of the private key file for JWT signing. +zenei.jwt.private.key.location=dev-private-key.pem +# Location of the public key file for JWT verification. +zenei.jwt.public.key.location=dev-public-key.pem +# Issuer identifier to be included in generated JWTs. +zenei.jwt.issuer=https://example.com/issuer + +# Default zenei admin user +zenei.user.default.admin.username=admin +zenei.user.default.admin.email=admin@test.com +zenei.user.default.admin.password=test +zenei.user.default.admin.role=admin \ No newline at end of file diff --git a/core/src/main/resources/application-providers.properties b/core/src/main/resources/application-providers.properties new file mode 100644 index 0000000..77e4819 --- /dev/null +++ b/core/src/main/resources/application-providers.properties @@ -0,0 +1,19 @@ +# Github OAuth +zenei.external.auth.providers.github.enabled=true +zenei.external.auth.providers.github.client-id= +zenei.external.auth.providers.github.client-secret= +zenei.external.auth.providers.github.authorization-uri=https://github.com/login/oauth/authorize +zenei.external.auth.providers.github.token-uri=https://github.com/login/oauth/access_token +zenei.external.auth.providers.github.base-uri=https://api.github.com +zenei.external.auth.providers.github.redirect-uri=http://localhost:8080/external/callback/github +zenei.external.auth.providers.github.scope=user:email + +# Discord OAuth +zenei.external.auth.providers.discord.enabled=true +zenei.external.auth.providers.discord.client-id= +zenei.external.auth.providers.discord.client-secret= +zenei.external.auth.providers.discord.authorization-uri=https://discord.com/api/oauth2/authorize +zenei.external.auth.providers.discord.token-uri=https://discord.com/api/oauth2/token +zenei.external.auth.providers.discord.base-uri=https://discord.com/api +zenei.external.auth.providers.discord.redirect-uri=http://localhost:8080/external/callback/discord +zenei.external.auth.providers.discord.scope=identify email \ No newline at end of file diff --git a/core/src/main/resources/application-test.properties b/core/src/main/resources/application-test.properties new file mode 100644 index 0000000..3eb2eed --- /dev/null +++ b/core/src/main/resources/application-test.properties @@ -0,0 +1,18 @@ +# In 'test' profile, drop and recreate the database schema on each startup. +quarkus.hibernate-orm.database.generation=drop-and-create + +# Zenei Configuration +# Type of database used by the Zenei application. +zenei.database.db-kind=postgresql +# Location of the private key file for JWT signing. +zenei.jwt.private.key.location=dev-private-key.pem +# Location of the public key file for JWT verification. +zenei.jwt.public.key.location=dev-public-key.pem +# Issuer identifier to be included in generated JWTs. +zenei.jwt.issuer=https://example.com/issuer + +# Default zenei admin user +zenei.user.default.admin.username=admin +zenei.user.default.admin.email=admin@test.com +zenei.user.default.admin.password=test +zenei.user.default.admin.role=admin \ No newline at end of file diff --git a/core/src/main/resources/application.properties b/core/src/main/resources/application.properties index f700b19..08c5fa2 100644 --- a/core/src/main/resources/application.properties +++ b/core/src/main/resources/application.properties @@ -1,48 +1,2 @@ -# Application configuration -# Name of the Quarkus application. This can be used in various places, e.g., metrics. -quarkus.application.name=cloudeko-zenei-auth-service -# Path to a custom banner text file to be displayed on application startup. -quarkus.banner.path=zenei_banner.txt - -# Database configuration -# JDBC driver class for the database connection. -quarkus.datasource.jdbc.driver=org.postgresql.Driver -# Hibernate ORM strategy for database schema generation. -# 'update' will attempt to update the existing schema to match the entities. -quarkus.hibernate-orm.database.generation=update -# In 'dev' profile, drop and recreate the database schema on each startup. -%dev.quarkus.hibernate-orm.database.generation=drop-and-create -# In 'test' profile, drop and recreate the database schema on each startup. -%test.quarkus.hibernate-orm.database.generation=drop-and-create - -# Mail configuration -# Use a mock mailer for development, not sending actual emails. -%dev.quarkus.mailer.mock=true -# In 'prod' profile, disable the mock mailer and send real emails. -%prod.quarkus.mailer.mock=false - -# Zenei Configuration -# Type of database used by the Zenei application. -zenei.database.db-kind=postgresql -# Location of the private key file for JWT signing. -zenei.jwt.private.key.location=dev-private-key.pem -# Location of the public key file for JWT verification. -zenei.jwt.public.key.location=dev-public-key.pem -# Issuer identifier to be included in generated JWTs. -zenei.jwt.issuer=https://example.com/issuer - -# Default zenei admin user -zenei.user.default.admin.username=admin -zenei.user.default.admin.email=admin@test.com -zenei.user.default.admin.password=test -zenei.user.default.admin.role=admin - -# Github OAuth -#zenei.external.auth.providers.github.enabled=false -#zenei.external.auth.providers.github.client-id= -#zenei.external.auth.providers.github.client-secret= -#zenei.external.auth.providers.github.authorization-uri=https://github.com/login/oauth/authorize -#zenei.external.auth.providers.github.token-uri=https://github.com/login/oauth/access_token -#zenei.external.auth.providers.github.base-uri=https://api.github.com -#zenei.external.auth.providers.github.redirect-uri=http://localhost:8080/external/callback/github -#zenei.external.auth.providers.github.scope=user:email \ No newline at end of file +%dev.quarkus.config.locations=application-common.properties,application-providers.properties,application-dev.properties +%test.quarkus.config.locations=application-common.properties,application-test.properties \ No newline at end of file diff --git a/core/src/test/java/dev/cloudeko/zenei/resource/MockDiscordAuthorizationServerTestResource.java b/core/src/test/java/dev/cloudeko/zenei/resource/MockDiscordAuthorizationServerTestResource.java index 35514b9..8978627 100644 --- a/core/src/test/java/dev/cloudeko/zenei/resource/MockDiscordAuthorizationServerTestResource.java +++ b/core/src/test/java/dev/cloudeko/zenei/resource/MockDiscordAuthorizationServerTestResource.java @@ -28,9 +28,11 @@ protected Map providerSpecificStubsAndConfig(WireMockServer serv // Mock the Discord access token endpoint server.stubFor(WireMock.post(WireMock.urlPathEqualTo("/discord/login/oauth/access_token")) - .withQueryParam("client_id", WireMock.matching(".*")) - .withQueryParam("client_secret", WireMock.matching(".*")) - .withQueryParam("code", WireMock.matching(".*")) + .withFormParam("client_id", WireMock.matching(".*")) + .withFormParam("client_secret", WireMock.matching(".*")) + .withFormParam("code", WireMock.matching(".*")) + .withFormParam("grant_type", WireMock.matching(".*")) + .withFormParam("redirect_uri", WireMock.matching(".*")) .willReturn(WireMock.aResponse() .withHeader("Content-Type", "application/json") .withBody(objectMapper.writeValueAsString( diff --git a/core/src/test/java/dev/cloudeko/zenei/resource/MockGithubAuthorizationServerTestResource.java b/core/src/test/java/dev/cloudeko/zenei/resource/MockGithubAuthorizationServerTestResource.java index dfcf5e5..fe8ddda 100644 --- a/core/src/test/java/dev/cloudeko/zenei/resource/MockGithubAuthorizationServerTestResource.java +++ b/core/src/test/java/dev/cloudeko/zenei/resource/MockGithubAuthorizationServerTestResource.java @@ -30,9 +30,11 @@ protected Map providerSpecificStubsAndConfig(WireMockServer serv // Mock the access token endpoint server.stubFor(WireMock.post(WireMock.urlPathEqualTo("/github/login/oauth/access_token")) - .withQueryParam("client_id", WireMock.matching(".*")) - .withQueryParam("client_secret", WireMock.matching(".*")) - .withQueryParam("code", WireMock.matching(".*")) + .withFormParam("client_id", WireMock.matching(".*")) + .withFormParam("client_secret", WireMock.matching(".*")) + .withFormParam("code", WireMock.matching(".*")) + .withFormParam("grant_type", WireMock.matching(".*")) + .withFormParam("redirect_uri", WireMock.matching(".*")) .willReturn(WireMock.aResponse() .withHeader("Content-Type", "application/json") .withBody(objectMapper.writeValueAsString( diff --git a/extensions/external-authentication/runtime/src/main/java/dev/cloudeko/zenei/extension/external/web/client/LoginOAuthClient.java b/extensions/external-authentication/runtime/src/main/java/dev/cloudeko/zenei/extension/external/web/client/LoginOAuthClient.java index c986f22..91bcf44 100644 --- a/extensions/external-authentication/runtime/src/main/java/dev/cloudeko/zenei/extension/external/web/client/LoginOAuthClient.java +++ b/extensions/external-authentication/runtime/src/main/java/dev/cloudeko/zenei/extension/external/web/client/LoginOAuthClient.java @@ -1,9 +1,6 @@ package dev.cloudeko.zenei.extension.external.web.client; -import jakarta.ws.rs.POST; -import jakarta.ws.rs.Path; -import jakarta.ws.rs.Produces; -import jakarta.ws.rs.QueryParam; +import jakarta.ws.rs.*; import jakarta.ws.rs.core.MediaType; import org.eclipse.microprofile.rest.client.annotation.ClientHeaderParam; @@ -12,9 +9,11 @@ public interface LoginOAuthClient { @POST @Produces(MediaType.APPLICATION_JSON) + @Consumes(MediaType.APPLICATION_FORM_URLENCODED) @ClientHeaderParam(name = "Accept", value = MediaType.APPLICATION_JSON) - ExternalAccessToken getAccessToken(@QueryParam("client_id") String clientId, - @QueryParam("client_secret") String clientSecret, - @QueryParam("code") String code, - @QueryParam("redirect_uri") String redirectUri); + ExternalAccessToken getAccessToken(@FormParam("grant_type") String grantType, + @FormParam("client_id") String clientId, + @FormParam("client_secret") String clientSecret, + @FormParam("code") String code, + @FormParam("redirect_uri") String redirectUri); } diff --git a/generate-dev-keys.sh b/generate-dev-keys.sh new file mode 100755 index 0000000..54b4a77 --- /dev/null +++ b/generate-dev-keys.sh @@ -0,0 +1,7 @@ +#!/bin/bash + +# Generate a development private key +openssl genpkey -algorithm RSA -out core/src/main/resources/dev-private-key.pem -pkeyopt rsa_keygen_bits:2048 + +# Extract the public key from the private key +openssl rsa -in core/src/main/resources/dev-private-key.pem -pubout -out core/src/main/resources/dev-public-key.pem \ No newline at end of file