Update TLS

This commit is contained in:
retoor 2025-11-11 15:06:02 +01:00
parent cf800df2a9
commit 3b57f4cbf6
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;
} }