diff --git a/.gitignore b/.gitignore
index 01fc4f3..4cee034 100644
--- a/.gitignore
+++ b/.gitignore
@@ -3,3 +3,4 @@
/.idea/
/.micronaut/
/build/
+AuthGenHash/bin
diff --git a/AuthGenHash/.sdkmanrc b/AuthGenHash/.sdkmanrc
new file mode 100644
index 0000000..fcebd7d
--- /dev/null
+++ b/AuthGenHash/.sdkmanrc
@@ -0,0 +1,3 @@
+# Enable auto-env through the sdkman_auto_env config
+# Add key=value pairs of SDKs to use below
+java=21.0.4-tem
diff --git a/AuthGenHash/build.gradle b/AuthGenHash/build.gradle
index 502357c..b4033b5 100644
--- a/AuthGenHash/build.gradle
+++ b/AuthGenHash/build.gradle
@@ -1,6 +1,8 @@
plugins {
- id("com.github.johnrengelman.shadow") version "8.1.1"
- id("io.micronaut.application") version "4.2.1"
+ id("com.gradleup.shadow") version "${shadowVersion}"
+ id("io.micronaut.application") version "${micronautPluginVersion}"
+ id("io.micronaut.test-resources") version "${micronautPluginVersion}"
+ id("io.micronaut.aot") version "${micronautPluginVersion}"
}
version = "0.1"
@@ -27,8 +29,8 @@ application {
mainClass.set("io.unityfoundation.auth.AuthGenHashCommand")
}
java {
- sourceCompatibility = JavaVersion.toVersion("17")
- targetCompatibility = JavaVersion.toVersion("17")
+ sourceCompatibility = JavaVersion.toVersion("21")
+ targetCompatibility = JavaVersion.toVersion("21")
}
diff --git a/AuthGenHash/gradle.properties b/AuthGenHash/gradle.properties
index 56a2e88..2cfaeda 100644
--- a/AuthGenHash/gradle.properties
+++ b/AuthGenHash/gradle.properties
@@ -1 +1,6 @@
-micronautVersion=4.2.4
+# Java
+javaVersion=21
+# Micronaut
+micronautVersion=4.8.2
+micronautPluginVersion=4.5.3
+shadowVersion=8.3.6
\ No newline at end of file
diff --git a/AuthGenHash/gradle/wrapper/gradle-wrapper.jar b/AuthGenHash/gradle/wrapper/gradle-wrapper.jar
index 7f93135..9bbc975 100644
Binary files a/AuthGenHash/gradle/wrapper/gradle-wrapper.jar and b/AuthGenHash/gradle/wrapper/gradle-wrapper.jar differ
diff --git a/AuthGenHash/gradle/wrapper/gradle-wrapper.properties b/AuthGenHash/gradle/wrapper/gradle-wrapper.properties
index 3fa8f86..37f853b 100644
--- a/AuthGenHash/gradle/wrapper/gradle-wrapper.properties
+++ b/AuthGenHash/gradle/wrapper/gradle-wrapper.properties
@@ -1,6 +1,6 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
-distributionUrl=https\://services.gradle.org/distributions/gradle-8.4-bin.zip
+distributionUrl=https\://services.gradle.org/distributions/gradle-8.13-bin.zip
networkTimeout=10000
validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME
diff --git a/AuthGenHash/gradlew b/AuthGenHash/gradlew
index 1aa94a4..faf9300 100755
--- a/AuthGenHash/gradlew
+++ b/AuthGenHash/gradlew
@@ -15,6 +15,8 @@
# See the License for the specific language governing permissions and
# limitations under the License.
#
+# SPDX-License-Identifier: Apache-2.0
+#
##############################################################################
#
@@ -55,7 +57,7 @@
# Darwin, MinGW, and NonStop.
#
# (3) This script is generated from the Groovy template
-# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
+# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
# within the Gradle project.
#
# You can find Gradle at https://github.com/gradle/gradle/.
@@ -84,7 +86,7 @@ done
# shellcheck disable=SC2034
APP_BASE_NAME=${0##*/}
# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
-APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit
+APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD=maximum
@@ -203,7 +205,7 @@ fi
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
# Collect all arguments for the java command:
-# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,
+# * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,
# and any embedded shellness will be escaped.
# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be
# treated as '${Hostname}' itself on the command line.
diff --git a/AuthGenHash/gradlew.bat b/AuthGenHash/gradlew.bat
index 6689b85..9b42019 100644
--- a/AuthGenHash/gradlew.bat
+++ b/AuthGenHash/gradlew.bat
@@ -13,6 +13,8 @@
@rem See the License for the specific language governing permissions and
@rem limitations under the License.
@rem
+@rem SPDX-License-Identifier: Apache-2.0
+@rem
@if "%DEBUG%"=="" @echo off
@rem ##########################################################################
@@ -43,11 +45,11 @@ set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
if %ERRORLEVEL% equ 0 goto execute
-echo.
-echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
-echo.
-echo Please set the JAVA_HOME variable in your environment to match the
-echo location of your Java installation.
+echo. 1>&2
+echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2
+echo. 1>&2
+echo Please set the JAVA_HOME variable in your environment to match the 1>&2
+echo location of your Java installation. 1>&2
goto fail
@@ -57,11 +59,11 @@ set JAVA_EXE=%JAVA_HOME%/bin/java.exe
if exist "%JAVA_EXE%" goto execute
-echo.
-echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
-echo.
-echo Please set the JAVA_HOME variable in your environment to match the
-echo location of your Java installation.
+echo. 1>&2
+echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2
+echo. 1>&2
+echo Please set the JAVA_HOME variable in your environment to match the 1>&2
+echo location of your Java installation. 1>&2
goto fail
diff --git a/FrontendDockerfile b/FrontendDockerfile
index 345f873..a12c03b 100644
--- a/FrontendDockerfile
+++ b/FrontendDockerfile
@@ -1,5 +1,5 @@
# Build the frontend and serve with nginx
-FROM node:18
+FROM node:24 AS builder
COPY frontend frontend
WORKDIR frontend
@@ -7,5 +7,5 @@ RUN npm install && npm run build
FROM nginx:1.24.0-alpine
-COPY --from=0 /frontend/build /usr/share/nginx/html
+COPY --from=builder /frontend/build /usr/share/nginx/html
COPY nginx-default.conf /etc/nginx/conf.d/default.conf
diff --git a/Makefile b/Makefile
new file mode 100644
index 0000000..c494b10
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,11 @@
+compose_api:
+ docker network create unity-network > /dev/null 2>&1 || true
+ docker compose -f ./docker-compose.local.yml up unity-auth-db unity-auth-api
+
+compose_ui:
+ docker network create unity-network > /dev/null 2>&1 || true
+ docker compose -f ./docker-compose.local.yml up unity-auth-db unity-auth-ui
+
+compose_all:
+ docker network create unity-network > /dev/null 2>&1 || true
+ docker compose -f ./docker-compose.local.yml up
\ No newline at end of file
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..0831d78
--- /dev/null
+++ b/README.md
@@ -0,0 +1,191 @@
+# UnityAuth
+
+UnityAuth is a comprehensive authentication and authorization service built with modern microservices architecture. It provides JWT-based authentication, user management, and a web-based administration interface.
+
+## Local Development QuickStart
+
+To launch and be a pure consumer of the auth service, you can use the docker compose from the root:
+
+If running on mac/linux
+
+```sh
+docker network create unity-network > /dev/null 2>&1 || true
+```
+
+Or simply using compose.
+
+```sh
+docker network create unity-network > /dev/null 2>&1 || true
+docker-compose -f docker-compose.local.yml up
+```
+
+This will start:
+
+- **UnityAuth API** on port http://localhost:8081
+- **UnityAuth UI** on port http://localhost:3001
+- **MySQL Database** for data persistence
+
+### Hosts File Updates
+
+For consistent internal-external service name resolution, add these to your `/etc/hosts` file
+
+```txt
+127.0.0.1 unity-auth-api
+127.0.0.1 unity-auth-ui
+127.0.0.1 libre311-api
+127.0.0.1 libre311-ui
+```
+
+You can log in with these accounts.
+**Password for all the following accounts is 'test'**
+
+- **Unity Administrator** `unity_admin@example.co`
+- **Tenant Administrator** `tenant_admin@example.co`
+- **Libre311 Administrator** `libre311_admin@example.co`
+- **Libre311 Request Manager** `libre311_request_manager@example.com`
+- **Libre311 Jurisdiction Administrator** `libre311_jurisdiction_admin@example.com`
+- **Libre311 Jurisdiction Request Manager** `libre311_jurisdiction_request_manager@example.com`
+- **Stl sub-tenant admin** `stl_subtenant_admin@example.com`
+
+## Project Structure
+
+This repository contains three main subprojects:
+
+### 1. UnityAuth (Main Service)
+
+**Location:** `/UnityAuth/`
+**Technology:** Java 21 + Micronaut Framework
+
+The core authentication service that provides:
+
+- JWT token generation and validation
+- User authentication and authorization
+- RESTful API endpoints for authentication operations
+- Database integration with MySQL
+- JWK (JSON Web Key) management for token signing
+- Flyway database migrations
+
+**Key Features:**
+
+- Micronaut-based microservice architecture
+- JWT security with configurable key rotation
+- BCrypt password hashing
+- Database connection pooling with HikariCP
+- Reactive programming support with Reactor
+
+### 2. AuthGenHash (Utility Tool)
+
+**Location:** `/AuthGenHash/`
+**Technology:** Java 17 + Micronaut + PicoCLI
+
+A command-line utility for generating secure password hashes compatible with the UnityAuth service.
+
+**Purpose:**
+
+- Generate BCrypt password hashes for administrative users
+- Secure password handling (interactive mode prevents history logging)
+- Standalone tool for initial system setup and user management
+
+**Usage:**
+
+```bash
+cd AuthGenHash
+./gradlew shadowJar
+java -jar build/libs/AuthGenHash-0.1-all.jar -p
+```
+
+### 3. Frontend (Web Administration Interface)
+
+**Location:** `/frontend/`
+**Technology:** SvelteKit + TypeScript + Tailwind CSS
+
+A modern web application providing administrative interface for the UnityAuth service.
+
+**Features:**
+
+- User authentication and session management
+- User administration and management
+- Tenant management capabilities
+- Settings configuration
+- Responsive design with Tailwind CSS
+- TypeScript for type safety
+- Comprehensive testing with Playwright and Vitest
+
+**Key Technologies:**
+
+- SvelteKit for the web framework
+- TypeScript for type safety
+- Tailwind CSS for styling
+- Playwright for end-to-end testing
+- Vitest for unit testing
+- ESLint and Prettier for code quality
+
+## Architecture Overview
+
+The system follows a microservices architecture:
+
+1. **Database Layer:** MySQL database for persistent storage
+2. **API Layer:** UnityAuth service provides REST APIs
+3. **Frontend Layer:** SvelteKit web application
+4. **Utility Layer:** AuthGenHash for administrative tasks
+
+## Client Integration
+
+To integrate with the UnityAuth service, add this configuration to your client application's `application.yaml`:
+
+```yaml
+security:
+ enabled: true
+ token:
+ enabled: true
+ jwt:
+ enabled: true
+ signatures:
+ jwks:
+ unity:
+ url: ${AUTH_JWKS:`http://localhost:8081/keys`}
+```
+
+## Security Configuration
+
+The service uses JSON Web Keys (JWK) for token signing. To generate primary and secondary keys:
+
+1. Visit
+2. Generate JSON Web Keys
+3. Set environment variables:
+ - `JWK_PRIMARY`: Primary signing key
+ - `JWK_SECONDARY`: Secondary signing key for rotation
+
+## Development Environment
+
+### Prerequisites
+
+- Java 17 or higher
+- Node.js 18 or higher
+- Docker and Docker Compose
+- MySQL 8.0 (if running locally)
+
+### Individual Service Development
+
+#### UnityAuth Service
+
+```bash
+cd UnityAuth
+./gradlew run
+```
+
+#### Frontend Development
+
+```bash
+cd frontend
+npm install
+npm run dev
+```
+
+#### AuthGenHash Utility
+
+```bash
+cd AuthGenHash
+./gradlew shadowJar
+java -jar build/libs/AuthGenHash-0.1-all.jar -p
+```
diff --git a/UnityAuth/.dockerignore b/UnityAuth/.dockerignore
new file mode 100644
index 0000000..24bf6e6
--- /dev/null
+++ b/UnityAuth/.dockerignore
@@ -0,0 +1,10 @@
+gradle/caches
+
+# Exclude gradle build caches but not wrapper
+.gradle/
+.DS_Store
+
+build/
+
+docker-compose.*
+DockerfileLocal
\ No newline at end of file
diff --git a/UnityAuth/.sdkmanrc b/UnityAuth/.sdkmanrc
new file mode 100644
index 0000000..fcebd7d
--- /dev/null
+++ b/UnityAuth/.sdkmanrc
@@ -0,0 +1,3 @@
+# Enable auto-env through the sdkman_auto_env config
+# Add key=value pairs of SDKs to use below
+java=21.0.4-tem
diff --git a/UnityAuth/DockerfileLocal b/UnityAuth/DockerfileLocal
new file mode 100644
index 0000000..e6526bb
--- /dev/null
+++ b/UnityAuth/DockerfileLocal
@@ -0,0 +1,34 @@
+# Stage 1: Build with Gradle
+FROM gradle:jdk21-jammy AS builder
+WORKDIR /workspace
+
+# 1. Copy Gradle wrapper first (rarely changes)
+COPY gradle/ gradle/
+COPY gradlew gradlew.bat ./
+
+# 2. Copy Gradle config files
+COPY gradle.properties settings.gradle ./
+
+# 3. Download Gradle
+RUN ./gradlew --version --no-daemon
+
+COPY --chown=gradle:gradle . .
+
+# Run the builder w/ the secret
+RUN ./gradlew buildLayers --no-daemon
+
+# Stage 2: Create the final image
+FROM eclipse-temurin:21-jre-jammy
+
+WORKDIR /home/app
+
+COPY --from=builder /workspace/build/docker/main/layers/libs /home/app/libs
+COPY --from=builder /workspace/build/docker/main/layers/app /home/app/
+COPY --from=builder /workspace/build/docker/main/layers/resources /home/app/resources
+
+RUN useradd -u 8877 unity
+# Change to non-root privilege
+USER unity
+
+EXPOSE 8080
+ENTRYPOINT ["java", "-jar", "/home/app/application.jar"]
diff --git a/UnityAuth/README.md b/UnityAuth/README.md
index abe22ea..12ed3a8 100644
--- a/UnityAuth/README.md
+++ b/UnityAuth/README.md
@@ -13,11 +13,11 @@ Insert this code to the client application.yaml file
signatures:
jwks:
unity:
- url: ${AUTH_JWKS:`http://localhost:8080/keys`}
+ url: ${AUTH_JWKS:`http://localhost:8081/keys`}
```
AUTH_JWKS points to this service:
-## How to create primary and seconday key
+## How to create primary and secondary key
Go to the https://mkjwk.org/. Create the JSON Web Keys and define JWK_PRIMARY and JWK_SECONDARY environment variables with generated JSON Web Key (JWK).
diff --git a/UnityAuth/bin/main/application-local.yml b/UnityAuth/bin/main/application-local.yml
new file mode 100644
index 0000000..14cfca2
--- /dev/null
+++ b/UnityAuth/bin/main/application-local.yml
@@ -0,0 +1,37 @@
+micronaut:
+ application:
+ name: unity-iam
+ server:
+ cors:
+ enabled: true
+ configurations:
+ web:
+ allowed-origins-regex: '^http:\/\/(.*?)(?:localhost|127\.0\.0\.1)(?::\d+)?$'
+ allowedOrigins:
+ - http://localhost:3000
+ - localhost:3000
+ - http://127.0.0.1:3000
+ - http://localhost:3001
+ - localhost:3001
+ - http://127.0.0.1:3001
+ localhost-pass-through: true
+ port: 8081
+ security:
+ authentication: bearer
+datasources:
+ default:
+ url: jdbc:mysql://localhost:13306/test?allowPublicKeyRetrieval=true&useSSL=false
+ driver-class-name: com.mysql.cj.jdbc.Driver
+ username: root
+ password: test
+ db-type: mysql
+flyway:
+ datasources:
+ default:
+ locations:
+ - classpath:db/migration
+ - classpath:local
+
+jwk:
+ primary: '{"p":"_OZyH1Mk3wR0oXw1C31t4kWOcaHFB6Njro1cYx52REnPiznn_JTtwvlAMpvV6LVCIZPgKMzdIEMY1gYs1LsO-5IFqWwegXmYJ0iKXbRrZshfWBCzRLK3QK5fER1le1XUBDhtDk7KIW_Xg-SZF4pf_LUEVKMnyUpspGI5F77jlJ8","kty":"RSA","q":"s9wvl7z8vkHQvo9xOUp-z0a2Z7LFBDil2uIjPh1FQzs34gFXH8dQPRox83TuN5d4KzdLPqQNQAfMXU9_KmxihNb_qDQahYugeELmcem04munxXqBdyZqWhWCy5YmujYqn44irwvoTbw6_RkMqjCmINPTPadptlPivsZ6RhKn8zk","d":"ok3wmhOy8NZEHAotnFiH6ecFD6xf_9x33_fMRkqa3_KE8NZM7vmvNgElox2UvcP_2K5E7jOdL2XQdJCTIW3Qlj66yE2a84SYlbvxIc4hDrIog0XNt4FhavvshxxUIfDQo6Q8qXDR5v7nwt6SCopYC3t3KVRdJh08GzKoVxysd7afJjxXxx178gY29uMRqnwxFN1OGnWaiBr-xGKb1frJ6jOI1zvuuCaljZ4aZjc9vOR4y9ZmobgrzkMFnpDAmQZ7MWcVMyodRMOA2dEOckywPhg-dIVNiVIqzJqe5Yg1ilNookjwtqj2TpNU7Z9gPqzYB73PmQ2p5LMDheAPxcOmEQ","e":"AQAB","use":"sig","kid":"e3be37177a7c42bcbadd7cc63715f216","qi":"r--nAtaYPAgJq_8R1-kynpd53E17n-loDUgtVWBCx_RmdORX4Auilv1S83dD1mbcnYCbV_LmxiEjOiz-4gS_E0qVGqakAqQrO1hVUvJa_Y2uftDgwFmuJNGbpRU-K4Td_uUzdm48za8yJCgOdYsWp6PNMCcmQgiInzkR3XYV83I","dp":"oQUcvmMSw8gzdin-IB2xW_MLecAVEgLu0dGBdD6N8HbKZQvub_xm0dAfFtnvvWXDAFwFyhR96i-uXX67Bos_Q9-6KSAE4E0KGmDucDESfPOw-QJREbl0QgOD1gLQfVGtVy6SCR0TR2zNXFWtP7bD3MNoSXdEOr5fI97CGSNaBWM","alg":"RS256","dq":"DM-WJDy10-dkMu6MpgQEXEcxHtnA5rgSODD7SaVUFaHWLSbjScQslu2SuUCO5y7GxG0_0spklzb2-356FE98BPI7a4Oqj_COEYLSXzLCS45XeN1s80utL5Vwp4eeYo0RJCQ_nDBA76iEmxp5qHWmn5f25-FQykfXUrdYZj1V8SE","n":"sa6m2i-iNvj6ZSTdSHZaBrnv6DId4AqAXhOyl0yA5fNWYe6r51h24SXqk7DsGYHHh74ii74tP1lTpmy6RD67tCK-tbN-d6yc4Z6FfM8R83v2QZUfaAixgHGtw0n2toqsiHf6EloDV-B8q4GYyKDD6cLecoaIuTmMBTY3kts59U2t9W10YoLGsmFqLSz8qNF5HkahzB6_--2DiBfVGUKAXHC-SICGZCi-8efOetv6pt9vFiWEgwU_DgjRNYzLFt1SEmbGFUU4kbjQ7tNTMkHfzfwcT6qLt4kVKy2FNYsEMk24keWtCvW_RyO_fisZc0W9smX7WtYjEXhcAjDeqHgEZw"}'
+ secondary: '{"p":"4qJ9RNlu6SuDT_MLArfzimvKEwmet_j12Z9EQeb5nMjZIOHTcWw__duebUytfWwxsRHhtSVXeMt-EryQAOulm2p1bfiVuparq93z9P5cPnb0oArFaw3eFNFEmX5U-lY8PzUTTsFxO4aVQYAKXD6DP7p5uPzuwpHFuNc71nNIXZE","kty":"RSA","q":"v4OhkWMbS_nq77HFanwZAT_obfJuQfOFOQBORL4ATAHGUXm2y4YqLNExZs7Wj1MA_6ya6Y00s2JBM7fWq_fPe4d9xo5aGrPdcp0G8W21kkfh9vuVPlHVQTgSP7FQ9qahvXxNwK_11yNr3p1HBmScJ5mHlMBpIJsFcvHA-uXe0Ps","d":"EunrjnQ1-jJPSCrt2L94PUpDrakup8a4pXys52YSkJY-W6XidM0roOS6kr06P3G6VQgc6AL_BkvTQ_XS0oXHbXVprDQ5Syam5p9oxHBhhW_vSqIMgUOfm28uyB3Mtw9rBxdUxW3yElHioaR8a-exYhhyVXb1QEhxL_rcnthmhAkM2NcHi2UnxGKFTsC0abQ2MuQc1OAuW5veDiIF2hfdC41qE0_d8vB6FDWbblgUpbwB6uSZaViPs15Buq2oX9dCCw54-PgzkfehDt7lyqgupktbV1psnVVhL86shzt4QFnhd3k7VpFbjCNFtiJTrufV-XBWT0pl2w3VR9wrHJ1bYQ","e":"AQAB","use":"sig","kid":"0794e938379540dc8eaa559508524a79","qi":"jy-TNyXVy_44_n4KGAwIbZO2C4r6uNWuEdehBfQKkPhiP90myG1KZVfOoKNOK9bCv2mvZJcBz4c1ArElgpuSCV4-KFac1ZzQo_ic5aoIej8Qa80y2ogc-_Yv6_ZLHC1S76M-lm4jayk2-rvuBpy2pUvHbW6Srhs_szwz7ZfSkLg","dp":"ApqdV9ortRAj7Ro8ySY17SQ56SgWI8T_hiWXUi6GNa_1FrShik8VGSSZ2GWmJKfGlmM_NaadL60e4LY77VbHy1ZYzQ-rIL60cEAXmnwFsU4Kl4AoLoe1QoX5BM53yXyOKqfAdgow898i_eKru82YEnZhCagWUjP8kpgefuNKNJE","alg":"RS256","dq":"bFF78WoXh0pMCdQHL2oPDnjh8kWa_OxKHmpA2nqIWnTqgSyRKd2xPvX2tgooqpmsx-8NEymNdCQPcrv4y_z2OgzxI3tiFRZEGs4bnjOJ7bmAYZv71mqcbi3TjHiyrT6j3jNPGrurFUpweVGFWWVQOMmKOKT3ELz9QPzhREb9Vj8","n":"qYvDpV8DRU5hx9eXpE4Ms8nUXicEwrxUUz5gb5gkXpIeY82mqfQKKCP6PSFnkKYtRFTOUSm9cgGGfOd7O4NFsIsxLwXCj34X7ORr19eXKBLvG3bZJLxqRlbYuQshDMkQOui1sDBxvYnj5p4iHne6l2btH5grHOCShUWG-bKps5Y8bKNHod1pIOOBabVCmn3sUVUkZw8nyXkQqZbv-c8x6z0TEfhNOPOIt2AmmlNgrE_8g7-dnCvqfJnhv0c7qkOJzsb7OMmvVwsQNiM59D6uaWZr-vdANo6NggiZmCKUS3tpUvdXW7ec9WMPJWhrVEkRcbWXQnZ_C7pXFrz7rLeNKw"}'
diff --git a/UnityAuth/bin/main/application-test.yml b/UnityAuth/bin/main/application-test.yml
new file mode 100644
index 0000000..9a6c8d8
--- /dev/null
+++ b/UnityAuth/bin/main/application-test.yml
@@ -0,0 +1,28 @@
+micronaut:
+ application:
+ name: unity-iam
+ server:
+ cors:
+ enabled: true
+ configurations:
+ web:
+ allowed-origins-regex: '^http:\/\/(.*?)localhost:3000$'
+ security:
+ authentication: bearer
+ enabled: true
+ token:
+ enabled: true
+ jwt:
+ enabled: true
+ signatures:
+ jwks:
+ unity:
+ url: http://localhost:8080/keys
+datasources:
+ default:
+ driver-class-name: com.mysql.cj.jdbc.Driver
+ db-type: mysql
+flyway:
+ datasources:
+ default:
+ enabled: true
diff --git a/UnityAuth/bin/main/application.yml b/UnityAuth/bin/main/application.yml
new file mode 100644
index 0000000..1246474
--- /dev/null
+++ b/UnityAuth/bin/main/application.yml
@@ -0,0 +1,32 @@
+micronaut:
+ application:
+ name: unity-iam
+ security:
+ endpoints:
+ logout:
+ path: /api/logout
+ get-allowed: true
+ login:
+ path: /api/login
+ introspection:
+ path: /api/token_info
+ intercept-url-map:
+ - pattern: /api/login
+ http-method: POST
+ access:
+ - isAnonymous()
+ authentication: bearer
+ server:
+ cors:
+ enabled: true
+ configurations:
+ web:
+ allowed-origins-regex: ${LIBRE311_UI_BASE_URL}
+datasources:
+ default:
+ driver-class-name: com.mysql.cj.jdbc.Driver
+ db-type: mysql
+flyway:
+ datasources:
+ default:
+ enabled: true
diff --git a/UnityAuth/bin/main/db/migration/V1__initial_schema.sql b/UnityAuth/bin/main/db/migration/V1__initial_schema.sql
new file mode 100644
index 0000000..eb7c0a5
--- /dev/null
+++ b/UnityAuth/bin/main/db/migration/V1__initial_schema.sql
@@ -0,0 +1,83 @@
+CREATE TABLE tenant
+(
+ id bigint AUTO_INCREMENT PRIMARY KEY,
+ name varchar(255) NOT NULL,
+ description text,
+ status varchar(255) NOT NULL,
+ UNIQUE (name)
+);
+
+CREATE TABLE service
+(
+ id bigint AUTO_INCREMENT PRIMARY KEY,
+ name varchar(255) NOT NULL,
+ description text,
+ status varchar(255),
+ UNIQUE (name)
+);
+
+CREATE TABLE tenant_service
+(
+ tenant_id bigint NOT NULL,
+ service_id bigint NOT NULL,
+ status varchar(255),
+ PRIMARY KEY (tenant_id, service_id)
+);
+ALTER TABLE tenant_service
+ ADD CONSTRAINT tenant_service_tenant_FK FOREIGN KEY (tenant_id) REFERENCES tenant (id);
+ALTER TABLE tenant_service
+ ADD CONSTRAINT tenant_service_service_FK FOREIGN KEY (service_id) REFERENCES service (id);
+
+CREATE TABLE permission
+(
+ id bigint AUTO_INCREMENT PRIMARY KEY,
+ name varchar(255) NOT NULL,
+ description text,
+ scope varchar(255),
+ UNIQUE (name)
+);
+
+CREATE TABLE role
+(
+ id bigint AUTO_INCREMENT PRIMARY KEY,
+ name varchar(255) NOT NULL,
+ description text,
+ UNIQUE (name)
+);
+
+CREATE TABLE role_permission
+(
+ role_id bigint NOT NULL,
+ permission_id bigint NOT NULL,
+ PRIMARY KEY (role_id, permission_id)
+);
+ALTER TABLE role_permission
+ ADD CONSTRAINT role_permission_permission_FK FOREIGN KEY (permission_id) REFERENCES permission (id);
+ALTER TABLE role_permission
+ ADD CONSTRAINT role_permission_role_FK FOREIGN KEY (role_id) REFERENCES role (id);
+
+
+CREATE TABLE user
+(
+ id bigint AUTO_INCREMENT PRIMARY KEY,
+ email varchar(255) NOT NULL,
+ password varchar(255),
+ status varchar(255),
+ UNIQUE KEY unique_email (email)
+);
+
+CREATE TABLE user_role
+(
+ tenant_id bigint NOT NULL,
+ user_id bigint NOT NULL,
+ role_id bigint NOT NULL,
+ PRIMARY KEY (tenant_id, user_id, role_id)
+);
+ALTER TABLE user_role
+ ADD CONSTRAINT user_role_tenant_FK FOREIGN KEY (tenant_id) REFERENCES tenant (id);
+ALTER TABLE user_role
+ ADD CONSTRAINT user_role_user_FK FOREIGN KEY (user_id) REFERENCES user (id);
+ALTER TABLE user_role
+ ADD CONSTRAINT user_role_role_FK FOREIGN KEY (role_id) REFERENCES role (id);
+
+
diff --git a/UnityAuth/bin/main/db/migration/V2__add_user_first_and_last_names.sql b/UnityAuth/bin/main/db/migration/V2__add_user_first_and_last_names.sql
new file mode 100644
index 0000000..3210aca
--- /dev/null
+++ b/UnityAuth/bin/main/db/migration/V2__add_user_first_and_last_names.sql
@@ -0,0 +1,2 @@
+ALTER TABLE user ADD COLUMN first_name varchar(255);
+ALTER TABLE user ADD COLUMN last_name varchar(255);
diff --git a/UnityAuth/bin/main/local/afterMigrate.sql b/UnityAuth/bin/main/local/afterMigrate.sql
new file mode 100644
index 0000000..eb004b2
--- /dev/null
+++ b/UnityAuth/bin/main/local/afterMigrate.sql
@@ -0,0 +1,147 @@
+DELETE FROM user_role;
+DELETE FROM role_permission;
+DELETE FROM tenant_service;
+DELETE FROM user;
+DELETE FROM tenant;
+DELETE FROM service;
+DELETE FROM permission;
+DELETE FROM role;
+
+-- Create a tenant
+INSERT IGNORE INTO tenant (id, name, description, status) VALUES(1, 'stl', 'St. Louis Metro Area', 'ENABLED');
+-- Create Libre311 Service
+INSERT IGNORE INTO service (id, name, description, status) VALUES(1, 'Libre311', 'Libre311', 'ENABLED');
+
+-- Add Libre311 Service to stl tenant
+INSERT IGNORE INTO tenant_service (tenant_id, service_id, status) VALUES(1, 1, 'ENABLED');
+
+
+INSERT IGNORE INTO permission (id, name, description, scope)
+VALUES (1, 'AUTH_SERVICE_EDIT-SYSTEM', NULL, 'SYSTEM'),
+ (2, 'AUTH_SERVICE_VIEW-SYSTEM', NULL, 'SYSTEM'),
+ (3, 'AUTH_SERVICE_EDIT-TENANT', NULL, 'TENANT'),
+ (4, 'AUTH_SERVICE_VIEW-TENANT', NULL, 'TENANT'),
+ (5, 'LIBRE311_ADMIN_EDIT-SYSTEM', NULL, 'SYSTEM'),
+ (6, 'LIBRE311_ADMIN_VIEW-SYSTEM', NULL, 'SYSTEM'),
+ (7, 'LIBRE311_ADMIN_EDIT-TENANT', NULL, 'TENANT'),
+ (8, 'LIBRE311_ADMIN_VIEW-TENANT', NULL, 'TENANT'),
+ (9, 'LIBRE311_ADMIN_EDIT-SUBTENANT', NULL, 'SUBTENANT'),
+ (10, 'LIBRE311_ADMIN_VIEW-SUBTENANT', NULL, 'SUBTENANT'),
+ (11, 'LIBRE311_REQUEST_EDIT-SYSTEM', NULL, 'SYSTEM'),
+ (12, 'LIBRE311_REQUEST_VIEW-SYSTEM', NULL, 'SYSTEM'),
+ (13, 'LIBRE311_REQUEST_EDIT-TENANT', NULL, 'TENANT'),
+ (14, 'LIBRE311_REQUEST_VIEW-TENANT', NULL, 'TENANT'),
+ (15, 'LIBRE311_REQUEST_EDIT-SUBTENANT', NULL, 'SUBTENANT'),
+ (16, 'LIBRE311_REQUEST_VIEW-SUBTENANT', NULL, 'SUBTENANT');
+
+
+INSERT IGNORE INTO role (id, name, description)
+VALUES (1, 'Unity Administrator', 'An administrator of the Unity Platform. A user with this role can perform any operation.'),
+ (2, 'Tenant Administrator', 'An administrator for a tenant. A user with this role can perform any operation for the tenant.'),
+ (3, 'Libre311 Administrator', 'An administrator for Libre311. A user with this role can perform any operation in Libre311 on behalf of their tenant.'),
+ (4, 'Libre311 Request Manager', 'A service request manager for Libre311. A user with this role can update and manage service requests.'),
+ (5, 'Libre311 Jurisdiction Administrator', 'An administrator for Libre311 that is scoped to specific jurisdictions. Additional access must be granted in Libre311 to enable access for specific jurisdictions.'),
+ (6, 'Libre311 Jurisdiction Request Manager', 'A service request manager for Libre311 that is scoped to specific jurisdictions. A user with this role can update and manage service requests. Additional access must be granted in Libre311 to enable access for specific jurisdictions.');
+
+
+-- Unity Administrator
+INSERT IGNORE INTO role_permission (role_id, permission_id)
+VALUES (1, 1), -- AUTH_SERVICE_EDIT-SYSTEM
+ (1, 2), -- AUTH_SERVICE_VIEW-SYSTEM
+ (1, 5), -- LIBRE311_ADMIN_EDIT-SYSTEM
+ (1, 6), -- LIBRE311_ADMIN_VIEW-SYSTEM
+ (1, 11), -- LIBRE311_REQUEST_EDIT-SYSTEM
+ (1, 12); -- LIBRE311_REQUEST_VIEW-SYSTEM
+
+-- Tenant Administrator
+INSERT IGNORE INTO role_permission (role_id, permission_id)
+VALUES (2, 3), -- AUTH_SERVICE_EDIT-TENANT
+ (2, 4), -- AUTH_SERVICE_VIEW-TENANT
+ (2, 7), -- LIBRE311_ADMIN_EDIT-TENANT
+ (2, 8), -- LIBRE311_ADMIN_VIEW-TENANT
+ (2, 13), -- LIBRE311_REQUEST_EDIT-TENANT
+ (2, 14); -- LIBRE311_REQUEST_VIEW-TENANT
+
+-- Libre311 Administrator
+INSERT IGNORE INTO role_permission (role_id, permission_id)
+VALUES (3, 7), -- LIBRE311_ADMIN_EDIT-TENANT
+ (3, 8), -- LIBRE311_ADMIN_VIEW-TENANT
+ (3, 13), -- LIBRE311_REQUEST_EDIT-TENANT
+ (3, 14); -- LIBRE311_REQUEST_VIEW-TENANT
+
+-- Libre311 Request Manager
+INSERT IGNORE INTO role_permission (role_id, permission_id)
+VALUES (4, 13), -- LIBRE311_REQUEST_EDIT-TENANT
+ (4, 14); -- LIBRE311_REQUEST_VIEW-TENANT
+
+-- Libre311 Jurisdiction Administrator
+INSERT IGNORE INTO role_permission (role_id, permission_id)
+VALUES (5, 9), -- LIBRE311_ADMIN_EDIT-SUBTENANT
+ (5, 10), -- LIBRE311_ADMIN_VIEW-SUBTENANT
+ (5, 15), -- LIBRE311_REQUEST_EDIT-SUBTENANT
+ (5, 16); -- LIBRE311_REQUEST_VIEW-SUBTENANT
+
+-- Libre311 Jurisdiction Request Manager
+INSERT IGNORE INTO role_permission (role_id, permission_id)
+VALUES (6, 15), -- LIBRE311_REQUEST_EDIT-SUBTENANT
+ (6, 16); -- LIBRE311_REQUEST_VIEW-SUBTENANT
+
+
+-- Password for all the following accounts is 'test'
+-- Unity Administrator
+INSERT IGNORE INTO user (id, email, first_name, last_name, password, status) VALUES
+ (1, 'unity_admin@example.com', 'Unity', 'Admin', '$2a$10$YJetsyoS.EzlVlb249w07uBR8uSqgtlqVH9Hl7bsHtvvwdKAhJp82', 'ENABLED');
+
+-- Tenant Administrator
+INSERT IGNORE INTO user (id, email, first_name, last_name, password, status) VALUES
+ (2, 'tenant_admin@example.com', 'Tenant', 'Admin', '$2a$10$YJetsyoS.EzlVlb249w07uBR8uSqgtlqVH9Hl7bsHtvvwdKAhJp82', 'ENABLED');
+
+-- Libre311 Administrator
+INSERT IGNORE INTO user (id, email, first_name, last_name, password, status) VALUES
+ (3, 'libre311_admin@example.com', 'Libre', 'Admin', '$2a$10$YJetsyoS.EzlVlb249w07uBR8uSqgtlqVH9Hl7bsHtvvwdKAhJp82', 'ENABLED');
+
+-- Libre311 Request Manager
+INSERT IGNORE INTO user (id, email, first_name, last_name, password, status) VALUES
+ (4, 'libre311_request_manager@example.com', 'Request', 'Manager', '$2a$10$YJetsyoS.EzlVlb249w07uBR8uSqgtlqVH9Hl7bsHtvvwdKAhJp82', 'ENABLED');
+
+-- Libre311 Jurisdiction Administrator
+INSERT IGNORE INTO user (id, email, first_name, last_name, password, status) VALUES
+ (5, 'libre311_jurisdiction_admin@example.com', 'Jurisdiction', 'Admin', '$2a$10$YJetsyoS.EzlVlb249w07uBR8uSqgtlqVH9Hl7bsHtvvwdKAhJp82', 'ENABLED');
+
+-- Libre311 Jurisdiction Request Manager
+INSERT IGNORE INTO user (id, email, first_name, last_name, password, status) VALUES
+ (6, 'libre311_jurisdiction_request_manager@example.com', 'Jurisdiction', 'Request Manager', '$2a$10$YJetsyoS.EzlVlb249w07uBR8uSqgtlqVH9Hl7bsHtvvwdKAhJp82', 'ENABLED');
+
+-- Stl sub-tenant admin
+INSERT IGNORE INTO user (id, email, first_name, last_name, password, status) VALUES
+ (7, 'stl_subtenant_admin@example.com', 'Subtenant', 'Admin', '$2a$10$YJetsyoS.EzlVlb249w07uBR8uSqgtlqVH9Hl7bsHtvvwdKAhJp82', 'ENABLED');
+
+
+-- Unity Administrator
+INSERT IGNORE INTO user_role (tenant_id, user_id, role_id) VALUES
+ (1, 1, 1);
+
+-- Tenant Administrator
+INSERT IGNORE INTO user_role (tenant_id, user_id, role_id) VALUES
+ (1, 2, 2);
+
+-- Libre311 Administrator
+INSERT IGNORE INTO user_role (tenant_id, user_id, role_id) VALUES
+ (1, 3, 3);
+
+-- Libre311 Request Manager
+INSERT IGNORE INTO user_role (tenant_id, user_id, role_id) VALUES
+ (1, 4, 4);
+
+-- Libre311 Jurisdiction Administrator
+INSERT IGNORE INTO user_role (tenant_id, user_id, role_id) VALUES
+ (1, 5, 5);
+
+-- Libre311 Jurisdiction Request Manager
+INSERT IGNORE INTO user_role (tenant_id, user_id, role_id) VALUES
+ (1, 6, 6);
+
+
+-- Stl sub-tenant admin
+INSERT IGNORE INTO user_role (tenant_id, user_id, role_id) VALUES
+ (1, 7, 5);
\ No newline at end of file
diff --git a/UnityAuth/bin/main/logback.xml b/UnityAuth/bin/main/logback.xml
new file mode 100644
index 0000000..b75176a
--- /dev/null
+++ b/UnityAuth/bin/main/logback.xml
@@ -0,0 +1,20 @@
+
+
+
+
+
+ %cyan(%d{HH:mm:ss.SSS}) %gray([%thread]) %highlight(%-5level) %magenta(%logger{36}) -
+ %msg%n
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/UnityAuth/bin/test/db/migration/afterMigrate.sql b/UnityAuth/bin/test/db/migration/afterMigrate.sql
new file mode 100644
index 0000000..1e13f4d
--- /dev/null
+++ b/UnityAuth/bin/test/db/migration/afterMigrate.sql
@@ -0,0 +1,32 @@
+DELETE FROM user_role;
+DELETE FROM role_permission;
+DELETE FROM tenant_service;
+DELETE FROM user;
+DELETE FROM tenant;
+DELETE FROM service;
+DELETE FROM permission;
+DELETE FROM role;
+INSERT INTO user (id, email, first_name, last_name, password, status) VALUES(1, 'person1@test.io', 'Person', 'One', '$2a$10$YJetsyoS.EzlVlb249w07uBR8uSqgtlqVH9Hl7bsHtvvwdKAhJp82', 'ENABLED');
+INSERT INTO user (id, email, first_name, last_name, password, status) VALUES(2, 'test@test.io', 'Test', 'Test', '$2a$10$YJetsyoS.EzlVlb249w07uBR8uSqgtlqVH9Hl7bsHtvvwdKAhJp82', 'ENABLED');
+INSERT INTO user (id, email, first_name, last_name, password, status) VALUES(3, 'disabled@test.io', 'Disabled', 'User', '$2a$10$YJetsyoS.EzlVlb249w07uBR8uSqgtlqVH9Hl7bsHtvvwdKAhJp82', 'DISABLED');
+INSERT INTO user (id, email, first_name, last_name, password, status) VALUES(4, 'acme-tenant-admin@test.io', 'Acme Tenant', 'Admin', '$2a$10$YJetsyoS.EzlVlb249w07uBR8uSqgtlqVH9Hl7bsHtvvwdKAhJp82', 'ENABLED');
+INSERT INTO tenant (id, name, description, status) VALUES(1, 'SYSTEM', 'SYSTEM', 'ENABLED');
+INSERT INTO tenant (id, name, description, status) VALUES(2, 'acme', 'Acme Corporation', 'ENABLED');
+INSERT INTO service (id, name, description, status) VALUES(1, 'Libre311', 'Libre311', 'ENABLED');
+INSERT INTO service (id, name, description, status) VALUES(2, 'Application2', 'Application2', 'ENABLED');
+INSERT INTO tenant_service (tenant_id, service_id, status) VALUES(2, 1, 'ENABLED');
+INSERT INTO permission (id, name, description, scope) VALUES(1, 'AUTH_SERVICE_EDIT-SYSTEM', 'Description', 'SYSTEM');
+INSERT INTO permission (id, name, description, scope) VALUES(2, 'LIBRE311_REQUEST_EDIT-TENANT', 'Description', 'TENANT');
+INSERT INTO permission (id, name, description, scope) VALUES(3, 'LIBRE311_REQUEST_EDIT-SUBTENANT', 'Description', 'SUBTENANT');
+INSERT INTO permission (id, name, description, scope) VALUES(4, 'AUTH_SERVICE_VIEW-SYSTEM', 'Description', 'SYSTEM');
+INSERT INTO role (id, name, description) VALUES(1, 'Unity Administrator', 'System role');
+INSERT INTO role (id, name, description) VALUES(2, 'Tenant role', 'Tenant role');
+INSERT INTO role (id, name, description) VALUES(3, 'Subtenant role', 'Subtenant role');
+INSERT INTO role_permission (role_id, permission_id) VALUES(1, 1);
+INSERT INTO role_permission (role_id, permission_id) VALUES(2, 2);
+INSERT INTO role_permission (role_id, permission_id) VALUES(3, 3);
+INSERT INTO role_permission (role_id, permission_id) VALUES(1, 4);
+INSERT INTO user_role (tenant_id, user_id, role_id) VALUES(1, 1, 1);
+INSERT INTO user_role (tenant_id, user_id, role_id) VALUES(2, 1, 2);
+INSERT INTO user_role (tenant_id, user_id, role_id) VALUES(2, 1, 3);
+INSERT INTO user_role (tenant_id, user_id, role_id) VALUES(2, 4, 2);
diff --git a/UnityAuth/bin/test/db/migration/application-test.yml b/UnityAuth/bin/test/db/migration/application-test.yml
new file mode 100644
index 0000000..b778288
--- /dev/null
+++ b/UnityAuth/bin/test/db/migration/application-test.yml
@@ -0,0 +1,9 @@
+micronaut:
+ http:
+ client:
+ read-timeout: 1m
+
+
+datasources:
+ default:
+ password: "test"
\ No newline at end of file
diff --git a/UnityAuth/build.gradle b/UnityAuth/build.gradle
index 27522d4..3612865 100644
--- a/UnityAuth/build.gradle
+++ b/UnityAuth/build.gradle
@@ -1,8 +1,8 @@
plugins {
- id("com.github.johnrengelman.shadow") version "8.1.1"
- id("io.micronaut.application") version "4.2.1"
- id("io.micronaut.test-resources") version "4.2.1"
- id("io.micronaut.aot") version "4.2.1"
+ id("com.gradleup.shadow") version "${shadowVersion}"
+ id("io.micronaut.application") version "${micronautPluginVersion}"
+ id("io.micronaut.test-resources") version "${micronautPluginVersion}"
+ id("io.micronaut.aot") version "${micronautPluginVersion}"
}
version = "0.1"
@@ -31,7 +31,8 @@ dependencies {
runtimeOnly("org.flywaydb:flyway-mysql")
runtimeOnly("org.yaml:snakeyaml")
testImplementation("io.micronaut:micronaut-http-client")
- aotPlugins platform("io.micronaut.platform:micronaut-platform:4.2.3")
+
+ aotPlugins platform("io.micronaut.platform:micronaut-platform:${micronautPluginVersion}")
aotPlugins("io.micronaut.security:micronaut-security-aot")
String databaseSelection = System.getenv("UNITYAUTH_DATABASE_DEPENDENCY")
@@ -46,8 +47,8 @@ application {
mainClass.set("io.unityfoundation.Application")
}
java {
- sourceCompatibility = JavaVersion.toVersion("17")
- targetCompatibility = JavaVersion.toVersion("17")
+ sourceCompatibility = JavaVersion.toVersion("21")
+ targetCompatibility = JavaVersion.toVersion("21")
}
graalvmNative.toolchainDetection = false
diff --git a/UnityAuth/docker-compose.local.yml b/UnityAuth/docker-compose.local-infra.yml
similarity index 52%
rename from UnityAuth/docker-compose.local.yml
rename to UnityAuth/docker-compose.local-infra.yml
index 285fc86..5e344b5 100644
--- a/UnityAuth/docker-compose.local.yml
+++ b/UnityAuth/docker-compose.local-infra.yml
@@ -1,15 +1,30 @@
# Use root/example as user/password credentials
-version: '3.1'
-
services:
unity-auth-db:
- image: mysql
+ container_name: unity-auth-db
+ image: mysql:8.0
# NOTE: use of "mysql_native_password" is not recommended: https://dev.mysql.com/doc/refman/8.0/en/upgrading-from-previous-series.html#upgrade-caching-sha2-password
# (this is just an example, not intended to be a production configuration)
command: --default-authentication-plugin=mysql_native_password
- restart: always
+ restart: unless-stopped
+ networks:
+ - unity-network
environment:
MYSQL_ROOT_PASSWORD: test
MYSQL_DATABASE: test
ports:
- - "13306:3306"
\ No newline at end of file
+ - "13306:3306"
+ healthcheck:
+ test:
+ ["CMD", "mysqladmin", "ping", "-h", "localhost", "-u", "root", "-ptest"]
+ timeout: 20s
+ retries: 10
+ interval: 10s
+ start_period: 30s
+
+networks:
+ default:
+ name: unity-network
+ external: true
+ unity-network:
+ external: true
diff --git a/UnityAuth/docker-compose.local-service.yml b/UnityAuth/docker-compose.local-service.yml
new file mode 100644
index 0000000..d2aaf0f
--- /dev/null
+++ b/UnityAuth/docker-compose.local-service.yml
@@ -0,0 +1,34 @@
+# You must start docker-compose.local-infra.yml prior to starting this
+services:
+ unity-auth-db:
+ container_name: unity-auth-db
+ networks:
+ - unity-network
+ extends:
+ file: UnityAuth/docker-compose.local-infra.yml
+ service: unity-auth-db
+
+ unity-auth-api:
+ container_name: unity-auth-api
+ build:
+ dockerfile: DockerfileLocal
+ context: .
+ depends_on:
+ unity-auth-db:
+ condition: service_healthy
+ restart: unless-stopped
+ networks:
+ - unity-network
+ environment:
+ MICRONAUT_ENVIRONMENTS: local
+ MICRONAUT_SERVER_PORT: 8081
+ DATASOURCES_DEFAULT_URL: "jdbc:mysql://unity-auth-db:3306/test?allowPublicKeyRetrieval=true&useSSL=false"
+ ports:
+ - "8081:8081"
+
+networks:
+ default:
+ name: unity-network
+ external: true
+ unity-network:
+ external: true
diff --git a/UnityAuth/gradle.properties b/UnityAuth/gradle.properties
index b6c7fb6..2cfaeda 100644
--- a/UnityAuth/gradle.properties
+++ b/UnityAuth/gradle.properties
@@ -1 +1,6 @@
-micronautVersion=4.2.3
+# Java
+javaVersion=21
+# Micronaut
+micronautVersion=4.8.2
+micronautPluginVersion=4.5.3
+shadowVersion=8.3.6
\ No newline at end of file
diff --git a/UnityAuth/gradle/wrapper/gradle-wrapper.jar b/UnityAuth/gradle/wrapper/gradle-wrapper.jar
index 7f93135..9bbc975 100644
Binary files a/UnityAuth/gradle/wrapper/gradle-wrapper.jar and b/UnityAuth/gradle/wrapper/gradle-wrapper.jar differ
diff --git a/UnityAuth/gradle/wrapper/gradle-wrapper.properties b/UnityAuth/gradle/wrapper/gradle-wrapper.properties
index 3fa8f86..37f853b 100644
--- a/UnityAuth/gradle/wrapper/gradle-wrapper.properties
+++ b/UnityAuth/gradle/wrapper/gradle-wrapper.properties
@@ -1,6 +1,6 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
-distributionUrl=https\://services.gradle.org/distributions/gradle-8.4-bin.zip
+distributionUrl=https\://services.gradle.org/distributions/gradle-8.13-bin.zip
networkTimeout=10000
validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME
diff --git a/UnityAuth/gradlew b/UnityAuth/gradlew
index 1aa94a4..faf9300 100755
--- a/UnityAuth/gradlew
+++ b/UnityAuth/gradlew
@@ -15,6 +15,8 @@
# See the License for the specific language governing permissions and
# limitations under the License.
#
+# SPDX-License-Identifier: Apache-2.0
+#
##############################################################################
#
@@ -55,7 +57,7 @@
# Darwin, MinGW, and NonStop.
#
# (3) This script is generated from the Groovy template
-# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
+# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
# within the Gradle project.
#
# You can find Gradle at https://github.com/gradle/gradle/.
@@ -84,7 +86,7 @@ done
# shellcheck disable=SC2034
APP_BASE_NAME=${0##*/}
# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
-APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit
+APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD=maximum
@@ -203,7 +205,7 @@ fi
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
# Collect all arguments for the java command:
-# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,
+# * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,
# and any embedded shellness will be escaped.
# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be
# treated as '${Hostname}' itself on the command line.
diff --git a/UnityAuth/gradlew.bat b/UnityAuth/gradlew.bat
index 93e3f59..9b42019 100644
--- a/UnityAuth/gradlew.bat
+++ b/UnityAuth/gradlew.bat
@@ -1,92 +1,94 @@
-@rem
-@rem Copyright 2015 the original author or authors.
-@rem
-@rem Licensed under the Apache License, Version 2.0 (the "License");
-@rem you may not use this file except in compliance with the License.
-@rem You may obtain a copy of the License at
-@rem
-@rem https://www.apache.org/licenses/LICENSE-2.0
-@rem
-@rem Unless required by applicable law or agreed to in writing, software
-@rem distributed under the License is distributed on an "AS IS" BASIS,
-@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-@rem See the License for the specific language governing permissions and
-@rem limitations under the License.
-@rem
-
-@if "%DEBUG%"=="" @echo off
-@rem ##########################################################################
-@rem
-@rem Gradle startup script for Windows
-@rem
-@rem ##########################################################################
-
-@rem Set local scope for the variables with windows NT shell
-if "%OS%"=="Windows_NT" setlocal
-
-set DIRNAME=%~dp0
-if "%DIRNAME%"=="" set DIRNAME=.
-@rem This is normally unused
-set APP_BASE_NAME=%~n0
-set APP_HOME=%DIRNAME%
-
-@rem Resolve any "." and ".." in APP_HOME to make it shorter.
-for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
-
-@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
-set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
-
-@rem Find java.exe
-if defined JAVA_HOME goto findJavaFromJavaHome
-
-set JAVA_EXE=java.exe
-%JAVA_EXE% -version >NUL 2>&1
-if %ERRORLEVEL% equ 0 goto execute
-
-echo.
-echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
-echo.
-echo Please set the JAVA_HOME variable in your environment to match the
-echo location of your Java installation.
-
-goto fail
-
-:findJavaFromJavaHome
-set JAVA_HOME=%JAVA_HOME:"=%
-set JAVA_EXE=%JAVA_HOME%/bin/java.exe
-
-if exist "%JAVA_EXE%" goto execute
-
-echo.
-echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
-echo.
-echo Please set the JAVA_HOME variable in your environment to match the
-echo location of your Java installation.
-
-goto fail
-
-:execute
-@rem Setup the command line
-
-set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
-
-
-@rem Execute Gradle
-"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
-
-:end
-@rem End local scope for the variables with windows NT shell
-if %ERRORLEVEL% equ 0 goto mainEnd
-
-:fail
-rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
-rem the _cmd.exe /c_ return code!
-set EXIT_CODE=%ERRORLEVEL%
-if %EXIT_CODE% equ 0 set EXIT_CODE=1
-if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
-exit /b %EXIT_CODE%
-
-:mainEnd
-if "%OS%"=="Windows_NT" endlocal
-
-:omega
+@rem
+@rem Copyright 2015 the original author or authors.
+@rem
+@rem Licensed under the Apache License, Version 2.0 (the "License");
+@rem you may not use this file except in compliance with the License.
+@rem You may obtain a copy of the License at
+@rem
+@rem https://www.apache.org/licenses/LICENSE-2.0
+@rem
+@rem Unless required by applicable law or agreed to in writing, software
+@rem distributed under the License is distributed on an "AS IS" BASIS,
+@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+@rem See the License for the specific language governing permissions and
+@rem limitations under the License.
+@rem
+@rem SPDX-License-Identifier: Apache-2.0
+@rem
+
+@if "%DEBUG%"=="" @echo off
+@rem ##########################################################################
+@rem
+@rem Gradle startup script for Windows
+@rem
+@rem ##########################################################################
+
+@rem Set local scope for the variables with windows NT shell
+if "%OS%"=="Windows_NT" setlocal
+
+set DIRNAME=%~dp0
+if "%DIRNAME%"=="" set DIRNAME=.
+@rem This is normally unused
+set APP_BASE_NAME=%~n0
+set APP_HOME=%DIRNAME%
+
+@rem Resolve any "." and ".." in APP_HOME to make it shorter.
+for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
+
+@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
+
+@rem Find java.exe
+if defined JAVA_HOME goto findJavaFromJavaHome
+
+set JAVA_EXE=java.exe
+%JAVA_EXE% -version >NUL 2>&1
+if %ERRORLEVEL% equ 0 goto execute
+
+echo. 1>&2
+echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2
+echo. 1>&2
+echo Please set the JAVA_HOME variable in your environment to match the 1>&2
+echo location of your Java installation. 1>&2
+
+goto fail
+
+:findJavaFromJavaHome
+set JAVA_HOME=%JAVA_HOME:"=%
+set JAVA_EXE=%JAVA_HOME%/bin/java.exe
+
+if exist "%JAVA_EXE%" goto execute
+
+echo. 1>&2
+echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2
+echo. 1>&2
+echo Please set the JAVA_HOME variable in your environment to match the 1>&2
+echo location of your Java installation. 1>&2
+
+goto fail
+
+:execute
+@rem Setup the command line
+
+set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
+
+
+@rem Execute Gradle
+"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
+
+:end
+@rem End local scope for the variables with windows NT shell
+if %ERRORLEVEL% equ 0 goto mainEnd
+
+:fail
+rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
+rem the _cmd.exe /c_ return code!
+set EXIT_CODE=%ERRORLEVEL%
+if %EXIT_CODE% equ 0 set EXIT_CODE=1
+if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
+exit /b %EXIT_CODE%
+
+:mainEnd
+if "%OS%"=="Windows_NT" endlocal
+
+:omega
diff --git a/UnityAuth/src/main/java/io/unityfoundation/auth/UnityAuthenticationProvider.java b/UnityAuth/src/main/java/io/unityfoundation/auth/UnityAuthenticationProvider.java
index 04b8791..0d97a79 100644
--- a/UnityAuth/src/main/java/io/unityfoundation/auth/UnityAuthenticationProvider.java
+++ b/UnityAuth/src/main/java/io/unityfoundation/auth/UnityAuthenticationProvider.java
@@ -2,13 +2,13 @@
import static io.micronaut.security.authentication.AuthenticationFailureReason.CREDENTIALS_DO_NOT_MATCH;
+import io.micronaut.core.annotation.NonNull;
import io.micronaut.core.annotation.Nullable;
import io.micronaut.http.HttpRequest;
-import io.micronaut.security.authentication.AuthenticationException;
import io.micronaut.security.authentication.AuthenticationFailed;
-import io.micronaut.security.authentication.AuthenticationProvider;
import io.micronaut.security.authentication.AuthenticationRequest;
import io.micronaut.security.authentication.AuthenticationResponse;
+import io.micronaut.security.authentication.provider.ReactiveAuthenticationProvider;
import io.unityfoundation.auth.entities.User;
import io.unityfoundation.auth.entities.UserRepo;
import jakarta.inject.Singleton;
@@ -20,7 +20,7 @@
import java.util.Objects;
@Singleton
-public class UnityAuthenticationProvider implements AuthenticationProvider> {
+public class UnityAuthenticationProvider implements ReactiveAuthenticationProvider,Object,Object> {
private final UserRepo userRepo;
private final PasswordEncoder passwordEncoder;
@@ -31,34 +31,6 @@ public UnityAuthenticationProvider(UserRepo userRepo,
this.passwordEncoder = passwordEncoder;
}
- /**
- * Authenticates the user with the given authentication request.
- *
- * @param httpRequest The HTTP request associated with the authentication.
- * @param authenticationRequest The authentication request containing user credentials.
- * @return A Publisher emitting an AuthenticationResponse upon successful authentication, or throwing
- * an AuthenticationException if authentication fails.
- */
- @Override
- public Publisher authenticate(@Nullable HttpRequest> httpRequest,
- AuthenticationRequest, ?> authenticationRequest) {
- return Mono.fromCallable(() -> findUser(authenticationRequest))
- .subscribeOn(Schedulers.boundedElastic())
- .flatMap(user -> {
- AuthenticationFailed authenticationFailed = validate(user, authenticationRequest);
- if (authenticationFailed != null) {
- return Mono.error(new AuthenticationException(authenticationFailed));
- } else {
- return Mono.just(AuthenticationResponse.success(
- (String) authenticationRequest.getIdentity(),
- Map.of(
- "first_name", Objects.toString(user.getFirstName(), ""),
- "last_name", Objects.toString(user.getLastName(), "")
- )
- ));
- }
- });
- }
private AuthenticationFailed validate(User user,
AuthenticationRequest, ?> authenticationRequest) {
@@ -78,5 +50,31 @@ private User findUser(AuthenticationRequest, ?> authRequest) {
return userRepo.findUserForAuthentication(username.toString()).orElse(null);
}
+ @Override
+ public @NonNull Publisher authenticate(@Nullable HttpRequest> requestContext,
+ @NonNull AuthenticationRequest