2020from http .cookies import Morsel
2121
2222from tornado import escape , httputil , web
23- from traitlets import Bool , Dict , Type , Unicode , default
23+ from traitlets import Bool , Dict , List , TraitError , Type , Unicode , default , validate
2424from traitlets .config import LoggingConfigurable
2525
2626from jupyter_server .transutils import _i18n
3131_non_alphanum = re .compile (r"[^A-Za-z0-9]" )
3232
3333
34+ # Define the User properties that can be updated
35+ UpdatableField = t .Literal ["name" , "display_name" , "initials" , "avatar_url" , "color" ]
36+
37+
3438@dataclass
3539class User :
3640 """Object representing a User
@@ -188,6 +192,14 @@ class IdentityProvider(LoggingConfigurable):
188192 help = _i18n ("The logout handler class to use." ),
189193 )
190194
195+ # Define the fields that can be updated
196+ updatable_fields = List (
197+ trait = Unicode (),
198+ default_value = ["color" ], # Default updatable field
199+ config = True ,
200+ help = _i18n ("List of fields in the User model that can be updated." ),
201+ )
202+
191203 token_generated = False
192204
193205 @default ("token" )
@@ -207,6 +219,17 @@ def _token_default(self):
207219 self .token_generated = True
208220 return binascii .hexlify (os .urandom (24 )).decode ("ascii" )
209221
222+ @validate ("updatable_fields" )
223+ def _validate_updatable_fields (self , proposal ):
224+ """Validate that all fields in updatable_fields are valid."""
225+ valid_updatable_fields = list (t .get_args (UpdatableField ))
226+ invalid_fields = [
227+ field for field in proposal ["value" ] if field not in valid_updatable_fields
228+ ]
229+ if invalid_fields :
230+ raise TraitError (f"Invalid fields in updatable_fields: { invalid_fields } " )
231+ return proposal ["value" ]
232+
210233 need_token : bool | Bool [bool , t .Union [bool , int ]] = Bool (True )
211234
212235 def get_user (self , handler : web .RequestHandler ) -> User | None | t .Awaitable [User | None ]:
@@ -269,6 +292,25 @@ async def _get_user(self, handler: web.RequestHandler) -> User | None:
269292
270293 return user
271294
295+ def update_user (
296+ self , handler : web .RequestHandler , user_data : dict [UpdatableField , str ]
297+ ) -> User :
298+ """Update user information."""
299+ current_user = handler .current_user # type:ignore[attr-defined]
300+
301+ for field in user_data :
302+ if field not in self .updatable_fields :
303+ raise ValueError (f"Field { field } is not updatable" )
304+
305+ # Update fields
306+ for field in self .updatable_fields :
307+ if field in user_data :
308+ setattr (current_user , field , user_data [field ])
309+
310+ # Persist changes (if applicable)
311+ self .set_login_cookie (handler , current_user ) # Save updated user to cookie/session
312+ return current_user
313+
272314 def identity_model (self , user : User ) -> dict [str , t .Any ]:
273315 """Return a User as an Identity model"""
274316 # TODO: validate?
@@ -617,6 +659,16 @@ class PasswordIdentityProvider(IdentityProvider):
617659 def _need_token_default (self ):
618660 return not bool (self .hashed_password )
619661
662+ @default ("updatable_fields" )
663+ def _default_updatable_fields (self ):
664+ return [
665+ "name" ,
666+ "display_name" ,
667+ "initials" ,
668+ "avatar_url" ,
669+ "color" ,
670+ ]
671+
620672 @property
621673 def login_available (self ) -> bool :
622674 """Whether a LoginHandler is needed - and therefore whether the login page should be displayed."""
0 commit comments