00bfe8bfca
Pluggable AuthProvider interface with a local (email+password) implementation, and a Mailer interface (ConsoleMailer for dev, SMTPMailer for operators). The auth service owns registration, login, opaque session issuance, email verification, and password reset (which revokes prior sessions). Endpoints under /api/v1/auth; sessions are returned as a Bearer token and set as an HttpOnly cookie. Replaces the temporary X-User-Id shim: get_current_user now resolves a real session (Bearer or cookie). The open user-bootstrap endpoint is gone (registration replaces it). App logging is configured so the ConsoleMailer's verification/reset links are visible to self-hosters. Verified end-to-end on the deploy target, including the email-verification flow. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Signed-off-by: Justin Paul <justin@jpaul.me>
28 lines
847 B
Python
28 lines
847 B
Python
"""Local (email + password) auth provider."""
|
|
|
|
from sqlalchemy import select
|
|
from sqlalchemy.ext.asyncio import AsyncSession
|
|
|
|
from app.core.security import verify_password
|
|
from app.integrations.auth.base import AuthProvider
|
|
from app.models.user import User
|
|
|
|
|
|
class LocalAuthProvider(AuthProvider):
|
|
name = "local"
|
|
|
|
async def authenticate(
|
|
self, session: AsyncSession, *, identifier: str, secret: str
|
|
) -> User | None:
|
|
email = identifier.strip().lower()
|
|
user = (
|
|
await session.execute(
|
|
select(User).where(User.email == email, User.deleted_at.is_(None))
|
|
)
|
|
).scalar_one_or_none()
|
|
if user is None or user.hashed_password is None:
|
|
return None
|
|
if not verify_password(user.hashed_password, secret):
|
|
return None
|
|
return user
|