System Architecture
CCDigital is designed as a Spring Boot monolith that integrates multiple storage backends and blockchain networks to provide secure, auditable document management with verifiable digital identity.High-Level Architecture
Architecture Principles
Monolithic Design
Single deployable Spring Boot JAR simplifies operations and maintains transactional consistency across modules.
Layered Architecture
Clear separation: Presentation (Controllers) → Business Logic (Services) → Data Access (Repositories)
Blockchain as Trust Layer
MySQL remains the primary source of truth. Blockchain provides auditable evidence and verification.
External Integration
Node.js and Python scripts encapsulate blockchain complexity, called via
ExternalToolsService.Component Breakdown
Spring Boot Application
The core application is built on Spring Boot 3.5.11 with Java 17, providing:Configuration Layer
Package:co.edu.unbosque.ccdigital.config
SecurityConfig: Multi-chain security configuration with separate authentication flows for Admin, Issuer, and UserFileStorageProperties: Mapsccdigital.fs.base-pathenvironment variableIndyProperties: Maps allccdigital.indy.*configuration for ACA-Py integrationSensitiveEndpointRateLimitFilter: Rate limiting filter with configurable windows and thresholds
Controller Layer
Package:co.edu.unbosque.ccdigital.controller
Key controllers and their responsibilities:
| Controller | Endpoints | Purpose |
|---|---|---|
AdminController | /admin/dashboard/admin/persons/admin/sync/admin/reports | Government operations: person management, document review, blockchain sync, analytics |
IssuerController | /issuer/issuer/upload/issuer/search | Institution operations: search citizens, upload PDFs, create access requests |
UserAuthController | /user/auth/start/user/auth/poll/user/auth/otp/verify | End-user authentication with Indy proof + MFA |
UserPagesController | /user/dashboard/user/requests | Citizen dashboard and access request management |
UserDocsController | /user/docs/view/{id}/user/docs/download/{id} | Document viewing and downloading with signed URLs |
UserRegistrationController | /register/user/register/user/verify-email | Self-service registration with email OTP verification |
Controllers are thin layers that delegate business logic to services. They handle HTTP concerns (request binding, redirects, model population) while services handle transactions and validation.
Service Layer
Package:co.edu.unbosque.ccdigital.service
Critical services:
Core Business Services:
PersonService: Person lifecycle management, includingcreatePersonAndFolder(Person)for atomic person + filesystem setupPersonDocumentService: Document upload, review workflow (PENDING → APPROVED/REJECTED), PDF validationAccessRequestService: Access request creation, approval/rejection, expiry validationUserAccountService: User account management, password hashing, normalizationFileStorageService: Physical file storage with SHA-256 hashing, size tracking, folder creation
UserAuthFlowService: Orchestrates the complete user login flow (Indy proof + MFA)IndyProofLoginService: Present-proof 2.0 protocol implementation, attribute extractionUserLoginOtpService: Email OTP generation and verification for 2FAUserTotpService: TOTP (Time-based OTP) management for authenticator appsSignedUrlService: Generates HMAC-signed time-limited URLs for document accessForgotPasswordService: Password recovery with OTP codes
ExternalToolsService: Executes Node.js and Python scripts with timeout controlFabricLedgerCliService: Wraps Fabric Node.js scripts for document listing and syncFabricAuditCliService: Records and queries access events on FabricIndyAdminClient: REST client for ACA-Py admin APIUserAccessGovernanceService: Synchronizes user access state to Indy connection metadata
AdminReportService: Aggregates analytics data with configurable date ranges and periodsAdminReportPdfService: Generates PDF reports with embedded chartsBlockchainTraceDetailService: Resolves blockchain references to full block/transaction details
Entity Layer (Data Model)
Package:co.edu.unbosque.ccdigital.entity
Key JPA entities:
Person,User,EntityUser(issuer accounts)IssuingEntity,Company,CompanyDocumentDefinitionDocument(document types),Category(document categories)PersonDocument(documents owned by persons)File(file metadata: path, sha256, size, version)AccessRequest,AccessRequestItem(request workflow)Consent(approval/rejection records)AuditEvent(system audit log)
MySQL Database
The database schema includes: Tables: 22+ tables for core data Views: Simplified queries for common operationsv_documents: Complete document informationv_person_full_documents: Person documents with all metadata
sp_add_person_document: Atomically add document with validationsp_create_user_with_person: Create user account linked to personsp_upload_file_path: Record file metadatasp_upload_pdf_blob: Store PDF as BLOB (optional)
trg_files_autoversion: Automatically increments version on file updates
The database remains the primary source of truth. All business logic operates against MySQL, and blockchain networks receive synchronized snapshots for audit and verification purposes.
File System Storage
Managed byFileStorageService:
Directory Structure:
- Normalized folder names:
FirstName_LastName_IdType_IdNumber - SHA-256 hash calculation for integrity verification
- File size tracking
- Automatic directory creation
- Path validation for security (prevents directory traversal)
UserDocsController.java):
Module Architecture
Admin Module
Primary Controller:AdminController.java
Key Flows:
Person Registration
- Admin fills
PersonCreateFormwith citizen data PersonService.createPersonAndFolder(Person)atomically:- Saves person to MySQL
- Creates filesystem folder
- Returns saved entity
- Admin is redirected to person detail page
Document Upload & Review
- Admin uploads file via
/admin/persons/{id}/upload PersonDocumentService.uploadForPerson()validates and stores file- Document enters
PENDINGreview status - Admin reviews via
/admin/person-documents/{id}/review - Status changes to
APPROVEDorREJECTED
Access Governance
- Admin modifies user access state via
/admin/persons/{id}/access-state UserAccessGovernanceService.updateState()updates MySQL- If
INDY_USER_ACCESS_SYNC_ENABLED=true, syncs to Indy connection metadata - User’s access is immediately affected system-wide
AdminController.java):
Issuer Module
Primary Controller:IssuerController.java
Authentication: Uses IssuerPrincipal security context
Key Flows:
Search Citizen
- Issuer submits
IssuerSearchFormwith ID type and number PersonService.findByIdTypeAndNumber()queries MySQL- If found, redirects to
/issuer?personId={id} - Person’s existing documents are displayed
Upload PDF Document
- Issuer selects allowed document type (based on
entity_document_definitions) - Uploads PDF file via
/issuer/upload - PDF Validation (from
IssuerController.java): PersonDocumentService.uploadFromIssuer()performs:- Authorization check (issuer allowed to issue this document type?)
- Deep PDF validation (extension, MIME, file signature
%PDF) - File storage with SHA-256 hash
- Creates
PersonDocumentwithPENDINGreview status
Create Access Request
- Issuer navigates to
/issuer/access-requests/new - Selects person and approved documents
AccessRequestService.create()creates request- User receives notification of pending request
- Issuers can only upload document types assigned to their entity
- Only PDF files accepted
- Documents require admin approval before appearing in access requests
- Access requests must be approved by end user
- Signed URLs expire after configured TTL
User Module
Primary Controllers:UserAuthController.java, UserPagesController.java, UserDocsController.java, UserRegistrationController.java
Authentication Flow (from UserAuthFlowService.java and IndyProofLoginService.java):
Initiate Login
Endpoint: Process:
POST /user/auth/start- Validates email and password against MySQL
- Checks user access state (must be
ENABLED) - Initiates Indy present-proof 2.0 via
IndyProofLoginService.requestProof() - Returns
presExId(presentation exchange ID) and QR code data
Poll Proof Status
Endpoint:
GET /user/auth/poll?presExId={id}Process:- Queries ACA-Py for presentation exchange state
- Returns one of:
pending,verified,failed - When
verified, extracts attributes: - Validates attributes match user record in MySQL
Second Factor Authentication
Endpoint: Two Options:A) TOTP (Time-based OTP) via
POST /user/auth/otp/verifyUserTotpService:- User has registered authenticator app (Google Authenticator, Authy)
- Verifies 6-digit code with configurable time window
- Algorithm: HMAC-SHA1 with 30-second period
UserLoginOtpService:- System generates random code
- Sends via SMTP to user’s registered email
- Code expires after configured TTL (default: 5 minutes)
- Max attempts limit prevents brute force
- Spring Security session established
- User redirected to
/user/dashboard
UserRegistrationFlowService.java):
Submit Registration Form
Endpoint:
POST /register/userUser provides:- ID type and number (must match existing
Personrecord) - Email (verified via OTP)
- Password (strong validation)
- Optional: TOTP setup
- Password must meet complexity requirements
- Email must be unique
- Person must exist in system (pre-registered by admin)
- Person must not already have a user account
Email Verification
Endpoint:
POST /register/user/verify-emailProcess (from UserRegisterEmailOtpService.java):- Generates random code (default: 6 digits)
- Stores in temporary cache with TTL
- Sends via SMTP
- User submits code
- If valid, account activated
UserDocsController.java):
- User requests document from dashboard
SignedUrlService.generateSignedUrl()creates URL with:- Document ID
- Expiration timestamp
- HMAC-SHA256 signature
- URL valid for configured TTL (default: 5 minutes)
- Controller validates signature and expiry
- Performs additional checks:
- User owns the document
- File exists on filesystem
- Path is within allowed directory
- Records access event to Fabric
- Returns file with appropriate Content-Type
Security Architecture
Spring Security Configuration
FromSecurityConfig.java, the application uses multiple security filter chains:
Security Controls
Role-Based Access Control (RBAC)
Role-Based Access Control (RBAC)
Three Roles:
ROLE_GOBIERNO: Full administrative accessROLE_ISSUER: Document issuance and access requestsROLE_USER: Personal document access
- Controller level:
@PreAuthorize("hasRole('GOBIERNO')") - Method level:
SecurityContextHolder.getContext().getAuthentication() - Custom principals:
IssuerPrincipal,UserPrincipal
Session Management
Session Management
Configuration:Features:
- Automatic expiry after inactivity
- Client-side expiry tracking via JavaScript
- Keepalive endpoint:
/api/session/keepalive - Manual expiry:
/api/session/expire - Same-origin frame policy
Rate Limiting
Rate Limiting
Filter: Implementation:
SensitiveEndpointRateLimitFilterConfiguration:- In-memory request counter per IP
- Sliding window algorithm
- Returns 429 Too Many Requests when exceeded
Content Security Policy
Content Security Policy
Headers (configured in
SecurityConfig):Signed URLs
Signed URLs
Service: Validation:
SignedUrlServiceAlgorithm:- Check expiration timestamp
- Recompute HMAC with secret
- Compare signatures (constant-time comparison)
- Reject if mismatch or expired
Data Flow and Integration
Document Upload Flow
User Authentication Flow
Blockchain Synchronization Flow
Technology Stack Details
Backend Framework
Spring Boot 3.5.11 (frompom.xml):
- MySQL Connector/J: JDBC driver for MySQL 8
- Thymeleaf Spring Security 6: Security dialect for templates
- OpenHTMLToPDF 1.0.10: HTML to PDF rendering with PDFBox
- XChart 3.8.8: Server-side chart generation
Blockchain Technologies
Hyperledger Fabric
Purpose: Document registry and audit trail Integration Method: Node.js CLI scripts Scripts (configured via environment variables):| Script | Purpose | Invoked By |
|---|---|---|
list-docs.js | List documents for a person from ledger | FabricLedgerCliService.listPersonDocs() |
read-block-by-ref.js | Resolve functional reference to actual block | BlockchainTraceDetailService.readDetail() |
record-access-event.js | Log document access/verification event | FabricAuditCliService.recordAccess() |
list-access-events.js | Query audit events for person or global | FabricAuditCliService.listEvents() |
sync-db-to-ledger.js | Full database → ledger sync | ExternalToolsService.runFabricSyncAll() |
sync-person.js | Single person sync | ExternalToolsService.runFabricSyncPerson() |
- Admin clicks “Sync to Fabric” in UI
ExternalToolsServiceconstructs command:- Script connects to Fabric peer via SDK
- Queries MySQL for current state
- Invokes chaincode functions to register documents
- Returns transaction IDs and status
registerDocument(docId, personId, metadata): Register new documentrecordAccessEvent(docId, actorId, eventType, timestamp): Audit logqueryDocumentsByPerson(personId): List person’s documentsqueryAccessEvents(filters): Query audit trail
Hyperledger Indy / ACA-Py
Purpose: Verifiable credential-based authentication Integration Method: REST API viaIndyAdminClient and IndyProofLoginService
Architecture:
id_type: Citizen ID type (CC, CE, TI, etc.)id_number: Citizen ID numberfirst_name: Given namelast_name: Family nameemail: Email address
IndyProofLoginService.java):
-
Request Proof:
-
Poll for Verification:
States:
request-sent→presentation-received→done(verified) -
Extract Attributes:
-
Validate Against Database:
UserAccessGovernanceService.java):
When admin changes user access state:
Deployment Architecture
Development Environment
Production Environment (Recommended)
Performance Considerations
Database Optimization
Indexes (defined in entity classes and schema):- Composite index on
persons(id_type, id_number)for fast lookups - Index on
person_documents(person_id, review_status) - Index on
access_requests(status, expiry_date) - Full-text index on document metadata fields
- Views (
v_documents,v_person_full_documents) pre-join frequently accessed data - Stored procedures for complex multi-table operations
- JPA
@BatchSizefor lazy loading optimization
Caching Strategy
Current implementation does not include extensive caching. For production, consider:
- Spring Cache abstraction with Redis
- HTTP caching headers for static resources
- ACA-Py response caching (proof states are ephemeral)
- Signed URL caching (short TTL)
Blockchain Performance
Fabric:- Batch synchronization during off-peak hours
- Async audit event recording (fire-and-forget)
- Connection pooling in Node.js scripts
- Connection reuse across proof requests
- Proof request caching (same structure per request)
- Parallel polling with configurable intervals
Monitoring and Observability
Application Logging
Spring Boot default logging configuration:- Authentication attempts and failures
- Document upload and review events
- Blockchain sync results (stdout/stderr from scripts)
- Access request approvals/rejections
- User access state changes
Audit Events
Database Table:audit_events
Recorded Events:
- User login (success/failure)
- Document access (view/download)
- Access request creation/approval/rejection
- User access state changes by admin
- Blockchain synchronization triggers
- All events recorded on-chain for immutability
- Queryable via
list-access-events.js - Block-level verification via
read-block-by-ref.js
Health Checks
Recommended endpoints for monitoring:- MySQL connectivity
- File system writability
- ACA-Py admin API reachability
- Fabric peer connectivity
Scalability Considerations
Current Architecture Limitations
As a monolith with local file storage:- Vertical scaling only: Single JVM, single file system
- Session affinity required: In-memory sessions don’t distribute
- File system bottleneck: Local I/O limits throughput
Future Scaling Options
Horizontal Scaling
Horizontal Scaling
Requirements:
- Externalize sessions (Spring Session + Redis)
- Move to network file storage (NFS, S3)
- Use load balancer with session affinity
- Consider database connection pooling limits
Microservices Evolution
Microservices Evolution
Potential Services:
- Auth Service: User authentication, MFA, Indy integration
- Document Service: Upload, storage, retrieval
- Access Service: Access requests, approvals, consent
- Blockchain Service: Fabric and Indy integrations
- Admin Service: Person management, reporting
Async Processing
Async Processing
Candidates for Asynchronization:
- Blockchain synchronization (use message queue)
- PDF generation for reports (background jobs)
- Email OTP sending (don’t block request)
- Audit event recording to Fabric
Next Steps
Quickstart Guide
Set up CCDigital locally and run your first instance
Admin Module Guide
Learn how to manage citizens and documents
Issuer Module Guide
Understand document issuance and access requests
User Module Guide
Explore citizen registration and document access
