88from  apscheduler .triggers .cron  import  CronTrigger 
99import  logging 
1010from  backup_service  import  BackupService 
11- from  models  import  db , User , Repository , BackupJob 
11+ from  models  import  db , User , Repository , BackupJob ,  PasswordResetCode 
1212import  atexit 
1313
1414# Configure logging 
@@ -82,6 +82,12 @@ def dashboard():
8282
8383@app .route ('/login' , methods = ['GET' , 'POST' ]) 
8484def  login ():
85+     # Auto-create default admin if no users 
86+     if  User .query .count () ==  0 :
87+         admin  =  User (username = 'admin' , password_hash = generate_password_hash ('changeme' ), is_admin = True )
88+         db .session .add (admin )
89+         db .session .commit ()
90+         logger .warning ('Default admin user created with username=admin password=changeme; please change immediately.' )
8591    if  request .method  ==  'POST' :
8692        username  =  request .form ['username' ]
8793        password  =  request .form ['password' ]
@@ -101,37 +107,84 @@ def logout():
101107    logout_user ()
102108    return  redirect (url_for ('login' ))
103109
104- @app .route ('/register' , methods = ['GET' , 'POST' ]) 
105- def  register ():
106-     # Check if this is the first user (admin) 
107-     user_count  =  User .query .count ()
108-     
110+ @app .route ('/settings' , methods = ['GET' , 'POST' ]) 
111+ @login_required  
112+ def  user_settings ():
109113    if  request .method  ==  'POST' :
110-         username  =  request .form ['username' ]
111-         password  =  request .form ['password' ]
112-         confirm_password  =  request .form ['confirm_password' ]
113-         
114-         if  password  !=  confirm_password :
115-             flash ('Passwords do not match' , 'error' )
116-             return  render_template ('register.html' , first_user = user_count  ==  0 )
117-         
118-         if  User .query .filter_by (username = username ).first ():
119-             flash ('Username already exists' , 'error' )
120-             return  render_template ('register.html' , first_user = user_count  ==  0 )
121-         
122-         user  =  User (
123-             username = username ,
124-             password_hash = generate_password_hash (password ),
125-             is_admin = (user_count  ==  0 )  # First user becomes admin 
126-         )
127-         db .session .add (user )
114+         new_username  =  request .form .get ('username' , '' ).strip ()
115+         current_password  =  request .form .get ('current_password' , '' )
116+         new_password  =  request .form .get ('new_password' , '' )
117+         confirm_password  =  request .form .get ('confirm_password' , '' )
118+ 
119+         # Change username 
120+         if  new_username  and  new_username  !=  current_user .username :
121+             if  User .query .filter_by (username = new_username ).first ():
122+                 flash ('Username already taken' , 'error' )
123+                 return  redirect (url_for ('user_settings' ))
124+             current_user .username  =  new_username 
125+             flash ('Username updated' , 'success' )
126+ 
127+         # Change password 
128+         if  new_password :
129+             if  not  check_password_hash (current_user .password_hash , current_password ):
130+                 flash ('Current password incorrect' , 'error' )
131+                 return  redirect (url_for ('user_settings' ))
132+             if  new_password  !=  confirm_password :
133+                 flash ('New passwords do not match' , 'error' )
134+                 return  redirect (url_for ('user_settings' ))
135+             current_user .password_hash  =  generate_password_hash (new_password )
136+             flash ('Password updated' , 'success' )
137+ 
128138        db .session .commit ()
129-         
130-         login_user (user )
131-         flash ('Registration successful' , 'success' )
132-         return  redirect (url_for ('dashboard' ))
133-     
134-     return  render_template ('register.html' , first_user = user_count  ==  0 )
139+         return  redirect (url_for ('user_settings' ))
140+ 
141+     return  render_template ('settings.html' )
142+ 
143+ import  secrets 
144+ 
145+ @app .route ('/forgot-password' , methods = ['GET' , 'POST' ]) 
146+ def  forgot_password ():
147+     if  request .method  ==  'POST' :
148+         username  =  request .form .get ('username' , '' ).strip ()
149+         user  =  User .query .filter_by (username = username ).first ()
150+         if  not  user :
151+             flash ('If that user exists, a reset code has been generated (check logs).' , 'info' )
152+             return  redirect (url_for ('forgot_password' ))
153+         # Invalidate previous unused codes for this user 
154+         PasswordResetCode .query .filter_by (user_id = user .id , used = False ).delete ()
155+         code  =  secrets .token_hex (4 )
156+         prc  =  PasswordResetCode (user_id = user .id , code = code )
157+         db .session .add (prc )
158+         db .session .commit ()
159+         logger .warning (f'PASSWORD RESET CODE for user={ user .username } { code }  )
160+         flash ('Reset code generated. Check server logs.' , 'info' )
161+         return  redirect (url_for ('reset_password' ))
162+     return  render_template ('forgot_password.html' )
163+ 
164+ @app .route ('/reset-password' , methods = ['GET' , 'POST' ]) 
165+ def  reset_password ():
166+     if  request .method  ==  'POST' :
167+         username  =  request .form .get ('username' , '' ).strip ()
168+         code  =  request .form .get ('code' , '' ).strip ()
169+         new_password  =  request .form .get ('new_password' , '' )
170+         confirm_password  =  request .form .get ('confirm_password' , '' )
171+         user  =  User .query .filter_by (username = username ).first ()
172+         if  not  user :
173+             flash ('Invalid code or user' , 'error' )
174+             return  redirect (url_for ('reset_password' ))
175+         prc  =  PasswordResetCode .query .filter_by (user_id = user .id , code = code , used = False ).first ()
176+         if  not  prc :
177+             flash ('Invalid or already used code' , 'error' )
178+             return  redirect (url_for ('reset_password' ))
179+         if  new_password  !=  confirm_password  or  not  new_password :
180+             flash ('Passwords do not match or empty' , 'error' )
181+             return  redirect (url_for ('reset_password' ))
182+         user .password_hash  =  generate_password_hash (new_password )
183+         prc .used  =  True 
184+         db .session .commit ()
185+         flash ('Password reset successful. You can now log in.' , 'success' )
186+         return  redirect (url_for ('login' ))
187+     return  render_template ('reset_password.html' )
135188
136189@app .route ('/repositories' ) 
137190@login_required  
0 commit comments