From 54a0ba1023d8e39fc133a2312beab349181f308b Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 22 Mar 2026 12:25:01 +0000 Subject: [PATCH] feat(auth): add GitHub, Keycloak, Generic OAuth2 social login providers and connections page - Fix Dropbox social login: add token_access_type=offline and userinfo compliance fix - Add GitHub social login provider with OAuth2 registration and userinfo normalization - Add Keycloak SSO provider with OpenID Connect discovery - Add Generic OAuth2 provider for custom SSO integrations - Add SAML2 SSO config fields (config only, no flow implementation) - Add SSO auto-login: redirect directly to SSO when configured - Add Telegram bot notification config fields - Add admin Connections page for managing auth providers and integrations - Add navigation links and translation keys for connections page Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- app/auth.py | 109 +++++++- app/config.py | 42 +++ app/utils/settings_service.py | 211 +++++++++++++++ app/views/settings.py | 293 +++++++++++++++++++++ frontend/templates/admin_connections.html | 305 ++++++++++++++++++++++ frontend/templates/base.html | 6 + frontend/translations/en.json | 17 ++ 7 files changed, 979 insertions(+), 4 deletions(-) create mode 100644 frontend/templates/admin_connections.html diff --git a/app/auth.py b/app/auth.py index 5b92b156..0090cf08 100644 --- a/app/auth.py +++ b/app/auth.py @@ -102,6 +102,24 @@ if AUTH_ENABLED and settings.social_auth_apple_enabled: else: logger.warning("SOCIAL_AUTH_APPLE_ENABLED=true but client ID/team ID not configured") + +def _dropbox_userinfo_compliance_fix(client, user_cls, token, data): + """Normalize Dropbox userinfo response for authlib compatibility. + + Dropbox's /2/users/get_current_account returns a non-standard response + format. This compliance fix normalizes the response data — the HTTP + method (POST) is handled by authlib's compliance infrastructure. + """ + # Dropbox returns account_id instead of sub + if "account_id" in data and "sub" not in data: + data["sub"] = data["account_id"] + # Normalize name field + name_info = data.get("name", {}) + if isinstance(name_info, dict) and "display_name" in name_info: + data["name"] = name_info["display_name"] + return data + + if AUTH_ENABLED and settings.social_auth_dropbox_enabled: # Determine which credentials to use for Dropbox social login _dropbox_client_id = settings.social_auth_dropbox_client_id @@ -118,13 +136,77 @@ if AUTH_ENABLED and settings.social_auth_dropbox_enabled: authorize_url="https://www.dropbox.com/oauth2/authorize", access_token_url="https://api.dropboxapi.com/oauth2/token", userinfo_endpoint="https://api.dropboxapi.com/2/users/get_current_account", - client_kwargs={"token_endpoint_auth_method": "client_secret_post"}, + userinfo_compliance_fix=_dropbox_userinfo_compliance_fix, + client_kwargs={ + "token_endpoint_auth_method": "client_secret_post", + "token_access_type": "offline", + }, ) SOCIAL_PROVIDERS["dropbox"] = {"name": "Dropbox", "icon": "fab fa-dropbox", "color": "blue"} logger.info("Social login provider registered: Dropbox") else: logger.warning("SOCIAL_AUTH_DROPBOX_ENABLED=true but client ID/secret not configured") +if AUTH_ENABLED and settings.social_auth_github_enabled: + if settings.social_auth_github_client_id and settings.social_auth_github_client_secret: + oauth.register( + name="github", + client_id=settings.social_auth_github_client_id, + client_secret=settings.social_auth_github_client_secret, + authorize_url="https://github.com/login/oauth/authorize", + access_token_url="https://github.com/login/oauth/access_token", + userinfo_endpoint="https://api.github.com/user", + client_kwargs={"scope": "read:user user:email"}, + ) + SOCIAL_PROVIDERS["github"] = {"name": "GitHub", "icon": "fab fa-github", "color": "gray"} + logger.info("Social login provider registered: GitHub") + else: + logger.warning("SOCIAL_AUTH_GITHUB_ENABLED=true but client ID/secret not configured") + +if AUTH_ENABLED and settings.social_auth_keycloak_enabled: + _kc_server = settings.social_auth_keycloak_server_url + _kc_realm = settings.social_auth_keycloak_realm + if ( + settings.social_auth_keycloak_client_id + and settings.social_auth_keycloak_client_secret + and _kc_server + and _kc_realm + ): + _kc_base = f"{_kc_server.rstrip('/')}/realms/{_kc_realm}" + oauth.register( + name="keycloak", + client_id=settings.social_auth_keycloak_client_id, + client_secret=settings.social_auth_keycloak_client_secret, + server_metadata_url=f"{_kc_base}/.well-known/openid-configuration", + client_kwargs={"scope": "openid profile email"}, + ) + SOCIAL_PROVIDERS["keycloak"] = {"name": "Keycloak", "icon": "fas fa-key", "color": "gray"} + logger.info("Social login provider registered: Keycloak (realm=%s)", _kc_realm) + else: + logger.warning("SOCIAL_AUTH_KEYCLOAK_ENABLED=true but required settings not configured") + +if AUTH_ENABLED and settings.social_auth_generic_oauth2_enabled: + if ( + settings.social_auth_generic_oauth2_client_id + and settings.social_auth_generic_oauth2_client_secret + and settings.social_auth_generic_oauth2_authorize_url + and settings.social_auth_generic_oauth2_token_url + ): + oauth.register( + name="generic_oauth2", + client_id=settings.social_auth_generic_oauth2_client_id, + client_secret=settings.social_auth_generic_oauth2_client_secret, + authorize_url=settings.social_auth_generic_oauth2_authorize_url, + access_token_url=settings.social_auth_generic_oauth2_token_url, + userinfo_endpoint=settings.social_auth_generic_oauth2_userinfo_url, + client_kwargs={"scope": settings.social_auth_generic_oauth2_scope}, + ) + _generic_name = settings.social_auth_generic_oauth2_name or "OAuth2" + SOCIAL_PROVIDERS["generic_oauth2"] = {"name": _generic_name, "icon": "fas fa-sign-in-alt", "color": "indigo"} + logger.info("Social login provider registered: Generic OAuth2 (%s)", _generic_name) + else: + logger.warning("SOCIAL_AUTH_GENERIC_OAUTH2_ENABLED=true but required settings not configured") + router = APIRouter() @@ -348,13 +430,21 @@ async def login(request: Request): get_client_ip(request), ) + error = request.query_params.get("error") + message = request.query_params.get("message") + show_oauth = OAUTH_CONFIGURED + + # SSO Auto Login: redirect directly to SSO provider if configured + if show_oauth and settings.sso_auto_login and not error and not message: + return RedirectResponse(url="/oauth-login", status_code=status.HTTP_302_FOUND) + return templates.TemplateResponse( "login.html", { "request": request, - "error": request.query_params.get("error"), - "message": request.query_params.get("message"), - "show_oauth": OAUTH_CONFIGURED, + "error": error, + "message": message, + "show_oauth": show_oauth, "oauth_provider_name": OAUTH_PROVIDER_NAME, "social_providers": SOCIAL_PROVIDERS, "app_version": settings.version, @@ -440,6 +530,17 @@ def _normalize_social_userinfo(provider: str, token: dict, raw_userinfo: dict | "picture": userinfo.get("profile_photo_url", ""), } + if provider == "github": + # GitHub returns login, id, name, email, avatar_url + email = userinfo.get("email", "") + return { + "sub": str(userinfo.get("id", "")), + "email": email, + "name": userinfo.get("name", "") or userinfo.get("login", ""), + "preferred_username": userinfo.get("login", email), + "picture": userinfo.get("avatar_url", ""), + } + # Standard OIDC providers (Google, Microsoft, Apple) return { "sub": userinfo.get("sub", ""), diff --git a/app/config.py b/app/config.py index ab0bfd4c..99c12757 100644 --- a/app/config.py +++ b/app/config.py @@ -305,6 +305,38 @@ class Settings(BaseSettings): authentik_client_secret: Optional[str] = None authentik_config_url: Optional[str] = None oauth_provider_name: Optional[str] = None # Name to display for the OAuth provider + sso_auto_login: bool = Field( + default=False, + description=( + "Automatically redirect to SSO login when authentication is required. " + "When enabled, users are sent directly to the SSO provider instead of " + "seeing the login page. Only effective when OIDC is configured." + ), + ) + + # Keycloak SSO + social_auth_keycloak_enabled: bool = False + social_auth_keycloak_client_id: Optional[str] = None + social_auth_keycloak_client_secret: Optional[str] = None + social_auth_keycloak_server_url: Optional[str] = None + social_auth_keycloak_realm: Optional[str] = None + + # Generic OAuth2 SSO + social_auth_generic_oauth2_enabled: bool = False + social_auth_generic_oauth2_client_id: Optional[str] = None + social_auth_generic_oauth2_client_secret: Optional[str] = None + social_auth_generic_oauth2_authorize_url: Optional[str] = None + social_auth_generic_oauth2_token_url: Optional[str] = None + social_auth_generic_oauth2_userinfo_url: Optional[str] = None + social_auth_generic_oauth2_scope: str = "openid profile email" + social_auth_generic_oauth2_name: str = "OAuth2" + + # SAML2 SSO + social_auth_saml2_enabled: bool = False + social_auth_saml2_entity_id: Optional[str] = None + social_auth_saml2_sso_url: Optional[str] = None + social_auth_saml2_certificate: Optional[str] = None + social_auth_saml2_name: str = "SAML2" # Social Login Providers # Google OAuth2 @@ -348,6 +380,11 @@ class Settings(BaseSettings): ), ) + # GitHub OAuth2 + social_auth_github_enabled: bool = False + social_auth_github_client_id: Optional[str] = None + social_auth_github_client_secret: Optional[str] = None + # Local user signup allow_local_signup: bool = Field( default=False, @@ -844,6 +881,11 @@ class Settings(BaseSettings): ), ) + # Telegram Bot + telegram_bot_token: Optional[str] = None + telegram_chat_id: Optional[str] = None + telegram_enabled: bool = False + # Notification settings notification_urls: Union[List[str], str] = Field( default_factory=list, diff --git a/app/utils/settings_service.py b/app/utils/settings_service.py index 0c990143..9388bf97 100644 --- a/app/utils/settings_service.py +++ b/app/utils/settings_service.py @@ -262,6 +262,17 @@ SETTING_METADATA = { "required": False, "restart_required": True, }, + "sso_auto_login": { + "category": "Authentication", + "description": ( + "Automatically redirect to SSO login when authentication is required. " + "Skips the login page and sends users directly to the configured SSO provider." + ), + "type": "boolean", + "sensitive": False, + "required": False, + "restart_required": False, + }, # Social Login Providers "social_auth_google_enabled": { "category": "Social Login", @@ -422,6 +433,182 @@ SETTING_METADATA = { "required": False, "restart_required": True, }, + "social_auth_github_enabled": { + "category": "Social Login", + "description": ( + "Enable GitHub Sign-In. Requires SOCIAL_AUTH_GITHUB_CLIENT_ID and " + "SOCIAL_AUTH_GITHUB_CLIENT_SECRET from GitHub Developer Settings." + ), + "type": "boolean", + "sensitive": False, + "required": False, + "restart_required": True, + "help_link": "https://github.com/settings/developers", + "help_link_label": "GitHub Developer Settings", + }, + "social_auth_github_client_id": { + "category": "Social Login", + "description": "GitHub OAuth2 client ID from GitHub Developer Settings.", + "type": "string", + "sensitive": False, + "required": False, + "restart_required": True, + }, + "social_auth_github_client_secret": { + "category": "Social Login", + "description": "GitHub OAuth2 client secret from GitHub Developer Settings.", + "type": "string", + "sensitive": True, + "required": False, + "restart_required": True, + }, + # Keycloak SSO + "social_auth_keycloak_enabled": { + "category": "Social Login", + "description": "Enable Keycloak SSO. Requires server URL, realm, client ID, and client secret.", + "type": "boolean", + "sensitive": False, + "required": False, + "restart_required": True, + }, + "social_auth_keycloak_client_id": { + "category": "Social Login", + "description": "Keycloak OAuth2 client ID.", + "type": "string", + "sensitive": False, + "required": False, + "restart_required": True, + }, + "social_auth_keycloak_client_secret": { + "category": "Social Login", + "description": "Keycloak OAuth2 client secret.", + "type": "string", + "sensitive": True, + "required": False, + "restart_required": True, + }, + "social_auth_keycloak_server_url": { + "category": "Social Login", + "description": "Keycloak server base URL (e.g. https://keycloak.example.com).", + "type": "string", + "sensitive": False, + "required": False, + "restart_required": True, + }, + "social_auth_keycloak_realm": { + "category": "Social Login", + "description": "Keycloak realm name.", + "type": "string", + "sensitive": False, + "required": False, + "restart_required": True, + }, + # Generic OAuth2 SSO + "social_auth_generic_oauth2_enabled": { + "category": "Social Login", + "description": "Enable a generic OAuth2 SSO provider.", + "type": "boolean", + "sensitive": False, + "required": False, + "restart_required": True, + }, + "social_auth_generic_oauth2_client_id": { + "category": "Social Login", + "description": "Generic OAuth2 client ID.", + "type": "string", + "sensitive": False, + "required": False, + "restart_required": True, + }, + "social_auth_generic_oauth2_client_secret": { + "category": "Social Login", + "description": "Generic OAuth2 client secret.", + "type": "string", + "sensitive": True, + "required": False, + "restart_required": True, + }, + "social_auth_generic_oauth2_authorize_url": { + "category": "Social Login", + "description": "Generic OAuth2 authorization URL.", + "type": "string", + "sensitive": False, + "required": False, + "restart_required": True, + }, + "social_auth_generic_oauth2_token_url": { + "category": "Social Login", + "description": "Generic OAuth2 token endpoint URL.", + "type": "string", + "sensitive": False, + "required": False, + "restart_required": True, + }, + "social_auth_generic_oauth2_userinfo_url": { + "category": "Social Login", + "description": "Generic OAuth2 userinfo endpoint URL.", + "type": "string", + "sensitive": False, + "required": False, + "restart_required": True, + }, + "social_auth_generic_oauth2_scope": { + "category": "Social Login", + "description": "Space-separated list of OAuth2 scopes to request (default: openid profile email).", + "type": "string", + "sensitive": False, + "required": False, + "restart_required": True, + }, + "social_auth_generic_oauth2_name": { + "category": "Social Login", + "description": "Display name for the generic OAuth2 provider button on the login page.", + "type": "string", + "sensitive": False, + "required": False, + "restart_required": True, + }, + # SAML2 SSO + "social_auth_saml2_enabled": { + "category": "Social Login", + "description": "Enable SAML2 SSO authentication.", + "type": "boolean", + "sensitive": False, + "required": False, + "restart_required": True, + }, + "social_auth_saml2_entity_id": { + "category": "Social Login", + "description": "SAML2 Identity Provider Entity ID.", + "type": "string", + "sensitive": False, + "required": False, + "restart_required": True, + }, + "social_auth_saml2_sso_url": { + "category": "Social Login", + "description": "SAML2 Identity Provider SSO URL.", + "type": "string", + "sensitive": False, + "required": False, + "restart_required": True, + }, + "social_auth_saml2_certificate": { + "category": "Social Login", + "description": "SAML2 Identity Provider X.509 certificate (PEM format).", + "type": "string", + "sensitive": True, + "required": False, + "restart_required": True, + }, + "social_auth_saml2_name": { + "category": "Social Login", + "description": "Display name for the SAML2 provider.", + "type": "string", + "sensitive": False, + "required": False, + "restart_required": True, + }, # AI Services "openai_api_key": { "category": "AI Services", @@ -1952,6 +2139,30 @@ SETTING_METADATA = { "required": False, "restart_required": False, }, + "telegram_enabled": { + "category": "Notifications", + "description": "Enable Telegram bot notifications.", + "type": "boolean", + "sensitive": False, + "required": False, + "restart_required": False, + }, + "telegram_bot_token": { + "category": "Notifications", + "description": "Telegram Bot API token from @BotFather.", + "type": "string", + "sensitive": True, + "required": False, + "restart_required": False, + }, + "telegram_chat_id": { + "category": "Notifications", + "description": "Telegram chat ID to send notifications to.", + "type": "string", + "sensitive": False, + "required": False, + "restart_required": False, + }, # Notifications Settings "notification_urls": { "category": "Notifications", diff --git a/app/views/settings.py b/app/views/settings.py index 1e520e33..25ccccea 100644 --- a/app/views/settings.py +++ b/app/views/settings.py @@ -195,6 +195,299 @@ async def credentials_page(request: Request, db: Session = Depends(get_db)): ) +@router.get("/admin/connections") +@require_login +@require_admin_access +async def connections_page(request: Request, db: Session = Depends(get_db)): + """ + Connections management page - admin only. + + Allows administrators to configure external authentication providers, + SSO settings, and service integrations through a wizard-like interface. + """ + try: + from app.auth import OAUTH_CONFIGURED, SOCIAL_PROVIDERS + + db_settings = get_all_settings_from_db(db) + + def _get_effective(key: str): + """Return DB value if present, else fall back to settings attr.""" + if key in db_settings and db_settings[key] is not None: + return db_settings[key] + return getattr(settings, key, None) + + def _is_truthy(val) -> bool: + if isinstance(val, bool): + return val + if isinstance(val, str): + return val.lower() in ("true", "1", "yes") + return bool(val) + + # Build service status list + services = [] + + # --- SSO (Authentik / OIDC) --- + services.append( + { + "key": "oidc", + "name": settings.oauth_provider_name or "Single Sign-On", + "icon": "fas fa-lock", + "type": "SSO", + "linked": OAUTH_CONFIGURED, + "description": "OpenID Connect SSO provider", + "settings_keys": [ + "authentik_client_id", + "authentik_client_secret", + "authentik_config_url", + "oauth_provider_name", + ], + } + ) + + # --- Google --- + services.append( + { + "key": "google", + "name": "Google", + "icon": "fab fa-google", + "type": "Sign-in authentication", + "linked": "google" in SOCIAL_PROVIDERS, + "description": "Sign-in authentication", + "settings_keys": [ + "social_auth_google_enabled", + "social_auth_google_client_id", + "social_auth_google_client_secret", + ], + } + ) + + # --- GitHub --- + services.append( + { + "key": "github", + "name": "GitHub", + "icon": "fab fa-github", + "type": "Sign-in authentication", + "linked": "github" in SOCIAL_PROVIDERS, + "description": "Sign-in authentication", + "settings_keys": [ + "social_auth_github_enabled", + "social_auth_github_client_id", + "social_auth_github_client_secret", + ], + } + ) + + # --- Microsoft --- + services.append( + { + "key": "microsoft", + "name": "Microsoft", + "icon": "fab fa-microsoft", + "type": "Sign-in authentication", + "linked": "microsoft" in SOCIAL_PROVIDERS, + "description": "Sign-in authentication", + "settings_keys": [ + "social_auth_microsoft_enabled", + "social_auth_microsoft_client_id", + "social_auth_microsoft_client_secret", + "social_auth_microsoft_tenant", + ], + } + ) + + # --- Apple --- + services.append( + { + "key": "apple", + "name": "Apple", + "icon": "fab fa-apple", + "type": "Sign-in authentication", + "linked": "apple" in SOCIAL_PROVIDERS, + "description": "Sign-in authentication", + "settings_keys": [ + "social_auth_apple_enabled", + "social_auth_apple_client_id", + "social_auth_apple_team_id", + "social_auth_apple_key_id", + "social_auth_apple_private_key", + ], + } + ) + + # --- Dropbox --- + services.append( + { + "key": "dropbox", + "name": "Dropbox", + "icon": "fab fa-dropbox", + "type": "Sign-in authentication", + "linked": "dropbox" in SOCIAL_PROVIDERS, + "description": "Sign-in authentication", + "settings_keys": [ + "social_auth_dropbox_enabled", + "social_auth_dropbox_client_id", + "social_auth_dropbox_client_secret", + "social_auth_dropbox_use_global_credentials", + ], + } + ) + + # --- Keycloak --- + services.append( + { + "key": "keycloak", + "name": "Keycloak", + "icon": "fas fa-key", + "type": "SSO", + "linked": "keycloak" in SOCIAL_PROVIDERS, + "description": "SSO", + "settings_keys": [ + "social_auth_keycloak_enabled", + "social_auth_keycloak_client_id", + "social_auth_keycloak_client_secret", + "social_auth_keycloak_server_url", + "social_auth_keycloak_realm", + ], + } + ) + + # --- Generic OAuth2 --- + services.append( + { + "key": "generic_oauth2", + "name": "Generic OAuth2", + "icon": "fas fa-sign-in-alt", + "type": "SSO", + "linked": "generic_oauth2" in SOCIAL_PROVIDERS, + "description": "SSO", + "settings_keys": [ + "social_auth_generic_oauth2_enabled", + "social_auth_generic_oauth2_client_id", + "social_auth_generic_oauth2_client_secret", + "social_auth_generic_oauth2_authorize_url", + "social_auth_generic_oauth2_token_url", + "social_auth_generic_oauth2_userinfo_url", + "social_auth_generic_oauth2_scope", + "social_auth_generic_oauth2_name", + ], + } + ) + + # --- SAML2 --- + _saml2_configured = bool( + _is_truthy(_get_effective("social_auth_saml2_enabled")) + and _get_effective("social_auth_saml2_sso_url") + and _get_effective("social_auth_saml2_entity_id") + ) + services.append( + { + "key": "saml2", + "name": settings.social_auth_saml2_name or "SAML2", + "icon": "fas fa-id-badge", + "type": "SSO (SAML)", + "linked": _saml2_configured, + "description": "SSO (SAML)", + "settings_keys": [ + "social_auth_saml2_enabled", + "social_auth_saml2_entity_id", + "social_auth_saml2_sso_url", + "social_auth_saml2_certificate", + "social_auth_saml2_name", + ], + } + ) + + # --- SMTP Mail --- + _smtp_configured = bool(_get_effective("email_host") and _get_effective("email_username")) + services.append( + { + "key": "smtp", + "name": "SMTP Mail", + "icon": "fas fa-envelope", + "type": "Email Notifications", + "linked": _smtp_configured, + "description": "Email Notifications", + "settings_keys": [ + "email_host", + "email_port", + "email_username", + "email_password", + "email_use_tls", + "email_sender", + ], + } + ) + + # --- Telegram Bot --- + _telegram_configured = bool( + _is_truthy(_get_effective("telegram_enabled")) and _get_effective("telegram_bot_token") + ) + services.append( + { + "key": "telegram", + "name": "Telegram Bot", + "icon": "fab fa-telegram", + "type": "Notifications", + "linked": _telegram_configured, + "description": "Configure Telegram bot connectivity, access controls, and feedback behavior.", + "settings_keys": [ + "telegram_enabled", + "telegram_bot_token", + "telegram_chat_id", + ], + } + ) + + # Get setting details for the modal forms + service_settings = {} + for svc in services: + svc_settings = [] + for skey in svc["settings_keys"]: + meta = get_setting_metadata(skey) + # Get current effective value + val = _get_effective(skey) + display_val = val + if meta.get("sensitive") and val: + display_val = mask_sensitive_value(val) + svc_settings.append( + { + "key": skey, + "value": val, + "display_value": display_val if display_val is not None else "", + "metadata": meta, + } + ) + service_settings[svc["key"]] = svc_settings + + # Feature toggles + sso_auto_login = _is_truthy(_get_effective("sso_auto_login")) + qr_login_enabled = _is_truthy(_get_effective("qr_login_challenge_ttl_seconds")) + frontend_url_configured = bool(_get_effective("public_base_url")) + + return templates.TemplateResponse( + "admin_connections.html", + { + "request": request, + "services": services, + "service_settings": service_settings, + "sso_auto_login": sso_auto_login, + "oauth_configured": OAUTH_CONFIGURED, + "qr_login_enabled": qr_login_enabled, + "frontend_url_configured": frontend_url_configured, + "app_version": settings.version, + }, + ) + except HTTPException: + raise + except Exception as e: + logger.error(f"Error loading connections page: {e}") + raise HTTPException( + status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, + detail="Failed to load connections page", + ) + + @router.get("/admin/settings/audit-log") @require_login @require_admin_access diff --git a/frontend/templates/admin_connections.html b/frontend/templates/admin_connections.html new file mode 100644 index 00000000..aca4f105 --- /dev/null +++ b/frontend/templates/admin_connections.html @@ -0,0 +1,305 @@ +{% extends "base.html" %} + +{% block title %}{{ _("connections.title") }} - {{ _("app.name") }}{% endblock %} + +{% block content %} +
+
+

{{ _("connections.title") }}

+

{{ _("connections.description") }}

+
+ + + {% if oauth_configured %} +
+
+
+

{{ _("connections.sso_auto_login_title") }}

+

{{ _("connections.sso_auto_login_description") }}

+
+
+ +
+
+
+ {% endif %} + + +
+
+
+

{{ _("connections.mobile_upload_title") }}

+

{{ _("connections.mobile_upload_description") }}

+ {% if not frontend_url_configured %} +

+ + {{ _("connections.frontend_url_note") }} +

+ {% endif %} +
+
+ +
+
+
+ + +
+

{{ _("connections.unlinked_services") }}

+
+ {% for service in services %} +
+
+
+
+ +
+
+

{{ service.name }}

+

{{ service.description }}

+
+
+
+ {% if service.linked %} + + {{ _("connections.linked") }} + + {% else %} + + {{ _("connections.unlinked") }} + + {% endif %} + +
+
+
+ {% endfor %} +
+
+ +

+ + {{ _("connections.save_note") }} +

+
+ + + + + +{% endblock %} diff --git a/frontend/templates/base.html b/frontend/templates/base.html index b7e47e44..afd24408 100644 --- a/frontend/templates/base.html +++ b/frontend/templates/base.html @@ -155,6 +155,9 @@ {{ _("nav.credentials") }} + + {{ _("nav.connections") }} + {{ _("nav.file_manager") }} @@ -429,6 +432,9 @@ {{ _("nav.credentials") }} + + {{ _("nav.connections") }} + {{ _("nav.file_manager") }} diff --git a/frontend/translations/en.json b/frontend/translations/en.json index 1af7a7a0..12050e5a 100644 --- a/frontend/translations/en.json +++ b/frontend/translations/en.json @@ -617,6 +617,22 @@ "cookie_policy.s5_p3_pre": "This Cookie Policy is part of and incorporated into our", "cookie_policy.s5_privacy_link": "Privacy Notice", "cookie_policy.s5_terms_link": "Terms of Service", + "connections.configure": "Configure", + "connections.configured": "Connected", + "connections.description": "Configure external authentication providers like OAuth2 and SAML.", + "connections.frontend_url_note": "Note: Requires Frontend URL to be configured. Configure in System Settings.", + "connections.linked": "Linked", + "connections.mobile_upload_description": "Allow users to upload files from mobile devices by scanning a QR code.", + "connections.mobile_upload_title": "Mobile Phone Upload", + "connections.qr_code_enabled": "Enable QR Code Upload", + "connections.save_note": "Changes to authentication providers require a restart to take effect.", + "connections.sso_auto_login": "Enable SSO Auto Login", + "connections.sso_auto_login_description": "Automatically redirect to SSO login when authentication is required.", + "connections.sso_auto_login_title": "SSO Auto Login", + "connections.title": "Connections", + "connections.unconfigure": "Disconnect", + "connections.unlinked": "Unlinked", + "connections.unlinked_services": "Services", "credentials.col_action": "Action", "credentials.col_credential": "Credential", "credentials.col_source": "Source", @@ -1230,6 +1246,7 @@ "nav.api_docs": "API Docs", "nav.api_tokens": "API Tokens", "nav.backup_restore": "Backup & Restore", + "nav.connections": "Connections", "nav.credentials": "Credentials", "nav.dark_mode": "Dark Mode", "nav.dashboard": "Dashboard",