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:
parent
38c733cb12
commit
cf7819d233
28
rbox/mail.py
28
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
|
||||
|
||||
@ -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"""
|
||||
<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)
|
||||
|
||||
@router.get("/my", response_model=List[ShareOut])
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@ -113,6 +113,7 @@ export class FileList extends HTMLElement {
|
||||
` : ''}
|
||||
|
||||
<div class="file-grid">
|
||||
${totalItems === 0 ? '<p class="empty-state">No files found.</p>' : ''}
|
||||
${this.folders.map(folder => this.renderFolder(folder)).join('')}
|
||||
${this.files.map(file => this.renderFile(file)).join('')}
|
||||
</div>
|
||||
|
||||
@ -30,7 +30,7 @@ class PhotoGallery extends HTMLElement {
|
||||
async renderPhotos() {
|
||||
const grid = this.querySelector('.gallery-grid');
|
||||
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;
|
||||
}
|
||||
|
||||
|
||||
@ -20,6 +20,11 @@ export class ShareModal extends HTMLElement {
|
||||
</div>
|
||||
|
||||
<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">
|
||||
<label>Permission Level</label>
|
||||
<select name="permission_level" class="input-field">
|
||||
@ -101,6 +106,7 @@ export class ShareModal extends HTMLElement {
|
||||
const errorDiv = this.querySelector('#share-error');
|
||||
|
||||
try {
|
||||
const inviteEmail = formData.get('invite_email') || null;
|
||||
const permissionLevel = formData.get('permission_level');
|
||||
const password = formData.get('password') || null;
|
||||
const expiresAt = formData.get('expires_at') || null;
|
||||
@ -110,13 +116,20 @@ export class ShareModal extends HTMLElement {
|
||||
this.folderId,
|
||||
expiresAt,
|
||||
password,
|
||||
permissionLevel
|
||||
permissionLevel,
|
||||
inviteEmail
|
||||
);
|
||||
|
||||
const shareLink = `${window.location.origin}/share/${share.token}`;
|
||||
this.querySelector('#share-link').value = shareLink;
|
||||
this.querySelector('#share-result').style.display = 'block';
|
||||
errorDiv.textContent = '';
|
||||
|
||||
if (inviteEmail) {
|
||||
document.dispatchEvent(new CustomEvent('show-toast', {
|
||||
detail: { message: `Invitation sent to ${inviteEmail}`, type: 'success' }
|
||||
}));
|
||||
}
|
||||
} catch (error) {
|
||||
errorDiv.textContent = 'Failed to create share link: ' + error.message;
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user