From 0809eb53c854f9a2603853d1ba0efb89476508e3 Mon Sep 17 00:00:00 2001 From: MrMicky Date: Sat, 30 Jul 2022 14:37:39 +0200 Subject: [PATCH] Version 1.0.0 for Azuriom v1.0 (#7) --- .github/workflows/build.yml | 3 + README.md | 9 +- build.gradle | 2 +- .../java/com/azuriom/azauth/AuthClient.java | 324 ++++++++++++++++++ .../java/com/azuriom/azauth/AuthResponse.java | 57 --- .../java/com/azuriom/azauth/AuthResult.java | 110 ++++++ .../azauth/AuthenticationException.java | 16 - .../com/azuriom/azauth/AzAuthenticator.java | 185 ---------- .../azauth/exception/AuthException.java | 16 + .../azuriom/azauth/model/ErrorResponse.java | 84 +++++ ...AuthenticatorTest.java => ClientTest.java} | 28 +- .../azuriom/azauth/gson/UuidAdapterTest.java | 2 +- 12 files changed, 560 insertions(+), 276 deletions(-) create mode 100644 src/main/java/com/azuriom/azauth/AuthClient.java delete mode 100644 src/main/java/com/azuriom/azauth/AuthResponse.java create mode 100644 src/main/java/com/azuriom/azauth/AuthResult.java delete mode 100644 src/main/java/com/azuriom/azauth/AuthenticationException.java delete mode 100644 src/main/java/com/azuriom/azauth/AzAuthenticator.java create mode 100644 src/main/java/com/azuriom/azauth/exception/AuthException.java create mode 100644 src/main/java/com/azuriom/azauth/model/ErrorResponse.java rename src/test/java/com/azuriom/azauth/{AuthenticatorTest.java => ClientTest.java} (57%) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 02bb1e4..176b1c9 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -27,6 +27,9 @@ jobs: - name: Validate Gradle Wrapper uses: gradle/wrapper-validation-action@v1 + - name: Setup Gradle + uses: gradle/gradle-build-action@v2 + - name: Build run: ./gradlew build diff --git a/README.md b/README.md index 5c372e3..3ba929f 100644 --- a/README.md +++ b/README.md @@ -2,6 +2,7 @@ [![Java CI](https://img.shields.io/github/workflow/status/Azuriom/AzAuth/Java%20CI?style=flat-square)](https://github.com/Azuriom/AzAuth/actions) [![Language grade](https://img.shields.io/lgtm/grade/java/github/Azuriom/AzAuth?label=code%20quality&logo=lgtm&logoWidth=18&style=flat-square)](https://lgtm.com/projects/g/Azuriom/AzAuth/context:java) +[![Maven Central](https://img.shields.io/maven-central/v/com.azuriom/azauth.svg?label=Maven%20Central&style=flat-square)](https://search.maven.org/search?q=g:%22com.azuriom%22%20AND%20a:%22azauth%22) [![Chat](https://img.shields.io/discord/625774284823986183?color=5865f2&label=Discord&logo=discord&logoColor=fff&style=flat-square)](https://azuriom.com/discord) A Java implementation of the Azuriom Auth API. @@ -15,7 +16,7 @@ A Java implementation of the Azuriom Auth API. com.azuriom azauth - 0.1.0 + 1.0.0 ``` @@ -23,8 +24,12 @@ A Java implementation of the Azuriom Auth API. ### Gradle ```groovy +repositories { + mavenCentral() +} + dependencies { - implementation 'com.azuriom:azauth:0.1.0' + implementation 'com.azuriom:azauth:1.0.0' } ``` diff --git a/build.gradle b/build.gradle index e934bdd..b9df0bd 100644 --- a/build.gradle +++ b/build.gradle @@ -5,7 +5,7 @@ plugins { } group 'com.azuriom' -version '0.1.0' +version '1.0.0' ext { isReleaseVersion = !version.endsWith('SNAPSHOT') diff --git a/src/main/java/com/azuriom/azauth/AuthClient.java b/src/main/java/com/azuriom/azauth/AuthClient.java new file mode 100644 index 0000000..f999dda --- /dev/null +++ b/src/main/java/com/azuriom/azauth/AuthClient.java @@ -0,0 +1,324 @@ +package com.azuriom.azauth; + +import com.azuriom.azauth.exception.AuthException; +import com.azuriom.azauth.gson.ColorAdapter; +import com.azuriom.azauth.gson.InstantAdapter; +import com.azuriom.azauth.gson.UuidAdapter; +import com.azuriom.azauth.model.ErrorResponse; +import com.azuriom.azauth.model.User; +import com.google.gson.FieldNamingPolicy; +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import com.google.gson.JsonObject; +import com.google.gson.JsonParseException; +import org.jetbrains.annotations.Blocking; +import org.jetbrains.annotations.Contract; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.awt.*; +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStreamReader; +import java.io.OutputStream; +import java.net.HttpURLConnection; +import java.net.URL; +import java.nio.charset.StandardCharsets; +import java.time.Instant; +import java.util.Objects; +import java.util.UUID; +import java.util.function.Supplier; +import java.util.logging.Logger; + +/** + * The authentication client for Azuriom. + */ +public class AuthClient { + + private static final Logger LOGGER = Logger.getLogger(AuthClient.class.getName()); + + private static final Gson GSON = new GsonBuilder() + .setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES) + .registerTypeAdapter(Color.class, new ColorAdapter()) + .registerTypeAdapter(Instant.class, new InstantAdapter()) + .registerTypeAdapter(UUID.class, new UuidAdapter()) + .create(); + + private final @NotNull String url; + + /** + * Construct a new AzAuthenticator instance. + * + * @param url the website url + */ + public AuthClient(@NotNull String url) { + this.url = Objects.requireNonNull(url, "url"); + + if (!url.startsWith("https://")) { + LOGGER.warning("HTTP links are not secure, use HTTPS instead."); + } + } + + /** + * Gets the website url. + * + * @return the website url + */ + public @NotNull String getUrl() { + return this.url; + } + + /** + * Try to authenticate the user on the website and get his profile. + * + * @param email the user email + * @param password the user password + * @return the user profile + * @throws AuthException if an error occurs (IO exception, invalid credentials, etc) + */ + @Blocking + public @NotNull AuthResult<@NotNull User> login(@NotNull String email, + @NotNull String password) throws AuthException { + return this.login(email, password, User.class); + } + + /** + * Try to authenticate the user on the website with a 2FA code, and get his profile. + * + * @param email the user email + * @param password the user password + * @param code2fa the 2FA code of the user + * @return the user profile + * @throws AuthException if an error occurs (IO exception, invalid credentials, etc) + */ + @Blocking + public @NotNull AuthResult<@NotNull User> login(@NotNull String email, + @NotNull String password, + @Nullable String code2fa) throws AuthException { + return this.login(email, password, code2fa, User.class); + } + + /** + * Try to authenticate the user on the website and get his profile with a given response type. + * + * @param email the user email + * @param password the user password + * @param responseType the class of the response + * @param the type of the response + * @return the user profile + * @throws AuthException if an error occurs (IO exception, invalid credentials, etc) + */ + @Blocking + public @NotNull AuthResult<@NotNull T> login(@NotNull String email, + @NotNull String password, + @NotNull Class responseType) throws AuthException { + return login(email, password, (String) null, responseType); + } + + /** + * Try to authenticate the user on the website and get his profile with a given response type. + * If the user has 2FA enabled, the {@code codeSupplier} will be called. + * + * @param email the user email + * @param password the user password + * @param codeSupplier the supplier called to get the 2FA code + * @return the user profile + * @throws AuthException if an error occurs (IO exception, invalid credentials, etc) + */ + @Blocking + public @NotNull User login(@NotNull String email, + @NotNull String password, + @NotNull Supplier codeSupplier) throws AuthException { + return login(email, password, codeSupplier, User.class); + } + + /** + * Try to authenticate the user on the website and get his profile with a given response type. + * If the user has 2FA enabled, the {@code codeSupplier} will be called. + * + * @param email the user email + * @param password the user password + * @param codeSupplier the supplier called to get the 2FA code + * @param responseType the class of the response + * @param the type of the response + * @return the user profile + * @throws AuthException if an error occurs (IO exception, invalid credentials, etc) + */ + @Blocking + public @NotNull T login(@NotNull String email, + @NotNull String password, + @NotNull Supplier codeSupplier, + @NotNull Class responseType) throws AuthException { + AuthResult result = login(email, password, responseType); + + if (result.isSuccess()) { + return result.getSuccessResult(); + } + + if (!result.isPending() || !result.asPending().require2fa()) { + throw new AuthException("Unknown login result: " + result); + } + + String code = codeSupplier.get(); + + if (code == null) { + throw new AuthException("No 2FA code provided."); + } + + result = login(email, password, code, responseType); + + if (!result.isSuccess()) { + throw new AuthException("Unknown login result: " + result); + } + + return result.getSuccessResult(); + } + + /** + * Try to authenticate the user on the website and get his profile with a 2FA code and a given response type. + * + * @param email the user email + * @param password the user password + * @param code2fa the 2FA code of the user + * @param responseType the class of the response + * @param the type of the response + * @return the user profile + * @throws AuthException if an error occurs (IO exception, invalid credentials, etc) + */ + @Blocking + public @NotNull AuthResult<@NotNull T> login(@NotNull String email, + @NotNull String password, + @Nullable String code2fa, + @NotNull Class responseType) throws AuthException { + JsonObject body = new JsonObject(); + body.addProperty("email", email); + body.addProperty("password", password); + body.addProperty("code", code2fa); + + return this.post("authenticate", body, responseType); + } + + /** + * Verify an access token and get the associated profile. + * + * @param accessToken the user access token + * @return the user profile + * @throws AuthException if an error occurs (IO exception, invalid credentials, etc) + */ + @Blocking + public @NotNull User verify(@NotNull String accessToken) throws AuthException { + return this.verify(accessToken, User.class); + } + + /** + * Verify an access token and get the associated profile with a given response type. + * + * @param accessToken the user access token + * @param responseType the class of the response + * @param the type of the response + * @return the user profile + * @throws AuthException if an error occurs (IO exception, invalid credentials, etc) + */ + @Blocking + public @NotNull T verify(@NotNull String accessToken, @NotNull Class responseType) + throws AuthException { + JsonObject body = new JsonObject(); + body.addProperty("access_token", accessToken); + + AuthResult result = this.post("verify", body, responseType); + + if (!result.isSuccess()) { + throw new AuthException("Unexpected verification result: " + result); + } + + return result.asSuccess().getResult(); + } + + /** + * Invalidate the given access token. + * To get a new valid access token you need to use {@link #login(String, String)} again. + * + * @param accessToken the user access token + * @throws AuthException if an error occurs (IO exception, invalid credentials, etc) + */ + @Blocking + public void logout(@NotNull String accessToken) throws AuthException { + JsonObject body = new JsonObject(); + body.addProperty("access_token", accessToken); + + this.post("logout", body, null); + } + + @Blocking + @Contract("_, _, null -> null; _, _, !null -> !null") + private AuthResult post(@NotNull String endPoint, @NotNull JsonObject body, + @Nullable Class responseType) throws AuthException { + try { + return this.doPost(endPoint, body, responseType); + } catch (IOException e) { + throw new AuthException(e); + } + } + + @Blocking + @Contract("_, _, null -> null; _, _, !null -> !null") + private AuthResult doPost(@NotNull String endPoint, @NotNull JsonObject body, @Nullable Class responseType) + throws AuthException, IOException { + try { + URL apiUrl = new URL(this.url + "/api/auth/" + endPoint); + HttpURLConnection connection = (HttpURLConnection) apiUrl.openConnection(); + connection.setRequestMethod("POST"); + connection.setDoOutput(true); + connection.addRequestProperty("User-Agent", "AzAuth authenticator v1"); + connection.addRequestProperty("Content-Type", "application/json; charset=utf-8"); + + try (OutputStream out = connection.getOutputStream()) { + out.write(body.toString().getBytes(StandardCharsets.UTF_8)); + } + + int status = connection.getResponseCode(); + + if (status >= 400 && status < 500) { + return this.handleClientError(connection); + } + + if (responseType == null) { + return null; + } + + return handleResponse(connection, responseType); + } catch (IOException e) { + throw new AuthException(e); + } + } + + private AuthResult handleResponse(HttpURLConnection connection, Class type) throws AuthException, IOException { + try (BufferedReader reader = new BufferedReader(new InputStreamReader(connection.getInputStream()))) { + T response = GSON.fromJson(reader, type); + + if (response == null) { + throw new AuthException("Empty JSON response from API"); + } + + return new AuthResult.Success<>(response); + } + } + + private AuthResult handleClientError(HttpURLConnection connection) + throws AuthException, IOException { + int status = connection.getResponseCode(); + + try (BufferedReader reader = new BufferedReader(new InputStreamReader(connection.getErrorStream()))) { + ErrorResponse response = GSON.fromJson(reader, ErrorResponse.class); + + if (response.getStatus().equals("pending") + && Objects.equals(response.getReason(), "2fa")) { + return new AuthResult.Pending<>(AuthResult.Pending.Reason.REQUIRE_2FA); + } + + throw new AuthException(response.getMessage()); + } catch (JsonParseException e) { + throw new AuthException("Invalid JSON response from API (http " + status + ")"); + } + } +} diff --git a/src/main/java/com/azuriom/azauth/AuthResponse.java b/src/main/java/com/azuriom/azauth/AuthResponse.java deleted file mode 100644 index 1538997..0000000 --- a/src/main/java/com/azuriom/azauth/AuthResponse.java +++ /dev/null @@ -1,57 +0,0 @@ -package com.azuriom.azauth; - -import org.jetbrains.annotations.NotNull; - -import java.util.Objects; - -public class AuthResponse { - - private final String status; - private final String message; - - public AuthResponse(@NotNull String status, @NotNull String message) { - this.status = Objects.requireNonNull(status, "status"); - this.message = Objects.requireNonNull(message, "message"); - } - - /** - * Get the response status. - * - * @return the response status - */ - public @NotNull String getStatus() { - return this.status; - } - - /** - * Get the response message. - * - * @return the response message - */ - public @NotNull String getMessage() { - return this.message; - } - - @Override - public boolean equals(Object o) { - if (this == o) { - return true; - } - if (o == null || getClass() != o.getClass()) { - return false; - } - - AuthResponse that = (AuthResponse) o; - return this.status.equals(that.status) && this.message.equals(that.message); - } - - @Override - public int hashCode() { - return Objects.hash(this.status, this.message); - } - - @Override - public String toString() { - return "AuthStatus{status='" + this.status + "', message='" + this.message + "'}"; - } -} diff --git a/src/main/java/com/azuriom/azauth/AuthResult.java b/src/main/java/com/azuriom/azauth/AuthResult.java new file mode 100644 index 0000000..604fdbf --- /dev/null +++ b/src/main/java/com/azuriom/azauth/AuthResult.java @@ -0,0 +1,110 @@ +package com.azuriom.azauth; + +import org.jetbrains.annotations.NotNull; + +import java.util.Objects; + +public interface AuthResult { + + default boolean isSuccess(){ + return this instanceof Success; + } + + default Success asSuccess() { + return (Success) this; + } + + default T getSuccessResult() { + return asSuccess().getResult(); + } + + default boolean isPending() { + return this instanceof Pending; + } + + default Pending asPending() { + return (Pending) this; + } + + default Pending.Reason getPendingReason() { + return asPending().getReason(); + } + + class Success implements AuthResult { + private final T result; + + public Success(@NotNull T result) { + this.result = Objects.requireNonNull(result); + } + + public @NotNull T getResult() { + return result; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + + Success success = (Success) o; + return this.result.equals(success.result); + } + + @Override + public int hashCode() { + return result.hashCode(); + } + + @Override + public String toString() { + return "Success{result=" + result + '}'; + } + } + + class Pending implements AuthResult { + private final Reason reason; + + public Pending(Reason reason) { + this.reason = reason; + } + + public Reason getReason() { + return reason; + } + + public boolean require2fa() { + return this.reason == Reason.REQUIRE_2FA; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + + Pending pending = (Pending) o; + return this.reason == pending.reason; + } + + @Override + public int hashCode() { + return this.reason.hashCode(); + } + + @Override + public String toString() { + return "Pending{reason=" + reason + '}'; + } + + public enum Reason { + REQUIRE_2FA + } + } +} diff --git a/src/main/java/com/azuriom/azauth/AuthenticationException.java b/src/main/java/com/azuriom/azauth/AuthenticationException.java deleted file mode 100644 index e1635ab..0000000 --- a/src/main/java/com/azuriom/azauth/AuthenticationException.java +++ /dev/null @@ -1,16 +0,0 @@ -package com.azuriom.azauth; - -public class AuthenticationException extends Exception { - - public AuthenticationException(String message) { - super(message); - } - - public AuthenticationException(String message, Throwable cause) { - super(message, cause); - } - - public AuthenticationException(Throwable cause) { - super(cause); - } -} diff --git a/src/main/java/com/azuriom/azauth/AzAuthenticator.java b/src/main/java/com/azuriom/azauth/AzAuthenticator.java deleted file mode 100644 index 17e6f09..0000000 --- a/src/main/java/com/azuriom/azauth/AzAuthenticator.java +++ /dev/null @@ -1,185 +0,0 @@ -package com.azuriom.azauth; - -import com.azuriom.azauth.gson.ColorAdapter; -import com.azuriom.azauth.gson.InstantAdapter; -import com.azuriom.azauth.gson.UuidAdapter; -import com.azuriom.azauth.model.User; -import com.google.gson.FieldNamingPolicy; -import com.google.gson.Gson; -import com.google.gson.GsonBuilder; -import com.google.gson.JsonObject; -import org.jetbrains.annotations.Blocking; -import org.jetbrains.annotations.Contract; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -import java.awt.*; -import java.io.BufferedReader; -import java.io.IOException; -import java.io.InputStreamReader; -import java.io.OutputStream; -import java.net.HttpURLConnection; -import java.net.URL; -import java.nio.charset.StandardCharsets; -import java.time.Instant; -import java.util.Objects; -import java.util.UUID; -import java.util.logging.Logger; - -public class AzAuthenticator { - - private static final Logger LOGGER = Logger.getLogger(AzAuthenticator.class.getName()); - - private static final Gson GSON = new GsonBuilder() - .setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES) - .registerTypeAdapter(Color.class, new ColorAdapter()) - .registerTypeAdapter(Instant.class, new InstantAdapter()) - .registerTypeAdapter(UUID.class, new UuidAdapter()) - .create(); - - private final String url; - - /** - * Construct a new AzAuthenticator instance. - * - * @param url the website url - */ - public AzAuthenticator(@NotNull String url) { - this.url = Objects.requireNonNull(url, "url"); - - if (!url.startsWith("https://")) { - LOGGER.warning("HTTP links are not secure, use HTTPS instead."); - } - } - - /** - * Gets the website url. - * - * @return the website url - */ - public @NotNull String getUrl() { - return this.url; - } - - /** - * Try to authenticate the user on the website and get his profile. - * - * @param email the user email - * @param password the user password - * @return the user profile - * @throws AuthenticationException if credentials are not valid - * @throws IOException if an IO exception occurs - */ - @Blocking - public @NotNull User authenticate(@NotNull String email, @NotNull String password) - throws AuthenticationException, IOException { - return this.authenticate(email, password, User.class); - } - - /** - * Try to authenticate the user on the website and get his profile with a given response type. - * - * @param email the user email - * @param password the user password - * @param responseType the class of the response - * @param the type of the response - * @return the user profile - * @throws AuthenticationException if credentials are not valid - * @throws IOException if an IO exception occurs - */ - @Blocking - public @NotNull T authenticate(@NotNull String email, @NotNull String password, @NotNull Class responseType) - throws AuthenticationException, IOException { - JsonObject body = new JsonObject(); - body.addProperty("email", email); - body.addProperty("password", password); - - return this.post("authenticate", body, responseType); - } - - /** - * Verify an access token and get the associated profile. - * - * @param accessToken the user access token - * @return the user profile - * @throws AuthenticationException if credentials are not valid - * @throws IOException if an IO exception occurs - */ - @Blocking - public @NotNull User verify(@NotNull String accessToken) throws AuthenticationException, IOException { - return this.verify(accessToken, User.class); - } - - /** - * Verify an access token and get the associated profile with a given response type. - * - * @param accessToken the user access token - * @param responseType the class of the response - * @param the type of the response - * @return the user profile - * @throws AuthenticationException if credentials are not valid - * @throws IOException if an IO exception occurs - */ - @Blocking - public @NotNull T verify(@NotNull String accessToken, @NotNull Class responseType) - throws AuthenticationException, IOException { - JsonObject body = new JsonObject(); - body.addProperty("access_token", accessToken); - - return this.post("verify", body, responseType); - } - - /** - * Invalidate the given access token. - * To get a new valid access token you need to use {@link #authenticate(String, String)} again. - * - * @param accessToken the user access token - * @throws AuthenticationException if credentials are not valid - * @throws IOException if an IO exception occurs - */ - @Blocking - public void logout(@NotNull String accessToken) throws AuthenticationException, IOException { - JsonObject body = new JsonObject(); - body.addProperty("access_token", accessToken); - - this.post("logout", body, null); - } - - @Blocking - @Contract("_, _, null -> null; _, _, !null -> !null") - private T post(@NotNull String endPoint, @NotNull JsonObject body, @Nullable Class responseType) - throws AuthenticationException, IOException { - URL apiUrl = new URL(this.url + "/api/auth/" + endPoint); - HttpURLConnection connection = (HttpURLConnection) apiUrl.openConnection(); - connection.setRequestMethod("POST"); - connection.setDoOutput(true); - connection.addRequestProperty("User-Agent", "AzAuth authenticator v1"); - connection.addRequestProperty("Content-Type", "application/json; charset=utf-8"); - - try (OutputStream out = connection.getOutputStream()) { - out.write(body.toString().getBytes(StandardCharsets.UTF_8)); - } - - if (connection.getResponseCode() == 422) { - try (BufferedReader reader = new BufferedReader(new InputStreamReader(connection.getErrorStream()))) { - AuthResponse status = GSON.fromJson(reader, AuthResponse.class); - - throw new AuthenticationException(status.getMessage()); - } - } - - if (responseType == null) { - return null; - } - - try (BufferedReader reader = new BufferedReader(new InputStreamReader(connection.getInputStream()))) { - T response = GSON.fromJson(reader, responseType); - - if (response == null) { - throw new IllegalStateException("Empty JSON response"); - } - - return response; - } - } -} diff --git a/src/main/java/com/azuriom/azauth/exception/AuthException.java b/src/main/java/com/azuriom/azauth/exception/AuthException.java new file mode 100644 index 0000000..e6539a2 --- /dev/null +++ b/src/main/java/com/azuriom/azauth/exception/AuthException.java @@ -0,0 +1,16 @@ +package com.azuriom.azauth.exception; + +public class AuthException extends Exception { + + public AuthException(String message) { + super(message); + } + + public AuthException(String message, Throwable cause) { + super(message, cause); + } + + public AuthException(Throwable cause) { + super(cause); + } +} diff --git a/src/main/java/com/azuriom/azauth/model/ErrorResponse.java b/src/main/java/com/azuriom/azauth/model/ErrorResponse.java new file mode 100644 index 0000000..f0ea004 --- /dev/null +++ b/src/main/java/com/azuriom/azauth/model/ErrorResponse.java @@ -0,0 +1,84 @@ +package com.azuriom.azauth.model; + +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.Objects; + +/** + * The content of the response when an error occurs. + */ +public class ErrorResponse { + + private final String status; + private final String message; + private final String reason; + + /** + * Create a new error response. + * + * @param status the response status + * @param message the response message + * @param reason the response reason + */ + public ErrorResponse(@NotNull String status, @NotNull String message, @Nullable String reason) { + this.status = Objects.requireNonNull(status, "status"); + this.message = Objects.requireNonNull(message, "message"); + this.reason = reason; + } + + /** + * Get the response status. + * + * @return the response status + */ + public @NotNull String getStatus() { + return this.status; + } + + /** + * Get the response message. + * + * @return the response message + */ + public @NotNull String getMessage() { + return this.message; + } + + /** + * Get the response error/pending reason, or null. + * + * @return the reason + */ + public @Nullable String getReason() { + return reason; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + + ErrorResponse that = (ErrorResponse) o; + return this.status.equals(that.status) + && this.message.equals(that.message) + && Objects.equals(this.reason, that.reason); + } + + @Override + public int hashCode() { + return Objects.hash(this.status, this.message, this.reason); + } + + @Override + public String toString() { + return "AuthStatus{status='" + this.status + + "', message='" + this.message + + "', reason='" + this.reason + + "'}"; + } +} \ No newline at end of file diff --git a/src/test/java/com/azuriom/azauth/AuthenticatorTest.java b/src/test/java/com/azuriom/azauth/ClientTest.java similarity index 57% rename from src/test/java/com/azuriom/azauth/AuthenticatorTest.java rename to src/test/java/com/azuriom/azauth/ClientTest.java index c45c639..8525ba7 100644 --- a/src/test/java/com/azuriom/azauth/AuthenticatorTest.java +++ b/src/test/java/com/azuriom/azauth/ClientTest.java @@ -1,5 +1,6 @@ package com.azuriom.azauth; +import com.azuriom.azauth.exception.AuthException; import com.azuriom.azauth.model.User; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.condition.EnabledIfEnvironmentVariable; @@ -12,39 +13,38 @@ @EnabledIfEnvironmentVariable(named = "AUTH_TEST_EMAIL", matches = ".+"), @EnabledIfEnvironmentVariable(named = "AUTH_TEST_PASSWORD", matches = ".+"), }) -class AuthenticatorTest { +class ClientTest { - private final AzAuthenticator authenticator = new AzAuthenticator(System.getenv("AUTH_TEST_URL")); + private final AuthClient client = new AuthClient(System.getenv("AUTH_TEST_URL")); private final String testEmail = System.getenv("AUTH_TEST_EMAIL"); private final String testPassword = System.getenv("AUTH_TEST_PASSWORD"); @Test void testAuthenticateAndLogout() { - User user = assertDoesNotThrow(() -> authenticator.authenticate(this.testEmail, this.testPassword)); + AuthResult result = assertDoesNotThrow(() -> client.login(this.testEmail, this.testPassword)); + assertTrue(result.isSuccess()); + User user = result.getSuccessResult(); assertEquals(this.testEmail, user.getEmail()); - assertNotNull(user.getAccessToken()); assertNotNull(user.getCreatedAt()); assertNotNull(user.getRole()); assertNotNull(user.getRole().getColor()); - assertNotNull(user.getUuid()); - - User verifiedUser = assertDoesNotThrow(() -> authenticator.verify(user.getAccessToken())); - - assertEquals(user, verifiedUser); - assertDoesNotThrow(() -> authenticator.logout(user.getAccessToken())); + User verifyResult = assertDoesNotThrow(() -> client.verify(user.getAccessToken())); - assertThrows(AuthenticationException.class, () -> authenticator.verify(user.getAccessToken())); + assertEquals(user, verifyResult); + assertDoesNotThrow(() -> client.logout(user.getAccessToken())); + assertThrows(AuthException.class, () -> client.verify(user.getAccessToken())); } @Test void testAuthenticateWithType() { - CustomUser user = assertDoesNotThrow(() -> - authenticator.authenticate(this.testEmail, this.testPassword, CustomUser.class)); + AuthResult result = assertDoesNotThrow(() -> + client.login(this.testEmail, this.testPassword, CustomUser.class)); - assertEquals(this.testEmail, user.email); + assertTrue(result.isSuccess()); + assertEquals(this.testEmail, result.getSuccessResult().email); } static class CustomUser { diff --git a/src/test/java/com/azuriom/azauth/gson/UuidAdapterTest.java b/src/test/java/com/azuriom/azauth/gson/UuidAdapterTest.java index e2d1d0c..faf8adb 100644 --- a/src/test/java/com/azuriom/azauth/gson/UuidAdapterTest.java +++ b/src/test/java/com/azuriom/azauth/gson/UuidAdapterTest.java @@ -6,7 +6,7 @@ import java.util.UUID; -import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.*; class UuidAdapterTest {