Skip to content

FoodchainGroup08/user-service

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

19 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

User Service

Handles authentication and user management for the FoodChain platform. Provides JWT-based login, registration, token refresh, password reset, Google OAuth2 sign-in, and role-based user administration.


Table of Contents


Tech Stack

  • Java 17 / Spring Boot 3.2.3
  • Spring Security — JWT filter chain, stateless sessions
  • Spring Data JPA — MySQL 8 persistence
  • Spring Data Redis — refresh token storage and token blacklist
  • Spring Cloud — Eureka service discovery, Config Server (optional in standalone mode)
  • jjwt 0.12.6 — JWT generation and validation
  • Springdoc OpenAPI 2.3.0 — Swagger UI

Running the Service

Standalone with Docker

Spins up MySQL, Redis, and the service — no Eureka or Config Server required.

Step 1 — build the JAR:

cd user-service
mvn package -Dmaven.test.skip=true

Step 2 — start the stack:

docker compose -f docker-compose.dev.yml up --build
URL Description
http://localhost:8081/api/swagger-ui/index.html Swagger UI
http://localhost:8081/api/actuator/health Health check

Ports exposed on the host:

Service Host Port Container Port
user-service 8081 8081
MySQL 3307 3306
Redis 6380 6379

To stop:

docker compose -f docker-compose.dev.yml down

To stop and wipe data volumes:

docker compose -f docker-compose.dev.yml down -v

Local (Maven)

Requires MySQL and Redis already running locally.

cd user-service
mvn spring-boot:run

Or with overrides:

mvn spring-boot:run \
  -Dspring-boot.run.arguments="--DB_HOST=localhost --DB_NAME=user_db --DB_USERNAME=root --DB_PASSWORD=secret"

Environment Variables

Variable Default Description
DB_HOST localhost MySQL host
DB_NAME user_db MySQL database name
DB_USERNAME root MySQL username
DB_PASSWORD devpassword MySQL password
REDIS_HOST localhost Redis host
REDIS_PORT 6379 Redis port
REDIS_PASSWORD (empty) Redis password
JWT_SECRET (required, 32+ chars) HMAC-SHA secret for signing JWTs
JWT_ACCESS_EXPIRY_MS 900000 Access token lifetime (ms) — default 15 min
JWT_REFRESH_EXPIRY_MS 604800000 Refresh token lifetime (ms) — default 7 days
FRONTEND_URL http://localhost:5173 Used in password-reset email links
OAUTH2_ENABLED false Enable server-side Google OAuth2 flow

API Base URL

http://localhost:8081/api

All paths below are relative to this base.


Authentication

Protected endpoints require a Bearer token in the Authorization header:

Authorization: Bearer <access_token>

The access token is returned by /auth/login, /auth/register, /auth/google, and /auth/refresh.


Roles

Role values are serialised as display names in all API responses and accepted as display names in requests.

Display name (JSON) Enum constant Description
Customer CUSTOMER Default role. Can view and manage their own profile.
Kitchen Staff KITCHEN_STAFF Kitchen operations staff. Must be assigned a branch.
Branch Manager BRANCH_MANAGER Manages a specific branch. Must be assigned a branch.
Admin HEAD_OFFICE_ADMIN Full administrative access across all users and branches.

Spring Security authority strings continue to use the enum constant names prefixed with ROLE_ internally (e.g. ROLE_HEAD_OFFICE_ADMIN).


Auth Endpoints

POST /auth/register

Creates a new user account. Returns the created user's profile.

Request body:

{
  "name": "John Doe",
  "email": "john@example.com",
  "password": "Secure@123!",
  "role": "Customer",
  "branchId": null
}
Field Type Required Notes
name string No Full name
email string Yes Must be a valid email address
password string Yes See Password Rules
role string No Defaults to Customer. One of: Customer, Kitchen Staff, Branch Manager, Admin
branchId UUID No Required for Branch Manager and Kitchen Staff. Leave null for Customer

Response 201 Created:

{
  "id": "7264-58d3-...",
  "name": "John Doe",
  "email": "john@example.com",
  "role": "Customer",
  "branchId": null,
  "isActive": true,
  "createdAt": "2026-05-10T04:30:00Z"
}

Error responses:

Code Reason
400 Validation failed (invalid email, weak password, email already registered)

POST /auth/login

Authenticates a user and returns a token pair. This endpoint is intercepted by the JWT filter — it does not reach the controller method.

Request body:

{
  "email": "john@example.com",
  "password": "Secure@123!"
}

Response 200 OK:

{
  "accessToken": "eyJhbGciOiJIUzI1NiJ9...",
  "token": "eyJhbGciOiJIUzI1NiJ9...",
  "refreshToken": "eyJhbGciOiJIUzI1NiJ9...",
  "tokenType": "Bearer",
  "expiresIn": 900,
  "user": {
    "id": "7264-58d3-...",
    "name": "John Doe",
    "email": "john@example.com",
    "role": "Customer",
    "branchId": null,
    "isActive": true,
    "createdAt": "2026-05-10T04:30:00Z"
  }
}
Field Type Description
accessToken string Short-lived JWT. Include in Authorization: Bearer <token> header
token string Alias for accessToken — same value, provided for frontend convenience
refreshToken string Long-lived token. Use with /auth/refresh to get a new pair
tokenType string Always "Bearer"
expiresIn number Access token lifetime in seconds (default 900 = 15 min)
user object The authenticated user's profile

Error responses:

Code Reason
401 Invalid email or password

POST /auth/google

Authenticates using a Google ID token obtained from Google Identity Services on the client side. Creates an account automatically if the email is not yet registered.

Request body:

{
  "credential": "eyJhbGciOiJSUzI1NiIsImtpZCI6Ii4uLiJ9..."
}
Field Type Required Notes
credential string Yes JWT ID token from window.google.accounts.id.initialize callback

Response 200 OK: Same shape as login response.

Error responses:

Code Reason
400 Invalid or expired Google credential

POST /auth/refresh

Exchanges a valid refresh token for a new access token and refresh token. The old refresh token is deleted (rotation).

Request body:

{
  "refreshToken": "eyJhbGciOiJIUzI1NiJ9..."
}

Response 200 OK: Same shape as login response with a new token pair.

Error responses:

Code Reason
400 Refresh token is missing or blank
401 Refresh token is expired, invalid, or already rotated
404 The user associated with the token no longer exists

POST /auth/logout

Invalidates the access token (adds the JTI to the blacklist) and deletes the refresh token from Redis.

Both the Authorization header and the request body are optional — provide what you have.

Headers (optional):

Authorization: Bearer <access_token>

Request body (optional):

{
  "refreshToken": "eyJhbGciOiJIUzI1NiJ9..."
}

Response 204 No Content — no body.


POST /auth/forgot-password

Sends a one-time password-reset link to the given email address. Always returns 200 regardless of whether the email is registered (prevents user enumeration).

Request body:

{
  "email": "john@example.com"
}

Response 200 OK:

{
  "message": "If that email is registered, a reset link has been sent."
}

POST /auth/reset-password

Sets a new password using the one-time token from the reset link.

Request body:

{
  "token": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
  "newPassword": "NewSecure@456!"
}
Field Type Required Notes
token string Yes One-time token received via email
newPassword string Yes Must satisfy Password Rules

Response 200 OK:

{
  "message": "Password updated successfully."
}

Error responses:

Code Reason
400 Token is invalid, expired, or new password is too weak

GET /auth/me

Returns the currently authenticated user's profile. Used for session rehydration on page load.

Headers:

Authorization: Bearer <access_token>

Response 200 OK:

{
  "id": "7264-58d3-...",
  "name": "John Doe",
  "email": "john@example.com",
  "role": "Customer",
  "branchId": null,
  "isActive": true,
  "createdAt": "2026-05-10T04:30:00Z"
}

Error responses:

Code Reason
401 Missing or invalid token

User Endpoints

GET /users/me

Returns the profile of the currently authenticated user.

Headers:

Authorization: Bearer <access_token>

Response 200 OK: See UserResponse shape above.

Error responses:

Code Reason
401 Missing or invalid token

GET /users

Returns all users. Requires Admin role.

Headers:

Authorization: Bearer <access_token>

Response 200 OK:

[
  {
    "id": "7264-58d3-...",
    "name": "John Doe",
    "email": "john@example.com",
    "role": "Customer",
    "branchId": null,
    "isActive": true,
    "createdAt": "2026-05-10T04:30:00Z"
  }
]

Error responses:

Code Reason
401 Missing or invalid token
403 Authenticated but not Admin

GET /users/{id}

Returns a single user's profile by UUID. Requires Admin or Branch Manager role.

Headers:

Authorization: Bearer <access_token>

Path parameter:

Parameter Type Description
id UUID The user's unique identifier

Response 200 OK: See UserResponse shape above.

Error responses:

Code Reason
401 Missing or invalid token
403 Insufficient role
404 User not found

PATCH /users/{id}

Partially updates a user. All fields are optional — only provided fields are changed. Requires Admin or Branch Manager role.

Headers:

Authorization: Bearer <access_token>

Path parameter:

Parameter Type Description
id UUID The user's unique identifier

Request body (all fields optional):

{
  "role": "Branch Manager",
  "branchId": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
  "isActive": true
}
Field Type Notes
role string One of: Customer, Kitchen Staff, Branch Manager, Admin
branchId UUID | null Set to null to remove branch association
isActive boolean Set to false to deactivate the account

Response 200 OK: Updated user profile (see UserResponse shape).

Error responses:

Code Reason
401 Missing or invalid token
403 Insufficient role
404 User not found

Admin User Endpoints

These endpoints read the X-User-Role header forwarded by the API gateway. Accepted values: Admin or HEAD_OFFICE_ADMIN. No JWT verification is performed inside the service — the gateway is expected to validate the token and set the header.

GET /admin/users

List all users. Optionally filter by role.

Request headersX-User-Role: Admin

Query parameters

Name Required Description
role No Filter by role display name: Customer, Kitchen Staff, Branch Manager, Admin

Response 200 OK:

[
  {
    "id": "7264-58d3-...",
    "name": "Alice",
    "email": "alice@example.com",
    "role": "Customer",
    "status": "active",
    "branchId": null
  }
]

Error responses:

Code Reason
400 Unrecognised role filter value
403 Caller is not an Admin

GET /admin/users/{id}

Fetch a single user by UUID.

Request headersX-User-Role: Admin

Path parameter:

Parameter Type Description
id UUID The user's unique identifier

Response 200 OK: Single AdminUserResponse object (same fields as list item).

Error responses:

Code Reason
403 Caller is not an Admin
404 User not found

PATCH /admin/users/{id}/status

Activate or deactivate a user account.

Request headersX-User-Role: Admin

Path parameter:

Parameter Type Description
id UUID The user's unique identifier

Request body:

{ "status": "inactive" }

Accepted values: active, inactive.

Response 200 OK: Updated AdminUserResponse.

Error responses:

Code Reason
400 Invalid status value
403 Caller is not an Admin
404 User not found

Error Responses

All errors return a consistent JSON body:

{
  "status": 401,
  "error": "Unauthorized",
  "message": "Authentication required — provide a valid Bearer token",
  "path": "/api/users",
  "timestamp": "2026-05-10T04:41:48Z"
}

For validation errors (400), a fields map is also included:

{
  "status": 400,
  "error": "Validation Failed",
  "message": "One or more fields are invalid",
  "path": "/api/auth/register",
  "timestamp": "2026-05-10T04:41:48Z",
  "fields": {
    "password": "Password must be at least 8 characters and include at least one uppercase letter, one lowercase letter, one digit, and one special character (@#$!%*?&-_+=)",
    "email": "Invalid email format"
  }
}
Status Meaning
400 Bad request — validation failed or duplicate email
401 Unauthenticated — missing, expired, or invalid token
403 Forbidden — authenticated but insufficient role
404 Resource not found
500 Unexpected server error

Password Rules

Passwords must satisfy all of the following:

Rule Requirement
Minimum length 8 characters
Uppercase letter At least one (A–Z)
Lowercase letter At least one (a–z)
Digit At least one (0–9)
Special character At least one from @ # $ ! % * ? & - _ + =

Valid example: Secure@123!

Invalid examples:

Password Reason
password No uppercase, digit, or special character
Password1 No special character
SECURE@1 No lowercase letter
Secure@! No digit
Se@1 Too short

Swagger UI

Interactive API explorer available at:

http://localhost:8081/api/swagger-ui/index.html

To test protected endpoints:

  1. Call POST /auth/login or POST /auth/register using Try it out
  2. Copy the accessToken from the response
  3. Click the Authorize button (top right)
  4. Paste the token (without the Bearer prefix) and click Authorize
  5. All subsequent requests will include the token automatically

About

Authentication and user services

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors