Files
microdao-daarion/services/auth-service/routes_sessions.py

132 lines
3.4 KiB
Python

"""
Session management routes
"""
from fastapi import APIRouter, HTTPException, Depends, Response
import asyncpg
from datetime import datetime, timedelta, timezone
import secrets
from models import LoginRequest, LoginResponse, ActorIdentity, ActorType
from actor_context import require_actor
import json
router = APIRouter(prefix="/auth", tags=["sessions"])
# Mock users for Phase 4
# In production, this would be in database with proper password hashing
MOCK_USERS = {
"admin@daarion.city": {
"actor_id": "user:1",
"actor_type": "human",
"microdao_ids": ["microdao:daarion"],
"roles": ["system_admin", "microdao_owner"]
},
"user@daarion.city": {
"actor_id": "user:93",
"actor_type": "human",
"microdao_ids": ["microdao:daarion", "microdao:7"],
"roles": ["member", "microdao_owner"]
},
"sofia@agents.daarion.city": {
"actor_id": "agent:sofia",
"actor_type": "agent",
"microdao_ids": ["microdao:daarion"],
"roles": ["agent"]
}
}
def get_db_pool(request) -> asyncpg.Pool:
"""Get database pool from app state"""
return request.app.state.db_pool
@router.post("/login", response_model=LoginResponse)
async def login(
request: LoginRequest,
response: Response,
db_pool: asyncpg.Pool = Depends(get_db_pool)
):
"""
Login and get session token
Phase 4: Mock implementation with predefined users
Phase 5: Real Passkey integration
"""
# Check mock users
if request.email not in MOCK_USERS:
raise HTTPException(401, "Invalid credentials")
user_data = MOCK_USERS[request.email]
# Build ActorIdentity
actor = ActorIdentity(
actor_id=user_data["actor_id"],
actor_type=ActorType(user_data["actor_type"]),
microdao_ids=user_data["microdao_ids"],
roles=user_data["roles"]
)
# Generate session token
token = secrets.token_urlsafe(32)
expires_at = datetime.now(timezone.utc) + timedelta(days=7)
# Store in database
async with db_pool.acquire() as conn:
await conn.execute(
"""
INSERT INTO sessions (token, actor_id, actor_data, expires_at)
VALUES ($1, $2, $3, $4)
""",
token,
actor.actor_id,
json.dumps(actor.model_dump()),
expires_at
)
# Set cookie
response.set_cookie(
key="session_token",
value=token,
httponly=True,
max_age=7 * 24 * 60 * 60, # 7 days
samesite="lax"
)
return LoginResponse(
session_token=token,
actor=actor,
expires_at=expires_at
)
@router.get("/me", response_model=ActorIdentity)
async def get_me(
actor: ActorIdentity = Depends(require_actor)
):
"""Get current actor identity"""
return actor
@router.post("/logout")
async def logout(
response: Response,
actor: ActorIdentity = Depends(require_actor),
db_pool: asyncpg.Pool = Depends(get_db_pool)
):
"""Logout and invalidate session"""
# Invalidate all sessions for this actor
async with db_pool.acquire() as conn:
await conn.execute(
"UPDATE sessions SET is_valid = false WHERE actor_id = $1",
actor.actor_id
)
# Clear cookie
response.delete_cookie("session_token")
return {"status": "logged_out"}