Browse Source

Fix Advanced Authentication cleanups: external URL, forgot password dialog, edit user modal, info box, i18n

Co-authored-by: cadtoolbox <12723486+cadtoolbox@users.noreply.github.com>
copilot-swe-agent[bot] 3 months ago
parent
commit
b024d02a04

+ 15 - 4
backend/app/api/routes/auth.py

@@ -41,6 +41,7 @@ from backend.app.services.email_service import (
     save_smtp_settings,
     save_smtp_settings,
     send_email,
     send_email,
 )
 )
+from backend.app.api.routes.settings import get_setting
 
 
 
 
 def _user_to_response(user: User) -> UserResponse:
 def _user_to_response(user: User) -> UserResponse:
@@ -556,8 +557,13 @@ async def forgot_password(request: ForgotPasswordRequest, db: AsyncSession = Dep
             user.password_hash = get_password_hash(new_password)
             user.password_hash = get_password_hash(new_password)
             await db.commit()
             await db.commit()
 
 
-            # Get login URL from environment or use default
-            login_url = os.environ.get("APP_URL", "http://localhost:5173") + "/login"
+            # Use external_url from settings if available, otherwise fall back to APP_URL env var
+            external_url = await get_setting(db, "external_url")
+            if external_url:
+                external_url = external_url.rstrip("/")
+            else:
+                external_url = os.environ.get("APP_URL", "http://localhost:5173")
+            login_url = external_url + "/login"
 
 
             # Send password reset email
             # Send password reset email
             subject, text_body, html_body = create_password_reset_email(user.username, new_password, login_url)
             subject, text_body, html_body = create_password_reset_email(user.username, new_password, login_url)
@@ -632,8 +638,13 @@ async def reset_user_password(
         user.password_hash = get_password_hash(new_password)
         user.password_hash = get_password_hash(new_password)
         await db.commit()
         await db.commit()
 
 
-        # Get login URL from environment or use default
-        login_url = os.environ.get("APP_URL", "http://localhost:5173") + "/login"
+        # Use external_url from settings if available, otherwise fall back to APP_URL env var
+        external_url = await get_setting(db, "external_url")
+        if external_url:
+            external_url = external_url.rstrip("/")
+        else:
+            external_url = os.environ.get("APP_URL", "http://localhost:5173")
+        login_url = external_url + "/login"
 
 
         # Send password reset email
         # Send password reset email
         subject, text_body, html_body = create_password_reset_email(user.username, new_password, login_url)
         subject, text_body, html_body = create_password_reset_email(user.username, new_password, login_url)

+ 8 - 1
backend/app/api/routes/users.py

@@ -24,6 +24,7 @@ from backend.app.services.email_service import (
     get_smtp_settings,
     get_smtp_settings,
     send_email,
     send_email,
 )
 )
+from backend.app.api.routes.settings import get_setting
 
 
 router = APIRouter(prefix="/users", tags=["users"])
 router = APIRouter(prefix="/users", tags=["users"])
 
 
@@ -152,7 +153,13 @@ async def create_user(
         try:
         try:
             smtp_settings = await get_smtp_settings(db)
             smtp_settings = await get_smtp_settings(db)
             if smtp_settings:
             if smtp_settings:
-                login_url = os.environ.get("APP_URL", "http://localhost:5173") + "/login"
+                # Use external_url from settings if available, otherwise fall back to APP_URL env var
+                external_url = await get_setting(db, "external_url")
+                if external_url:
+                    external_url = external_url.rstrip("/")
+                else:
+                    external_url = os.environ.get("APP_URL", "http://localhost:5173")
+                login_url = external_url + "/login"
                 subject, text_body, html_body = create_welcome_email(new_user.username, password, login_url)
                 subject, text_body, html_body = create_welcome_email(new_user.username, password, login_url)
                 send_email(smtp_settings, new_user.email, subject, text_body, html_body)
                 send_email(smtp_settings, new_user.email, subject, text_body, html_body)
                 logger.info(f"Welcome email sent to {new_user.email}")
                 logger.info(f"Welcome email sent to {new_user.email}")

+ 2 - 1
frontend/src/components/CreateUserAdvancedAuthModal.tsx

@@ -84,7 +84,7 @@ export function CreateUserAdvancedAuthModal({
             {/* Username Field */}
             {/* Username Field */}
             <div>
             <div>
               <label className="block text-sm font-medium text-white mb-2">
               <label className="block text-sm font-medium text-white mb-2">
-                {t('users.form.username')}
+                {t('users.form.username')} <span className="text-red-400">*</span>
               </label>
               </label>
               <input
               <input
                 type="text"
                 type="text"
@@ -93,6 +93,7 @@ export function CreateUserAdvancedAuthModal({
                 className="w-full px-4 py-3 bg-bambu-dark-secondary border border-bambu-dark-tertiary rounded-lg text-white placeholder-bambu-gray focus:outline-none focus:ring-2 focus:ring-bambu-green/50 focus:border-bambu-green transition-colors"
                 className="w-full px-4 py-3 bg-bambu-dark-secondary border border-bambu-dark-tertiary rounded-lg text-white placeholder-bambu-gray focus:outline-none focus:ring-2 focus:ring-bambu-green/50 focus:border-bambu-green transition-colors"
                 placeholder={t('users.form.usernamePlaceholder')}
                 placeholder={t('users.form.usernamePlaceholder')}
                 autoComplete="username"
                 autoComplete="username"
+                required
               />
               />
             </div>
             </div>
 
 

+ 3 - 0
frontend/src/i18n/locales/de.ts

@@ -1021,6 +1021,7 @@ export default {
       saving: 'Wird gespeichert...',
       saving: 'Wird gespeichert...',
       advancedAuth: 'Erweiterte Authentifizierung',
       advancedAuth: 'Erweiterte Authentifizierung',
       advancedAuthEnabled: 'Erweiterte Authentifizierung ist aktiviert',
       advancedAuthEnabled: 'Erweiterte Authentifizierung ist aktiviert',
+      advancedAuthEnabledDesc: 'E-Mail-basierte Benutzerverwaltungsfunktionen sind aktiv. Neue Benutzer erhalten automatisch generierte Passwörter per E-Mail und können ihr Passwort über die Passwort vergessen Funktion zurücksetzen.',
       advancedAuthDisabled: 'Erweiterte Authentifizierung ist deaktiviert',
       advancedAuthDisabled: 'Erweiterte Authentifizierung ist deaktiviert',
       advancedAuthDisabledDesc: 'Aktivieren Sie die erweiterte Authentifizierung, um E-Mail-basierte Funktionen für die Benutzerverwaltung zu aktivieren.',
       advancedAuthDisabledDesc: 'Aktivieren Sie die erweiterte Authentifizierung, um E-Mail-basierte Funktionen für die Benutzerverwaltung zu aktivieren.',
       enable: 'Aktivieren',
       enable: 'Aktivieren',
@@ -1623,6 +1624,7 @@ export default {
       creating: 'Erstellen...',
       creating: 'Erstellen...',
       saving: 'Speichern...',
       saving: 'Speichern...',
       saveChanges: 'Änderungen speichern',
       saveChanges: 'Änderungen speichern',
+      advancedAuthSubtitle: 'mit erweiterter Authentifizierung',
     },
     },
     form: {
     form: {
       username: 'Benutzername',
       username: 'Benutzername',
@@ -1639,6 +1641,7 @@ export default {
       groups: 'Gruppen',
       groups: 'Gruppen',
       optional: 'optional',
       optional: 'optional',
       autoGeneratedPassword: 'Ein sicheres Passwort wird automatisch generiert und per E-Mail an den Benutzer gesendet.',
       autoGeneratedPassword: 'Ein sicheres Passwort wird automatisch generiert und per E-Mail an den Benutzer gesendet.',
+      passwordManagedByAdvancedAuth: 'Das Passwort wird durch erweiterte Authentifizierung verwaltet. Verwenden Sie "Passwort zurücksetzen", um ein neues Passwort per E-Mail an den Benutzer zu senden.',
     },
     },
     deleteModal: {
     deleteModal: {
       title: 'Benutzer löschen',
       title: 'Benutzer löschen',

+ 3 - 0
frontend/src/i18n/locales/en.ts

@@ -1021,6 +1021,7 @@ export default {
       saving: 'Saving...',
       saving: 'Saving...',
       advancedAuth: 'Advanced Authentication',
       advancedAuth: 'Advanced Authentication',
       advancedAuthEnabled: 'Advanced Authentication is enabled',
       advancedAuthEnabled: 'Advanced Authentication is enabled',
+      advancedAuthEnabledDesc: 'Email-based user management features are active. New users will receive auto-generated passwords via email, and users can reset their passwords through the forgot password feature.',
       advancedAuthDisabled: 'Advanced Authentication is disabled',
       advancedAuthDisabled: 'Advanced Authentication is disabled',
       advancedAuthDisabledDesc: 'Enable advanced authentication to activate email-based features for user management.',
       advancedAuthDisabledDesc: 'Enable advanced authentication to activate email-based features for user management.',
       enable: 'Enable',
       enable: 'Enable',
@@ -1623,6 +1624,7 @@ export default {
       creating: 'Creating...',
       creating: 'Creating...',
       saving: 'Saving...',
       saving: 'Saving...',
       saveChanges: 'Save Changes',
       saveChanges: 'Save Changes',
+      advancedAuthSubtitle: 'with Advanced Authentication',
     },
     },
     form: {
     form: {
       username: 'Username',
       username: 'Username',
@@ -1639,6 +1641,7 @@ export default {
       groups: 'Groups',
       groups: 'Groups',
       optional: 'optional',
       optional: 'optional',
       autoGeneratedPassword: 'A secure password will be automatically generated and emailed to the user.',
       autoGeneratedPassword: 'A secure password will be automatically generated and emailed to the user.',
+      passwordManagedByAdvancedAuth: 'Password is managed by Advanced Authentication. Use "Reset Password" to send a new password to the user via email.',
     },
     },
     deleteModal: {
     deleteModal: {
       title: 'Delete User',
       title: 'Delete User',

+ 3 - 0
frontend/src/i18n/locales/ja.ts

@@ -1173,6 +1173,7 @@ export default {
       saving: '保存中...',
       saving: '保存中...',
       advancedAuth: '高度な認証',
       advancedAuth: '高度な認証',
       advancedAuthEnabled: '高度な認証が有効です',
       advancedAuthEnabled: '高度な認証が有効です',
+      advancedAuthEnabledDesc: 'メールベースのユーザー管理機能が有効になっています。新規ユーザーには自動生成されたパスワードがメールで送信され、ユーザーはパスワード忘れ機能でパスワードをリセットできます。',
       advancedAuthDisabled: '高度な認証が無効です',
       advancedAuthDisabled: '高度な認証が無効です',
       advancedAuthDisabledDesc: '高度な認証を有効にして、ユーザー管理のメールベース機能を有効化してください。',
       advancedAuthDisabledDesc: '高度な認証を有効にして、ユーザー管理のメールベース機能を有効化してください。',
       enable: '有効にする',
       enable: '有効にする',
@@ -2172,6 +2173,7 @@ export default {
       groups: 'グループ',
       groups: 'グループ',
       optional: 'オプション',
       optional: 'オプション',
       autoGeneratedPassword: '安全なパスワードが自動的に生成され、ユーザーにメールで送信されます。',
       autoGeneratedPassword: '安全なパスワードが自動的に生成され、ユーザーにメールで送信されます。',
+      passwordManagedByAdvancedAuth: 'パスワードは高度な認証によって管理されています。「パスワードのリセット」を使用して、メールで新しいパスワードをユーザーに送信してください。',
     },
     },
     modal: {
     modal: {
       createUser: 'ユーザーを作成',
       createUser: 'ユーザーを作成',
@@ -2180,6 +2182,7 @@ export default {
       creating: '作成中...',
       creating: '作成中...',
       saving: '保存中...',
       saving: '保存中...',
       saveChanges: '変更を保存',
       saveChanges: '変更を保存',
+      advancedAuthSubtitle: '高度な認証を使用',
     },
     },
     deleteModal: {
     deleteModal: {
       title: 'ユーザーを削除',
       title: 'ユーザーを削除',

+ 91 - 83
frontend/src/pages/LoginPage.tsx

@@ -7,6 +7,8 @@ import { useToast } from '../contexts/ToastContext';
 import { useTheme } from '../contexts/ThemeContext';
 import { useTheme } from '../contexts/ThemeContext';
 import { X, Mail } from 'lucide-react';
 import { X, Mail } from 'lucide-react';
 import { api } from '../api/client';
 import { api } from '../api/client';
+import { Card, CardHeader, CardContent } from '../components/Card';
+import { Button } from '../components/Button';
 
 
 export function LoginPage() {
 export function LoginPage() {
   const navigate = useNavigate();
   const navigate = useNavigate();
@@ -151,97 +153,103 @@ export function LoginPage() {
       {/* Forgot Password Modal */}
       {/* Forgot Password Modal */}
       {showForgotPassword && (
       {showForgotPassword && (
         <div
         <div
-          className="fixed inset-0 bg-black/80 flex items-center justify-center z-50 p-4"
+          className="fixed inset-0 bg-black/70 flex items-center justify-center z-50 p-4"
           onClick={() => setShowForgotPassword(false)}
           onClick={() => setShowForgotPassword(false)}
         >
         >
-          <div
-            className="w-full max-w-md bg-bambu-card rounded-xl border border-bambu-dark-tertiary shadow-lg p-6"
-            onClick={(e) => e.stopPropagation()}
+          <Card
+            className="w-full max-w-md"
+            onClick={(e: React.MouseEvent) => e.stopPropagation()}
           >
           >
-            <div className="flex items-center justify-between mb-4">
-              <div className="flex items-center gap-2">
-                <Mail className="w-5 h-5 text-bambu-green" />
-                <h2 className="text-lg font-semibold text-white">{t('login.forgotPasswordTitle')}</h2>
+            <CardHeader>
+              <div className="flex items-center justify-between">
+                <div className="flex items-center gap-2">
+                  <Mail className="w-5 h-5 text-bambu-green" />
+                  <h2 className="text-lg font-semibold text-white">{t('login.forgotPasswordTitle')}</h2>
+                </div>
+                <Button
+                  variant="ghost"
+                  size="sm"
+                  onClick={() => {
+                    setShowForgotPassword(false);
+                    setForgotEmail('');
+                  }}
+                >
+                  <X className="w-5 h-5" />
+                </Button>
               </div>
               </div>
-              <button
-                onClick={() => {
-                  setShowForgotPassword(false);
-                  setForgotEmail('');
-                }}
-                className="p-1 rounded-lg hover:bg-bambu-dark-tertiary text-bambu-gray hover:text-white transition-colors"
-              >
-                <X className="w-5 h-5" />
-              </button>
-            </div>
+            </CardHeader>
+            <CardContent>
+              {advancedAuthStatus?.advanced_auth_enabled ? (
+                <form onSubmit={handleForgotPassword} className="space-y-4">
+                  <p className="text-bambu-gray text-sm">
+                    {t('login.forgotPasswordEmailMessage') || 'Enter your email address and we\'ll send you a new password.'}
+                  </p>
 
 
-            {advancedAuthStatus?.advanced_auth_enabled ? (
-              <form onSubmit={handleForgotPassword} className="space-y-4">
-                <p className="text-bambu-gray text-sm">
-                  {t('login.forgotPasswordEmailMessage') || 'Enter your email address and we\'ll send you a new password.'}
-                </p>
-
-                <div>
-                  <label htmlFor="forgot-email" className="block text-sm font-medium text-white mb-2">
-                    {t('login.emailAddress') || 'Email Address'}
-                  </label>
-                  <input
-                    id="forgot-email"
-                    type="email"
-                    required
-                    value={forgotEmail}
-                    onChange={(e) => setForgotEmail(e.target.value)}
-                    className="block w-full px-4 py-3 bg-bambu-dark-secondary border border-bambu-dark-tertiary rounded-lg text-white placeholder-bambu-gray focus:outline-none focus:ring-2 focus:ring-bambu-green/50 focus:border-bambu-green transition-colors"
-                    placeholder={t('login.emailPlaceholder') || 'your.email@example.com'}
-                  />
-                </div>
+                  <div>
+                    <label htmlFor="forgot-email" className="block text-sm font-medium text-white mb-2">
+                      {t('login.emailAddress') || 'Email Address'}
+                    </label>
+                    <input
+                      id="forgot-email"
+                      type="email"
+                      required
+                      value={forgotEmail}
+                      onChange={(e) => setForgotEmail(e.target.value)}
+                      className="block w-full px-4 py-3 bg-bambu-dark-secondary border border-bambu-dark-tertiary rounded-lg text-white placeholder-bambu-gray focus:outline-none focus:ring-2 focus:ring-bambu-green/50 focus:border-bambu-green transition-colors"
+                      placeholder={t('login.emailPlaceholder') || 'your.email@example.com'}
+                    />
+                  </div>
 
 
-                <div className="flex gap-2">
-                  <button
-                    type="button"
-                    onClick={() => {
-                      setShowForgotPassword(false);
-                      setForgotEmail('');
-                    }}
-                    className="flex-1 py-2 px-4 bg-bambu-dark-tertiary hover:bg-bambu-dark text-white rounded-lg transition-colors"
-                  >
-                    {t('login.cancel') || 'Cancel'}
-                  </button>
-                  <button
-                    type="submit"
-                    disabled={forgotPasswordMutation.isPending}
-                    className="flex-1 py-2 px-4 bg-bambu-green hover:bg-bambu-green-light text-white rounded-lg transition-colors disabled:opacity-50 disabled:cursor-not-allowed"
+                  <div className="flex gap-2">
+                    <Button
+                      type="button"
+                      variant="secondary"
+                      className="flex-1"
+                      onClick={() => {
+                        setShowForgotPassword(false);
+                        setForgotEmail('');
+                      }}
+                    >
+                      {t('login.cancel') || 'Cancel'}
+                    </Button>
+                    <Button
+                      type="submit"
+                      className="flex-1"
+                      disabled={forgotPasswordMutation.isPending}
+                    >
+                      {forgotPasswordMutation.isPending 
+                        ? (t('login.sending') || 'Sending...') 
+                        : (t('login.sendResetEmail') || 'Send Reset Email')}
+                    </Button>
+                  </div>
+                </form>
+              ) : (
+                <div className="space-y-4">
+                  <p className="text-bambu-gray">
+                    {t('login.forgotPasswordMessage')}
+                  </p>
+
+                  <div className="bg-bambu-dark rounded-lg p-4 space-y-2">
+                    <p className="text-sm text-white font-medium">{t('login.howToReset')}</p>
+                    <ol className="text-sm text-bambu-gray space-y-1 list-decimal list-inside">
+                      <li>{t('login.resetStep1')}</li>
+                      <li>{t('login.resetStep2')}</li>
+                      <li>{t('login.resetStep3')}</li>
+                      <li>{t('login.resetStep4')}</li>
+                    </ol>
+                  </div>
+
+                  <Button
+                    variant="secondary"
+                    className="w-full"
+                    onClick={() => setShowForgotPassword(false)}
                   >
                   >
-                    {forgotPasswordMutation.isPending 
-                      ? (t('login.sending') || 'Sending...') 
-                      : (t('login.sendResetEmail') || 'Send Reset Email')}
-                  </button>
+                    {t('login.gotIt')}
+                  </Button>
                 </div>
                 </div>
-              </form>
-            ) : (
-              <div className="space-y-4">
-                <p className="text-bambu-gray">
-                  {t('login.forgotPasswordMessage')}
-                </p>
-
-                <div className="bg-bambu-dark rounded-lg p-4 space-y-2">
-                  <p className="text-sm text-white font-medium">{t('login.howToReset')}</p>
-                  <ol className="text-sm text-bambu-gray space-y-1 list-decimal list-inside">
-                    <li>{t('login.resetStep1')}</li>
-                    <li>{t('login.resetStep2')}</li>
-                    <li>{t('login.resetStep3')}</li>
-                    <li>{t('login.resetStep4')}</li>
-                  </ol>
-                </div>
-
-                <button
-                  onClick={() => setShowForgotPassword(false)}
-                  className="w-full py-2 px-4 bg-bambu-dark-tertiary hover:bg-bambu-dark text-white rounded-lg transition-colors"
-                >
-                  {t('login.gotIt')}
-                </button>
-              </div>
-            )}
-          </div>
+              )}
+            </CardContent>
+          </Card>
         </div>
         </div>
       )}
       )}
     </div>
     </div>

+ 91 - 32
frontend/src/pages/SettingsPage.tsx

@@ -3426,6 +3426,25 @@ export function SettingsPage() {
             </CardContent>
             </CardContent>
           </Card>
           </Card>
 
 
+          {/* Advanced Authentication Notice Box */}
+          {advancedAuthStatus?.advanced_auth_enabled && (
+            <Card className="border-blue-500/30 bg-blue-500/5">
+              <CardContent className="py-4">
+                <div className="flex items-start gap-3">
+                  <div className="w-10 h-10 rounded-full flex items-center justify-center bg-blue-500/20 flex-shrink-0">
+                    <Mail className="w-5 h-5 text-blue-400" />
+                  </div>
+                  <div>
+                    <h3 className="text-white font-medium">{t('settings.email.advancedAuthEnabled') || 'Advanced Authentication is enabled'}</h3>
+                    <p className="text-sm text-bambu-gray mt-1">
+                      {t('settings.email.advancedAuthEnabledDesc') || 'Email-based user management features are active. New users will receive auto-generated passwords via email, and users can reset their passwords through the forgot password feature.'}
+                    </p>
+                  </div>
+                </div>
+              </CardContent>
+            </Card>
+          )}
+
           {authEnabled && (
           {authEnabled && (
             <div className="grid grid-cols-1 xl:grid-cols-2 gap-6">
             <div className="grid grid-cols-1 xl:grid-cols-2 gap-6">
               {/* Left Column: Current User + User List */}
               {/* Left Column: Current User + User List */}
@@ -3860,8 +3879,11 @@ export function SettingsPage() {
             </CardHeader>
             </CardHeader>
             <CardContent>
             <CardContent>
               <div className="space-y-4">
               <div className="space-y-4">
+                {/* Username Field */}
                 <div>
                 <div>
-                  <label className="block text-sm font-medium text-white mb-2">{t('settings.username')}</label>
+                  <label className="block text-sm font-medium text-white mb-2">
+                    {t('settings.username')} {advancedAuthStatus?.advanced_auth_enabled && <span className="text-red-400">*</span>}
+                  </label>
                   <input
                   <input
                     type="text"
                     type="text"
                     value={userFormData.username}
                     value={userFormData.username}
@@ -3871,43 +3893,75 @@ export function SettingsPage() {
                     autoComplete="username"
                     autoComplete="username"
                   />
                   />
                 </div>
                 </div>
+
+                {/* Email Field */}
                 <div>
                 <div>
                   <label className="block text-sm font-medium text-white mb-2">
                   <label className="block text-sm font-medium text-white mb-2">
-                    Password <span className="text-bambu-gray font-normal">(leave blank to keep current)</span>
+                    {t('users.form.email') || 'Email'} {advancedAuthStatus?.advanced_auth_enabled ? <span className="text-red-400">*</span> : <span className="text-bambu-gray font-normal">({t('users.form.optional') || 'optional'})</span>}
                   </label>
                   </label>
                   <input
                   <input
-                    type="password"
-                    value={userFormData.password}
-                    onChange={(e) => setUserFormData({ ...userFormData, password: e.target.value, confirmPassword: '' })}
+                    type="email"
+                    value={userFormData.email}
+                    onChange={(e) => setUserFormData({ ...userFormData, email: e.target.value })}
                     className="w-full px-4 py-3 bg-bambu-dark-secondary border border-bambu-dark-tertiary rounded-lg text-white placeholder-bambu-gray focus:outline-none focus:ring-2 focus:ring-bambu-green/50 focus:border-bambu-green transition-colors"
                     className="w-full px-4 py-3 bg-bambu-dark-secondary border border-bambu-dark-tertiary rounded-lg text-white placeholder-bambu-gray focus:outline-none focus:ring-2 focus:ring-bambu-green/50 focus:border-bambu-green transition-colors"
-                    placeholder={t('settings.enterNewPassword')}
-                    autoComplete="new-password"
-                    minLength={6}
+                    placeholder={t('users.form.emailPlaceholder') || 'user@example.com'}
+                    required={advancedAuthStatus?.advanced_auth_enabled}
                   />
                   />
                 </div>
                 </div>
-                {userFormData.password && (
-                  <div>
-                    <label className="block text-sm font-medium text-white mb-2">{t('settings.confirmPassword')}</label>
-                    <input
-                      type="password"
-                      value={userFormData.confirmPassword}
-                      onChange={(e) => setUserFormData({ ...userFormData, confirmPassword: e.target.value })}
-                      className={`w-full px-4 py-3 bg-bambu-dark-secondary border rounded-lg text-white placeholder-bambu-gray focus:outline-none focus:ring-2 focus:ring-bambu-green/50 focus:border-bambu-green transition-colors ${
-                        userFormData.confirmPassword && userFormData.password !== userFormData.confirmPassword
-                          ? 'border-red-500'
-                          : 'border-bambu-dark-tertiary'
-                      }`}
-                      placeholder={t('settings.confirmNewPassword')}
-                      autoComplete="new-password"
-                      minLength={6}
-                    />
-                    {userFormData.confirmPassword && userFormData.password !== userFormData.confirmPassword && (
-                      <p className="text-red-400 text-xs mt-1">{t('settings.passwordsDoNotMatch')}</p>
+
+                {/* Password Fields - only show when Advanced Auth is disabled */}
+                {!advancedAuthStatus?.advanced_auth_enabled && (
+                  <>
+                    <div>
+                      <label className="block text-sm font-medium text-white mb-2">
+                        {t('users.form.password') || 'Password'} <span className="text-bambu-gray font-normal">({t('users.form.leaveBlankToKeep') || 'leave blank to keep current'})</span>
+                      </label>
+                      <input
+                        type="password"
+                        value={userFormData.password}
+                        onChange={(e) => setUserFormData({ ...userFormData, password: e.target.value, confirmPassword: '' })}
+                        className="w-full px-4 py-3 bg-bambu-dark-secondary border border-bambu-dark-tertiary rounded-lg text-white placeholder-bambu-gray focus:outline-none focus:ring-2 focus:ring-bambu-green/50 focus:border-bambu-green transition-colors"
+                        placeholder={t('settings.enterNewPassword')}
+                        autoComplete="new-password"
+                        minLength={6}
+                      />
+                    </div>
+                    {userFormData.password && (
+                      <div>
+                        <label className="block text-sm font-medium text-white mb-2">{t('settings.confirmPassword')}</label>
+                        <input
+                          type="password"
+                          value={userFormData.confirmPassword}
+                          onChange={(e) => setUserFormData({ ...userFormData, confirmPassword: e.target.value })}
+                          className={`w-full px-4 py-3 bg-bambu-dark-secondary border rounded-lg text-white placeholder-bambu-gray focus:outline-none focus:ring-2 focus:ring-bambu-green/50 focus:border-bambu-green transition-colors ${
+                            userFormData.confirmPassword && userFormData.password !== userFormData.confirmPassword
+                              ? 'border-red-500'
+                              : 'border-bambu-dark-tertiary'
+                          }`}
+                          placeholder={t('settings.confirmNewPassword')}
+                          autoComplete="new-password"
+                          minLength={6}
+                        />
+                        {userFormData.confirmPassword && userFormData.password !== userFormData.confirmPassword && (
+                          <p className="text-red-400 text-xs mt-1">{t('settings.passwordsDoNotMatch')}</p>
+                        )}
+                      </div>
                     )}
                     )}
+                  </>
+                )}
+
+                {/* Info box about auto-generated password when Advanced Auth is enabled */}
+                {advancedAuthStatus?.advanced_auth_enabled && (
+                  <div className="bg-bambu-dark-secondary/50 border border-bambu-green/20 rounded-lg p-3">
+                    <p className="text-sm text-bambu-gray">
+                      {t('users.form.passwordManagedByAdvancedAuth') || 'Password is managed by Advanced Authentication. Use "Reset Password" to send a new password to the user via email.'}
+                    </p>
                   </div>
                   </div>
                 )}
                 )}
+
+                {/* Groups Field */}
                 <div>
                 <div>
-                  <label className="block text-sm font-medium text-white mb-2">Groups</label>
+                  <label className="block text-sm font-medium text-white mb-2">{t('users.form.groups') || 'Groups'}</label>
                   <div className="space-y-2 max-h-40 overflow-y-auto p-2 bg-bambu-dark-secondary border border-bambu-dark-tertiary rounded-lg">
                   <div className="space-y-2 max-h-40 overflow-y-auto p-2 bg-bambu-dark-secondary border border-bambu-dark-tertiary rounded-lg">
                     {groupsData.map(group => (
                     {groupsData.map(group => (
                       <label
                       <label
@@ -3922,7 +3976,7 @@ export function SettingsPage() {
                         />
                         />
                         <span className="text-sm text-white">{group.name}</span>
                         <span className="text-sm text-white">{group.name}</span>
                         {group.is_system && (
                         {group.is_system && (
-                          <span className="text-xs text-yellow-400">(System)</span>
+                          <span className="text-xs text-yellow-400">({t('users.system') || 'System'})</span>
                         )}
                         )}
                       </label>
                       </label>
                     ))}
                     ))}
@@ -3938,21 +3992,26 @@ export function SettingsPage() {
                     setUserFormData({ username: '', password: '', email: '', confirmPassword: '', role: 'user', group_ids: [] });
                     setUserFormData({ username: '', password: '', email: '', confirmPassword: '', role: 'user', group_ids: [] });
                   }}
                   }}
                 >
                 >
-                  Cancel
+                  {t('users.modal.cancel') || 'Cancel'}
                 </Button>
                 </Button>
                 <Button
                 <Button
                   onClick={() => handleUpdateUser(editingUserId)}
                   onClick={() => handleUpdateUser(editingUserId)}
-                  disabled={updateUserMutation.isPending || !userFormData.username || !!(userFormData.password && (userFormData.password !== userFormData.confirmPassword || userFormData.password.length < 6))}
+                  disabled={
+                    updateUserMutation.isPending || 
+                    !userFormData.username || 
+                    (advancedAuthStatus?.advanced_auth_enabled && !userFormData.email) ||
+                    Boolean(!advancedAuthStatus?.advanced_auth_enabled && userFormData.password && (userFormData.password !== userFormData.confirmPassword || userFormData.password.length < 6))
+                  }
                 >
                 >
                   {updateUserMutation.isPending ? (
                   {updateUserMutation.isPending ? (
                     <>
                     <>
                       <Loader2 className="w-4 h-4 animate-spin" />
                       <Loader2 className="w-4 h-4 animate-spin" />
-                      Saving...
+                      {t('users.modal.saving') || 'Saving...'}
                     </>
                     </>
                   ) : (
                   ) : (
                     <>
                     <>
                       <Save className="w-4 h-4" />
                       <Save className="w-4 h-4" />
-                      Save Changes
+                      {t('users.modal.saveChanges') || 'Save Changes'}
                     </>
                     </>
                   )}
                   )}
                 </Button>
                 </Button>

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


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


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


+ 2 - 2
static/index.html

@@ -23,8 +23,8 @@
 
 
     <!-- Splash screens for iOS -->
     <!-- Splash screens for iOS -->
     <link rel="apple-touch-startup-image" href="/img/android-chrome-512x512.png" />
     <link rel="apple-touch-startup-image" href="/img/android-chrome-512x512.png" />
-    <script type="module" crossorigin src="/assets/index-C0uTHy_F.js"></script>
-    <link rel="stylesheet" crossorigin href="/assets/index-DbCAeYz5.css">
+    <script type="module" crossorigin src="/assets/index-NgO9xbrN.js"></script>
+    <link rel="stylesheet" crossorigin href="/assets/index-RuMjImbZ.css">
   </head>
   </head>
   <body>
   <body>
     <div id="root"></div>
     <div id="root"></div>

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