Add user delete process

This commit is contained in:
Urtzi Alfaro
2025-10-31 11:54:19 +01:00
parent 63f5c6d512
commit 269d3b5032
74 changed files with 16783 additions and 213 deletions

View File

@@ -336,3 +336,73 @@ analytics_tier_required = require_subscription_tier(['professional', 'enterprise
enterprise_tier_required = require_subscription_tier(['enterprise'])
admin_role_required = require_user_role(['admin', 'owner'])
owner_role_required = require_user_role(['owner'])
def service_only_access(func: Callable) -> Callable:
"""
Decorator to restrict endpoint access to service-to-service calls only
This decorator validates that:
1. The request has a valid service token (type='service' in JWT)
2. The token is from an authorized internal service
Usage:
@router.delete("/tenant/{tenant_id}")
@service_only_access
async def delete_tenant_data(
tenant_id: str,
current_user: dict = Depends(get_current_user_dep),
db = Depends(get_db)
):
# Service-only logic here
The decorator expects current_user to be injected via get_current_user_dep
dependency, which should already contain the user/service context from JWT.
"""
@wraps(func)
async def wrapper(*args, **kwargs):
# Get current user from kwargs (injected by get_current_user_dep)
current_user = kwargs.get('current_user')
if not current_user:
# Try to find in args
for arg in args:
if isinstance(arg, dict) and 'user_id' in arg:
current_user = arg
break
if not current_user:
logger.error("Service-only access: current user not found in request context")
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Authentication required"
)
# Check if this is a service token
user_type = current_user.get('type', '')
is_service = current_user.get('is_service', False)
if user_type != 'service' and not is_service:
logger.warning(
"Service-only access denied: not a service token",
user_id=current_user.get('user_id'),
user_type=user_type,
is_service=is_service
)
raise HTTPException(
status_code=status.HTTP_403_FORBIDDEN,
detail="This endpoint is only accessible to internal services"
)
# Log successful service access
service_name = current_user.get('service', current_user.get('user_id', 'unknown'))
logger.info(
"Service-only access granted",
service=service_name,
endpoint=func.__name__
)
return await func(*args, **kwargs)
return wrapper

View File

@@ -201,6 +201,43 @@ class JWTHandler:
return None
def create_service_token(self, service_name: str, expires_delta: Optional[timedelta] = None) -> str:
"""
Create JWT token for service-to-service communication
Args:
service_name: Name of the service (e.g., 'auth-service', 'tenant-service')
expires_delta: Optional expiration time (defaults to 365 days for services)
Returns:
Encoded JWT service token
"""
to_encode = {
"sub": service_name,
"user_id": service_name,
"service": service_name,
"type": "service",
"is_service": True,
"role": "admin", # Services have admin privileges
"email": f"{service_name}@internal.service"
}
# Set expiration (default to 1 year for service tokens)
if expires_delta:
expire = datetime.now(timezone.utc) + expires_delta
else:
expire = datetime.now(timezone.utc) + timedelta(days=365)
to_encode.update({
"exp": expire,
"iat": datetime.now(timezone.utc),
"iss": "bakery-auth"
})
encoded_jwt = jwt.encode(to_encode, self.secret_key, algorithm=self.algorithm)
logger.info(f"Created service token for {service_name}")
return encoded_jwt
def get_token_info(self, token: str) -> Dict[str, Any]:
"""
Get comprehensive token information for debugging
@@ -214,7 +251,7 @@ class JWTHandler:
"exp": None,
"iat": None
}
try:
# Try unsafe decode first
payload = self.decode_token_no_verify(token)
@@ -227,12 +264,12 @@ class JWTHandler:
"iat": payload.get("iat"),
"expired": self.is_token_expired(token)
})
# Try full verification
verified_payload = self.verify_token(token)
info["valid"] = verified_payload is not None
except Exception as e:
logger.warning(f"Failed to get token info: {e}")
return info