feat(domains): support username root domain claims
Build and publish Docker image / build-and-publish (push) Successful in 9s

This commit is contained in:
wfalkowski committed 2026-10-20 18:14:30 +00:00
1 parent 2768f5f97d
commit 079140ce49
4 files changed
+19 -14

No files matched your search

+11 -6
View File
@@ -463,7 +463,7 @@ class DomainManager:
"""
try:
# Validate inputs
if not username or not subdomain_name:
if not username or subdomain_name is None:
return {
'success': False,
'message': "Username and subdomain name are required",
@@ -478,7 +478,7 @@ class DomainManager:
return {'success': False, 'message': "Username cannot be represented as a DNS name", 'domain_data': None}
# Hostnames are single labels below u-<username>.
if not self._is_valid_subdomain(subdomain_name):
if subdomain_name and not self._is_valid_subdomain(subdomain_name):
return {
'success': False,
'message': "Invalid subdomain name. Use only letters, numbers, and hyphens",
@@ -487,7 +487,7 @@ class DomainManager:
# Construct domains
user_domain = f"u-{username}.{self.base_domain}"
full_subdomain = f"{subdomain_name}.{user_domain}"
full_subdomain = f"{subdomain_name}.{user_domain}" if subdomain_name else user_domain
namespace = self.ensure_user_namespace(username)
if not namespace['success']:
return {'success': False, 'message': namespace['message'], 'domain_data': None}
@@ -631,7 +631,7 @@ class DomainManager:
def delete_subdomain(self, username: str, subdomain_name: str) -> Dict:
"""Delete a subdomain"""
try:
if not username or not subdomain_name:
if not username or subdomain_name is None:
return {
'success': False,
'message': "Username and subdomain name are required"
@@ -639,9 +639,14 @@ class DomainManager:
username = username.lower().strip()
subdomain_name = subdomain_name.lower().strip()
if not self._is_valid_username_label(username) or not self._is_valid_subdomain(subdomain_name):
if not self._is_valid_username_label(username):
return {'success': False, 'message': "Invalid username or subdomain"}
user_domain = f"u-{username}.{self.base_domain}"
# Releasing the optional root claim must never remove the shared namespace DNS.
if not subdomain_name:
return {'success': True, 'message': f"Domain claim {user_domain} deleted"}
if not self._is_valid_subdomain(subdomain_name):
return {'success': False, 'message': "Invalid username or subdomain"}
full_subdomain = f"{subdomain_name}.{user_domain}"
# New namespaces use a shared wildcard in the parent zone. Do not
@@ -848,7 +853,7 @@ class DomainManager:
subdomain_name = subdomain_name.lower().strip()
# First validate the subdomain name
if not self._is_valid_subdomain(subdomain_name):
if subdomain_name and not self._is_valid_subdomain(subdomain_name):
return {
'available': False,
'message': f"Invalid subdomain name: {subdomain_name}"
+2 -2
View File
@@ -113,10 +113,10 @@ def create_domain_api():
return jsonify({'error': 'Domain service not available'}), 503
data = request.get_json()
subdomain = data.get('subdomain')
subdomain = '' if data.get('useUsernameDomain') is True else data.get('subdomain')
server_id = data.get('serverId')
if not subdomain:
if subdomain is None:
return jsonify({'error': 'Subdomain name required'}), 400
# Check if subdomain is available
+2 -2
View File
@@ -258,9 +258,9 @@ def apply_plan(api: DeSec, namespaces: Iterable[str], copies: List[Dict],
UPDATE user_domains
SET record_type = ?, ip_address = ?, ttl = 3600
WHERE COALESCE(is_custom, FALSE) = FALSE
AND lower(full_domain) LIKE ? ESCAPE '!'
AND (lower(full_domain) = ? OR lower(full_domain) LIKE ? ESCAPE '!')
""",
('CNAME', api.base_domain, pattern),
('CNAME', api.base_domain, f'{namespace}.{api.base_domain}', pattern),
)
connection.commit()
+4 -4
View File
@@ -1383,12 +1383,12 @@ async def handle_create_domain(client_id, data):
user_id = client_info['user_id']
username = client_info['username']
subdomain_name = data.get('subdomain')
subdomain_name = '' if data.get('useUsernameDomain') is True else data.get('subdomain')
server_id = data.get('serverId')
logger.info(f"DEBUG: Creating domain for user {username}, subdomain: {subdomain_name}")
if not subdomain_name:
if subdomain_name is None:
await send_message(client_id, {'type': 'error', 'error': 'Subdomain name required'})
return
@@ -1398,7 +1398,7 @@ async def handle_create_domain(client_id, data):
return
# Validate subdomain name
if not domain_manager._is_valid_subdomain(subdomain_name):
if subdomain_name and not domain_manager._is_valid_subdomain(subdomain_name):
await send_message(client_id, {'type': 'error', 'error': 'Invalid subdomain name. Use only lowercase letters, numbers, and hyphens (1-63 chars)'})
return
@@ -1409,7 +1409,7 @@ async def handle_create_domain(client_id, data):
try:
# Check if subdomain is available (improved check)
user_domain = f"u-{username}.{domain_manager.base_domain}"
full_subdomain = f"{subdomain_name}.{user_domain}"
full_subdomain = f"{subdomain_name}.{user_domain}" if subdomain_name else user_domain
# Check if domain already exists in database
with database_connection() as conn: