1515 */
1616
1717import express , { Request , Response , NextFunction } from 'express' ;
18+ import bcrypt from 'bcryptjs' ;
1819import { getPassport , authStrategies } from '../passport' ;
1920import { getAuthMethods , getUIHost , getUIPort } from '../../config' ;
2021
@@ -24,13 +25,38 @@ import * as passportAD from '../passport/activeDirectory';
2425
2526import { User } from '../../db/types' ;
2627import { AuthenticationElement } from '../../config/generated/config' ;
27-
28- import { isAdminUser , toPublicUser } from './utils' ;
28+ import { isAdminUser , mustChangePassword , toPublicUser } from './utils' ;
2929import { handleErrorAndLog } from '../../utils/errors' ;
3030
3131const router = express . Router ( ) ;
3232const passport = getPassport ( ) ;
3333
34+ const PASSWORD_MIN_LENGTH = 8 ;
35+ const PASSWORD_CHANGE_ALLOWED_PATHS = new Set ( [
36+ '/' ,
37+ '/config' ,
38+ '/login' ,
39+ '/logout' ,
40+ '/profile' ,
41+ '/change-password' ,
42+ '/openidconnect' ,
43+ '/openidconnect/callback' ,
44+ ] ) ;
45+
46+ router . use ( ( req : Request , res : Response , next : NextFunction ) => {
47+ if ( ! mustChangePassword ( req . user ) ) {
48+ return next ( ) ;
49+ }
50+
51+ if ( PASSWORD_CHANGE_ALLOWED_PATHS . has ( req . path ) ) {
52+ return next ( ) ;
53+ }
54+
55+ return res . status ( 428 ) . send ( {
56+ message : 'Password change required before accessing this endpoint' ,
57+ } ) ;
58+ } ) ;
59+
3460router . get ( '/' , ( _req : Request , res : Response ) => {
3561 res . status ( 200 ) . json ( {
3662 login : {
@@ -148,6 +174,85 @@ router.post('/logout', (req: Request, res: Response, next: NextFunction) => {
148174 res . send ( { isAuth : req . isAuthenticated ( ) , user : req . user } ) ;
149175} ) ;
150176
177+ router . post ( '/change-password' , async ( req : Request , res : Response ) => {
178+ if ( ! req . user ) {
179+ res
180+ . status ( 401 )
181+ . send ( {
182+ message : 'Not logged in' ,
183+ } )
184+ . end ( ) ;
185+ return ;
186+ }
187+
188+ const { currentPassword, newPassword } = req . body ?? { } ;
189+ if (
190+ typeof currentPassword !== 'string' ||
191+ typeof newPassword !== 'string' ||
192+ currentPassword . trim ( ) . length === 0 ||
193+ newPassword . trim ( ) . length < PASSWORD_MIN_LENGTH
194+ ) {
195+ res
196+ . status ( 400 )
197+ . send ( {
198+ message : `currentPassword and newPassword are required, and newPassword must be at least ${ PASSWORD_MIN_LENGTH } characters` ,
199+ } )
200+ . end ( ) ;
201+ return ;
202+ }
203+
204+ if ( currentPassword === newPassword ) {
205+ res
206+ . status ( 400 )
207+ . send ( {
208+ message : 'newPassword must be different from currentPassword' ,
209+ } )
210+ . end ( ) ;
211+ return ;
212+ }
213+
214+ try {
215+ const user = await db . findUser ( ( req . user as User ) . username ) ;
216+ if ( ! user ) {
217+ res . status ( 404 ) . send ( { message : 'User not found' } ) . end ( ) ;
218+ return ;
219+ }
220+
221+ if ( ! user . password ) {
222+ res
223+ . status ( 400 )
224+ . send ( { message : 'Password changes are not supported for this account' } )
225+ . end ( ) ;
226+ return ;
227+ }
228+
229+ const currentPasswordCorrect = await bcrypt . compare ( currentPassword , user . password ?? '' ) ;
230+ if ( ! currentPasswordCorrect ) {
231+ res . status ( 401 ) . send ( { message : 'Current password is incorrect' } ) . end ( ) ;
232+ return ;
233+ }
234+
235+ const hashedPassword = await bcrypt . hash ( newPassword , 10 ) ;
236+ await db . updateUser ( {
237+ username : user . username ,
238+ password : hashedPassword ,
239+ mustChangePassword : false ,
240+ } ) ;
241+
242+ ( req . user as User ) . mustChangePassword = false ;
243+
244+ res . status ( 200 ) . send ( { message : 'Password updated successfully' } ) . end ( ) ;
245+ } catch ( error : unknown ) {
246+ const msg = handleErrorAndLog ( error , 'Failed to update password' ) ;
247+ res
248+ . status ( 500 )
249+ . send ( {
250+ message : msg ,
251+ } )
252+ . end ( ) ;
253+ }
254+ } ) ;
255+
151256router . get ( '/profile' , async ( req : Request , res : Response ) => {
152257 if ( ! req . user ) {
153258 res
@@ -219,16 +324,11 @@ router.post('/gitAccount', async (req: Request, res: Response) => {
219324 }
220325
221326 user . gitAccount = req . body . gitAccount ;
222- db . updateUser ( user ) ;
223- res . status ( 200 ) . end ( ) ;
327+ await db . updateUser ( user ) ;
328+ return res . status ( 200 ) . send ( { message : 'Git account updated successfully' } ) . end ( ) ;
224329 } catch ( error : unknown ) {
225330 const msg = handleErrorAndLog ( error , 'Failed to update git account' ) ;
226- res
227- . status ( 500 )
228- . send ( {
229- message : msg ,
230- } )
231- . end ( ) ;
331+ return res . status ( 500 ) . send ( { message : msg } ) . end ( ) ;
232332 }
233333} ) ;
234334
0 commit comments