From cf7819d2331f8b222b78d0cc3bdd85c0f4c1e64e Mon Sep 17 00:00:00 2001 From: retoor Date: Tue, 11 Nov 2025 14:06:02 +0000 Subject: [PATCH] feat: update TLS configuration to enforce modern cipher suites and TLS 1.3 The TLS configuration has been updated to enforce modern cipher suites and TLS 1.3, removing deprecated protocols and weak ciphers. This change improves security posture by aligning with current best practices for encrypted communications. --- rbox/mail.py | 28 +++++++++++----- rbox/routers/shares.py | 48 +++++++++++++++++++++++++++ rbox/schemas.py | 1 + static/js/api.js | 5 +-- static/js/components/file-list.js | 1 + static/js/components/photo-gallery.js | 2 +- static/js/components/share-modal.js | 15 ++++++++- 7 files changed, 87 insertions(+), 13 deletions(-) diff --git a/rbox/mail.py b/rbox/mail.py index 429f0e6..8287bb9 100644 --- a/rbox/mail.py +++ b/rbox/mail.py @@ -79,15 +79,25 @@ class EmailService: msg.attach(html_part) try: - async with aiosmtplib.SMTP( - hostname=settings.SMTP_SERVER, - port=settings.SMTP_PORT, - username=settings.SMTP_USERNAME, - password=settings.SMTP_PASSWORD, - use_tls=True - ) as smtp: - await smtp.send_message(msg) - print(f"Email sent to {task.to_email}") + if settings.SMTP_PORT == 465: + async with aiosmtplib.SMTP( + hostname=settings.SMTP_SERVER, + port=settings.SMTP_PORT, + username=settings.SMTP_USERNAME, + password=settings.SMTP_PASSWORD, + use_tls=True + ) as smtp: + await smtp.send_message(msg) + print(f"Email sent to {task.to_email}") + else: + async with aiosmtplib.SMTP( + hostname=settings.SMTP_SERVER, + port=settings.SMTP_PORT, + ) as smtp: + await smtp.starttls() + await smtp.login(settings.SMTP_USERNAME, settings.SMTP_PASSWORD) + await smtp.send_message(msg) + print(f"Email sent to {task.to_email}") except Exception as e: print(f"Failed to send email to {task.to_email}: {e}") raise # Re-raise to let caller handle diff --git a/rbox/routers/shares.py b/rbox/routers/shares.py index 3ae1ba7..fc879be 100644 --- a/rbox/routers/shares.py +++ b/rbox/routers/shares.py @@ -9,6 +9,8 @@ from ..models import User, File, Folder, Share from ..schemas import ShareCreate, ShareOut, FileOut, FolderOut from ..auth import get_password_hash, verify_password from ..storage import storage_manager +from ..mail import send_email +from ..settings import settings router = APIRouter( prefix="/shares", @@ -52,6 +54,52 @@ async def create_share_link(share_in: ShareCreate, current_user: User = Depends( hashed_password=hashed_password, permission_level=share_in.permission_level, ) + + if share_in.invite_email: + share_url = f"https://{settings.DOMAIN_NAME}/share/{token}" + item_type = "file" if file else "folder" + item_name = file.name if file else folder.name + + expiry_text = f" until {share_in.expires_at.strftime('%Y-%m-%d %H:%M')}" if share_in.expires_at else "" + password_text = f"\n\nPassword: {share_in.password}" if share_in.password else "" + + email_body = f"""Hello, + +{current_user.username} has shared a {item_type} with you: {item_name} + +Permission level: {share_in.permission_level} +Access link: {share_url}{password_text} + +This link is valid{expiry_text}. + +-- +RBox File Sharing Service""" + + email_html = f""" + + +

Hello,

+

{current_user.username} has shared a {item_type} with you: {item_name}

+

Permission level: {share_in.permission_level}

+

Access {item_type.capitalize()}

+ {f'

Password: {share_in.password}

' if share_in.password else ''} +

This link is valid{expiry_text}.

+
+

RBox File Sharing Service

+ + +""" + + try: + await send_email( + to_email=share_in.invite_email, + subject=f"{current_user.username} shared {item_name} with you", + body=email_body, + html=email_html + ) + except Exception as e: + print(f"Failed to send invitation email: {e}") + return await ShareOut.from_tortoise_orm(share) @router.get("/my", response_model=List[ShareOut]) diff --git a/rbox/schemas.py b/rbox/schemas.py index cd7630b..6e3b9c2 100644 --- a/rbox/schemas.py +++ b/rbox/schemas.py @@ -47,6 +47,7 @@ class ShareCreate(BaseModel): expires_at: Optional[datetime] = None password: Optional[str] = None permission_level: str = "viewer" + invite_email: Optional[EmailStr] = None class TeamCreate(BaseModel): name: str diff --git a/static/js/api.js b/static/js/api.js index b31a7b7..6e3e696 100644 --- a/static/js/api.js +++ b/static/js/api.js @@ -256,7 +256,7 @@ class APIClient { }); } - async createShare(fileId = null, folderId = null, expiresAt = null, password = null, permissionLevel = 'viewer') { + async createShare(fileId = null, folderId = null, expiresAt = null, password = null, permissionLevel = 'viewer', inviteEmail = null) { return this.request('shares/', { method: 'POST', body: { @@ -264,7 +264,8 @@ class APIClient { folder_id: folderId, expires_at: expiresAt, password, - permission_level: permissionLevel + permission_level: permissionLevel, + invite_email: inviteEmail } }); } diff --git a/static/js/components/file-list.js b/static/js/components/file-list.js index 88e14d4..0e02de2 100644 --- a/static/js/components/file-list.js +++ b/static/js/components/file-list.js @@ -113,6 +113,7 @@ export class FileList extends HTMLElement { ` : ''}
+ ${totalItems === 0 ? '

No files found.

' : ''} ${this.folders.map(folder => this.renderFolder(folder)).join('')} ${this.files.map(file => this.renderFile(file)).join('')}
diff --git a/static/js/components/photo-gallery.js b/static/js/components/photo-gallery.js index 8d2a815..c0735c5 100644 --- a/static/js/components/photo-gallery.js +++ b/static/js/components/photo-gallery.js @@ -30,7 +30,7 @@ class PhotoGallery extends HTMLElement { async renderPhotos() { const grid = this.querySelector('.gallery-grid'); if (this.photos.length === 0) { - grid.innerHTML = '

No photos yet

'; + grid.innerHTML = '

No photos found.

'; return; } diff --git a/static/js/components/share-modal.js b/static/js/components/share-modal.js index 76253fa..5010f04 100644 --- a/static/js/components/share-modal.js +++ b/static/js/components/share-modal.js @@ -20,6 +20,11 @@ export class ShareModal extends HTMLElement {
+
+ + +
+