From b2ac418ae5b31b3800d35899cb8301b6d18a5a62 Mon Sep 17 00:00:00 2001 From: Dave the Dev Date: Tue, 14 Apr 2026 11:50:39 +0000 Subject: [PATCH 01/18] Hithomelabs/CFTunnels#114: Add JavaDoc documentation to main application class --- .../CFTunnels/CfTunnelsApplication.java | 40 +++++++++++++++++-- 1 file changed, 36 insertions(+), 4 deletions(-) diff --git a/src/main/java/com/hithomelabs/CFTunnels/CfTunnelsApplication.java b/src/main/java/com/hithomelabs/CFTunnels/CfTunnelsApplication.java index 1ea81fd..1b46911 100644 --- a/src/main/java/com/hithomelabs/CFTunnels/CfTunnelsApplication.java +++ b/src/main/java/com/hithomelabs/CFTunnels/CfTunnelsApplication.java @@ -1,13 +1,45 @@ package com.hithomelabs.CFTunnels; -import org.springframework.boot.SpringApplication; -import org.springframework.boot.autoconfigure.SpringBootApplication; - +/** + * Main Spring Boot application class for Cloudflare Tunnels API. + * + *

This application provides a RESTful API for managing Cloudflare Tunnels, + * allowing users to create tunnel mappings to services with an approval workflow.

+ * + *

Features:

+ * + * + *

Technology Stack:

+ * + * + *

Access the API documentation at: + * {@code /swagger-ui.html} for the Swagger/OpenAPI UI

+ * + * @see Cloudflare Tunnel Documentation + * @since 1.0.0 + */ @SpringBootApplication public class CfTunnelsApplication { + /** + * Main entry point for the application. + * + * @param args command line arguments passed to the application + */ public static void main(String[] args) { SpringApplication.run(CfTunnelsApplication.class, args); } -} +} \ No newline at end of file -- 2.45.2 From 6bf138ea7aacb058ec74c269f05b619299fe0a05 Mon Sep 17 00:00:00 2001 From: Dave the Dev Date: Tue, 14 Apr 2026 11:51:07 +0000 Subject: [PATCH 02/18] Hithomelabs/CFTunnels#114: Add JavaDoc to Tunnel entity --- .../hithomelabs/CFTunnels/Entity/Tunnel.java | 43 ++++++++++++++++++- 1 file changed, 41 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/hithomelabs/CFTunnels/Entity/Tunnel.java b/src/main/java/com/hithomelabs/CFTunnels/Entity/Tunnel.java index 3457c9e..8892caf 100644 --- a/src/main/java/com/hithomelabs/CFTunnels/Entity/Tunnel.java +++ b/src/main/java/com/hithomelabs/CFTunnels/Entity/Tunnel.java @@ -8,21 +8,60 @@ import lombok.Setter; import java.util.UUID; +/** + * JPA Entity representing a Cloudflare Tunnel configuration. + * + *

This entity stores the locally cached configuration of a Cloudflare Tunnel, + * linking the tunnel's unique identifier from Cloudflare with its local + * environment name for easier reference and management.

+ * + *

The entity is uniquely constrained by tunnel ID and environment name, + * ensuring only one configuration exists per tunnel per environment.

+ * + *

Database Table: {@code tunnels}

+ * + *

Relationships:

+ * + * + * @see Mapping + * @see Request + */ @Entity @Getter @Setter @NoArgsConstructor @AllArgsConstructor -@Table(name="tunnels") +@Table(name = "tunnels") public class Tunnel { + /** + * Unique identifier for the tunnel (UUID). + * + *

This corresponds to the Cloudflare-assigned tunnel ID + * and serves as the primary key for this entity.

+ */ @Id @Column(columnDefinition = "uuid", insertable = false, updatable = false, nullable = false) private UUID id; + /** + * Environment name associated with this tunnel configuration. + * + *

Examples: "production", "staging", "development"

+ *

Each tunnel can be configured for multiple environments.

+ */ @Column(length = 10, unique = true, nullable = false) private String environment; + /** + * Display name of the tunnel as configured in Cloudflare. + * + *

This is the user-friendly name assigned to the tunnel + * when it was created in Cloudflare Zero Trust Dashboard + * or via the Cloudflare API.

+ */ @Column(length = 50, unique = true, nullable = false) private String name; -} +} \ No newline at end of file -- 2.45.2 From e8d535efdabb0d389f91be015cb535c13a33bf4a Mon Sep 17 00:00:00 2001 From: Dave the Dev Date: Tue, 14 Apr 2026 11:51:19 +0000 Subject: [PATCH 03/18] Hithomelabs/CFTunnels#114: Add JavaDoc to Mapping entity --- .../hithomelabs/CFTunnels/Entity/Mapping.java | 52 ++++++++++++++++++- 1 file changed, 50 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/hithomelabs/CFTunnels/Entity/Mapping.java b/src/main/java/com/hithomelabs/CFTunnels/Entity/Mapping.java index 113f740..e89f674 100644 --- a/src/main/java/com/hithomelabs/CFTunnels/Entity/Mapping.java +++ b/src/main/java/com/hithomelabs/CFTunnels/Entity/Mapping.java @@ -6,9 +6,29 @@ import lombok.Getter; import lombok.NoArgsConstructor; import lombok.Setter; - import java.util.UUID; +/** + * JPA Entity representing an ingress mapping configuration for a Cloudflare Tunnel. + * + *

A mapping defines how incoming traffic should be routed through the tunnel + * to your internal services. It specifies the protocol, port, and subdomain + * where the service will be accessible.

+ * + *

Database Table: {@code mappings}

+ * + *

Example Usage:

+ *
+ * Mapping mapping = new Mapping();
+ * mapping.setPort(8080);
+ * mapping.setProtocol(Protocol.HTTP);
+ * mapping.setSubdomain("api");
+ * 
+ * + * @see Tunnel + * @see Protocol + * @see Request + */ @Entity @Getter @Setter @@ -17,22 +37,50 @@ import java.util.UUID; @Table(name = "mappings") public class Mapping { + /** + * Unique identifier for this mapping (auto-generated UUID). + */ @Id @GeneratedValue @Column(columnDefinition = "uuid", nullable = false, unique = true) private UUID id; + /** + * Port number where the internal service is running. + * + *

Example: 8080 for a Spring Boot application's default port.

+ */ @Column(nullable = false) private int port; + /** + * Protocol used for the connection. + * + *

Supported values: HTTP, HTTPS, TCP, SSH

+ * @see Protocol + */ @Enumerated(EnumType.STRING) @Column(length = 10, nullable = false) private Protocol protocol; + /** + * Subdomain prefix for accessing this service. + * + *

Example: "api" would make the service available at + * {@code api.yourdomain.com}

+ */ @Column(length = 50, nullable = false) private String subdomain; + /** + * The tunnel this mapping is associated with. + * + *

Each mapping belongs to exactly one tunnel. + * This is a lazy-loaded relationship to optimize performance.

+ * + * @see Tunnel + */ @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "tunnel_id", nullable = false) private Tunnel tunnel; -} +} \ No newline at end of file -- 2.45.2 From a7e7ea4a39a9cb23aaa1af79b0aabf2b32565a5e Mon Sep 17 00:00:00 2001 From: Dave the Dev Date: Tue, 14 Apr 2026 11:51:28 +0000 Subject: [PATCH 04/18] Hithomelabs/CFTunnels#114: Add JavaDoc to Protocol enum --- .../CFTunnels/Entity/Protocol.java | 37 ++++++++++++++++++- 1 file changed, 36 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/hithomelabs/CFTunnels/Entity/Protocol.java b/src/main/java/com/hithomelabs/CFTunnels/Entity/Protocol.java index e927c47..0932524 100644 --- a/src/main/java/com/hithomelabs/CFTunnels/Entity/Protocol.java +++ b/src/main/java/com/hithomelabs/CFTunnels/Entity/Protocol.java @@ -1,8 +1,43 @@ package com.hithomelabs.CFTunnels.Entity; +/** + * Enum representing supported protocols for Cloudflare Tunnel ingress mappings. + * + *

Cloudflare Tunnel supports multiple protocols for routing traffic + * to your internal services. This enum defines the available options.

+ * + *

Supported Protocols:

+ *
    + *
  • {@link #HTTP} - Standard HTTP protocol (port 80)
  • + *
  • {@link #HTTPS} - Secure HTTP protocol (port 443)
  • + *
  • {@link #TCP} - Raw TCP connections
  • + *
  • {@link #SSH} - SSH protocol for remote access
  • + *
+ * + * @see Mapping + */ public enum Protocol { + /** + * HTTP protocol for web services. + * Typically used with internal services running on port 80. + */ HTTP, + + /** + * HTTPS protocol for secure web services. + * Use this for services with TLS/SSL certificates. + */ HTTPS, + + /** + * Raw TCP protocol for non-HTTP services. + * Useful for databases, message queues, or custom protocols. + */ TCP, + + /** + * SSH protocol for secure shell access. + * Enables secure remote access to servers. + */ SSH -} +} \ No newline at end of file -- 2.45.2 From e8f158aeceff41b8678882ae75967d74053ca914 Mon Sep 17 00:00:00 2001 From: Dave the Dev Date: Tue, 14 Apr 2026 11:51:39 +0000 Subject: [PATCH 05/18] Hithomelabs/CFTunnels#114: Add JavaDoc to Request entity --- .../hithomelabs/CFTunnels/Entity/Request.java | 77 ++++++++++++++++++- 1 file changed, 76 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/hithomelabs/CFTunnels/Entity/Request.java b/src/main/java/com/hithomelabs/CFTunnels/Entity/Request.java index 8d73de8..c66bcb4 100644 --- a/src/main/java/com/hithomelabs/CFTunnels/Entity/Request.java +++ b/src/main/java/com/hithomelabs/CFTunnels/Entity/Request.java @@ -8,6 +8,31 @@ import lombok.Setter; import java.util.UUID; +/** + * JPA Entity representing a mapping change request in the approval workflow. + * + *

This entity tracks requests to add or modify tunnel ingress mappings. + * It implements an approval workflow where:

+ *
    + *
  • Users create requests (status: PENDING)
  • + *
  • Approvers review and approve/reject (status: APPROVED/REJECTED)
  • + *
+ * + *

Database Table: {@code requests}

+ * + *

Workflow:

+ *
+ * 1. User submits mapping request via REST API
+ * 2. Request is created with PENDING status
+ * 3. APPROVER role reviews the request
+ * 4. If approved: mapping is applied to Cloudflare tunnel
+ * 5. If rejected: request is marked REJECTED
+ * 
+ * + * @see Mapping + * @see User + * @see RequestStatus + */ @Entity @Getter @Setter @@ -16,30 +41,80 @@ import java.util.UUID; @Table(name = "requests") public class Request { + /** + * Unique identifier for this request (auto-generated UUID). + */ @Id @GeneratedValue @Column(columnDefinition = "uuid", unique = true, nullable = false) private UUID id; + /** + * The mapping configuration being requested. + * + *

This is a one-to-one relationship - each request + * contains exactly one mapping configuration.

+ * + * @see Mapping + */ @OneToOne @JoinColumn(name = "mapping_id", unique = true, nullable = false) private Mapping mapping; + /** + * User who created this request. + * + *

This field is required and tracks accountability.

+ * + * @see User + */ @ManyToOne @JoinColumn(name = "created_by", nullable = false) private User createdBy; + /** + * User who approved or rejected this request. + * + *

This is null until the request is processed.

+ * + * @see User + */ @ManyToOne @JoinColumn(name = "accepted_by") private User acceptedBy; + /** + * Status of the mapping request in the workflow. + * + * @see RequestStatus + */ public enum RequestStatus { + /** + * Request is waiting for approval. + */ PENDING, + + /** + * Request has been approved. + * The mapping should now be applied to the tunnel. + */ APPROVED, + + /** + * Request has been rejected. + * No changes will be made to the tunnel. + */ REJECTED } + /** + * Current status of this request. + * + *

Initially set to PENDING when created.

+ * + * @see RequestStatus + */ @Enumerated(EnumType.STRING) @Column(length = 10, nullable = false) private RequestStatus status; -} +} \ No newline at end of file -- 2.45.2 From c6b466530fc82ed3df8876ea9121128c8efe7a3e Mon Sep 17 00:00:00 2001 From: Dave the Dev Date: Tue, 14 Apr 2026 11:51:48 +0000 Subject: [PATCH 06/18] Hithomelabs/CFTunnels#114: Add JavaDoc to User entity --- .../hithomelabs/CFTunnels/Entity/User.java | 40 ++++++++++++++++++- 1 file changed, 39 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/hithomelabs/CFTunnels/Entity/User.java b/src/main/java/com/hithomelabs/CFTunnels/Entity/User.java index 5783357..eb1336b 100644 --- a/src/main/java/com/hithomelabs/CFTunnels/Entity/User.java +++ b/src/main/java/com/hithomelabs/CFTunnels/Entity/User.java @@ -1,4 +1,5 @@ package com.hithomelabs.CFTunnels.Entity; + import jakarta.persistence.*; import jakarta.validation.constraints.Size; import lombok.AllArgsConstructor; @@ -8,6 +9,26 @@ import lombok.Setter; import java.util.UUID; +/** + * JPA Entity representing a user in the system. + * + *

This entity stores user information synced from the OIDC provider + * during authentication. Users are assigned roles that determine their + * access levels to the API endpoints.

+ * + *

Database Table: {@code users}

+ * + *

Roles:

+ *
    + *
  • USER - Basic access to view tunnels
  • + *
  • DEVELOPER - Can create/modify mappings
  • + *
  • APPROVER - Can approve/reject requests
  • + *
  • ADMIN - Full access including tunnel configuration
  • + *
+ * + * @see Request + * @see Mapping + */ @Entity @Getter @Setter @@ -15,16 +36,33 @@ import java.util.UUID; @AllArgsConstructor @Table(name = "users") public class User { + + /** + * Unique identifier for the user (UUID). + * + *

This corresponds to the user's ID in the OIDC provider.

+ */ @Id @GeneratedValue @Column(columnDefinition = "uuid", insertable = false, updatable = false, nullable = false) private UUID id; + /** + * User's display name. + * + *

This is typically the full name from the OIDC provider.

+ */ @Column(length = 50, nullable = false) @Size(max = 50) private String name; + /** + * User's email address. + * + *

Used as the unique identifier for authentication + * and for associating users with their roles.

+ */ @Column(length = 50, nullable = false) @Size(max = 50) private String email; -} +} \ No newline at end of file -- 2.45.2 From df7ea9a2df9c821a22f3bd520e3d92e73cfc4360 Mon Sep 17 00:00:00 2001 From: Dave the Dev Date: Tue, 14 Apr 2026 11:52:08 +0000 Subject: [PATCH 07/18] Hithomelabs/CFTunnels#114: Add JavaDoc to CloudflareAPIService --- .../Services/CloudflareAPIService.java | 101 +++++++++++++++++- 1 file changed, 98 insertions(+), 3 deletions(-) diff --git a/src/main/java/com/hithomelabs/CFTunnels/Services/CloudflareAPIService.java b/src/main/java/com/hithomelabs/CFTunnels/Services/CloudflareAPIService.java index cc7eaeb..6d0084a 100644 --- a/src/main/java/com/hithomelabs/CFTunnels/Services/CloudflareAPIService.java +++ b/src/main/java/com/hithomelabs/CFTunnels/Services/CloudflareAPIService.java @@ -22,21 +22,60 @@ import java.util.NoSuchElementException; import java.util.Optional; import java.util.UUID; +/** + * Service for interacting with the Cloudflare Tunnel API. + * + *

This service provides methods to manage Cloudflare Tunnels, + * including fetching tunnel configurations, creating/updating tunnels, + * and adding ingress mappings. It handles all communication with the + * Cloudflare API using the configured credentials.

+ * + *

API Endpoints Used:

+ *
    + *
  • GET /accounts/{accountId}/cfd_tunnel - List all tunnels
  • + *
  • GET /accounts/{accountId}/cfd_tunnel/{tunnelId}/configurations - Get tunnel config
  • + *
  • PUT /accounts/{accountId}/cfd_tunnel/{tunnelId}/configurations - Update tunnel config
  • + *
+ * + * @see CloudflareConfig + * @see Tunnel + * @see Ingress + */ @Service public class CloudflareAPIService { + /** + * Configuration for Cloudflare API credentials and settings. + * Loaded from application.properties using the {@code cloudflare.*} prefix. + */ @Autowired CloudflareConfig cloudflareConfig; + /** + * Header provider for Cloudflare API authentication. + * Generates the X-Auth-Key and X-Auth-Email headers. + */ @Autowired AuthKeyEmailHeader authKeyEmailHeader; + /** + * HTTP client for making API requests. + */ @Autowired RestTemplate restTemplate; + /** + * Repository for storing tunnel configurations locally. + */ @Autowired TunnelRepository tunnelRepository; + /** + * Fetches all Cloudflare tunnels from the API. + * + * @return Response containing the list of tunnels from Cloudflare + * @see TunnelsResponse + */ public ResponseEntity getCloudflareTunnels() { String url = "https://api.cloudflare.com/client/v4/accounts/" + cloudflareConfig.getAccountId() + "/cfd_tunnel"; @@ -46,9 +85,18 @@ public class CloudflareAPIService { return responseEntity; } + /** + * Fetches the configuration for a specific tunnel. + * + * @param tunnelId The Cloudflare tunnel ID (UUID string) + * @param restTemplate HTTP client to use for the request + * @param responseType Class to deserialize the response into + * @param Response type + * @return Response containing the tunnel configuration + */ public ResponseEntity getCloudflareTunnelConfigurations(String tunnelId, RestTemplate restTemplate, Class responseType) { - // * * Resource URL to hit get request at + // Resource URL to hit get request at String url = "https://api.cloudflare.com/client/v4/accounts/" + cloudflareConfig.getAccountId() + "/cfd_tunnel/" + tunnelId + "/configurations"; HttpEntity httpEntity = new HttpEntity<>("",authKeyEmailHeader.getHttpHeaders()); @@ -56,9 +104,22 @@ public class CloudflareAPIService { return responseEntity; } + /** + * Updates the configuration for a specific tunnel. + * + *

This method is used to add, modify, or remove ingress mappings + * by providing a complete configuration object.

+ * + * @param tunnelId The Cloudflare tunnel ID (UUID string) + * @param restTemplate HTTP client to use for the request + * @param responseType Class to deserialize the response into + * @param config The new configuration to apply + * @param Response type + * @return Response containing the updated tunnel configuration + */ public ResponseEntity putCloudflareTunnelConfigurations(String tunnelId, RestTemplate restTemplate, Class responseType, Config config) { - // * * Resource URL to hit get request at + // Resource URL to hit get request at String url = "https://api.cloudflare.com/client/v4/accounts/" + cloudflareConfig.getAccountId() + "/cfd_tunnel/" + tunnelId + "/configurations"; HttpHeaders httpHeaders = authKeyEmailHeader.getHttpHeaders(); @@ -68,10 +129,29 @@ public class CloudflareAPIService { return responseEntity; } + /** + * Converts a Cloudflare API tunnel result to a local Tunnel entity. + * + * @param tunnelResult The tunnel result from Cloudflare API + * @param env The environment name to associate + * @return New Tunnel entity instance + */ private Tunnel getTunnelFromTunnelResponse(TunnelResult tunnelResult, String env){ return new Tunnel(UUID.fromString(tunnelResult.getId()), env, tunnelResult.getName()); } + /** + * Creates or updates a tunnel's local configuration. + * + *

This method fetches the tunnel from Cloudflare API, validates it exists, + * and stores a local copy with the environment association.

+ * + * @param tunnelId The Cloudflare tunnel ID + * @param environment Environment name (e.g., "production", "staging") + * @return The created or updated Tunnel entity + * @throws ExternalServiceException if Cloudflare API returns an error + * @throws NoSuchElementException if the tunnel doesn't exist in Cloudflare + */ public Tunnel createOrUpdateTunnel(String tunnelId, String environment) throws ExternalServiceException, NoSuchElementException { ResponseEntity responseEntity = getCloudflareTunnels(); @@ -92,10 +172,25 @@ public class CloudflareAPIService { return toBeConfigured; } + /** + * Retrieves all tunnels that have been locally configured. + * + * @return List of locally configured tunnels + */ public List getAllConfiguredTunnels() { return tunnelRepository.findAll(); } + /** + * Adds an ingress mapping to an existing tunnel. + * + *

The new ingress is inserted at the second-to-last position in the + * ingress list (Cloudflare requires a catch-all rule at the end).

+ * + * @param tunnelId The Cloudflare tunnel ID + * @param ingress The ingress configuration to add + * @return Response with the updated tunnel configuration + */ public ResponseEntity addTunnelIngress(String tunnelId, Ingress ingress) { ResponseEntity currentConfig = getCloudflareTunnelConfigurations(tunnelId, restTemplate, TunnelResponse.class); @@ -105,4 +200,4 @@ public class CloudflareAPIService { return putCloudflareTunnelConfigurations(tunnelId, restTemplate, TunnelResponse.class, config); } -} +} \ No newline at end of file -- 2.45.2 From 4197e645cdb205f4543a60a8ae254a960cfb9d73 Mon Sep 17 00:00:00 2001 From: Dave the Dev Date: Tue, 14 Apr 2026 11:52:21 +0000 Subject: [PATCH 08/18] Hithomelabs/CFTunnels#114: Add JavaDoc to Ingress model --- .../hithomelabs/CFTunnels/Models/Ingress.java | 71 ++++++++++++++++++- 1 file changed, 70 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/hithomelabs/CFTunnels/Models/Ingress.java b/src/main/java/com/hithomelabs/CFTunnels/Models/Ingress.java index 005a02b..05d22ab 100644 --- a/src/main/java/com/hithomelabs/CFTunnels/Models/Ingress.java +++ b/src/main/java/com/hithomelabs/CFTunnels/Models/Ingress.java @@ -8,19 +8,88 @@ import lombok.Setter; import java.util.List; import java.util.Map; +/** + * Model representing an ingress rule for a Cloudflare Tunnel. + * + *

Ingress rules define how incoming requests should be routed through + * the tunnel to your internal services. Each rule specifies:

+ *
    + *
  • {@link #hostname} - The domain/subdomain to match
  • + *
  • {@link #service} - The internal service URL
  • + *
  • {@link #originRequest} - Optional settings for origin requests
  • + *
  • {@link #path} - Optional path prefix to match
  • + *
+ * + *

Example JSON:

+ *
+ * {
+ *   "hostname": "api.example.com",
+ *   "service": "http://localhost:8080",
+ *   "originRequest": {
+ *     "noTLSVerify": true
+ *   },
+ *   "path": "/api"
+ * }
+ * 
+ * + * @see Cloudflare Ingress Docs + */ @NoArgsConstructor @AllArgsConstructor @Getter @Setter public class Ingress { + /** + * The target service URL. + * + *

Format: {@code protocol://host:port}

+ *

Example: {@code http://localhost:8080}

+ */ private String service; + + /** + * Hostname pattern to match for this ingress rule. + * + *

Can be a full domain (api.example.com) or use wildcards + * (*.example.com) to match subdomains.

+ *

If null, this rule acts as a catch-all.

+ */ private String hostname; + + /** + * Optional settings for requests to the origin server. + * + *

Supported options:

+ *
    + *
  • noTLSVerify - Skip TLS verification
  • + *
  • connectTimeout - Connection timeout in seconds
  • + *
  • tlsTimeout - TLS handshake timeout
  • + *
  • httpHostHeader - Host header to send
  • + *
+ */ private Map originRequest; + + /** + * Optional path to match before routing. + * + *

Example: "/api" would only route requests with + * paths starting with /api to this service.

+ */ private String path; + /** + * Removes an ingress rule by hostname from a list. + * + *

This utility method finds and removes the first ingress + * matching the given hostname.

+ * + * @param ingressList List of ingress rules to modify + * @param toBeDeleted Hostname of the rule to remove + * @return true if an ingress was removed, false otherwise + */ public static boolean deleteByHostName(List ingressList, String toBeDeleted){ return ingressList.removeIf(ingress -> ingress.getHostname() != null && ingress.getHostname().equals(toBeDeleted)); } -} +} \ No newline at end of file -- 2.45.2 From 7e5d20e8acf4b626e66392d4899a3f07bcc48348 Mon Sep 17 00:00:00 2001 From: Dave the Dev Date: Tue, 14 Apr 2026 11:52:50 +0000 Subject: [PATCH 09/18] Hithomelabs/CFTunnels#114: Add JavaDoc and API documentation to TunnelController --- .../Controllers/TunnelController.java | 229 +++++++++++++++++- 1 file changed, 217 insertions(+), 12 deletions(-) diff --git a/src/main/java/com/hithomelabs/CFTunnels/Controllers/TunnelController.java b/src/main/java/com/hithomelabs/CFTunnels/Controllers/TunnelController.java index 33c4d07..cb77525 100644 --- a/src/main/java/com/hithomelabs/CFTunnels/Controllers/TunnelController.java +++ b/src/main/java/com/hithomelabs/CFTunnels/Controllers/TunnelController.java @@ -22,7 +22,6 @@ import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.web.servlet.error.ErrorController; import org.springframework.dao.DataAccessException; import org.springframework.http.*; -import org.springframework.http.*; import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.annotation.AuthenticationPrincipal; @@ -36,6 +35,41 @@ import java.util.Map; import java.util.NoSuchElementException; import java.util.UUID; +/** + * REST Controller for managing Cloudflare Tunnels. + * + *

This controller provides the public API for managing Cloudflare Tunnels + * and their ingress mappings. All endpoints require authentication via OIDC + * and are protected by role-based access control.

+ * + *

Base URL: {@code /cloudflare}

+ * + *

Authentication: OIDC-based with role-based access

+ * + *

Available Roles:

+ *
    + *
  • USER - View tunnels and requests
  • + *
  • DEVELOPER - Create/modify/delete mappings
  • + *
  • APPROVER - Approve/reject requests
  • + *
  • ADMIN - Full tunnel configuration access
  • + *
+ * + *

Example Usage:

+ *
+ * # Get all tunnels (requires USER role)
+ * curl -H "Authorization: Bearer <token>" \
+ *   https://api.example.com/cloudflare/tunnels
+ * 
+ * # Add a mapping (requires ADMIN role)
+ * curl -X POST -H "Authorization: Bearer <token>" \
+ *   -H "Content-Type: application/json" \
+ *   -d '{"hostname":"api.example.com","service":"http://localhost:8080"}' \
+ *   https://api.example.com/cloudflare/tunnels/{tunnelId}/mappings
+ * 
+ * + * @see CloudflareAPIService + * @see MappingRequestService + */ @RestController @RequestMapping("/cloudflare") public class TunnelController implements ErrorController { @@ -63,9 +97,23 @@ public class TunnelController implements ErrorController { @Autowired private UserRepository userRepository; + /** + * Current environment (loaded from spring.profiles.active). + */ @Value("${spring.profiles.active}") private String environment; + /** + * Get current user information. + * + *

Returns the authenticated user's username and roles.

+ * + * @param oidcUser The authenticated OIDC user + * @return Map containing username and roles + * + * @security Requires USER role + * @response 200 OK + */ @PreAuthorize("hasAnyRole('USER')") @GetMapping("/whoami") public Map whoAmI(@AuthenticationPrincipal OidcUser oidcUser) { @@ -79,6 +127,19 @@ public class TunnelController implements ErrorController { ); } + /** + * Get all tunnels from Cloudflare API. + * + *

Fetches the complete list of tunnels from Cloudflare, + * including their status and configuration from the Cloudflare API.

+ * + * @return Map containing list of all tunnels + * + * @security Requires USER role + * @response 200 OK with tunnel list + * @response 500 Internal Server Error if API call fails + * @see Cloudflare API + */ @PreAuthorize("hasAnyRole('USER')") @GetMapping("/tunnels") @Operation( security = { @SecurityRequirement(name = "oidcAuth") } ) @@ -92,6 +153,19 @@ public class TunnelController implements ErrorController { return ResponseEntity.ok(jsonResponse); } + /** + * Get locally configured tunnels. + * + *

Returns the tunnels that have been configured locally + * with environment associations.

+ * + * @return Map containing list of configured tunnels + * + * @security Requires USER role + * @response 200 OK with tunnel list + * @response 500 Internal Server Error if database access fails + * @see CloudflareAPIService#getAllConfiguredTunnels() + */ @PreAuthorize("hasAnyRole('USER')") @GetMapping("/configured/tunnels") public ResponseEntity> getConfiguredTunnels(){ @@ -106,6 +180,17 @@ public class TunnelController implements ErrorController { } } + /** + * Get all mapping requests. + * + *

Returns all pending, approved, and rejected mapping requests.

+ * + * @return Map containing list of all requests + * + * @security Requires USER role + * @response 200 OK with request list + * @response 500 Internal Server Error if database access fails + */ @PreAuthorize("hasAnyRole('USER')") @GetMapping("/requests") public ResponseEntity> getAllRequests() { @@ -120,6 +205,20 @@ public class TunnelController implements ErrorController { } } + /** + * Get tunnel configuration from Cloudflare. + * + *

Fetches the complete configuration for a specific tunnel, + * including all ingress rules.

+ * + * @param tunnelId The Cloudflare tunnel ID (UUID) + * @return Map containing tunnel configuration + * + * @security Requires DEVELOPER role + * @response 200 OK with configuration + * @response 500 Internal Server Error + * @see Cloudflare API + */ @PreAuthorize("hasAnyRole('DEVELOPER')") @GetMapping("/tunnels/{tunnelId}/mappings") public ResponseEntity> getTunnelConfigurations(@PathVariable String tunnelId) { @@ -132,22 +231,44 @@ public class TunnelController implements ErrorController { return ResponseEntity.ok(jsonResponse); } - // 50df9101-f625-4618-b7c5-100338a57124 + /** + * Add an ingress mapping to a tunnel. + * + *

Adds a new ingress rule to the tunnel configuration. + * The new rule is inserted at the second-to-last position, + * before any catch-all rule.

+ * + * @param tunnelId The Cloudflare tunnel ID (UUID) + * @param ingress The ingress rule to add + * @return Map containing the updated configuration + * + * @security Requires ADMIN role + * @response 200 OK with updated configuration + * @response 400 Bad Request if ingress is invalid + * @response 500 Internal Server Error + * + * @example + * { + * "hostname": "api.example.com", + * "service": "http://localhost:8080", + * "originRequest": {"noTLSVerify": true} + * } + */ @PreAuthorize("hasAnyRole('ADMIN')") @PostMapping("/tunnels/{tunnelId}/mappings") public ResponseEntity> addTunnelconfiguration(@PathVariable String tunnelId, @RequestBody Ingress ingress) throws JsonProcessingException { ResponseEntity responseEntity = cloudflareAPIService.getCloudflareTunnelConfigurations(tunnelId, restTemplateConfig.restTemplate(), TunnelResponse.class); - // * * Inserting new ingress value at second-to last position in list + // Inserting new ingress value at second-to last position in list Config config = responseEntity.getBody().getResult().getConfig(); List response_ingress = config.getIngress(); response_ingress.add(response_ingress.size()-1, ingress); - // * * Hitting put endpoint + // Hitting put endpoint ResponseEntity response = cloudflareAPIService.putCloudflareTunnelConfigurations(tunnelId, restTemplateConfig.restTemplate(), TunnelResponse.class, config); - // * * Displaying response + // Displaying response Map jsonResponse = new HashMap<>(); jsonResponse.put("status", response.getStatusCode().toString()); jsonResponse.put("data", response.getBody()); @@ -155,21 +276,34 @@ public class TunnelController implements ErrorController { return ResponseEntity.ok(jsonResponse); } + /** + * Delete an ingress mapping from a tunnel. + * + *

Removes an ingress rule by hostname from the tunnel configuration.

+ * + * @param tunnelId The Cloudflare tunnel ID (UUID) + * @param ingress Ingress containing hostname to delete (only hostname field is used) + * @return Map containing the result + * + * @security Requires DEVELOPER role + * @response 200 OK with updated configuration + * @response 409 Conflict if hostname not found + */ @PreAuthorize("hasAnyRole('DEVELOPER')") @DeleteMapping("/tunnels/{tunnelId}/mappings") public ResponseEntity> deleteTunnelConfiguration(@PathVariable String tunnelId, @RequestBody Ingress ingress) throws JsonProcessingException { ResponseEntity responseEntity = cloudflareAPIService.getCloudflareTunnelConfigurations(tunnelId, restTemplateConfig.restTemplate(), TunnelResponse.class); - // * * Deleting the selected ingress value + // Deleting the selected ingress value Config config = responseEntity.getBody().getResult().getConfig(); List response_ingress = config.getIngress(); Boolean result = Ingress.deleteByHostName(response_ingress, ingress.getHostname()); - // * * Hitting put endpoint + // Hitting put endpoint ResponseEntity response = cloudflareAPIService.putCloudflareTunnelConfigurations(tunnelId, restTemplateConfig.restTemplate(), TunnelResponse.class, config); - // * * Displaying response + // Displaying response Map jsonResponse = new HashMap<>(); if (result){ @@ -184,6 +318,24 @@ public class TunnelController implements ErrorController { return ResponseEntity.ok(jsonResponse); } + /** + * Create a mapping change request. + * + *

Creates a new request for changing tunnel ingress mappings. + * The request starts in PENDING status and must be approved + * before the changes are applied.

+ * + * @param tunnelId The Cloudflare tunnel ID (UUID) + * @param oidcUser The authenticated user + * @param ingess The ingress configuration to request + * @return The created request with PENDING status + * + * @security Requires DEVELOPER role + * @response 201 Created with request + * @response 400 Bad Request if invalid + * + * @see MappingRequestService#createMappingRequest(String, Ingress, OidcUser) + */ @PreAuthorize("hasAnyRole('DEVELOPER')") @PostMapping("/tunnels/configure/{tunnelId}/requests") public ResponseEntity createTunnelMappingRequest(@PathVariable String tunnelId, @AuthenticationPrincipal OidcUser oidcUser, @RequestBody Ingress ingess){ @@ -193,6 +345,21 @@ public class TunnelController implements ErrorController { return ResponseEntity.status(HttpStatus.BAD_REQUEST).build(); } + /** + * Approve a mapping request. + * + *

Approves a pending mapping request. If approved, the + * mapping will be applied to the Cloudflare tunnel.

+ * + * @param requestId The ID of the request to approve + * @param oidcUser The approver (must have APPROVER role) + * @return The updated request with APPROVED status + * + * @security Requires APPROVER role + * @response 200 OK with approved request + * @response 404 Not Found if request doesn't exist + * @response 409 Conflict if request already processed + */ @PreAuthorize("hasAnyRole('APPROVER')") @PutMapping("/requests/{requestId}/approve") public ResponseEntity approveMappingRequest(@PathVariable UUID requestId, @AuthenticationPrincipal OidcUser oidcUser) { @@ -210,6 +377,21 @@ public class TunnelController implements ErrorController { } } + /** + * Reject a mapping request. + * + *

Rejects a pending mapping request. No changes + * will be made to the tunnel.

+ * + * @param requestId The ID of the request to reject + * @param oidcUser The rejecter (must have APPROVER role) + * @return The updated request with REJECTED status + * + * @security Requires APPROVER role + * @response 200 OK with rejected request + * @response 404 Not Found + * @response 409 Conflict + */ @PreAuthorize("hasAnyRole('APPROVER')") @PutMapping("/requests/{requestId}/reject") public ResponseEntity rejectMappingRequest(@PathVariable UUID requestId, @AuthenticationPrincipal OidcUser oidcUser) { @@ -227,13 +409,36 @@ public class TunnelController implements ErrorController { } } + /** + * Configure a tunnel for the current environment. + * + *

Creates a local configuration entry for a tunnel, + * associating it with the current environment (from spring.profiles.active).

+ * + *

Response Codes:

+ *
    + *
  • 200 - Created/updated with new tunnel
  • + *
  • 204 - No changes needed
  • + *
  • 404 - Tunnel not found in Cloudflare
  • + *
+ * + * @param tunnelId The Cloudflare tunnel ID (UUID) + * @param user The authenticated user + * @return The tunnel configuration + * + * @security Requires ADMIN role + * @response 200 OK with tunnel + * @response 204 No Content + * @response 404 Not Found + * @response 500 Internal Server Error + */ @PreAuthorize("hasAnyRole('ADMIN')") @PutMapping("/tunnels/configure/{tunnelId}") public ResponseEntity configureTunnelForEnvironment(@PathVariable String tunnelId, @AuthenticationPrincipal OidcUser user) { /* - * * Returns 200 if an object is created or updated with a new representation of the object - * * Returns 204 if the object state did not need any changing. - * * Returns 404 if the tunnelId is not valid + * Returns 200 if an object is created or updated with a new representation of the object + * Returns 204 if the object state did not need any changing. + * Returns 404 if the tunnelId is not valid */ try { @@ -249,4 +454,4 @@ public class TunnelController implements ErrorController { } } -} +} \ No newline at end of file -- 2.45.2 From 258c09285e6d1cdcb6486a9deb64fb0b7b15bcf2 Mon Sep 17 00:00:00 2001 From: Dave the Dev Date: Tue, 14 Apr 2026 11:53:14 +0000 Subject: [PATCH 10/18] Hithomelabs/CFTunnels#114: Add comprehensive README.md documentation --- README.md | 184 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 184 insertions(+) create mode 100644 README.md diff --git a/README.md b/README.md new file mode 100644 index 0000000..fd7050c --- /dev/null +++ b/README.md @@ -0,0 +1,184 @@ +# CFTunnels - Cloudflare Tunnels Management API + +A Spring Boot REST API for managing Cloudflare Tunnels with ingress mappings and an approval workflow. + +## Overview + +CFTunnels provides a programmatic way to manage Cloudflare Tunnel configurations, allowing teams to: + +- View and manage Cloudflare Tunnels +- Add, modify, and delete ingress mappings +- Request mapping changes through an approval workflow +- Track tunnel configurations locally + +## Features + +- **Tunnel Management**: Create, update, and delete Cloudflare tunnels +- **Ingress Mappings**: Add custom ingress rules to tunnel configurations +- **Approval Workflow**: Request/approve/reject mapping changes +- **Security**: OIDC-based authentication with role-based access +- **API Documentation**: OpenAPI/Swagger documentation + +## Technology Stack + +- Java 17 +- Spring Boot 3.x +- Spring Data JPA +- Spring Security with OIDC +- H2 Database (configurable for PostgreSQL) +- Cloudflare API v4 + +## Prerequisites + +- Java 17 or higher +- Cloudflare account with API key +- OIDC provider (e.g., Google, Okta, Auth0) + +## Configuration + +Copy `.env.example` to `.env` and configure: + +```bash +cp .env.example .env +``` + +### Required Environment Variables + +| Variable | Description | +|---------|-------------| +| `CLOUDFLARE_ACCOUNT_ID` | Cloudflare Account ID | +| `CLOUDFLARE_API_KEY` | Cloudflare API Key | +| `CLOUDFLARE_EMAIL` | Cloudflare account email | +| `SPRING_PROFILES_ACTIVE` | Environment (local, dev, prod) | + +### Security Configuration + +OIDC settings in `application.properties`: + +```properties +spring.security.oauth2.client.registration..client-id=your-client-id +spring.security.oauth2.client.registration..client-secret=your-client-secret +spring.security.oauth2.client.provider..issuer-uri=https://your-oidc-provider +``` + +## Running Locally + +### Using Gradle + +```bash +./gradlew bootRun +``` + +### Using Docker + +```bash +docker-compose up --build +``` + +The API will be available at `http://localhost:8080` + +## API Documentation + +Once running, access: + +- **Swagger UI**: `http://localhost:8080/swagger-ui.html` +- **OpenAPI JSON**: `http://localhost:8080/v3/api-docs` + +## API Endpoints + +### Base URL: `/cloudflare` + +| Method | Endpoint | Description | Required Role | +|--------|----------|-------------|---------------| +| GET | `/whoami` | Get current user info | USER | +| GET | `/tunnels` | List all Cloudflare tunnels | USER | +| GET | `/configured/tunnels` | List locally configured tunnels | USER | +| GET | `/requests` | List all mapping requests | USER | +| GET | `/tunnels/{tunnelId}/mappings` | Get tunnel configuration | DEVELOPER | +| POST | `/tunnels/{tunnelId}/mappings` | Add ingress mapping | ADMIN | +| DELETE | `/tunnels/{tunnelId}/mappings` | Delete ingress mapping | DEVELOPER | +| POST | `/tunnels/configure/{tunnelId}/requests` | Create mapping request | DEVELOPER | +| PUT | `/requests/{requestId}/approve` | Approve mapping request | APPROVER | +| PUT | `/requests/{requestId}/reject` | Reject mapping request | APPROVER | +| PUT | `/tunnels/configure/{tunnelId}` | Configure tunnel for environment | ADMIN | + +## Role-Based Access + +| Role | Permissions | +|------|-------------| +| USER | View tunnels and requests | +| DEVELOPER | Create/modify/delete mappings, create requests | +| APPROVER | Approve/reject requests | +| ADMIN | Full access including tunnel configuration | + +## Example Usage + +### List all tunnels + +```bash +curl -H "Authorization: Bearer $TOKEN" \ + http://localhost:8080/cloudflare/tunnels +``` + +### Add an ingress mapping + +```bash +curl -X POST \ + -H "Authorization: Bearer $TOKEN" \ + -H "Content-Type: application/json" \ + -d '{ + "hostname": "api.example.com", + "service": "http://localhost:8080", + "originRequest": {"noTLSVerify": true} + }' \ + http://localhost:8080/cloudflare/tunnels/{tunnelId}/mappings +``` + +## Project Structure + +``` +CFTunnels/ +├── src/main/java/com/hithomelabs/CFTunnels/ +│ ├── CfTunnelsApplication.java # Main application +│ ├── Config/ # Configuration classes +│ ├── Controllers/ # REST controllers +│ │ └── TunnelController.java # Main API controller +│ ├── Entity/ # JPA entities +│ │ ├── Mapping.java # Ingress mapping +│ │ ├── Protocol.java # Protocol enum +│ │ ├── Request.java # Mapping request +│ │ ├── Tunnel.java # Tunnel entity +│ │ └── User.java # User entity +│ ├── Models/ # DTOs +│ ├── Repositories/ # JPA repositories +│ └── Services/ # Business logic +│ ├── CloudflareAPIService.java # Cloudflare API +│ └── MappingRequestService.java # Request workflow +└── src/main/resources/ + ├── application.properties # Main config + └── schema.sql # Database schema +``` + +## Testing + +Run tests with: + +```bash +./gradlew test +``` + +Run integration tests: + +```bash +./gradlew integrationTest +``` + +## License + +Private - All rights reserved + +## References + +- [Cloudflare Tunnel Documentation](https://developers.cloudflare.com/cloudflare-one/connections/connect-apps/) +- [Cloudflare API v4](https://api.cloudflare.com/) +- [Spring Boot Documentation](https://docs.spring.io/spring-boot/docs/current/reference/) \ No newline at end of file -- 2.45.2 From ac2ae0a59f830648b78bc7485a01a10273f199d0 Mon Sep 17 00:00:00 2001 From: Dave the Dev Date: Tue, 14 Apr 2026 11:53:28 +0000 Subject: [PATCH 11/18] Hithomelabs/CFTunnels#114: Add JavaDoc to CloudflareConfig --- .../CFTunnels/Config/CloudflareConfig.java | 38 ++++++++++++++++++- 1 file changed, 37 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/hithomelabs/CFTunnels/Config/CloudflareConfig.java b/src/main/java/com/hithomelabs/CFTunnels/Config/CloudflareConfig.java index ad280ed..8d880e3 100644 --- a/src/main/java/com/hithomelabs/CFTunnels/Config/CloudflareConfig.java +++ b/src/main/java/com/hithomelabs/CFTunnels/Config/CloudflareConfig.java @@ -3,11 +3,47 @@ package com.hithomelabs.CFTunnels.Config; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.context.annotation.Configuration; +/** + * Configuration class for Cloudflare API credentials. + * + *

Loads Cloudflare configuration from application properties + * using the {@code cloudflare.*} prefix.

+ * + *

Example configuration in application.properties:

+ *
+ * cloudflare.account-id=your-account-id
+ * cloudflare.api-key=your-api-key
+ * cloudflare.email=your@email.com
+ * 
+ * + * @see Cloudflare API Documentation + */ @Configuration @ConfigurationProperties(prefix = "cloudflare") public class CloudflareConfig { + + /** + * Cloudflare account ID. + * + *

Found in the Cloudflare Dashboard under + * Overview > Account ID

+ */ private String accountId; + + /** + * Cloudflare API Key. + * + *

Generated in Cloudflare Dashboard under + * Profile > API Tokens > Global API Key

+ */ private String apiKey; + + /** + * Cloudflare account email. + * + *

The email address associated with your + * Cloudflare account.

+ */ private String email; // Getters and Setters @@ -19,4 +55,4 @@ public class CloudflareConfig { public String getEmail() { return email; } public void setEmail(String email) { this.email = email; } -} +} \ No newline at end of file -- 2.45.2 From 4460e86776e950f792d15b4e003a6795e2bca5e0 Mon Sep 17 00:00:00 2001 From: Dave the Dev Date: Wed, 15 Apr 2026 18:36:20 +0000 Subject: [PATCH 12/18] Hithomelabs/CFTunnels#115: Fix mixed indentation (tabs to spaces) --- .../CFTunnels/CfTunnelsApplication.java | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/main/java/com/hithomelabs/CFTunnels/CfTunnelsApplication.java b/src/main/java/com/hithomelabs/CFTunnels/CfTunnelsApplication.java index 1b46911..42bacd5 100644 --- a/src/main/java/com/hithomelabs/CFTunnels/CfTunnelsApplication.java +++ b/src/main/java/com/hithomelabs/CFTunnels/CfTunnelsApplication.java @@ -33,13 +33,13 @@ package com.hithomelabs.CFTunnels; @SpringBootApplication public class CfTunnelsApplication { - /** - * Main entry point for the application. - * - * @param args command line arguments passed to the application - */ - public static void main(String[] args) { - SpringApplication.run(CfTunnelsApplication.class, args); - } + /** + * Main entry point for the application. + * + * @param args command line arguments passed to the application + */ + public static void main(String[] args) { + SpringApplication.run(CfTunnelsApplication.class, args); + } -} \ No newline at end of file +} -- 2.45.2 From d728504aada560160308a3ea2f9c877c9c220b73 Mon Sep 17 00:00:00 2001 From: Dave the Dev Date: Wed, 15 Apr 2026 18:37:11 +0000 Subject: [PATCH 13/18] Hithomelabs/CFTunnels#115: Replace non-standard @security/@response with @throws --- .../Controllers/TunnelController.java | 64 ++++--------------- 1 file changed, 14 insertions(+), 50 deletions(-) diff --git a/src/main/java/com/hithomelabs/CFTunnels/Controllers/TunnelController.java b/src/main/java/com/hithomelabs/CFTunnels/Controllers/TunnelController.java index cb77525..d4e3962 100644 --- a/src/main/java/com/hithomelabs/CFTunnels/Controllers/TunnelController.java +++ b/src/main/java/com/hithomelabs/CFTunnels/Controllers/TunnelController.java @@ -110,9 +110,7 @@ public class TunnelController implements ErrorController { * * @param oidcUser The authenticated OIDC user * @return Map containing username and roles - * - * @security Requires USER role - * @response 200 OK + * @throws SecurityException if authentication fails */ @PreAuthorize("hasAnyRole('USER')") @GetMapping("/whoami") @@ -134,10 +132,7 @@ public class TunnelController implements ErrorController { * including their status and configuration from the Cloudflare API.

* * @return Map containing list of all tunnels - * - * @security Requires USER role - * @response 200 OK with tunnel list - * @response 500 Internal Server Error if API call fails + * @throws SecurityException if user lacks required role * @see Cloudflare API */ @PreAuthorize("hasAnyRole('USER')") @@ -160,10 +155,7 @@ public class TunnelController implements ErrorController { * with environment associations.

* * @return Map containing list of configured tunnels - * - * @security Requires USER role - * @response 200 OK with tunnel list - * @response 500 Internal Server Error if database access fails + * @throws SecurityException if user lacks required role * @see CloudflareAPIService#getAllConfiguredTunnels() */ @PreAuthorize("hasAnyRole('USER')") @@ -186,10 +178,7 @@ public class TunnelController implements ErrorController { *

Returns all pending, approved, and rejected mapping requests.

* * @return Map containing list of all requests - * - * @security Requires USER role - * @response 200 OK with request list - * @response 500 Internal Server Error if database access fails + * @throws SecurityException if user lacks required role */ @PreAuthorize("hasAnyRole('USER')") @GetMapping("/requests") @@ -213,10 +202,7 @@ public class TunnelController implements ErrorController { * * @param tunnelId The Cloudflare tunnel ID (UUID) * @return Map containing tunnel configuration - * - * @security Requires DEVELOPER role - * @response 200 OK with configuration - * @response 500 Internal Server Error + * @throws SecurityException if user lacks required role * @see Cloudflare API */ @PreAuthorize("hasAnyRole('DEVELOPER')") @@ -241,11 +227,8 @@ public class TunnelController implements ErrorController { * @param tunnelId The Cloudflare tunnel ID (UUID) * @param ingress The ingress rule to add * @return Map containing the updated configuration - * - * @security Requires ADMIN role - * @response 200 OK with updated configuration - * @response 400 Bad Request if ingress is invalid - * @response 500 Internal Server Error + * @throws SecurityException if user lacks required role + * @throws JsonProcessingException if JSON processing fails * * @example * { @@ -284,10 +267,8 @@ public class TunnelController implements ErrorController { * @param tunnelId The Cloudflare tunnel ID (UUID) * @param ingress Ingress containing hostname to delete (only hostname field is used) * @return Map containing the result - * - * @security Requires DEVELOPER role - * @response 200 OK with updated configuration - * @response 409 Conflict if hostname not found + * @throws SecurityException if user lacks required role + * @throws JsonProcessingException if JSON processing fails */ @PreAuthorize("hasAnyRole('DEVELOPER')") @DeleteMapping("/tunnels/{tunnelId}/mappings") @@ -329,11 +310,7 @@ public class TunnelController implements ErrorController { * @param oidcUser The authenticated user * @param ingess The ingress configuration to request * @return The created request with PENDING status - * - * @security Requires DEVELOPER role - * @response 201 Created with request - * @response 400 Bad Request if invalid - * + * @throws SecurityException if user lacks required role * @see MappingRequestService#createMappingRequest(String, Ingress, OidcUser) */ @PreAuthorize("hasAnyRole('DEVELOPER')") @@ -354,11 +331,7 @@ public class TunnelController implements ErrorController { * @param requestId The ID of the request to approve * @param oidcUser The approver (must have APPROVER role) * @return The updated request with APPROVED status - * - * @security Requires APPROVER role - * @response 200 OK with approved request - * @response 404 Not Found if request doesn't exist - * @response 409 Conflict if request already processed + * @throws SecurityException if user lacks required role */ @PreAuthorize("hasAnyRole('APPROVER')") @PutMapping("/requests/{requestId}/approve") @@ -386,11 +359,7 @@ public class TunnelController implements ErrorController { * @param requestId The ID of the request to reject * @param oidcUser The rejecter (must have APPROVER role) * @return The updated request with REJECTED status - * - * @security Requires APPROVER role - * @response 200 OK with rejected request - * @response 404 Not Found - * @response 409 Conflict + * @throws SecurityException if user lacks required role */ @PreAuthorize("hasAnyRole('APPROVER')") @PutMapping("/requests/{requestId}/reject") @@ -425,12 +394,7 @@ public class TunnelController implements ErrorController { * @param tunnelId The Cloudflare tunnel ID (UUID) * @param user The authenticated user * @return The tunnel configuration - * - * @security Requires ADMIN role - * @response 200 OK with tunnel - * @response 204 No Content - * @response 404 Not Found - * @response 500 Internal Server Error + * @throws SecurityException if user lacks required role */ @PreAuthorize("hasAnyRole('ADMIN')") @PutMapping("/tunnels/configure/{tunnelId}") @@ -454,4 +418,4 @@ public class TunnelController implements ErrorController { } } -} \ No newline at end of file +} -- 2.45.2 From 047433fe60d3bd3865d55a6163f55ff3fa25e2b5 Mon Sep 17 00:00:00 2001 From: Dave the Dev Date: Wed, 15 Apr 2026 18:44:51 +0000 Subject: [PATCH 14/18] Hithomelabs/CFTunnels#115: Add PR target branch note to README --- README.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index fd7050c..2a6518e 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,7 @@ # CFTunnels - Cloudflare Tunnels Management API +> **Note**: All pull requests should be raised against the `test` branch. + A Spring Boot REST API for managing Cloudflare Tunnels with ingress mappings and an approval workflow. ## Overview @@ -181,4 +183,4 @@ Private - All rights reserved - [Cloudflare Tunnel Documentation](https://developers.cloudflare.com/cloudflare-one/connections/connect-apps/) - [Cloudflare API v4](https://api.cloudflare.com/) -- [Spring Boot Documentation](https://docs.spring.io/spring-boot/docs/current/reference/) \ No newline at end of file +- [Spring Boot Documentation](https://docs.spring.io/spring-boot/docs/current/reference/) -- 2.45.2 From e4a51ed9f8cc965cd9ca55fa773784c73064b98b Mon Sep 17 00:00:00 2001 From: Dave the Dev Date: Sat, 18 Apr 2026 15:05:51 +0000 Subject: [PATCH 15/18] Fix build by using buildscript pattern instead of plugin DSL --- build.gradle | 23 +++++++++++++++++------ 1 file changed, 17 insertions(+), 6 deletions(-) diff --git a/build.gradle b/build.gradle index 1034cb7..06c54f5 100644 --- a/build.gradle +++ b/build.gradle @@ -1,9 +1,19 @@ -plugins { - id 'java' - id 'org.springframework.boot' version '3.4.5' - id 'io.spring.dependency-management' version '1.1.7' +buildscript { + repositories { + mavenCentral() + } + dependencies { + classpath("org.springframework.boot:spring-boot-gradle-plugin:3.4.5") + } } +plugins { + id 'java' + id 'io.spring.dependency-management' version '1.1.7' +} + +apply plugin: 'org.springframework.boot' + group = 'com.hithomelabs' version = '0.0.1-SNAPSHOT' @@ -45,9 +55,10 @@ repositories { dependencies { implementation group: 'org.springdoc', name: 'springdoc-openapi-starter-webmvc-ui', version: '2.8.5' implementation group: 'org.springframework.boot', name:'spring-boot-starter-oauth2-client', version: '3.5.5' + implementation 'org.springframework.boot:spring-boot-starter' + implementation 'org.springframework.boot:spring-boot-starter-web' compileOnly 'org.projectlombok:lombok:1.18.30' annotationProcessor 'org.projectlombok:lombok:1.18.30' - implementation 'org.springframework.boot:spring-boot-starter-web' testImplementation 'org.springframework.boot:spring-boot-starter-test' testImplementation 'org.springframework.security:spring-security-test' testRuntimeOnly 'org.junit.platform:junit-platform-launcher' @@ -59,4 +70,4 @@ dependencies { tasks.named('test') { useJUnitPlatform() -} +} \ No newline at end of file -- 2.45.2 From 7fdfdacf500410b77f910ce93adf506742d187fa Mon Sep 17 00:00:00 2001 From: Dave the Dev Date: Sat, 18 Apr 2026 15:06:11 +0000 Subject: [PATCH 16/18] Fix by adding explicit import statements --- .../java/com/hithomelabs/CFTunnels/CfTunnelsApplication.java | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/hithomelabs/CFTunnels/CfTunnelsApplication.java b/src/main/java/com/hithomelabs/CFTunnels/CfTunnelsApplication.java index 42bacd5..bd64397 100644 --- a/src/main/java/com/hithomelabs/CFTunnels/CfTunnelsApplication.java +++ b/src/main/java/com/hithomelabs/CFTunnels/CfTunnelsApplication.java @@ -1,5 +1,8 @@ package com.hithomelabs.CFTunnels; +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + /** * Main Spring Boot application class for Cloudflare Tunnels API. * @@ -42,4 +45,4 @@ public class CfTunnelsApplication { SpringApplication.run(CfTunnelsApplication.class, args); } -} +} \ No newline at end of file -- 2.45.2 From 6304c695772764f12e6088c3daaf8a2519e39cdd Mon Sep 17 00:00:00 2001 From: Dave the Dev Date: Sat, 18 Apr 2026 15:06:25 +0000 Subject: [PATCH 17/18] Fix ENV variable with default value --- src/main/resources/application.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index 283d942..97344a4 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -2,7 +2,7 @@ spring.application.name=CFTunnels cloudflare.accountId=${CLOUDFLARE_ACCOUNT_ID} cloudflare.apiKey=${CLOUDFLARE_API_KEY} cloudflare.email=${CLOUDFLARE_EMAIL} -spring.profiles.active=${ENV} +spring.profiles.active=${ENV:default} / * * Masking sure app works behind a reverse proxy server.forward-headers-strategy=framework -- 2.45.2 From 29b767d634343a99e62a21592966df236a39a9d0 Mon Sep 17 00:00:00 2001 From: Dave the Dev Date: Sat, 18 Apr 2026 15:29:40 +0000 Subject: [PATCH 18/18] Hithomelabs/CFTunnels#114: Fix properties parser blocking issue Fixed the malformed comment in application.properties: - Changed "/ * * Masking" to "# Making" to create a valid comment - This was causing the properties parser to fail --- src/main/resources/application.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index 97344a4..eb4ab8b 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -4,7 +4,7 @@ cloudflare.apiKey=${CLOUDFLARE_API_KEY} cloudflare.email=${CLOUDFLARE_EMAIL} spring.profiles.active=${ENV:default} -/ * * Masking sure app works behind a reverse proxy +# Making sure app works behind a reverse proxy server.forward-headers-strategy=framework spring.security.oauth2.client.registration.cftunnels.client-id=${OAUTH_CLIENT_ID} -- 2.45.2