Feat(Chore): Verify Flow, Invites, Suspend, Depart Cert Policy
feat: add password reset and email verification flow feat: add org invite listing, cancellation, and invite link fallback feat: add user suspend/unsuspend with audit logging feat: add department certificate policy (expiry, extensions) feat: enforce dept cert policy on SSH certificate signing feat: wire up OIDC consent and token flow (replace mocks) feat: rework CLI auth bridge to use frontend login flow feat: add admin OAuth provider management (CRUD) chore: refactor model import paths after module reorganisation chore: clean up config, decorators, and dev tooling
This commit is contained in:
@@ -61,6 +61,8 @@ class AuditAction(str, Enum):
|
||||
USER_REGISTER = "user.register"
|
||||
USER_UPDATE = "user.update"
|
||||
USER_DELETE = "user.delete"
|
||||
USER_SUSPEND = "user.suspend"
|
||||
USER_UNSUSPEND = "user.unsuspend"
|
||||
PASSWORD_CHANGE = "user.password_change"
|
||||
PASSWORD_RESET = "user.password_reset"
|
||||
|
||||
|
||||
@@ -64,11 +64,47 @@ def login_required(f):
|
||||
session.last_activity_at = datetime.now(timezone.utc)
|
||||
from gatehouse_app import db
|
||||
db.session.commit()
|
||||
|
||||
|
||||
# Set context variables
|
||||
g.current_user = session.user
|
||||
g.current_session = session
|
||||
|
||||
|
||||
user = session.user
|
||||
token_groups: list = []
|
||||
try:
|
||||
if session.device_info:
|
||||
# device_info may carry OIDC claims stored at login time
|
||||
claims = session.device_info
|
||||
# Normalise: Gatehouse stores roles as [{"organization_id":…,"role":…}]
|
||||
roles_claim = claims.get("roles", [])
|
||||
if isinstance(roles_claim, list):
|
||||
for entry in roles_claim:
|
||||
if isinstance(entry, dict):
|
||||
role_val = entry.get("role")
|
||||
if role_val:
|
||||
token_groups.append(str(role_val))
|
||||
elif isinstance(entry, str):
|
||||
token_groups.append(entry)
|
||||
# Standard OIDC groups claim
|
||||
groups_claim = claims.get("groups", [])
|
||||
if isinstance(groups_claim, list):
|
||||
token_groups.extend(str(g_) for g_ in groups_claim if g_)
|
||||
except Exception:
|
||||
pass # Never block auth over token_groups enrichment failure
|
||||
user.token_groups = token_groups
|
||||
|
||||
# Activation check: if the user has an `activated` attribute and it is
|
||||
# explicitly False, block access. New accounts without the attribute are
|
||||
# treated as active to avoid breaking existing sessions.
|
||||
activated = getattr(user, "activated", None)
|
||||
if activated is False:
|
||||
return api_response(
|
||||
success=False,
|
||||
message="Account not yet activated. Please check your email for an activation link.",
|
||||
status=403,
|
||||
error_type="ACCOUNT_NOT_ACTIVATED",
|
||||
)
|
||||
|
||||
return f(*args, **kwargs)
|
||||
|
||||
return decorated_function
|
||||
@@ -97,11 +133,12 @@ def require_role(*allowed_roles):
|
||||
raise ForbiddenError("Organization context required")
|
||||
|
||||
# Check user's role in the organization
|
||||
from gatehouse_app.models.organization_member import OrganizationMember
|
||||
from gatehouse_app.models.organization.organization_member import OrganizationMember
|
||||
|
||||
membership = OrganizationMember.query.filter_by(
|
||||
user_id=g.current_user.id,
|
||||
organization_id=org_id,
|
||||
deleted_at=None,
|
||||
).first()
|
||||
|
||||
if not membership:
|
||||
|
||||
Reference in New Issue
Block a user