diff --git a/.gitignore b/.gitignore index 01fc4f3..b0a4f11 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,9 @@ *~ /.gradle/ /.idea/ +/*/.idea/ /.micronaut/ /build/ +AuthGenHash/bin +setenv.sh +/UnityAuth/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..dbcdb13 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 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/AuthGenHash/settings.gradle b/AuthGenHash/settings.gradle index ab73e66..0ff502e 100644 --- a/AuthGenHash/settings.gradle +++ b/AuthGenHash/settings.gradle @@ -1 +1 @@ -rootProject.name="AuthGenHash" \ No newline at end of file +rootProject.name="AuthGenHash" diff --git a/DockerfileFrontend-dev b/DockerfileFrontend-dev new file mode 100644 index 0000000..c54c554 --- /dev/null +++ b/DockerfileFrontend-dev @@ -0,0 +1,10 @@ +FROM node:24 + +COPY frontend frontend +WORKDIR frontend +RUN npm install + +EXPOSE 3000 + +# Start the app in development mode +ENTRYPOINT ["npm", "run", "dev"] diff --git a/FrontendDockerfile b/DockerfileFrontend-prod similarity index 70% rename from FrontendDockerfile rename to DockerfileFrontend-prod index 345f873..a12c03b 100644 --- a/FrontendDockerfile +++ b/DockerfileFrontend-prod @@ -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..6015454 --- /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 diff --git a/README.md b/README.md new file mode 100644 index 0000000..e541346 --- /dev/null +++ b/README.md @@ -0,0 +1,202 @@ +# 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. + +## QuickStart + +### Local Environment +From the project root, copy `setenv.sh.example` into `setenv.sh` and update the correct +environment variable values. +* If used together with Libre311 project +(https://github.com/UnityFoundation-io/Libre311.git), `LIBRE311_UI_BASE_URL` must be updated +to base URL of Libre311 UI. +* If use a local database, also update the `DATASOURCES_DEFAULT_*` environment variables + for the corresponding database. + +Run the UnityAuth API with: +```shell +source setenv.sh +cd UnityAuth +./gradlew run +``` + +Run the UnityAuth UI in another terminal windows: +```shell +source setenv.sh +cd frontend +npm install +npm run dev +``` + +### Docker Environment +To launch the auth service, you can use the docker compose from the project root: + +```sh +docker compose -f docker-compose.local.yml up +``` + +This will start containers for the UnityAuth API, UI and database server with service +names `unity-auth-api`, `unity-auth-ui`, and `unity-auth-db`, respectively. + +- **UnityAuth API** on http://localhost:9090 (inside Docker http://unity-auth-api:9090) +- **UnityAuth UI** on http://localhost:3001 (inside Docker http://unity-auth-ui:3000) +- **MySQL Database** is open on port `13306` in `localhost` (within Docker is port `3306` + with host name `unity-auth-db`) + +#### Hosts File Updates + +When using Docker environment, add these to your `/etc/hosts` file +for consistent internal-external service name resolution: + +```txt +127.0.0.1 unity-auth-api +127.0.0.1 unity-auth-ui +``` + +You can log in with these accounts. +**Password for all the following accounts is `test`** + +- **Unity Administrator** `unity_admin@example.com` +- **Tenant Administrator** `tenant_admin@example.com` +- **Libre311 Administrator** `libre311_admin@example.com` +- **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 Administrator** `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 + +### 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..4a4327e --- /dev/null +++ b/UnityAuth/.dockerignore @@ -0,0 +1,10 @@ +gradle/caches + +# Exclude gradle build caches but not wrapper +.gradle/ +.DS_Store + +build/ + +docker-compose.* +DockerfileAPI 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/DockerfileAPI b/UnityAuth/DockerfileAPI new file mode 100644 index 0000000..e6526bb --- /dev/null +++ b/UnityAuth/DockerfileAPI @@ -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/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.api.yml b/UnityAuth/docker-compose.api.yml new file mode 100644 index 0000000..e98d5e3 --- /dev/null +++ b/UnityAuth/docker-compose.api.yml @@ -0,0 +1,31 @@ +services: + unity-auth-db: + container_name: unity-auth-db + networks: + - unity-network + extends: + file: docker-compose.db.yml + service: unity-auth-db + + unity-auth-api: + container_name: unity-auth-api + build: + dockerfile: DockerfileAPI + context: . + depends_on: + unity-auth-db: + condition: service_healthy + restart: unless-stopped + networks: + - unity-network + environment: + MICRONAUT_ENVIRONMENTS: docker + ports: + - "9090:9090" + +networks: + default: + name: unity-network + external: true + unity-network: + external: true diff --git a/UnityAuth/docker-compose.db.yml b/UnityAuth/docker-compose.db.yml new file mode 100644 index 0000000..7507e5a --- /dev/null +++ b/UnityAuth/docker-compose.db.yml @@ -0,0 +1,29 @@ +services: + unity-auth-db: + 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: unless-stopped + networks: + - unity-network + environment: + MYSQL_ROOT_PASSWORD: test + MYSQL_DATABASE: test + ports: + - "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.yml b/UnityAuth/docker-compose.local.yml deleted file mode 100644 index 285fc86..0000000 --- a/UnityAuth/docker-compose.local.yml +++ /dev/null @@ -1,15 +0,0 @@ -# Use root/example as user/password credentials -version: '3.1' - -services: - unity-auth-db: - image: mysql - # 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 - environment: - MYSQL_ROOT_PASSWORD: test - MYSQL_DATABASE: test - ports: - - "13306:3306" \ No newline at end of file diff --git a/UnityAuth/gradle.properties b/UnityAuth/gradle.properties index b6c7fb6..dbcdb13 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 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/settings.gradle b/UnityAuth/settings.gradle index 50d302a..77d1cd3 100644 --- a/UnityAuth/settings.gradle +++ b/UnityAuth/settings.gradle @@ -1,2 +1 @@ rootProject.name = "unity-auth" - 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 authenticationRequest) { + return authenticate(authenticationRequest); + } + + @Override + public @NonNull Publisher authenticate( + @NonNull AuthenticationRequest authenticationRequest) { + return Mono.fromCallable(() -> findUser(authenticationRequest)) + .subscribeOn(Schedulers.boundedElastic()) + .flatMap(user -> { + AuthenticationFailed authenticationFailed = validate(user, authenticationRequest); + if (authenticationFailed != null) { + return Mono.just(AuthenticationResponse.failure(authenticationFailed.getReason().toString())); + } else { + return Mono.just(AuthenticationResponse.success( + (String) authenticationRequest.getIdentity(), + Map.of( + "first_name", Objects.toString(user.getFirstName(), ""), + "last_name", Objects.toString(user.getLastName(), "") + ) + )); + } + }); + } } diff --git a/UnityAuth/src/main/resources/application-docker.yml b/UnityAuth/src/main/resources/application-docker.yml new file mode 100644 index 0000000..3e707c8 --- /dev/null +++ b/UnityAuth/src/main/resources/application-docker.yml @@ -0,0 +1,43 @@ +# Config for Docker environment. See docker-compose.local.yml. +# Hostname and port of the API and frontend containers accessed from the host machine are: +# - API server: unity-auth-api:9090 (maps to port 9090 within the container) +# - Frontend: unity-auth-ui-dev:3001 or localhost:3001 (maps to port 3000 within the container) +# Note: Add DNS entries for unity-auth-api, unity-auth-ui-dev hostnames to your host, e.g. +# /etc/hosts on Linux or Mac. +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: 9090 + security: + authentication: bearer +datasources: + default: + url: jdbc:mysql://unity-auth-db:3306/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/src/main/resources/application-local.yml b/UnityAuth/src/main/resources/application-local.yml index 1f225ce..14cfca2 100644 --- a/UnityAuth/src/main/resources/application-local.yml +++ b/UnityAuth/src/main/resources/application-local.yml @@ -6,12 +6,16 @@ micronaut: enabled: true configurations: web: - allowed-origins-regex: '^http:\/\/(.*?)localhost:3000$' + 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: @@ -29,5 +33,5 @@ flyway: - 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\"}" + 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/src/main/resources/db/migration/V1__initial_schema.sql b/UnityAuth/src/main/resources/db/migration/V1__initial_schema.sql index eb7c0a5..648dda0 100644 --- a/UnityAuth/src/main/resources/db/migration/V1__initial_schema.sql +++ b/UnityAuth/src/main/resources/db/migration/V1__initial_schema.sql @@ -79,5 +79,3 @@ 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/src/main/resources/local/afterMigrate.sql b/UnityAuth/src/main/resources/local/afterMigrate.sql index eb004b2..2424bd6 100644 --- a/UnityAuth/src/main/resources/local/afterMigrate.sql +++ b/UnityAuth/src/main/resources/local/afterMigrate.sql @@ -144,4 +144,4 @@ INSERT IGNORE INTO user_role (tenant_id, user_id, role_id) VALUES -- 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 + (1, 7, 5); diff --git a/UnityAuth/src/test/resources/db/migration/application-test.yml b/UnityAuth/src/test/resources/db/migration/application-test.yml index 0cbb150..4cad87e 100644 --- a/UnityAuth/src/test/resources/db/migration/application-test.yml +++ b/UnityAuth/src/test/resources/db/migration/application-test.yml @@ -1,4 +1,9 @@ micronaut: http: client: - read-timeout: 1m \ No newline at end of file + read-timeout: 1m + + +datasources: + default: + password: "test" diff --git a/docker-compose.local.yml b/docker-compose.local.yml new file mode 100644 index 0000000..e00aa4f --- /dev/null +++ b/docker-compose.local.yml @@ -0,0 +1,55 @@ +services: + ## Infrastructure Extensions + unity-auth-db: + container_name: unity-auth-db + networks: + - unity-network + extends: + file: UnityAuth/docker-compose.db.yml + service: unity-auth-db + + ## API Extensions + unity-auth-api: + container_name: unity-auth-api + networks: + - unity-network + extends: + file: UnityAuth/docker-compose.api.yml + service: unity-auth-api + depends_on: + unity-auth-db: + condition: service_healthy + + ## UI Service - Development environment + unity-auth-ui-dev: + container_name: unity-auth-ui-dev + build: + dockerfile: DockerfileFrontend-dev + context: . + restart: no + environment: + VITE_BACKEND_URL: http://unity-auth-api:8081 + ports: + - "3001:3000" + + ## UI Service - Production environment + ## Start by: docker compose -f docker-compose.local.yml --profile prod up + ## Make sure to stop unity-auth-ui-dev beforehand since they bind to the same 3001 port. + ## NOTE: This seems to require updates to Nginx config to make it work. + unity-auth-ui-prod: + profiles: + - prod + container_name: unity-auth-ui-prod + build: + dockerfile: DockerfileFrontend-prod + context: . + restart: no + ports: + - "3001:80" + +networks: + default: + name: unity-network + external: true + unity-network: + external: true diff --git a/frontend/setenv.sh b/frontend/setenv.sh index c68e5ed..11c2156 100644 --- a/frontend/setenv.sh +++ b/frontend/setenv.sh @@ -1 +1 @@ -export VITE_BACKEND_URL=http://127.0.0.1:8080 +export VITE_BACKEND_URL=http://127.0.0.1:8081 diff --git a/nginx-default.conf b/nginx-default.conf index 7525d45..2bc7789 100644 --- a/nginx-default.conf +++ b/nginx-default.conf @@ -19,6 +19,24 @@ server { root /usr/share/nginx/html; } + # TODO: This tries to route the requests from localhost to the API container + # with hostname unity-auth-api but currently receive 504 Gateway Timeout + location /api/ { + proxy_pass http://unity-auth-api:8081; + proxy_http_version 1.1; + + proxy_connect_timeout 60s; + proxy_send_timeout 60s; + proxy_read_timeout 60s; + send_timeout 60s; + + # Forward headers + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + } + # proxy the PHP scripts to Apache listening on 127.0.0.1:80 # #location ~ \.php$ { diff --git a/setenv.sh.example b/setenv.sh.example new file mode 100644 index 0000000..d550340 --- /dev/null +++ b/setenv.sh.example @@ -0,0 +1,16 @@ +#!/bin/bash + +export MICRONAUT_ENVIRONMENTS= # Example: local +# Don't set MICRONAUT_SERVER_PORT environment variable since it seems to be picked +# up by both the UnityAuth server and the Test Resources server which cause port conflict. +# Instead, set port for the main server in the application-*.yml file for the +# specific config environment, e.g., application-local.yml. + +export LIBRE311_UI_BASE_URL= # Example: http://localhost:3000 + +export VITE_BACKEND_URL= # Example: http://localhost:8081 + +# Update these if necessary, for example, to use a local database +export DATASOURCES_DEFAULT_URL= +export DATASOURCES_DEFAULT_USERNAME= +export DATASOURCES_DEFAULT_PASSWORD=