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.
This commit is contained in:
retoor 2025-11-11 14:06:02 +00:00
parent 38c733cb12
commit cf7819d233
7 changed files with 87 additions and 13 deletions

View File

@ -79,15 +79,25 @@ class EmailService:
msg.attach(html_part) msg.attach(html_part)
try: try:
async with aiosmtplib.SMTP( if settings.SMTP_PORT == 465:
hostname=settings.SMTP_SERVER, async with aiosmtplib.SMTP(
port=settings.SMTP_PORT, hostname=settings.SMTP_SERVER,
username=settings.SMTP_USERNAME, port=settings.SMTP_PORT,
password=settings.SMTP_PASSWORD, username=settings.SMTP_USERNAME,
use_tls=True password=settings.SMTP_PASSWORD,
) as smtp: use_tls=True
await smtp.send_message(msg) ) as smtp:
print(f"Email sent to {task.to_email}") 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: except Exception as e:
print(f"Failed to send email to {task.to_email}: {e}") print(f"Failed to send email to {task.to_email}: {e}")
raise # Re-raise to let caller handle raise # Re-raise to let caller handle

View File

@ -9,6 +9,8 @@ from ..models import User, File, Folder, Share
from ..schemas import ShareCreate, ShareOut, FileOut, FolderOut from ..schemas import ShareCreate, ShareOut, FileOut, FolderOut
from ..auth import get_password_hash, verify_password from ..auth import get_password_hash, verify_password
from ..storage import storage_manager from ..storage import storage_manager
from ..mail import send_email
from ..settings import settings
router = APIRouter( router = APIRouter(
prefix="/shares", prefix="/shares",
@ -52,6 +54,52 @@ async def create_share_link(share_in: ShareCreate, current_user: User = Depends(
hashed_password=hashed_password, hashed_password=hashed_password,
permission_level=share_in.permission_level, 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"""
<html>
<body>
<p>Hello,</p>
<p><strong>{current_user.username}</strong> has shared a {item_type} with you: <strong>{item_name}</strong></p>
<p><strong>Permission level:</strong> {share_in.permission_level}</p>
<p><a href="{share_url}" style="background-color: #4CAF50; color: white; padding: 10px 20px; text-decoration: none; border-radius: 4px; display: inline-block;">Access {item_type.capitalize()}</a></p>
{f'<p><strong>Password:</strong> {share_in.password}</p>' if share_in.password else ''}
<p><small>This link is valid{expiry_text}.</small></p>
<hr>
<p><small>RBox File Sharing Service</small></p>
</body>
</html>
"""
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) return await ShareOut.from_tortoise_orm(share)
@router.get("/my", response_model=List[ShareOut]) @router.get("/my", response_model=List[ShareOut])

View File

@ -47,6 +47,7 @@ class ShareCreate(BaseModel):
expires_at: Optional[datetime] = None expires_at: Optional[datetime] = None
password: Optional[str] = None password: Optional[str] = None
permission_level: str = "viewer" permission_level: str = "viewer"
invite_email: Optional[EmailStr] = None
class TeamCreate(BaseModel): class TeamCreate(BaseModel):
name: str name: str

View File

@ -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/', { return this.request('shares/', {
method: 'POST', method: 'POST',
body: { body: {
@ -264,7 +264,8 @@ class APIClient {
folder_id: folderId, folder_id: folderId,
expires_at: expiresAt, expires_at: expiresAt,
password, password,
permission_level: permissionLevel permission_level: permissionLevel,
invite_email: inviteEmail
} }
}); });
} }

View File

@ -113,6 +113,7 @@ export class FileList extends HTMLElement {
` : ''} ` : ''}
<div class="file-grid"> <div class="file-grid">
${totalItems === 0 ? '<p class="empty-state">No files found.</p>' : ''}
${this.folders.map(folder => this.renderFolder(folder)).join('')} ${this.folders.map(folder => this.renderFolder(folder)).join('')}
${this.files.map(file => this.renderFile(file)).join('')} ${this.files.map(file => this.renderFile(file)).join('')}
</div> </div>

View File

@ -30,7 +30,7 @@ class PhotoGallery extends HTMLElement {
async renderPhotos() { async renderPhotos() {
const grid = this.querySelector('.gallery-grid'); const grid = this.querySelector('.gallery-grid');
if (this.photos.length === 0) { if (this.photos.length === 0) {
grid.innerHTML = '<p class="empty-state">No photos yet</p>'; grid.innerHTML = '<p class="empty-state">No photos found.</p>';
return; return;
} }

View File

@ -20,6 +20,11 @@ export class ShareModal extends HTMLElement {
</div> </div>
<form id="share-form"> <form id="share-form">
<div class="form-group">
<label>Invite Email (optional)</label>
<input type="email" name="invite_email" class="input-field" placeholder="Send invitation to email">
</div>
<div class="form-group"> <div class="form-group">
<label>Permission Level</label> <label>Permission Level</label>
<select name="permission_level" class="input-field"> <select name="permission_level" class="input-field">
@ -101,6 +106,7 @@ export class ShareModal extends HTMLElement {
const errorDiv = this.querySelector('#share-error'); const errorDiv = this.querySelector('#share-error');
try { try {
const inviteEmail = formData.get('invite_email') || null;
const permissionLevel = formData.get('permission_level'); const permissionLevel = formData.get('permission_level');
const password = formData.get('password') || null; const password = formData.get('password') || null;
const expiresAt = formData.get('expires_at') || null; const expiresAt = formData.get('expires_at') || null;
@ -110,13 +116,20 @@ export class ShareModal extends HTMLElement {
this.folderId, this.folderId,
expiresAt, expiresAt,
password, password,
permissionLevel permissionLevel,
inviteEmail
); );
const shareLink = `${window.location.origin}/share/${share.token}`; const shareLink = `${window.location.origin}/share/${share.token}`;
this.querySelector('#share-link').value = shareLink; this.querySelector('#share-link').value = shareLink;
this.querySelector('#share-result').style.display = 'block'; this.querySelector('#share-result').style.display = 'block';
errorDiv.textContent = ''; errorDiv.textContent = '';
if (inviteEmail) {
document.dispatchEvent(new CustomEvent('show-toast', {
detail: { message: `Invitation sent to ${inviteEmail}`, type: 'success' }
}));
}
} catch (error) { } catch (error) {
errorDiv.textContent = 'Failed to create share link: ' + error.message; errorDiv.textContent = 'Failed to create share link: ' + error.message;
} }