main
py 124 lines 4.01 KB
Raw
1 """SSO configuration and allowed email models."""
2
3 import datetime
4 from typing import Optional
5
6 from pydantic import BaseModel
7 from pydantic import EmailStr
8 from sqlmodel import Field
9 from sqlmodel import SQLModel
10
11
12 class SSOConfig(SQLModel, table=True):
13 """Stores SSO provider configuration. Only one row should exist (id=1)."""
14
15 __tablename__ = "sso_config"
16
17 id: Optional[int] = Field(default=None, primary_key=True)
18
19 # Global toggle
20 sso_enabled: bool = Field(default=False)
21
22 # --- Azure Entra ID (OAuth2 / OIDC) ---
23 azure_enabled: bool = Field(default=False)
24 azure_tenant_id: Optional[str] = Field(default=None, max_length=256)
25 azure_client_id: Optional[str] = Field(default=None, max_length=256)
26 azure_client_secret: Optional[str] = Field(default=None, max_length=512)
27 azure_redirect_uri: Optional[str] = Field(default=None, max_length=512)
28
29 # --- Google OAuth2 / OIDC ---
30 google_enabled: bool = Field(default=False)
31 google_client_id: Optional[str] = Field(default=None, max_length=256)
32 google_client_secret: Optional[str] = Field(default=None, max_length=512)
33 google_redirect_uri: Optional[str] = Field(default=None, max_length=512)
34
35 # --- Cloudflare Access (JWT assertion) ---
36 cf_enabled: bool = Field(default=False)
37 cf_team_domain: Optional[str] = Field(default=None, max_length=256)
38 cf_audience: Optional[str] = Field(default=None, max_length=512)
39
40 updated_at: datetime.datetime = Field(default_factory=datetime.datetime.utcnow)
41
42
43 class SSOAllowedEmail(SQLModel, table=True):
44 """Allowlist of emails permitted to authenticate via SSO."""
45
46 __tablename__ = "sso_allowed_email"
47
48 id: Optional[int] = Field(default=None, primary_key=True)
49 email: str = Field(max_length=256, index=True)
50 role_id: int = Field(default=2) # default to analyst
51 created_at: datetime.datetime = Field(default_factory=datetime.datetime.utcnow)
52
53
54 # ── Pydantic request / response schemas ──────────────────────────────────────
55
56
57 class SSOConfigUpdate(BaseModel):
58 """Request body for updating SSO settings."""
59
60 sso_enabled: bool = False
61
62 azure_enabled: bool = False
63 azure_tenant_id: Optional[str] = None
64 azure_client_id: Optional[str] = None
65 azure_client_secret: Optional[str] = None
66 azure_redirect_uri: Optional[str] = None
67
68 google_enabled: bool = False
69 google_client_id: Optional[str] = None
70 google_client_secret: Optional[str] = None
71 google_redirect_uri: Optional[str] = None
72
73 cf_enabled: bool = False
74 cf_team_domain: Optional[str] = None
75 cf_audience: Optional[str] = None
76
77
78 class SSOConfigResponse(BaseModel):
79 """Response body — never exposes the client_secret in full."""
80
81 sso_enabled: bool
82 azure_enabled: bool
83 azure_tenant_id: Optional[str] = None
84 azure_client_id: Optional[str] = None
85 azure_client_secret_set: bool = False # True if a secret is stored
86 azure_redirect_uri: Optional[str] = None
87 google_enabled: bool = False
88 google_client_id: Optional[str] = None
89 google_client_secret_set: bool = False # True if a secret is stored
90 google_redirect_uri: Optional[str] = None
91 cf_enabled: bool
92 cf_team_domain: Optional[str] = None
93 cf_audience: Optional[str] = None
94 message: str = "SSO configuration retrieved"
95 success: bool = True
96
97
98 class SSOAllowedEmailInput(BaseModel):
99 email: EmailStr
100 role_id: int = 2 # analyst by default
101
102
103 class SSOAllowedEmailOut(BaseModel):
104 id: int
105 email: str
106 role_id: int
107 created_at: datetime.datetime
108
109
110 class SSOAllowedEmailListResponse(BaseModel):
111 emails: list
112 message: str = "Allowed emails retrieved"
113 success: bool = True
114
115
116 class SSOPublicStatusResponse(BaseModel):
117 """Public endpoint — tells the login page which SSO providers are active."""
118
119 sso_enabled: bool
120 azure_enabled: bool
121 google_enabled: bool = False
122 cf_enabled: bool
123 azure_authorization_url: Optional[str] = None
124 google_authorization_url: Optional[str] = None