2025-05-10 02:09:06 +02:00
/ * !
* Copyright ( c ) 2024 PLANKA Software GmbH
* Licensed under the Fair Use License : https : //github.com/plankanban/planka/blob/master/LICENSE.md
* /
2025-09-08 16:20:27 +02:00
/ * *
* @ swagger
2025-09-08 18:25:26 +02:00
* /users/ { id } / username :
2025-09-08 16:20:27 +02:00
* patch :
* summary : Update user username
* description : Updates a user 's username. Users must provide a current password when updating their own username (unless they are SSO users with `oidcIgnoreUsername` enabled). Admins can update any user' s username without the current password .
* tags :
* - Users
* parameters :
* - in : path
* name : id
* required : true
* description : ID of the user whose username to update
* schema :
* type : string
* example : 1357158568008091264
* requestBody :
* required : true
* content :
* application / json :
* schema :
* type : object
* properties :
* username :
* type : string
* minLength : 3
* maxLength : 16
* pattern : '^[a-zA-Z0-9]+((_|\.)?[a-zA-Z0-9])*$'
* nullable : true
* description : Unique username for user identification
* example : john _doe
* currentPassword :
* type : string
* maxLength : 256
* description : Current password ( required when updating own username )
* example : SecurePassword123 !
* responses :
* 200 :
* description : Username updated successfully
* content :
* application / json :
* schema :
* type : object
* required :
* - item
* properties :
* item :
* $ref : '#/components/schemas/User'
* 400 :
* $ref : '#/components/responses/ValidationError'
* 401 :
* $ref : '#/components/responses/Unauthorized'
* 403 :
* $ref : '#/components/responses/Forbidden'
* 404 :
* $ref : '#/components/responses/NotFound'
* 409 :
* $ref : '#/components/responses/Conflict'
* /
2020-04-03 00:35:25 +05:00
const bcrypt = require ( 'bcrypt' ) ;
2025-05-10 02:09:06 +02:00
const { idInput } = require ( '../../../utils/inputs' ) ;
2020-04-03 00:35:25 +05:00
const Errors = {
2023-10-17 19:18:19 +02:00
NOT _ENOUGH _RIGHTS : {
notEnoughRights : 'Not enough rights' ,
} ,
2020-04-03 00:35:25 +05:00
INVALID _CURRENT _PASSWORD : {
invalidCurrentPassword : 'Invalid current password' ,
} ,
2025-05-10 02:09:06 +02:00
USER _NOT _FOUND : {
userNotFound : 'User not found' ,
} ,
2020-04-03 00:35:25 +05:00
USERNAME _ALREADY _IN _USE : {
usernameAlreadyInUse : 'Username already in use' ,
} ,
} ;
module . exports = {
inputs : {
id : {
2025-05-10 02:09:06 +02:00
... idInput ,
2020-04-03 00:35:25 +05:00
required : true ,
} ,
username : {
2025-05-10 02:09:06 +02:00
type : 'string' ,
2020-04-03 00:35:25 +05:00
isNotEmptyString : true ,
minLength : 3 ,
maxLength : 16 ,
2021-04-13 18:59:02 +05:00
regex : /^[a-zA-Z0-9]+((_|\.)?[a-zA-Z0-9])*$/ ,
2020-04-03 00:35:25 +05:00
allowNull : true ,
} ,
currentPassword : {
type : 'string' ,
isNotEmptyString : true ,
2025-05-10 02:09:06 +02:00
maxLength : 256 ,
2020-04-03 00:35:25 +05:00
} ,
} ,
exits : {
2023-10-17 19:18:19 +02:00
notEnoughRights : {
responseType : 'forbidden' ,
} ,
2020-04-03 00:35:25 +05:00
invalidCurrentPassword : {
responseType : 'forbidden' ,
} ,
2025-05-10 02:09:06 +02:00
userNotFound : {
responseType : 'notFound' ,
} ,
2020-04-03 00:35:25 +05:00
usernameAlreadyInUse : {
responseType : 'conflict' ,
} ,
} ,
2021-06-24 01:05:22 +05:00
async fn ( inputs ) {
2020-04-03 00:35:25 +05:00
const { currentUser } = this . req ;
2025-05-10 02:09:06 +02:00
if ( inputs . id !== currentUser . id && currentUser . role !== User . Roles . ADMIN ) {
2020-04-03 00:35:25 +05:00
throw Errors . USER _NOT _FOUND ; // Forbidden
}
2025-05-10 02:09:06 +02:00
let user = await User . qm . getOneById ( inputs . id ) ;
2020-04-03 00:35:25 +05:00
if ( ! user ) {
throw Errors . USER _NOT _FOUND ;
}
2024-01-25 23:01:59 +01:00
if ( user . email === sails . config . custom . defaultAdminEmail ) {
2023-10-17 19:18:19 +02:00
throw Errors . NOT _ENOUGH _RIGHTS ;
2023-09-12 01:12:38 +02:00
}
2025-05-10 02:09:06 +02:00
if ( user . isSsoUser ) {
2024-01-25 23:01:59 +01:00
if ( ! sails . config . custom . oidcIgnoreUsername ) {
throw Errors . NOT _ENOUGH _RIGHTS ;
}
} else if ( inputs . id === currentUser . id ) {
2025-05-10 02:09:06 +02:00
if ( ! inputs . currentPassword ) {
throw Errors . INVALID _CURRENT _PASSWORD ;
}
const isCurrentPasswordValid = await bcrypt . compare ( inputs . currentPassword , user . password ) ;
if ( ! isCurrentPasswordValid ) {
2024-01-25 23:01:59 +01:00
throw Errors . INVALID _CURRENT _PASSWORD ;
}
2020-04-03 00:35:25 +05:00
}
const values = _ . pick ( inputs , [ 'username' ] ) ;
2022-12-26 21:10:50 +01:00
user = await sails . helpers . users . updateOne
. with ( {
values ,
record : user ,
2024-06-12 00:51:36 +02:00
actorUser : currentUser ,
2022-12-26 21:10:50 +01:00
request : this . req ,
} )
2020-04-03 00:35:25 +05:00
. intercept ( 'usernameAlreadyInUse' , ( ) => Errors . USERNAME _ALREADY _IN _USE ) ;
if ( ! user ) {
throw Errors . USER _NOT _FOUND ;
}
2021-06-24 01:05:22 +05:00
return {
2025-05-10 02:09:06 +02:00
item : sails . helpers . users . presentOne ( user , currentUser ) ,
2021-06-24 01:05:22 +05:00
} ;
2020-04-03 00:35:25 +05:00
} ,
} ;