Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 0 additions & 5 deletions src/authsome/paths.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,3 @@ def get_client_log_path(home: Path | None = None) -> Path:
def get_server_log_path(home: Path | None = None) -> Path:
"""Return the default server log file path."""
return get_server_home(home) / "logs" / "authsome.log"


def get_server_audit_db_path(home: Path | None = None) -> Path:
"""Return the server-owned audit event database path."""
return get_server_home(home) / "audit" / "events.sqlite3"
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
"""Hosted account authentication for browser UI sessions."""
"""Account authentication for browser UI sessions."""

from __future__ import annotations

Expand All @@ -22,18 +22,18 @@


@dataclass(frozen=True)
class HostedAccountSession:
"""Authenticated hosted browser account session."""
class AccountSession:
"""Authenticated browser account session."""

principal_id: str
email: str
token: str


class HostedAccountService:
"""Register and authenticate hosted accounts.
class AccountAuthService:
"""Register and authenticate browser accounts.

In hosted mode, the hosted account is the principal.
An account is represented by a Principal and can claim one or more identities.
"""

def __init__(
Expand All @@ -58,7 +58,7 @@ async def register(self, *, email: str, password: str) -> PrincipalRecord:
if principal is None:
principal = await self._principals.create_by_email(normalized, password_hash=password_hash)
elif principal.password_hash is not None:
raise ValueError(f"Hosted account '{normalized}' is already registered")
raise ValueError(f"Account '{normalized}' is already registered")
else:
principal = await self._principals.update_password(principal.principal_id, password_hash=password_hash)
await ensure_principal_default_vault(
Expand All @@ -68,23 +68,23 @@ async def register(self, *, email: str, password: str) -> PrincipalRecord:
)
return principal

async def register_and_login(self, *, email: str, password: str) -> HostedAccountSession:
async def register_and_login(self, *, email: str, password: str) -> AccountSession:
record = await self.register(email=email, password=password)
return HostedAccountSession(
return AccountSession(
principal_id=record.principal_id,
email=record.email,
token=self.issue_token(principal_id=record.principal_id, email=record.email),
)

async def login(self, *, email: str, password: str) -> HostedAccountSession:
async def login(self, *, email: str, password: str) -> AccountSession:
principal = await self._principals.get_by_email(self._normalize_email(email))
if principal is None or not principal.password_hash:
raise ValueError("Invalid email or password")
try:
self._hasher.verify(principal.password_hash, password)
except (VerificationError, VerifyMismatchError) as exc:
raise ValueError("Invalid email or password") from exc
return HostedAccountSession(
return AccountSession(
principal_id=principal.principal_id,
email=principal.email,
token=self.issue_token(principal_id=principal.principal_id, email=principal.email),
Expand Down
8 changes: 3 additions & 5 deletions src/authsome/server/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,14 +13,12 @@
from authsome.errors import AuthsomeError
from authsome.identity.proof import ReplayCache
from authsome.server.analytics import init_posthog, shutdown_posthog
from authsome.server.audit import configure_server_audit_log
from authsome.server.dependencies import (
create_hosted_account_service,
create_account_auth_service,
create_identity_bootstrap_service,
create_ownership_resolver,
create_store,
create_vault,
get_server_audit_db_path,
get_server_base_url,
load_server_config,
load_ui_session_signing_secret,
Expand All @@ -44,7 +42,7 @@ async def lifespan(app: FastAPI):
"""Manage daemon lifecycle."""
app.state.store = await create_store()
app.state.server_config = await load_server_config(app.state.store)
app.state.audit_log = configure_server_audit_log(get_server_audit_db_path(app.state.store.home))
app.state.audit_log = app.state.store.audit_events.configure_exporter()
app.state.vault = await create_vault(app.state.store.home)
app.state.auth_sessions = AuthSessionStore()
app.state.ui_sessions = UiSessionStore(load_ui_session_signing_secret(app.state.store.home))
Expand All @@ -54,7 +52,7 @@ async def lifespan(app: FastAPI):
app.state.identity_claim_registry = app.state.store.identity_claims
app.state.principal_vault_binding_registry = app.state.store.principal_vault_bindings
app.state.provider_repository = ProviderRepository(app.state.store.provider_definitions)
app.state.hosted_account_service = create_hosted_account_service(app.state.store)
app.state.account_auth_service = create_account_auth_service(app.state.store)
app.state.server_base_url = get_server_base_url()
init_posthog()
app.state.identity_bootstrap = create_identity_bootstrap_service(
Expand Down
207 changes: 0 additions & 207 deletions src/authsome/server/audit.py

This file was deleted.

10 changes: 9 additions & 1 deletion src/authsome/server/credential_service.py
Original file line number Diff line number Diff line change
Expand Up @@ -235,7 +235,7 @@ async def get_connection(
raise ConnectionNotFoundError(
provider=provider,
connection=connection,
identity=self._identity or self._principal_id or "hosted-ui",
identity=self._identity or self._principal_id or "account-ui",
)
return record

Expand Down Expand Up @@ -489,6 +489,14 @@ async def resume_login_flow(

await self._save_connection(result.connection)
await self._update_provider_metadata(provider, connection_name)
audit.emit_event(
"provider.login",
provider=provider,
connection=connection_name,
identity=self._identity,
principal_id=self._principal_id,
status="success",
)

logger.info(
"Login successful: provider={} connection={} identity={}",
Expand Down
12 changes: 3 additions & 9 deletions src/authsome/server/dependencies.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,10 @@
from authsome.auth.models.config import ServerConfig
from authsome.identity import current_from_home
from authsome.paths import get_authsome_home as _get_authsome_home
from authsome.paths import get_server_audit_db_path as _get_server_audit_db_path
from authsome.paths import get_server_home as _get_server_home
from authsome.paths import get_server_log_path as _get_server_log_path
from authsome.server.account_auth import AccountAuthService
from authsome.server.credential_repository import CredentialRepository
from authsome.server.hosted_auth import HostedAccountService
from authsome.server.identity_bootstrap import IdentityBootstrapService
from authsome.server.ownership import OwnershipResolver
from authsome.server.provider_repository import ProviderRepository
Expand Down Expand Up @@ -45,11 +44,6 @@ def get_server_log_path(home: Path | None = None) -> Path:
return _get_server_log_path(home)


def get_server_audit_db_path(home: Path | None = None) -> Path:
"""Return the daemon-owned audit event database path."""
return _get_server_audit_db_path(home)


def get_server_base_url() -> str:
"""Return the daemon's canonical external base URL."""
return build_server_base_url()
Expand Down Expand Up @@ -135,8 +129,8 @@ async def create_auth_service(
)


def create_hosted_account_service(store: ServerStore) -> HostedAccountService:
return HostedAccountService(
def create_account_auth_service(store: ServerStore) -> AccountAuthService:
return AccountAuthService(
principals=store.principals,
vaults=store.vaults,
bindings=store.principal_vault_bindings,
Expand Down
4 changes: 2 additions & 2 deletions src/authsome/server/routes/_deps.py
Original file line number Diff line number Diff line change
Expand Up @@ -93,12 +93,12 @@ async def require_auth_service(
async def get_principal_browser_auth_service(request: Request) -> AuthService:
cookie_value = request.cookies.get(UI_SESSION_COOKIE_NAME)
if not cookie_value:
raise HTTPException(status_code=401, detail="Missing hosted browser session")
raise HTTPException(status_code=401, detail="Missing browser session")

try:
session = request.app.state.ui_sessions.get_browser_session(cookie_value)
except KeyError as exc:
raise HTTPException(status_code=401, detail="Hosted browser session expired") from exc
raise HTTPException(status_code=401, detail="Browser session expired") from exc

auth = await require_auth_service(
request,
Expand Down
Loading
Loading