Browse Source

Post work PR #933

maziggy 1 month ago
parent
commit
2f0cca186b

+ 2 - 0
README.md

@@ -249,6 +249,8 @@ Perfect for remote print farms, traveling makers, or accessing your home printer
 - Admin creates users with email — system sends secure random password automatically
 - Users can reset their own password from the login screen (no admin needed)
 - Customizable email templates (welcome email, password reset)
+- **Two-Factor Authentication (TOTP + Email OTP)** — Per-user opt-in 2FA compatible with Google Authenticator, Authy, 2FAS and any standard TOTP app, or a 6-digit code delivered by email. Each user gets 10 single-use backup codes. Brute-force-protected (per-user + per-IP rate limits), replay-protected (same code cannot be accepted twice in the same 30 s window), and the pre-auth token is a single-use DB-backed challenge bound to the browser session via an HttpOnly cookie.
+- **Single Sign-On (OIDC / SSO)** — Log in via PocketID, Authentik, Keycloak, or any standards-compliant OIDC provider. PKCE (S256) for public clients, `email_verified` gating, issuer & `aud`/`nonce` validation, opt-in account linking via verified email, optional auto-provisioning of new BamBuddy accounts, and strict SSRF hardening on every URL pulled from the OIDC discovery document (scheme + private/loopback/link-local IP checks).
 - **Per-user email notifications** — Users receive email alerts for their own print jobs (start, complete, failed, stopped) with individual toggle controls
 
 </td>

+ 5 - 1
backend/app/core/database.py

@@ -1471,7 +1471,11 @@ async def run_migrations(conn):
     # Migration: Add password_changed_at to users (M-R7-B)
     # Tracks the last time a user's password was changed/reset.  JWTs whose iat
     # predates this timestamp are rejected in all six auth validation paths.
-    await _safe_execute(conn, "ALTER TABLE users ADD COLUMN password_changed_at DATETIME")
+    # R4 fix: TIMESTAMP is accepted by both SQLite and PostgreSQL; DATETIME
+    # is rejected by Postgres ("type 'datetime' does not exist"), which made
+    # _safe_execute swallow the error and leave existing Postgres installs
+    # without the column — causing UndefinedColumnError on every User query.
+    await _safe_execute(conn, "ALTER TABLE users ADD COLUMN password_changed_at TIMESTAMP")
 
     # Migration: Back-fill password_changed_at = created_at for existing users (I2).
     # Users who never changed their password would have NULL here, meaning old

+ 26 - 0
frontend/src/pages/SettingsPage.tsx

@@ -437,6 +437,18 @@ export function SettingsPage() {
     queryFn: () => api.getLDAPStatus(),
   });
 
+  // Tab-indicator queries: green bullet when 2FA is enabled for the current
+  // user, or when at least one OIDC provider is configured and enabled.
+  const { data: twoFAStatus } = useQuery({
+    queryKey: ['twoFAStatus'],
+    queryFn: () => api.get2FAStatus(),
+  });
+  const { data: oidcProvidersAll = [] } = useQuery({
+    queryKey: ['oidcProvidersAll'],
+    queryFn: () => api.getOIDCProvidersAll(),
+    enabled: isAdmin,
+  });
+
   // User management queries and mutations
   const { data: usersData = [], isLoading: usersLoading } = useQuery({
     queryKey: ['users'],
@@ -4415,6 +4427,13 @@ export function SettingsPage() {
             >
               <Shield className="w-4 h-4" />
               {t('settings.tabs.twoFa')}
+              <span
+                className={`w-2 h-2 rounded-full ${
+                  twoFAStatus?.totp_enabled || twoFAStatus?.email_otp_enabled
+                    ? 'bg-green-400'
+                    : 'bg-bambu-gray/40'
+                }`}
+              />
             </button>
             {isAdmin && (
               <button
@@ -4427,6 +4446,13 @@ export function SettingsPage() {
               >
                 <Globe className="w-4 h-4" />
                 {t('settings.tabs.oidc')}
+                <span
+                  className={`w-2 h-2 rounded-full ${
+                    oidcProvidersAll.some((p) => p.is_enabled)
+                      ? 'bg-green-400'
+                      : 'bg-bambu-gray/40'
+                  }`}
+                />
               </button>
             )}
           </div>

File diff suppressed because it is too large
+ 0 - 0
static/assets/index-B1-InwHl.css


File diff suppressed because it is too large
+ 0 - 0
static/assets/index-B7mnhxng.css


File diff suppressed because it is too large
+ 0 - 0
static/assets/index-BFeV6ocI.js


+ 5 - 2
static/index.html

@@ -3,6 +3,9 @@
   <head>
     <meta charset="UTF-8" />
     <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" />
+    <!-- L-4: Restrict Referer header to origin-only on cross-origin navigation so
+         sensitive tokens in query parameters are not leaked to third-party servers. -->
+    <meta name="referrer" content="strict-origin-when-cross-origin" />
     <title>Bambuddy</title>
 
     <!-- PWA Meta Tags -->
@@ -23,8 +26,8 @@
 
     <!-- Splash screens for iOS -->
     <link rel="apple-touch-startup-image" href="/img/android-chrome-512x512.png" />
-    <script type="module" crossorigin src="/assets/index-D7ghDKX2.js"></script>
-    <link rel="stylesheet" crossorigin href="/assets/index-B1-InwHl.css">
+    <script type="module" crossorigin src="/assets/index-BFeV6ocI.js"></script>
+    <link rel="stylesheet" crossorigin href="/assets/index-B7mnhxng.css">
   </head>
   <body>
     <div id="root"></div>

Some files were not shown because too many files changed in this diff