diff --git a/app/auth/models.py b/app/auth/models.py
index c9cfa137fa73a2d7c060ecaea4e3ca3ded212056..b79ce703713921fdc6852c2118b31be8930988a8 100644
--- a/app/auth/models.py
+++ b/app/auth/models.py
@@ -12,7 +12,7 @@ from typing import Optional
 
 import cracklib  # pylint: disable=import-error
 
-from flask import current_app, g
+from flask import current_app, flash, g
 from flask_login import UserMixin, AnonymousUserMixin
 from sqlalchemy.exc import (
     IntegrityError,
@@ -67,6 +67,14 @@ def is_valid_user_name(user_name: str) -> bool:
     )
 
 
+def is_new_cas_id(cas_id: str) -> bool:
+    "Check that cas_id is a valid new id (uniqueness, allow nulls)"
+    if not cas_id:
+        return True
+    nb_with_this_id = db.session.query(User).filter_by(cas_id=cas_id).count()
+    return nb_with_this_id == 0
+
+
 class User(UserMixin, ScoDocModel):
     """ScoDoc users, handled by Flask / SQLAlchemy"""
 
@@ -178,13 +186,16 @@ class User(UserMixin, ScoDocModel):
             raise ValueError("invalid user_id")
         return query.first_or_404() if not accept_none else query.first()
 
-    def set_password(self, password):
+    def set_password(self, password: str):
         "Set password"
         log(f"set_password({self})")
+        previous_hash = self.password_hash
         if password:
             self.password_hash = generate_password_hash(password)
         else:
             self.password_hash = None
+        if self.password_hash != previous_hash:
+            self.date_modif_passwd = datetime.now()
         # La création d'un mot de passe efface l'éventuel mot de passe historique
         self.password_scodoc7 = None
         self.passwd_temp = False
@@ -350,9 +361,7 @@ class User(UserMixin, ScoDocModel):
             date_expiration = args.get("date_expiration")
             if isinstance(date_expiration, str):
                 args["date_expiration"] = (
-                    datetime.fromisoformat(date_expiration)
-                    if date_expiration
-                    else None
+                    datetime.fromisoformat(date_expiration) if date_expiration else None
                 )
         # booléens:
         for field in ("active", "cas_allow_login", "cas_allow_scodoc_login"):
@@ -394,17 +403,43 @@ class User(UserMixin, ScoDocModel):
                     role, dept = UserRole.role_dept_from_string(r_d)
                     self.add_role(role, dept)
 
-        super().from_dict(args, excluded={"user_name", "roles_string", "roles"})
+        # email_institutionnel may not be unique, but check and warns user
+        email_institutionnel = args.get("email_institutionnel")
+        if email_institutionnel and email_institutionnel != self.email_institutionnel:
+            nb_with_this_mail = (
+                db.session.query(User)
+                .filter_by(email_institutionnel=email_institutionnel)
+                .count()
+            )
+            if nb_with_this_mail > 0:
+                log(
+                    "User.from_dict: plusieurs utilisateurs avec ce mail institutionnel"
+                )
+                flash(
+                    "Attention: plusieurs utilisateurs avec ce mail institutionnel",
+                    "warning",
+                )
+
+        super().from_dict(
+            args, excluded={"cas_id", "user_name", "roles_string", "roles"}
+        )
 
+        new_cas_id = args.get("cas_id")
         if ScoDocSiteConfig.cas_uid_use_scodoc():
-            self.cas_id = self.user_name
+            new_cas_id = self.user_name
         else:
             # Set cas_id using regexp if configured:
             exp = ScoDocSiteConfig.get("cas_uid_from_mail_regexp")
             if exp and self.email_institutionnel:
                 cas_id = ScoDocSiteConfig.extract_cas_id(self.email_institutionnel)
                 if cas_id:
-                    self.cas_id = cas_id
+                    new_cas_id = cas_id
+        if new_cas_id != self.cas_id:
+            if is_new_cas_id(new_cas_id):
+                self.cas_id = new_cas_id
+            else:
+                log(f"User.from_dict: CAS id invalide pour {self.user_name}")
+                raise ScoValueError(f"CAS id invalide pour {self.user_name}")
 
     def get_token(self, expires_in=3600):
         "Un jeton pour cet user. Stocké en base, non commité."
diff --git a/app/templates/auth/user_info_page.j2 b/app/templates/auth/user_info_page.j2
index 6b36704a586c35a444bbe5d62e8bfa22d275bd0a..882b5370b699778a896ec50b748a9e61c6ea9965 100644
--- a/app/templates/auth/user_info_page.j2
+++ b/app/templates/auth/user_info_page.j2
@@ -39,7 +39,7 @@
 {% endif %}
 <div class="user_basics">
     <b>Dernière modif mot de passe:</b>
-    {{user.date_modif_passwd.strftime(scu.DATE_FMT) if user.date_modif_passwd else ""}}<br>
+    {{user.date_modif_passwd.strftime(scu.DATEATIME_FMT) if user.date_modif_passwd else ""}}<br>
     <b>Date d'expiration:</b>
     {{user.date_expiration.strftime(scu.DATE_FMT) if user.date_expiration else "(sans limite)"}}
 </div>