From ffbf4f75000fb80180d3524e7a3849383b261b7f Mon Sep 17 00:00:00 2001 From: CodingPhoenixx Date: Sun, 15 Feb 2026 21:32:06 +0100 Subject: [PATCH] Added Register Workflow Request Handler and fixed some bugs --- .../dev/coph/flightscore/backend/Backend.java | 2 + .../backend/country/CountryRepository.java | 25 ++++++ .../auth/LoginRequestHandler.java | 2 +- .../auth/RegisterRequestHandler.java | 81 +++++++++++++++++++ .../coph/flightscore/backend/user/User.java | 9 ++- .../backend/user/UserProvider.java | 50 +++++++++--- yaak/yaak.ev_rnW4WEytTs.yaml | 38 +++++++++ yaak/yaak.fl_gBnxCkQD7y.yaml | 13 +++ yaak/yaak.rq_eRxcYSQsbe.yaml | 31 +++++++ yaak/yaak.rq_qMQNa95s3H.yaml | 27 +++++++ yaak/yaak.wk_gViMDNsLC9.yaml | 14 ++++ 11 files changed, 280 insertions(+), 12 deletions(-) create mode 100644 src/main/java/dev/coph/flightscore/backend/country/CountryRepository.java create mode 100644 src/main/java/dev/coph/flightscore/backend/requestHandler/auth/RegisterRequestHandler.java create mode 100644 yaak/yaak.ev_rnW4WEytTs.yaml create mode 100644 yaak/yaak.fl_gBnxCkQD7y.yaml create mode 100644 yaak/yaak.rq_eRxcYSQsbe.yaml create mode 100644 yaak/yaak.rq_qMQNa95s3H.yaml create mode 100644 yaak/yaak.wk_gViMDNsLC9.yaml diff --git a/src/main/java/dev/coph/flightscore/backend/Backend.java b/src/main/java/dev/coph/flightscore/backend/Backend.java index 6e5778a..e1b9523 100644 --- a/src/main/java/dev/coph/flightscore/backend/Backend.java +++ b/src/main/java/dev/coph/flightscore/backend/Backend.java @@ -3,6 +3,7 @@ package dev.coph.flightscore.backend; import dev.coph.flightscore.backend.config.Config; import dev.coph.flightscore.backend.provider.ProviderManager; import dev.coph.flightscore.backend.requestHandler.auth.LoginRequestHandler; +import dev.coph.flightscore.backend.requestHandler.auth.RegisterRequestHandler; import dev.coph.flightscore.backend.user.UserProvider; import dev.coph.flightscore.backend.user.permission.PermissionProvider; import dev.coph.flightscore.backend.user.role.RoleProvider; @@ -78,6 +79,7 @@ public class Backend { logger.info("Loading routes..."); webServer.registerRequestHandler(new LoginRequestHandler(this)); + webServer.registerRequestHandler(new RegisterRequestHandler(this)); logger.success("Routes loaded!"); diff --git a/src/main/java/dev/coph/flightscore/backend/country/CountryRepository.java b/src/main/java/dev/coph/flightscore/backend/country/CountryRepository.java new file mode 100644 index 0000000..99a5f67 --- /dev/null +++ b/src/main/java/dev/coph/flightscore/backend/country/CountryRepository.java @@ -0,0 +1,25 @@ +package dev.coph.flightscore.backend.country; + +import java.util.*; +import java.util.stream.Collectors; + +public final class CountryRepository { + private static final Map byCode = Arrays.stream(Locale.getISOCountries()) + .map(code -> new Locale("", code)) + .collect(Collectors.toMap(locale -> locale.getCountry().toLowerCase(), locale -> locale)); + + private static final Map byName = byCode.values().stream() + .collect(Collectors.toMap(locale -> locale.getDisplayCountry(Locale.ENGLISH).toLowerCase(), locale -> locale)); + + public static Locale fromCode(String code) { + return byCode.get(code.toLowerCase()); + } + + public static Locale fromName(String name) { + return byName.get(name.toLowerCase()); + } + + public static List all() { + return new ArrayList<>(byCode.values()); + } +} diff --git a/src/main/java/dev/coph/flightscore/backend/requestHandler/auth/LoginRequestHandler.java b/src/main/java/dev/coph/flightscore/backend/requestHandler/auth/LoginRequestHandler.java index 9ecff74..ab3a83a 100644 --- a/src/main/java/dev/coph/flightscore/backend/requestHandler/auth/LoginRequestHandler.java +++ b/src/main/java/dev/coph/flightscore/backend/requestHandler/auth/LoginRequestHandler.java @@ -47,7 +47,7 @@ public class LoginRequestHandler { if (email == null || password == null) { response.setStatus(HttpStatus.BAD_REQUEST_400); - ResponseUtil.writeAnswer(response, callback, "Missing email or password"); + ResponseUtil.writeAnswer(response, callback, "Invalid body"); return; } var loginResponse = backend.userProvider().login(email, password); diff --git a/src/main/java/dev/coph/flightscore/backend/requestHandler/auth/RegisterRequestHandler.java b/src/main/java/dev/coph/flightscore/backend/requestHandler/auth/RegisterRequestHandler.java new file mode 100644 index 0000000..6367e27 --- /dev/null +++ b/src/main/java/dev/coph/flightscore/backend/requestHandler/auth/RegisterRequestHandler.java @@ -0,0 +1,81 @@ +package dev.coph.flightscore.backend.requestHandler.auth; + +import dev.coph.flightscore.backend.Backend; +import dev.coph.flightscore.backend.country.CountryRepository; +import dev.coph.simplelogger.Logger; +import dev.coph.simplerequest.body.Body; +import dev.coph.simplerequest.body.JsonBody; +import dev.coph.simplerequest.handler.RequestHandler; +import dev.coph.simplerequest.handler.RequestMethod; +import dev.coph.simplerequest.util.ResponseUtil; +import org.eclipse.jetty.http.HttpStatus; +import org.eclipse.jetty.server.Response; +import org.eclipse.jetty.util.Callback; +import org.json.JSONObject; + +import java.io.IOException; +import java.util.Locale; + +public class RegisterRequestHandler { + private final Logger logger = Logger.of("RH - Register"); + private final Backend backend; + + + public RegisterRequestHandler(Backend backend) { + this.backend = backend; + } + + + @RequestHandler( + path = "/auth/register", + method = RequestMethod.POST + ) + public void handle(Response response, Callback callback, Body body) { + JsonBody json; + try { + json = body.asJSON(); + } catch (IOException e) { + logger.error("Failed to parse JSON body", e); + response.setStatus(HttpStatus.BAD_REQUEST_400); + ResponseUtil.writeAnswer(response, callback, "Invalid body"); + return; + } + + + String email = json.getString("email", response, callback, "Invalid body"); + if (email == null) return; + String fistName = json.getString("firstName", response, callback, "Invalid body"); + if (fistName == null) return; + String lastName = json.getString("lastName", response, callback, "Invalid body"); + if (lastName == null) return; + String phoneNumber = json.getString("phoneNumber", response, callback, "Invalid body"); + if (phoneNumber == null) return; + String countryString = json.getString("country", response, callback, "Invalid body"); + if (countryString == null) return; + String password = json.getString("password", response, callback, "Invalid body"); + if (password == null) return; + + if (backend.userProvider().userExists(email)) { + logger.warn("User with email " + email + " already exists"); + response.setStatus(HttpStatus.CONFLICT_409); + ResponseUtil.writeAnswer(response, callback, "User already exists"); + return; + } + + Locale country = CountryRepository.fromCode(countryString); + + var loginResponse = backend.userProvider().register(fistName, lastName, email, phoneNumber, password, country); + + if (!loginResponse.success()) { + ResponseUtil.writeAnswer(response, callback, loginResponse.message()); + return; + } + + var responseObject = new JSONObject(); + + responseObject.put("accessToken", loginResponse.accessToken()); + responseObject.put("refreshToken", loginResponse.refreshToken()); + + ResponseUtil.writeSuccessfulAnswer(response, callback, responseObject); + } +} diff --git a/src/main/java/dev/coph/flightscore/backend/user/User.java b/src/main/java/dev/coph/flightscore/backend/user/User.java index 1f8c99f..f7845b3 100644 --- a/src/main/java/dev/coph/flightscore/backend/user/User.java +++ b/src/main/java/dev/coph/flightscore/backend/user/User.java @@ -5,6 +5,8 @@ import dev.coph.simpleutilities.ulid.ULID; import lombok.Getter; import lombok.experimental.Accessors; +import java.util.Locale; + @Getter @Accessors(fluent = true) public class User { @@ -14,25 +16,28 @@ public class User { private String lastName; private String email; private String phoneNumber; + private Locale country; private Role role; private boolean blocked; - public User(ULID id, String firstname, String lastname, String email, String phoneNumber, boolean blocked) { + public User(ULID id, String firstname, String lastname, String email, String phoneNumber, Locale country, boolean blocked) { this.id = id; this.firstName = firstname; this.lastName = lastname; this.email = email; this.phoneNumber = phoneNumber; + this.country = country; this.blocked = blocked; } - public User(ULID id, String firstname, String lastname, String email, String phoneNumber, boolean blocked, Role role) { + public User(ULID id, String firstname, String lastname, String email, String phoneNumber, Locale country, boolean blocked, Role role) { this.id = id; this.firstName = firstname; this.lastName = lastname; this.email = email; this.phoneNumber = phoneNumber; this.role = role; + this.country = country; this.blocked = blocked; } } diff --git a/src/main/java/dev/coph/flightscore/backend/user/UserProvider.java b/src/main/java/dev/coph/flightscore/backend/user/UserProvider.java index ca7179a..a09c811 100644 --- a/src/main/java/dev/coph/flightscore/backend/user/UserProvider.java +++ b/src/main/java/dev/coph/flightscore/backend/user/UserProvider.java @@ -3,6 +3,7 @@ package dev.coph.flightscore.backend.user; import dev.coph.flightscore.backend.Backend; import dev.coph.flightscore.backend.actionResult.LoginActionResult; import dev.coph.flightscore.backend.config.Config; +import dev.coph.flightscore.backend.country.CountryRepository; import dev.coph.flightscore.backend.provider.Provider; import dev.coph.flightscore.backend.user.role.Role; import dev.coph.flightscore.backend.utils.Hash; @@ -20,11 +21,13 @@ import dev.coph.simplesql.database.attributes.DataType; import dev.coph.simplesql.query.Query; import dev.coph.simpleutilities.ulid.ULID; import lombok.extern.slf4j.Slf4j; +import org.json.JSONArray; import java.sql.Timestamp; import java.time.Duration; import java.time.Instant; import java.util.List; +import java.util.Locale; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicReference; @@ -62,6 +65,7 @@ public class UserProvider implements Provider { .column("lastname", DataType.VARCHAR, 255) .column("email", DataType.VARCHAR, 255, ColumnType.UNIQUE, true) .column("phoneNumber", DataType.VARCHAR, 255) + .column("country", DataType.CHAR, 2) .column("password", DataType.VARCHAR, 255, true) .column(new Column("blocked", DataType.BOOLEAN, true).defaultValue(false)) .column("role", DataType.BINARY, 26) @@ -127,6 +131,7 @@ public class UserProvider implements Provider { resultSet.getString("lastname"), resultSet.getString("email"), resultSet.getString("phoneNumber"), + CountryRepository.fromCode(resultSet.getString("country")), resultSet.getBoolean("blocked"), role ); @@ -138,6 +143,7 @@ public class UserProvider implements Provider { resultSet.getString("lastname"), resultSet.getString("email"), resultSet.getString("phoneNumber"), + CountryRepository.fromCode(resultSet.getString("country")), resultSet.getBoolean("blocked") ); userReference.set(user); @@ -197,6 +203,7 @@ public class UserProvider implements Provider { resultSet.getString("lastname"), resultSet.getString("email"), resultSet.getString("phoneNumber"), + CountryRepository.fromCode(resultSet.getString("country")), resultSet.getBoolean("blocked"), role ); @@ -208,6 +215,7 @@ public class UserProvider implements Provider { resultSet.getString("lastname"), resultSet.getString("email"), resultSet.getString("phoneNumber"), + CountryRepository.fromCode(resultSet.getString("country")), resultSet.getBoolean("blocked") ); userReference.set(user); @@ -298,7 +306,7 @@ public class UserProvider implements Provider { return new LoginActionResult(user, accessToken, refreshToken); } - public LoginActionResult register(String firstName, String lastName, String email, String phoneNumber, String password) { + public LoginActionResult register(String firstName, String lastName, String email, String phoneNumber, String password, Locale country) { Role role = backend.roleProvider().defaultRole(); var userId = ULID.randomUlid(); @@ -313,10 +321,15 @@ public class UserProvider implements Provider { .entry("email", email) .entry("phoneNumber", phoneNumber) .entry("password", hashedPassword) - .entry("role", role.id().toString()); + .entry("country", country.getCountry()); + + if (role != null) + insert.entry("role", role.id().toString()); + query.executeQuery(insert); - User user = new User(userId, firstName, lastName, email, phoneNumber, false, role); + User user = new User(userId, firstName, lastName, email, phoneNumber, country, false, role); + userCache.put(user.id(), user); long expiresAt = System.currentTimeMillis() + JWT_EXPIRATION_TIME; @@ -336,17 +349,36 @@ public class UserProvider implements Provider { return new LoginActionResult(user, accessToken, refreshToken); } + public boolean userExists(String email) { + Query query = new Query(backend.databaseAdapter()); + AtomicBoolean exists = new AtomicBoolean(false); + var select = Query.select() + .table("users") + .condition("email", email) + .columnKey("id") + .resultActionAfterQuery(srs -> { + srs.next(_ -> exists.set(true), () -> exists.set(false), e -> logger.error("Error checking if user exists", e)); + }); + query.executeQuery(select); + return exists.get(); + } + private String createAccessToken(User user, long expiresAt) { try { - return new JWT.Builder() + JWT.Builder builder = new JWT.Builder() .audience("flightscore-api") .issuer("flightscore-api") - .subject(user.email()) - .claim("id", user.id().toString()) - .claim("role", user.role().id().toString()) + .subject(user.id().toString()) .expiresAt(expiresAt / 1000) - .issuedAt(System.currentTimeMillis() / 1000) - .sign(JWT_SECRET); + .issuedAt(System.currentTimeMillis() / 1000); + + if (user.role() != null) { + JSONArray permissions = new JSONArray(); + user.role().permissions().forEach(permission -> permissions.put(permission.name())); + builder.claim("permission", permissions.toString()); + builder.claim("role", user.role().id().toString()); + } + return builder.sign(JWT_SECRET); } catch (JwtException e) { logger.error("Error creating JWT", e); } diff --git a/yaak/yaak.ev_rnW4WEytTs.yaml b/yaak/yaak.ev_rnW4WEytTs.yaml new file mode 100644 index 0000000..de2e854 --- /dev/null +++ b/yaak/yaak.ev_rnW4WEytTs.yaml @@ -0,0 +1,38 @@ +type: environment +model: environment +id: ev_rnW4WEytTs +workspaceId: wk_gViMDNsLC9 +createdAt: 2025-07-25T17:21:10.849676300 +updatedAt: 2026-02-15T19:39:14.624560900 +name: Global Variables +public: true +base: true +parentModel: workspace +parentId: null +variables: +- enabled: true + name: BASE_URL + value: http://127.0.0.1:8080 + id: 9FaWWJII9c +- enabled: true + name: email + value: jan.meinl@coph.dev + id: 5g83mX2k0a +- enabled: true + name: firstname + value: Jan + id: CDpsUgXN2G +- enabled: true + name: lastname + value: Meinl + id: WJrnFEGNTM +- enabled: true + name: password + value: TMm#J0r0iAlKliG6gBP73OnaN#ldOI&x + id: sCrraX5tUu +- enabled: true + name: phoneNumber + value: '+4915202989709' + id: U8B5xaQqXb +color: null +sortPriority: 0.0 diff --git a/yaak/yaak.fl_gBnxCkQD7y.yaml b/yaak/yaak.fl_gBnxCkQD7y.yaml new file mode 100644 index 0000000..7098a68 --- /dev/null +++ b/yaak/yaak.fl_gBnxCkQD7y.yaml @@ -0,0 +1,13 @@ +type: folder +model: folder +id: fl_gBnxCkQD7y +createdAt: 2025-07-24T10:00:13.759993700 +updatedAt: 2025-07-24T10:00:13.759994200 +workspaceId: wk_gViMDNsLC9 +folderId: null +authentication: {} +authenticationType: null +description: '' +headers: [] +name: Auth +sortPriority: -1753351200000.0 diff --git a/yaak/yaak.rq_eRxcYSQsbe.yaml b/yaak/yaak.rq_eRxcYSQsbe.yaml new file mode 100644 index 0000000..5fd8c38 --- /dev/null +++ b/yaak/yaak.rq_eRxcYSQsbe.yaml @@ -0,0 +1,31 @@ +type: http_request +model: http_request +id: rq_eRxcYSQsbe +createdAt: 2026-02-15T19:37:26.962157100 +updatedAt: 2026-02-15T19:39:42.170904100 +workspaceId: wk_gViMDNsLC9 +folderId: fl_gBnxCkQD7y +authentication: {} +authenticationType: null +body: + text: |- + { + "firstName": "${[ firstname ]}", + "lastName": "${[ lastname ]}", + "email": "${[ email ]}", + "phoneNumber": "${[ phoneNumber ]}", + "country": "de", + "password": "${[ password ]}" + } +bodyType: application/json +description: '' +headers: +- enabled: true + name: Content-Type + value: application/json + id: d7yQvygRwt +method: POST +name: Register +sortPriority: -1753351216695.0 +url: ${[ BASE_URL ]}/auth/register +urlParameters: [] diff --git a/yaak/yaak.rq_qMQNa95s3H.yaml b/yaak/yaak.rq_qMQNa95s3H.yaml new file mode 100644 index 0000000..8ae2d51 --- /dev/null +++ b/yaak/yaak.rq_qMQNa95s3H.yaml @@ -0,0 +1,27 @@ +type: http_request +model: http_request +id: rq_qMQNa95s3H +createdAt: 2025-07-24T10:00:16.697661100 +updatedAt: 2025-07-25T17:35:16.939160200 +workspaceId: wk_gViMDNsLC9 +folderId: fl_gBnxCkQD7y +authentication: {} +authenticationType: null +body: + text: |- + { + "email":"${[ email ]}", + "password":"${[ password ]}" + } +bodyType: application/json +description: '' +headers: +- enabled: true + name: Content-Type + value: application/json + id: bMujbbncVX +method: POST +name: Login +sortPriority: -1753351216695.0 +url: ${[ BASE_URL ]}/auth/login +urlParameters: [] diff --git a/yaak/yaak.wk_gViMDNsLC9.yaml b/yaak/yaak.wk_gViMDNsLC9.yaml new file mode 100644 index 0000000..b067ac3 --- /dev/null +++ b/yaak/yaak.wk_gViMDNsLC9.yaml @@ -0,0 +1,14 @@ +type: workspace +model: workspace +id: wk_gViMDNsLC9 +createdAt: 2025-07-24T09:59:20.801215 +updatedAt: 2025-07-24T09:59:22.805146400 +authentication: {} +authenticationType: null +description: '' +headers: [] +name: FlightScore +encryptionKeyChallenge: eUE0azNuQwGJlYhActHWAdNJxx20lcK8NFEhIOxgSadHRtl/TBAjdViFGuBpTqe2IxTYA9GbNSNeofdXpXD7psJqTH/UsWrzK2mqE7MwUbClYLVwi26gDREvzu7TK+aTJiA= +settingValidateCertificates: true +settingFollowRedirects: true +settingRequestTimeout: 0