feat(caldav): app-specific passwords so MFA accounts can use CalDAV
CalDAV clients send only user+password over Basic Auth and can't provide a TOTP code, so account passwords would bypass 2FA. Add revocable app passwords: - models: AppPassword table (bcrypt hash, label, last_used); auto-created via create_all - profile_router: GET/POST/DELETE /profile/app-passwords (plaintext shown once) - dav_router: Basic Auth accepts any app password; the account password is accepted only when 2FA is disabled - frontend: "App-Passwörter (CalDAV)" section in the profile modal (create/show- once/copy/revoke) + i18n (de/en); login hint now says app password Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -114,6 +114,26 @@ class UserSettings(Base):
|
||||
user = relationship("User", back_populates="settings")
|
||||
|
||||
|
||||
class AppPassword(Base):
|
||||
"""Per-device app-specific password for CalDAV (Basic Auth).
|
||||
|
||||
Keeps MFA intact: accounts with 2FA can't use their normal password over
|
||||
CalDAV (clients can't send a TOTP code), so they authenticate with one of
|
||||
these revocable app passwords instead. Only the bcrypt hash is stored.
|
||||
"""
|
||||
|
||||
__tablename__ = "app_passwords"
|
||||
|
||||
id = Column(Integer, primary_key=True, index=True)
|
||||
user_id = Column(Integer, ForeignKey("users.id"), nullable=False)
|
||||
label = Column(String(100), nullable=False)
|
||||
password_hash = Column(String(255), nullable=False)
|
||||
created_at = Column(String(50), nullable=True)
|
||||
last_used_at = Column(String(50), nullable=True)
|
||||
|
||||
user = relationship("User")
|
||||
|
||||
|
||||
class LocalCalendar(Base):
|
||||
__tablename__ = "local_calendars"
|
||||
|
||||
|
||||
Reference in New Issue
Block a user