Initial commit: VoIdeaAI - voice-first AI idea assistant
This commit is contained in:
@@ -0,0 +1,101 @@
|
||||
"""2FA TOTP service for VoIdea."""
|
||||
|
||||
import base64
|
||||
import io
|
||||
import json
|
||||
from typing import Any
|
||||
|
||||
import pyotp
|
||||
import qrcode
|
||||
from sqlalchemy import select
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
from app.models.agent import AgentConfig
|
||||
from app.models.user import User
|
||||
|
||||
|
||||
def generate_totp_secret() -> str:
|
||||
return pyotp.random_base32()
|
||||
|
||||
|
||||
def get_totp_uri(secret: str, email: str, issuer: str = "VoIdeaAI") -> str:
|
||||
return pyotp.totp.TOTP(secret).provisioning_uri(name=email, issuer_name=issuer)
|
||||
|
||||
|
||||
def generate_qr_base64(uri: str) -> str:
|
||||
qr = qrcode.make(uri)
|
||||
buf = io.BytesIO()
|
||||
qr.save(buf, format="PNG")
|
||||
return base64.b64encode(buf.getvalue()).decode()
|
||||
|
||||
|
||||
def verify_totp(secret: str, token: str) -> bool:
|
||||
totp = pyotp.TOTP(secret)
|
||||
return totp.verify(token)
|
||||
|
||||
|
||||
def get_user_secret(user: User) -> str | None:
|
||||
tuning = user.pipeline_tuning or {}
|
||||
return tuning.get("totp_secret")
|
||||
|
||||
|
||||
def set_user_secret(user: User, secret: str) -> None:
|
||||
tuning = user.pipeline_tuning or {}
|
||||
tuning["totp_secret"] = secret
|
||||
user.pipeline_tuning = tuning
|
||||
|
||||
|
||||
def is_2fa_enabled(user: User) -> bool:
|
||||
tuning = user.pipeline_tuning or {}
|
||||
return bool(tuning.get("totp_enabled", False))
|
||||
|
||||
|
||||
def set_2fa_enabled(user: User, enabled: bool) -> None:
|
||||
tuning = user.pipeline_tuning or {}
|
||||
tuning["totp_enabled"] = enabled
|
||||
user.pipeline_tuning = tuning
|
||||
|
||||
|
||||
# ── Global 2FA toggle (stored in AgentConfig for "system") ──
|
||||
|
||||
_GLOBAL_2FA_KEY = "2fa_globally_enabled"
|
||||
|
||||
|
||||
async def is_2fa_globally_enabled(db: AsyncSession) -> bool:
|
||||
result = await db.execute(
|
||||
select(AgentConfig).where(AgentConfig.agent_name == "system")
|
||||
)
|
||||
cfg = result.scalar_one_or_none()
|
||||
if not cfg or not cfg.config:
|
||||
return True
|
||||
try:
|
||||
data = json.loads(cfg.config)
|
||||
return bool(data.get(_GLOBAL_2FA_KEY, True))
|
||||
except (json.JSONDecodeError, TypeError):
|
||||
return True
|
||||
|
||||
|
||||
async def set_2fa_globally_enabled(db: AsyncSession, enabled: bool) -> None:
|
||||
result = await db.execute(
|
||||
select(AgentConfig).where(AgentConfig.agent_name == "system")
|
||||
)
|
||||
cfg = result.scalar_one_or_none()
|
||||
if not cfg:
|
||||
cfg = AgentConfig(
|
||||
agent_name="system",
|
||||
description="System-wide configuration",
|
||||
is_enabled=True,
|
||||
version="1.0.0",
|
||||
config=json.dumps({_GLOBAL_2FA_KEY: enabled}, ensure_ascii=False),
|
||||
)
|
||||
db.add(cfg)
|
||||
else:
|
||||
current = {}
|
||||
if cfg.config:
|
||||
try:
|
||||
current = json.loads(cfg.config)
|
||||
except (json.JSONDecodeError, TypeError):
|
||||
current = {}
|
||||
current[_GLOBAL_2FA_KEY] = enabled
|
||||
cfg.config = json.dumps(current, ensure_ascii=False)
|
||||
await db.commit()
|
||||
Reference in New Issue
Block a user