diff --git a/README.md b/README.md index 2104b74..c08428c 100644 --- a/README.md +++ b/README.md @@ -1,355 +1,53 @@ # User Service - Java/Spring Boot -A comprehensive user authentication and management service built with **Java 25** and **Spring Boot 4**, architected as -a **modular monolith** using **Spring Modulith**. +A comprehensive user authentication and management service featuring OAuth2, OTP, and JWT-based security. Built with **Java 25** and **Spring Boot 4**, architected as a **modular monolith** using **Spring Modulith**. -## Features +![springboot-oauth2-demo](./springboot-oauth2-demo.gif) -| Category | Feature | Description | -|-----------------------------------------|---------------------|------------------------------------------------| -| **User Authentication & Authorization** | User Registration | Email/password registration system | -| | Secure Login | JWT token-based authentication | -| | Token Management | Refresh token rotation with token families | -| | Logout Options | Single device and all devices logout | -| | Access Control | Role-based permissions (MEMBER/ADMIN) | -| **User Management** | Profile Operations | Get and update user profiles | -| | Password Management | Change password functionality | -| | Admin Controls | Administrative user management | -| | Data Validation | Email uniqueness validation | -| **Security** | JWT Tokens | Access and refresh token system | -| | Password Security | BCrypt password hashing | -| | CORS Support | Cross-origin resource sharing configuration | -| | Input Validation | Bean Validation framework integration | -| | Error Handling | Global exception handling | -| **Database** | Database Engine | PostgreSQL with Spring Data JPA | -| | Primary Keys | UUID-based primary keys | -| | Auditing | Automatic creation and modification timestamps | -| | Migrations | Automated database migrations with Flyway | +## Key Features -## Tech Stack +| Category | Features | +| ------------------ | ----------------------------------------------------------------------- | +| **Authentication** | Registration, Login, Token-based (JWT) with rotation & families | +| **Social Login** | OAuth2 integration (Google, GitHub) with account linking | +| **Passwordless** | One-Time Password (OTP) login flow via email | +| **Governance** | Role-based access control (MEMBER, ADMIN), profile management | +| **Architecture** | **Modular Monolith** (Spring Modulith), DDD, Event-driven communication | +| **Performance** | **Java 25 Virtual Threads**, gRPC for high-speed communication | -- **Java 25** with Virtual Threads (Project Loom) -- **Spring Boot 4.0** with Spring Framework 7 -- **Spring Modulith 2.0** for modular architecture -- **Spring Security 7** -- **Spring Data JPA** -- **PostgreSQL 17** -- **gRPC** alongside REST APIs -- **JWT (JJWT 0.13)** -- **Gradle 8+** -- **Docker & Docker Compose** -- **Swagger/OpenAPI 3** (springdoc-openapi) +## Documentation Hub + +- [**API Guide**](docs/api.md) - Detailed REST and gRPC endpoints, OTP flow, and Swagger access. +- [**Architecture & Design**](docs/architecture.md) - Modular design, security strategy, and technical decisions. +- [**Development Guide**](docs/development.md) - Build, run, test (JUnit 5, Testcontainers), and code quality using `./auto/` scripts. +- [**Configuration Reference**](docs/configuration.md) - Environment variables and application profiles. +- [**Database/DDD Plan**](docs/ddd_plan.md) - Database schema and domain-driven design roadmap. +- [**Troubleshooting**](docs/troubleshooting.md) - Known issues and solutions. ## Quick Start ### Prerequisites - -- Java 25 -- Gradle 8+ -- PostgreSQL 17+ -- Docker & Docker Compose (optional) - -### Environment Variables - -Copy `.env.example` to `.env` and update the values: - -```bash -# Database -DATABASE_URL=jdbc:postgresql://localhost:54321/timor_users -DATABASE_USERNAME=timor_users_rw -DATABASE_PASSWORD=Timor_password1 - -# JWT (must be at least 64 bytes for HS512) -JWT_ACCESS_SECRET=your-super-secret-jwt-access-key-here-must-be-at-least-64-bytes-long -JWT_REFRESH_SECRET=your-super-secret-jwt-refresh-key-here-must-be-at-least-64-bytes-long -JWT_ACCESS_EXPIRES_IN=15m -JWT_REFRESH_EXPIRES_IN=7d - -# CORS -CLIENT_URL=http://localhost:3000 -``` - -### Running with Docker - -```bash -# Start all services -docker compose up -d - -docker compose up --build - -# Check logs -docker compose logs -f user-service - -# Stop services -docker compose down -``` +- **Java 25** & Gradle 8+ +- PostgreSQL 17 or Docker ### Running Locally - +1. Copy `.env.example` to `.env` and configure secrets (JWT, Mail, OAuth2). +2. Start the service using the automation script: ```bash -# Build the application -./gradlew build - -# Run the application -./gradlew bootRun - -# Or run with specific profile -./gradlew bootRun --args='--spring.profiles.active=local' +./auto/run ``` +*HTTP: `http://localhost:8080` \| gRPC: `localhost:9090`* -### Health Check - +### Running with Docker +To build and start the entire stack (Application + Database): ```bash -curl http://localhost:3001/health +./auto/docker_start ``` -## API Documentation - -### Swagger UI - -Access the interactive API documentation at: - -- **Local**: http://localhost:3001/swagger-ui.html -- **Docker**: http://localhost:3001/swagger-ui.html - -### OpenAPI Specification - -Download the OpenAPI spec at: - -- http://localhost:3001/v3/api-docs - -## Default Users - -| Role | Email | Password | -|--------|-----------------|-------------| -| Admin | admin@timor.com | Admin12345! | -| Member | demo@timor.com | Demo12345! | - -## API Endpoints - -### Authentication - -- `POST /api/users/auth/register` - User registration -- `POST /api/users/auth/login` - User login -- `POST /api/users/auth/refresh` - Refresh tokens -- `POST /api/users/auth/logout` - Logout all devices -- `POST /api/users/auth/logout-single` - Logout current device - -### User Management - -- `GET /api/users/me` - Get current user profile -- `PATCH /api/users/me` - Update current user profile -- `PATCH /api/users/me/password` - Change current user password - -### Admin Endpoints - -- `GET /api/users/{userId}` - Get user by ID (Admin only) -- `PATCH /api/users/{userId}` - Update user by ID (Admin only) -- `PATCH /api/users/{userId}/password` - Change user password (Admin only) - ### Health Check - -- `GET /health` - Health check endpoint - -## API Usage Examples - -### Register a new user - -```bash -curl -X POST http://localhost:3001/api/users/auth/register \ - -H "Content-Type: application/json" \ - -d '{ - "email": "user@example.com", - "password": "Password12345!", - "name": "John Doe" - }' -``` - -### Login - -```bash -curl -X POST http://localhost:3001/api/users/auth/login \ - -H "Content-Type: application/json" \ - -d '{ - "email": "user@example.com", - "password": "Password12345!" - }' -``` - -### Get current user profile - ```bash -curl -X GET http://localhost:3001/api/users/me \ - -H "Authorization: Bearer YOUR_ACCESS_TOKEN" -``` - -### Update profile - -```bash -curl -X PATCH http://localhost:3001/api/users/me \ - -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \ - -H "Content-Type: application/json" \ - -d '{ - "name": "John Smith", - "email": "john.smith@example.com" - }' -``` - -### Refresh tokens - -```bash -curl -X POST http://localhost:3001/api/users/auth/refresh \ - -H "Content-Type: application/json" \ - -d '{ - "refreshToken": "YOUR_REFRESH_TOKEN" - }' -``` - -## Development - -### Building and Testing - -```bash -# Run tests -./gradlew test - -# Run tests with coverage -./gradlew test jacocoTestReport - -# Build without tests -./gradlew build -x test - -# Create production JAR -./gradlew bootJar -``` - -### Code Quality - -```bash -# Apply code formatting (Palantir Java Format) -./gradlew spotlessApply - -# Check code formatting -./gradlew spotlessCheck - -# Verify module architecture -./gradlew test --tests "org.nkcoder.ModulithArchitectureTest" - -# Generate dependency report -./gradlew dependencyInsight --dependency -``` - -## Project Structure - -The application uses **Spring Modulith** for a modular monolith architecture: - -``` -src/main/java/org/nkcoder/ -├── Application.java # Bootstrap (@Modulith entry point) -│ -├── user/ # User Module -│ ├── package-info.java # @ApplicationModule definition -│ ├── interfaces/rest/ # REST controllers -│ │ ├── AuthController.java -│ │ ├── UserController.java -│ │ └── AdminUserController.java -│ ├── application/service/ # Application services -│ │ ├── AuthApplicationService.java -│ │ └── UserApplicationService.java -│ ├── domain/ # Domain layer -│ │ ├── model/ # Aggregates, entities, value objects -│ │ ├── service/ # Domain services (ports) -│ │ └── repository/ # Repository interfaces (ports) -│ └── infrastructure/ # Infrastructure layer -│ ├── persistence/ # JPA entities & repository adapters -│ └── security/ # JWT filter, SecurityConfig -│ -├── notification/ # Notification Module -│ ├── package-info.java -│ ├── NotificationService.java # Public API -│ └── application/ # Event listeners -│ -├── shared/ # Shared Kernel (OPEN module) -│ ├── kernel/ -│ │ ├── domain/event/ # Domain events -│ │ └── exception/ # Common exceptions -│ └── local/rest/ # ApiResponse, GlobalExceptionHandler -│ -└── infrastructure/ # Infrastructure Module (OPEN module) - └── config/ # CORS, OpenAPI, JPA auditing -``` - -### Module Dependencies - +curl http://localhost:8080/health ``` -notification ──→ shared ←── user - ↑ - infrastructure -``` - -- Modules communicate via **domain events** (not direct calls) -- `shared` and `infrastructure` are OPEN modules (accessible by all) - -## Security Features - -- **JWT Authentication**: Stateless authentication with HS512 algorithm -- **Token Rotation**: Refresh tokens rotated on each use -- **Token Families**: Grouped tokens for multi-device support -- **Password Security**: BCrypt hashing with configurable strength -- **Input Validation**: Bean Validation annotations -- **CORS**: Configurable cross-origin resource sharing -- **Audit Trail**: Automatic creation and modification timestamps - -## Monitoring & Observability - -- **Health Checks**: Spring Boot Actuator endpoints -- **Structured Logging**: SLF4J with Logback -- **API Documentation**: OpenAPI 3 with Swagger UI -- **Error Handling**: Global exception handling with proper HTTP status codes - -## Configuration - -### Application Profiles - -- `local`: Local development with Docker Compose for PostgreSQL -- `dev`: Development environment with external database -- `prod`: Production environment -- `test`: Testing with TestContainers - -### Key Configuration Properties - -```yaml -# JWT Configuration -jwt: - secret: - access: ${JWT_ACCESS_SECRET} # Must be 64+ bytes for HS512 - refresh: ${JWT_REFRESH_SECRET} # Must be 64+ bytes for HS512 - expiration: - access: ${JWT_ACCESS_EXPIRES_IN:15m} - refresh: ${JWT_REFRESH_EXPIRES_IN:7d} - -# Database Configuration -spring: - datasource: - url: ${DATABASE_URL} - username: ${DATABASE_USERNAME} - password: ${DATABASE_PASSWORD} -``` - -## Users - -- username/password: daniel1@yopmail.com/daniel@Pass01 -- OTP: nkcoder.24@yopmail.com -- OAuth2: - - Github: daniel5hbs@gmail.com - - Google: daniel5hbs@gmail.com - -## References - -- [Spring Modulith Documentation](https://docs.spring.io/spring-modulith/reference/) -- [Spring Boot 4.0 Migration Guide](https://github.com/spring-projects/spring-boot/wiki/Spring-Boot-4.0-Migration-Guide) -- [Implementing Domain Driven Design with Spring](https://github.com/maciejwalkowiak/implementing-ddd-with-spring-talk) ## License - -This project is licensed under the MIT License. \ No newline at end of file +MIT License \ No newline at end of file diff --git a/docs/api.md b/docs/api.md new file mode 100644 index 0000000..9ef40c4 --- /dev/null +++ b/docs/api.md @@ -0,0 +1,78 @@ +# API Guide + +This document describes the REST and gRPC API endpoints, usage examples, and documentation access for the User Service. + +## Documentation Access + +### Swagger UI +- **Local**: http://localhost:8080/swagger-ui.html (Default port 8080) +- **Docker**: http://localhost:8080/swagger-ui.html + +### OpenAPI Specification +- http://localhost:8080/v3/api-docs + +## REST API Endpoints + +### Authentication `/api/auth` +- `POST /api/auth/register` - User registration +- `POST /api/auth/login` - User login +- `POST /api/auth/refresh` - Refresh tokens +- `POST /api/auth/logout` - Logout all devices (Authenticated) +- `POST /api/auth/logout-single` - Logout current device (Authenticated) + +### One-Time Password (OTP) `/api/auth/otp` +- `POST /api/auth/otp/request` - Request a login OTP via email +- `POST /api/auth/otp/verify` - Verify OTP and receive auth tokens + +### OAuth2 Authentication `/api/auth/oauth2` +- `GET /api/auth/oauth2/providers` - Get list of configured social login providers +- `GET /api/auth/oauth2/authorize/{provider}` - Start OAuth2 flow (e.g., `google`, `github`) + +### User Profile `/api/users/me` (Authenticated) +- `GET /api/users/me` - Get current user profile +- `PATCH /api/users/me` - Update profile (name, email) +- `PATCH /api/users/me/password` - Change password +- `GET /api/users/me/oauth2` - List connected OAuth2 accounts +- `DELETE /api/users/me/oauth2/{provider}` - Unlink an OAuth2 account + +### Admin Management `/api/admin/users` (Admin Only) +- `GET /api/admin/users` - List all users +- `GET /api/admin/users/{userId}` - Get user by ID +- `PATCH /api/admin/users/{userId}` - Update user details +- `PATCH /api/admin/users/{userId}/password` - Reset user password + +### Health & Monitoring +- `GET /health` - Health check endpoint +- `GET /actuator/health` - Spring Boot Actuator health +- `GET /actuator/metrics` - Application metrics + +## gRPC API +The service exposes a gRPC interface on port **9090**. + +### AuthService +- `rpc Register(RegisterRequest) returns (ApiResponse)` +- `rpc Login(LoginRequest) returns (ApiResponse)` + +## Usage Examples + +### OTP Login Flow +1. **Request OTP**: +```bash +curl -X POST http://localhost:8080/api/auth/otp/request \ + -H "Content-Type: application/json" \ + -d '{"email": "user@example.com"}' +``` +2. **Verify OTP**: +```bash +curl -X POST http://localhost:8080/api/auth/otp/verify \ + -H "Content-Type: application/json" \ + -d '{"email": "user@example.com", "code": "123456"}' +``` + +### OAuth2 Connection +If already logged in, you can link a Google account: +```bash +curl -X DELETE http://localhost:8080/api/users/me/oauth2/google \ + -H "Authorization: Bearer YOUR_ACCESS_TOKEN" +``` +*(Note: Linkage is usually performed via the browser redirection flow)* diff --git a/docs/architecture.md b/docs/architecture.md new file mode 100644 index 0000000..3aa9ca1 --- /dev/null +++ b/docs/architecture.md @@ -0,0 +1,50 @@ +# Architecture & Design + +This document describes the modular monolith architecture, security design, and technical decisions behind the User Service. + +## Modular Monolith Architecture + +The application uses **Spring Modulith** to enforce a clean, modular structure within a single deployment unit. + +### Core Modules +- **User Module**: Handles core identity, authentication, profile management, and OAuth2/OTP logic. +- **Notification Module**: Asynchronous processing of user-related notifications (e.g., sending OTP emails). +- **Shared Kernel**: Common domain events, exceptions, and utility classes used across modules. +- **Infrastructure**: Cross-cutting concerns like CORS, OpenAPI, and JPA auditing. + +### Communication Pattern +Modules communicate primarily via **Domain Events** to ensure loose coupling: +- When a user registers or requests an OTP, a domain event is published. +- The `NotificationModule` listens for these events (`UserEventListener`, `OtpEventListener`) and processes them (e.g., sends an email via SMTP). + +```mermaid +graph TD + UserModule[User Module] -- "Domain Event" --> NotificationModule[Notification Module] + UserModule --> SharedKernel[Shared Kernel] + NotificationModule --> SharedKernel[Shared Kernel] + Infrastructure --> SharedKernel[Shared Kernel] +``` + +## Security Design + +### JWT & Token Strategy +- **Stateless Authentication**: No session state is stored on the server. +- **Token Rotation**: Every time a refresh token is used, a new pair (Access + Refresh) is issued, and the old refresh token is invalidated. +- **Token Families**: Refresh tokens are grouped into families to detect and prevent replay attacks across multiple devices. + +### Social Login (OAuth2) +- Supports OpenID Connect (Google) and OAuth2 (GitHub). +- Users can link multiple providers to a single account or register directly via social login. + +### One-Time Password (OTP) +- Secure 6-digit codes generated for passwordless login. +- Codes are hashed in the database and expire after a configurable duration. + +## Performance & Scalability +- **Virtual Threads (Project Loom)**: Enabled to handle high concurrency with minimal resource overhead. +- **PostgreSQL 17**: Leverages modern database features with UUID-based primary keys. +- **gRPC Support**: High-performance binary communication for internal service-to-service calls. + +## Observability +- **Actuator**: Comprehensive health and metrics exposure. +- **Structured Logging**: JSON-formatted logs for easy ingestion by monitoring tools. diff --git a/docs/configuration.md b/docs/configuration.md new file mode 100644 index 0000000..d429552 --- /dev/null +++ b/docs/configuration.md @@ -0,0 +1,54 @@ +# Configuration Reference + +This document details the configuration options and environment variables for the User Service. + +## Environment Variables + +### Database +| Variable | Description | Default | +| ------------------- | ------------------- | ----------------------------------------------- | +| `DATABASE_URL` | JDBC Connection URL | `jdbc:postgresql://localhost:54321/timor_users` | +| `DATABASE_USERNAME` | DB Username | `timor_users_rw` | +| `DATABASE_PASSWORD` | DB Password | `Timor_password1` | + +### Security (JWT) +| Variable | Description | Default | +| ------------------------ | ---------------------------------- | ---------------- | +| `JWT_ACCESS_SECRET` | 64+ byte secret for access tokens | (auto-generated) | +| `JWT_REFRESH_SECRET` | 64+ byte secret for refresh tokens | (auto-generated) | +| `JWT_ACCESS_EXPIRES_IN` | Access token lifespan | `15m` | +| `JWT_REFRESH_EXPIRES_IN` | Refresh token lifespan | `7d` | +| `JWT_ISSUER` | Token issuer name | `user-service` | + +### OAuth2 (Social Login) +| Variable | Description | Default | +| ----------------------------- | --------------------------- | ---------------- | +| `GOOGLE_CLIENT_ID` | Google OAuth2 Client ID | - | +| `GOOGLE_CLIENT_SECRET` | Google OAuth2 Client Secret | - | +| `GITHUB_CLIENT_ID` | GitHub OAuth2 Client ID | - | +| `GITHUB_CLIENT_SECRET` | GitHub OAuth2 Client Secret | - | +| `OAUTH2_SUCCESS_REDIRECT_URL` | Redirect after success | `/callback.html` | +| `OAUTH2_FAILURE_REDIRECT_URL` | Redirect after failure | `/error.html` | + +### Mail (SMTP) +| Variable | Description | Default | +| ---------------- | -------------------- | --------------------- | +| `MAIL_HOST` | SMTP Server Host | `smtp.gmail.com` | +| `MAIL_PORT` | SMTP Server Port | `587` | +| `MAIL_USERNAME` | SMTP Username | - | +| `MAIL_PASSWORD` | SMTP Password | - | +| `MAIL_FROM` | Sender email address | `noreply@example.com` | +| `MAIL_FROM_NAME` | Sender display name | `User Service` | + +### System & Ports +| Variable | Description | Default | +| ---------------------- | -------------------- | ----------------------- | +| `PORT` | HTTP Server Port | `8080` | +| `GRPC_PORT` | gRPC Server Port | `9090` | +| `CORS_ALLOWED_ORIGINS` | Allowed CORS origins | `http://localhost:3000` | + +## Application Profiles +- `local`: Enables Docker Compose for local PostgreSQL and gRPC reflection. +- `dev`: Standard development settings. +- `prod`: Optimized for production (caching, production database). +- `test`: Used for integration tests with Testcontainers. diff --git a/docs/development.md b/docs/development.md new file mode 100644 index 0000000..323b907 --- /dev/null +++ b/docs/development.md @@ -0,0 +1,65 @@ +# Development Guide + +This guide covers building, running, and maintaining the User Service using the provided automation scripts. + +## Automation Scripts + +The project includes a set of scripts in the `auto/` directory to simplify common development tasks. These are the recommended way to interact with the project. + +### Core Scripts +| Script | Description | +| --------------- | ----------------------------------------------------------------------- | +| `./auto/run` | Runs the application locally with the `local` profile. | +| `./auto/build` | Cleans, generates proto files, and builds the project (skipping tests). | +| `./auto/test` | Runs all tests with coverage reports. | +| `./auto/format` | Applies code formatting using Spotless. | +| `./auto/clean` | Cleans the build directory. | + +### Docker Operations +| Script | Description | +| --------------------- | -------------------------------------------------------- | +| `./auto/docker_start` | Starts the entire stack (App + DB) using Docker Compose. | +| `./auto/docker_stop` | Stops all running containers for the project. | +| `./auto/docker_logs` | Tails the logs for the application container. | + +### Advanced +| Script | Description | +| -------------------- | ------------------------------------------- | +| `./auto/buf-gen` | Generates code from gRPC proto definitions. | +| `./auto/setup-hooks` | Sets up local git hooks for the project. | + +## Detailed Workflows + +### Running Locally +To start the application with local environment variables and a PostgreSQL database (via Docker Compose): +```bash +./auto/run +``` + +### Building the Project +To compile and build the JAR file: +```bash +./auto/build +``` + +### Testing +We use JUnit 5 and Testcontainers for testing. +```bash +# Run all tests and generate coverage +./auto/test +``` +*Stack: JUnit 5, Mockito, Testcontainers (PostgreSQL), AssertJ, WebTestClient.* + +### Code Quality +Maintain code style and verify architecture boundaries: +```bash +# Format code +./auto/format + +# Verify Modulith architecture +./gradlew test --tests "org.nkcoder.ModulithArchitectureTest" +``` + +## Local Testing Environment +- `spring-boot-docker-compose`: Automatically manages the `docker-compose.yml` for local development. +- `application-test.yml`: Test-specific configuration. diff --git a/docs/testing.md b/docs/testing.md deleted file mode 100644 index 80c5350..0000000 --- a/docs/testing.md +++ /dev/null @@ -1,18 +0,0 @@ -## Unit testing - -- Junit 5 -- Mockito -- WebMvcTest - -## Integration testing - -- Spring Boot Test -- Testcontainers -- Testcontainers PostgreSQL -- Testcontainers Junit Jupiter - -## Local running - -- spring-boot-docker-compose (docker-compose.yml) -- application-test.yml - diff --git a/springboot-oauth2-demo.gif b/springboot-oauth2-demo.gif new file mode 100644 index 0000000..bd012c5 Binary files /dev/null and b/springboot-oauth2-demo.gif differ