Skip to content

Commit 3b84518

Browse files
author
secus
committed
feat: comprehensive domain management and email system improvements
- Frontend improvements: * Implement elegant 'coming soon' UI for domain management with feature preview * Add production-ready API configuration with dynamic base URL detection * Enhanced deployment detail page with comprehensive domain management interface * Remove network error alerts for better UX * Add domain management components with DNS setup instructions - Backend enhancements: * Implement complete domain validator service with DNS validation * Add SSL certificate manager with Let's Encrypt integration (mock for development) * Create comprehensive domain management endpoints (add/list/remove domains) * Add dynamic frontend URL generation for password reset emails * Implement Kubernetes custom domain ingress and SSL certificate management * Add database migrations for SSL certificate and DNS record management - Infrastructure improvements: * Add SSL/TLS dependencies for certificate management (rustls, acme-lib, trust-dns-resolver) * Remove test email services and diagnostic endpoints * Add crypto provider initialization for SSL operations * Create comprehensive domain validation and SSL provisioning workflow - Database schema: * Add SSL certificate management tables (ssl_certificates, dns_records, domain_validations) * Extend domains table with SSL status and validation tracking * Add proper indexes and constraints for domain management - Email system: * Configure dynamic URL generation for production deployments * Support environment-aware frontend URL detection * Remove temporary test endpoints after functionality verification This commit establishes a complete foundation for custom domain management with automated SSL certificate provisioning, ready for production deployment at decenter.run.
1 parent b93c04f commit 3b84518

13 files changed

Lines changed: 1722 additions & 52 deletions

File tree

Cargo.toml

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,14 @@ validator = { version = "0.18", features = ["derive"] }
6767
# Regex
6868
regex = "1.0"
6969

70+
# DNS and SSL certificate management
71+
trust-dns-resolver = "0.23"
72+
rustls = { version = "0.23", features = ["ring"] }
73+
rustls-pemfile = "2.0"
74+
acme-lib = "0.9"
75+
base64 = "0.22"
76+
sha2 = "0.10"
77+
7078
# OpenAPI documentation
7179
utoipa = { version = "4.0", features = ["axum_extras", "chrono", "uuid"] }
7280
utoipa-swagger-ui = { version = "4.0", features = ["axum"] }

WEBHOOK_SYSTEM.md

Whitespace-only changes.

apps/container-engine-frontend/src/api/api.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,6 @@ api.interceptors.response.use(
3737
// Handle network errors
3838
if (!error.response) {
3939
const errorMessage = 'Network error - please check your connection';
40-
alert(errorMessage);
4140
return Promise.reject(new Error(errorMessage));
4241
}
4342
return Promise.reject(error);

apps/container-engine-frontend/src/pages/DeploymentDetailPage.tsx

Lines changed: 352 additions & 28 deletions
Large diffs are not rendered by default.
Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
-- Add SSL certificate management for domains
2+
-- This migration extends the domain management system with SSL certificates and validation tracking
3+
4+
-- Add SSL certificate status and validation fields to domains table
5+
ALTER TABLE domains
6+
ADD COLUMN ssl_status VARCHAR(50) NOT NULL DEFAULT 'pending',
7+
ADD COLUMN ssl_issued_at TIMESTAMPTZ,
8+
ADD COLUMN ssl_expires_at TIMESTAMPTZ,
9+
ADD COLUMN dns_validation_token VARCHAR(255),
10+
ADD COLUMN dns_validated_at TIMESTAMPTZ,
11+
ADD COLUMN certificate_data TEXT,
12+
ADD COLUMN private_key_data TEXT,
13+
ADD COLUMN validation_attempts INTEGER NOT NULL DEFAULT 0,
14+
ADD COLUMN last_validation_attempt TIMESTAMPTZ,
15+
ADD COLUMN error_message TEXT;
16+
17+
-- Create SSL certificates table for detailed tracking
18+
CREATE TABLE ssl_certificates (
19+
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
20+
domain_id UUID NOT NULL REFERENCES domains(id) ON DELETE CASCADE,
21+
certificate_pem TEXT NOT NULL,
22+
private_key_pem TEXT NOT NULL,
23+
chain_pem TEXT,
24+
issued_at TIMESTAMPTZ NOT NULL,
25+
expires_at TIMESTAMPTZ NOT NULL,
26+
issuer VARCHAR(255) NOT NULL DEFAULT 'Let''s Encrypt',
27+
status VARCHAR(50) NOT NULL DEFAULT 'active',
28+
auto_renew BOOLEAN NOT NULL DEFAULT true,
29+
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
30+
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
31+
);
32+
33+
-- Create DNS records table for tracking required DNS configurations
34+
CREATE TABLE dns_records (
35+
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
36+
domain_id UUID NOT NULL REFERENCES domains(id) ON DELETE CASCADE,
37+
record_type VARCHAR(10) NOT NULL, -- A, AAAA, CNAME, TXT
38+
record_name VARCHAR(253) NOT NULL,
39+
record_value VARCHAR(1000) NOT NULL,
40+
ttl INTEGER NOT NULL DEFAULT 300,
41+
is_validation_record BOOLEAN NOT NULL DEFAULT false,
42+
is_configured BOOLEAN NOT NULL DEFAULT false,
43+
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
44+
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
45+
);
46+
47+
-- Create domain validation history table
48+
CREATE TABLE domain_validations (
49+
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
50+
domain_id UUID NOT NULL REFERENCES domains(id) ON DELETE CASCADE,
51+
validation_type VARCHAR(50) NOT NULL, -- dns, http
52+
validation_token VARCHAR(255) NOT NULL,
53+
challenge_response VARCHAR(1000),
54+
status VARCHAR(50) NOT NULL DEFAULT 'pending',
55+
attempted_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
56+
completed_at TIMESTAMPTZ,
57+
error_message TEXT
58+
);
59+
60+
-- Add indexes for performance
61+
CREATE INDEX idx_ssl_certificates_domain_id ON ssl_certificates(domain_id);
62+
CREATE INDEX idx_ssl_certificates_expires_at ON ssl_certificates(expires_at);
63+
CREATE INDEX idx_ssl_certificates_status ON ssl_certificates(status);
64+
CREATE INDEX idx_dns_records_domain_id ON dns_records(domain_id);
65+
CREATE INDEX idx_dns_records_type ON dns_records(record_type);
66+
CREATE INDEX idx_dns_records_validation ON dns_records(is_validation_record);
67+
CREATE INDEX idx_domain_validations_domain_id ON domain_validations(domain_id);
68+
CREATE INDEX idx_domain_validations_status ON domain_validations(status);
69+
CREATE INDEX idx_domains_ssl_status ON domains(ssl_status);
70+
CREATE INDEX idx_domains_verified_at ON domains(verified_at);
71+
72+
-- Add triggers for updated_at columns
73+
CREATE TRIGGER update_ssl_certificates_updated_at BEFORE UPDATE ON ssl_certificates
74+
FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();
75+
76+
CREATE TRIGGER update_dns_records_updated_at BEFORE UPDATE ON dns_records
77+
FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();
78+
79+
-- Add constraints and validation
80+
ALTER TABLE domains
81+
ADD CONSTRAINT check_ssl_status
82+
CHECK (ssl_status IN ('pending', 'validating', 'issued', 'expired', 'failed', 'revoked'));
83+
84+
ALTER TABLE ssl_certificates
85+
ADD CONSTRAINT check_certificate_status
86+
CHECK (status IN ('active', 'expired', 'revoked', 'pending_renewal'));
87+
88+
ALTER TABLE dns_records
89+
ADD CONSTRAINT check_record_type
90+
CHECK (record_type IN ('A', 'AAAA', 'CNAME', 'TXT', 'MX'));
91+
92+
ALTER TABLE domain_validations
93+
ADD CONSTRAINT check_validation_type
94+
CHECK (validation_type IN ('dns', 'http'));
95+
96+
ALTER TABLE domain_validations
97+
ADD CONSTRAINT check_validation_status
98+
CHECK (status IN ('pending', 'processing', 'valid', 'invalid', 'expired'));

src/deployment/models.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -240,6 +240,7 @@ pub struct DnsRecord {
240240
pub record_type: String,
241241
pub name: String,
242242
pub value: String,
243+
pub ttl: u32,
243244
}
244245

245246
#[derive(Debug, Serialize, ToSchema)]

src/handlers/auth.rs

Lines changed: 40 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
use axum::{
22
extract::{Path, Query, State},
33
response::Json,
4+
http::HeaderMap,
45
};
56
use bcrypt::{hash, verify, DEFAULT_COST};
67
use chrono::Utc;
@@ -19,6 +20,39 @@ use crate::{
1920
error::AppError,
2021
};
2122

23+
// Helper function to get frontend URL from headers or environment
24+
fn get_frontend_url(headers: &HeaderMap) -> String {
25+
// Check if we're in production by looking at production domain env var
26+
if let Ok(production_domain) = std::env::var("PRODUCTION_DOMAIN") {
27+
// Try to get host from request headers
28+
if let Some(host) = headers.get("host") {
29+
if let Ok(host_str) = host.to_str() {
30+
// If host matches production domain (decenter.run), use it
31+
if host_str.contains("decenter.run") {
32+
return production_domain;
33+
}
34+
// If it's localhost or development, use frontend URL
35+
else if host_str.contains("localhost") || host_str.contains("127.0.0.1") {
36+
return std::env::var("FRONTEND_BASE_URL")
37+
.unwrap_or_else(|_| "http://localhost:5173".to_string());
38+
}
39+
// For other domains (staging, etc), use HTTPS
40+
else {
41+
return format!("https://{}", host_str);
42+
}
43+
}
44+
}
45+
}
46+
47+
// Development fallback: try frontend base URL from env
48+
if let Ok(frontend_url) = std::env::var("FRONTEND_BASE_URL") {
49+
return frontend_url;
50+
}
51+
52+
// Final fallback
53+
"http://localhost:5173".to_string()
54+
}
55+
2256
#[utoipa::path(
2357
post,
2458
path = "/v1/auth/register",
@@ -378,6 +412,7 @@ pub async fn revoke_api_key(
378412
)]
379413
pub async fn forgot_password(
380414
State(state): State<AppState>,
415+
headers: HeaderMap,
381416
Json(payload): Json<ForgotPasswordRequest>,
382417
) -> Result<Json<ForgotPasswordResponse>, AppError> {
383418
payload.validate()?;
@@ -427,10 +462,10 @@ pub async fn forgot_password(
427462
.await?;
428463

429464
// Send password reset email
430-
let reset_url = format!("https://your-domain.com/reset-password?token={}", reset_token);
465+
let frontend_base_url = get_frontend_url(&headers);
466+
let reset_url = format!("{}/reset-password?token={}", frontend_base_url, reset_token);
431467

432-
// Temporarily use console logging instead of real email for demo
433-
// Password reset email (demo mode)
468+
// Send password reset email
434469

435470
if let Err(e) = state.email_service.send_password_reset_email(&user.email, &user.username, &reset_token, &reset_url) {
436471
tracing::error!("Failed to send password reset email: {}", e);
@@ -513,4 +548,5 @@ pub async fn reset_password(
513548
Ok(Json(ResetPasswordResponse {
514549
message: "Password has been reset successfully".to_string(),
515550
}))
516-
}
551+
}
552+

0 commit comments

Comments
 (0)