Compare commits

...

2 Commits

Author SHA1 Message Date
gongzhongqiang
8fa681f883 feat: add password change validation to ensure new password differs from current password (#4134) 2025-09-01 17:22:06 +08:00
DacongDA
3b16406442 feat: add signinMethod in JWT token (#4136) 2025-08-31 18:01:05 +08:00
32 changed files with 81 additions and 14 deletions

View File

@@ -157,7 +157,7 @@ func (c *ApiController) HandleLoggedIn(application *object.Application, user *ob
c.ResponseError(c.T("auth:Challenge method should be S256"))
return
}
code, err := object.GetOAuthCode(userId, clientId, form.Provider, responseType, redirectUri, scope, state, nonce, codeChallenge, c.Ctx.Request.Host, c.GetAcceptLanguage())
code, err := object.GetOAuthCode(userId, clientId, form.Provider, form.SigninMethod, responseType, redirectUri, scope, state, nonce, codeChallenge, c.Ctx.Request.Host, c.GetAcceptLanguage())
if err != nil {
c.ResponseError(err.Error(), nil)
return

View File

@@ -551,6 +551,12 @@ func (c *ApiController) SetPassword() {
return
}
// Check if the new password is the same as the current password
if !object.CheckPasswordNotSameAsCurrent(targetUser, newPassword, organization) {
c.ResponseError(c.T("user:The new password must be different from your current password"))
return
}
application, err := object.GetApplicationByUser(targetUser)
if err != nil {
c.ResponseError(err.Error())

View File

@@ -167,6 +167,7 @@
"MFA email is enabled but email is empty": "تم تمكين MFA للبريد الإلكتروني لكن البريد الإلكتروني فارغ",
"MFA phone is enabled but phone number is empty": "تم تمكين MFA للهاتف لكن رقم الهاتف فارغ",
"New password cannot contain blank space.": "كلمة المرور الجديدة لا يمكن أن تحتوي على مسافات.",
"The new password must be different from your current password": "The new password must be different from your current password",
"the user's owner and name should not be empty": "مالك المستخدم واسمه لا يجب أن يكونا فارغين"
},
"util": {

View File

@@ -167,6 +167,7 @@
"MFA email is enabled but email is empty": "MFA email aktiv edilib, lakin email boşdur",
"MFA phone is enabled but phone number is empty": "MFA telefon aktiv edilib, lakin telefon nömrəsi boşdur",
"New password cannot contain blank space.": "Yeni şifrə boş yer ehtiva edə bilməz.",
"The new password must be different from your current password": "The new password must be different from your current password",
"the user's owner and name should not be empty": "istifadəçinin sahibi və adı boş olmamalıdır"
},
"util": {

View File

@@ -167,6 +167,7 @@
"MFA email is enabled but email is empty": "MFA e-mail je povolen, ale e-mail je prázdný",
"MFA phone is enabled but phone number is empty": "MFA telefon je povolen, ale telefonní číslo je prázdné",
"New password cannot contain blank space.": "Nové heslo nemůže obsahovat prázdné místo.",
"The new password must be different from your current password": "The new password must be different from your current password",
"the user's owner and name should not be empty": "vlastník a jméno uživatele by neměly být prázdné"
},
"util": {

View File

@@ -167,6 +167,7 @@
"MFA email is enabled but email is empty": "MFA-E-Mail ist aktiviert, aber E-Mail ist leer",
"MFA phone is enabled but phone number is empty": "MFA-Telefon ist aktiviert, aber Telefonnummer ist leer",
"New password cannot contain blank space.": "Das neue Passwort darf keine Leerzeichen enthalten.",
"The new password must be different from your current password": "The new password must be different from your current password",
"the user's owner and name should not be empty": "Eigentümer und Name des Benutzers dürfen nicht leer sein"
},
"util": {

View File

@@ -167,6 +167,7 @@
"MFA email is enabled but email is empty": "MFA email is enabled but email is empty",
"MFA phone is enabled but phone number is empty": "MFA phone is enabled but phone number is empty",
"New password cannot contain blank space.": "New password cannot contain blank space.",
"The new password must be different from your current password": "The new password must be different from your current password",
"the user's owner and name should not be empty": "the user's owner and name should not be empty"
},
"util": {

View File

@@ -167,6 +167,7 @@
"MFA email is enabled but email is empty": "El correo electrónico MFA está habilitado pero el correo está vacío",
"MFA phone is enabled but phone number is empty": "El teléfono MFA está habilitado pero el número de teléfono está vacío",
"New password cannot contain blank space.": "La nueva contraseña no puede contener espacios en blanco.",
"The new password must be different from your current password": "The new password must be different from your current password",
"the user's owner and name should not be empty": "el propietario y el nombre del usuario no deben estar vacíos"
},
"util": {

View File

@@ -167,6 +167,7 @@
"MFA email is enabled but email is empty": "ایمیل MFA فعال است اما ایمیل خالی است",
"MFA phone is enabled but phone number is empty": "تلفن MFA فعال است اما شماره تلفن خالی است",
"New password cannot contain blank space.": "رمز عبور جدید نمی‌تواند حاوی فاصله خالی باشد.",
"The new password must be different from your current password": "The new password must be different from your current password",
"the user's owner and name should not be empty": "مالک و نام کاربر نباید خالی باشند"
},
"util": {

View File

@@ -167,6 +167,7 @@
"MFA email is enabled but email is empty": "MFA-sähköposti on käytössä, mutta sähköposti on tyhjä",
"MFA phone is enabled but phone number is empty": "MFA-puhelin on käytössä, mutta puhelinnumero on tyhjä",
"New password cannot contain blank space.": "Uusi salasana ei voi sisältää välilyöntejä.",
"The new password must be different from your current password": "The new password must be different from your current password",
"the user's owner and name should not be empty": "käyttäjän omistaja ja nimi eivät saa olla tyhjiä"
},
"util": {

View File

@@ -167,6 +167,7 @@
"MFA email is enabled but email is empty": "L'authentification MFA par e-mail est activée mais l'e-mail est vide",
"MFA phone is enabled but phone number is empty": "L'authentification MFA par téléphone est activée mais le numéro de téléphone est vide",
"New password cannot contain blank space.": "Le nouveau mot de passe ne peut pas contenir d'espace.",
"The new password must be different from your current password": "The new password must be different from your current password",
"the user's owner and name should not be empty": "le propriétaire et le nom de l'utilisateur ne doivent pas être vides"
},
"util": {

View File

@@ -167,6 +167,7 @@
"MFA email is enabled but email is empty": "MFA דוא\"ל מופעל אך הדוא\"ל ריק",
"MFA phone is enabled but phone number is empty": "MFA טלפון מופעל אך מספר הטלפון ריק",
"New password cannot contain blank space.": "הסיסמה החדשה אינה יכולה להכיל רווחים.",
"The new password must be different from your current password": "The new password must be different from your current password",
"the user's owner and name should not be empty": "הבעלים והשם של המשתמש אינם יכולים להיות ריקים"
},
"util": {

View File

@@ -167,6 +167,7 @@
"MFA email is enabled but email is empty": "Email MFA diaktifkan tetapi email kosong",
"MFA phone is enabled but phone number is empty": "Telepon MFA diaktifkan tetapi nomor telepon kosong",
"New password cannot contain blank space.": "Sandi baru tidak boleh mengandung spasi kosong.",
"The new password must be different from your current password": "The new password must be different from your current password",
"the user's owner and name should not be empty": "pemilik dan nama pengguna tidak boleh kosong"
},
"util": {

View File

@@ -167,6 +167,7 @@
"MFA email is enabled but email is empty": "L'email MFA è abilitata ma l'email è vuota",
"MFA phone is enabled but phone number is empty": "Il telefono MFA è abilitato ma il numero di telefono è vuoto",
"New password cannot contain blank space.": "Nuova password non può contenere spazi",
"The new password must be different from your current password": "The new password must be different from your current password",
"the user's owner and name should not be empty": "il proprietario e il nome dell'utente non devono essere vuoti"
},
"util": {

View File

@@ -167,6 +167,7 @@
"MFA email is enabled but email is empty": "MFA メールが有効になっていますが、メールアドレスが空です",
"MFA phone is enabled but phone number is empty": "MFA 電話番号が有効になっていますが、電話番号が空です",
"New password cannot contain blank space.": "新しいパスワードにはスペースを含めることはできません。",
"The new password must be different from your current password": "The new password must be different from your current password",
"the user's owner and name should not be empty": "ユーザーのオーナーと名前は空にできません"
},
"util": {

View File

@@ -167,6 +167,7 @@
"MFA email is enabled but email is empty": "MFA-e-mail is ingeschakeld maar e-mailadres is leeg",
"MFA phone is enabled but phone number is empty": "MFA-telefoon is ingeschakeld maar telefoonnummer is leeg",
"New password cannot contain blank space.": "Nieuw wachtwoord mag geen spaties bevatten.",
"The new password must be different from your current password": "The new password must be different from your current password",
"the user's owner and name should not be empty": "eigenaar en naam van gebruiker mogen niet leeg zijn"
},
"util": {

View File

@@ -167,6 +167,7 @@
"MFA email is enabled but email is empty": "MFA 이메일이 활성화되었지만 이메일이 비어 있습니다",
"MFA phone is enabled but phone number is empty": "MFA 전화번호가 활성화되었지만 전화번호가 비어 있습니다",
"New password cannot contain blank space.": "새 비밀번호에는 공백이 포함될 수 없습니다.",
"The new password must be different from your current password": "The new password must be different from your current password",
"the user's owner and name should not be empty": "사용자의 소유자와 이름은 비워둘 수 없습니다"
},
"util": {

View File

@@ -167,6 +167,7 @@
"MFA email is enabled but email is empty": "MFA emel dibenarkan tetapi emel kosong",
"MFA phone is enabled but phone number is empty": "MFA telefon dibenarkan tetapi nombor telefon kosong",
"New password cannot contain blank space.": "Kata laluan baharu tidak boleh ada ruang kosong.",
"The new password must be different from your current password": "The new password must be different from your current password",
"the user's owner and name should not be empty": "pemilik dan nama pengguna tidak boleh kosong"
},
"util": {

View File

@@ -167,6 +167,7 @@
"MFA email is enabled but email is empty": "MFA-e-mail ingeschakeld maar e-mailadres leeg",
"MFA phone is enabled but phone number is empty": "MFA-telefoon ingeschakeld maar nummer leeg",
"New password cannot contain blank space.": "Nieuw wachtwoord mag geen spaties bevatten",
"The new password must be different from your current password": "The new password must be different from your current password",
"the user's owner and name should not be empty": "Eigenaar en naam van gebruiker mogen niet leeg zijn"
},
"util": {

View File

@@ -167,6 +167,7 @@
"MFA email is enabled but email is empty": "MFA email jest włączone, ale email jest pusty",
"MFA phone is enabled but phone number is empty": "MFA telefon jest włączony, ale numer telefonu jest pusty",
"New password cannot contain blank space.": "Nowe hasło nie może zawierać spacji.",
"The new password must be different from your current password": "The new password must be different from your current password",
"the user's owner and name should not be empty": "właściciel i nazwa użytkownika nie powinny być puste"
},
"util": {

View File

@@ -167,6 +167,7 @@
"MFA email is enabled but email is empty": "MFA por e-mail está ativado, mas o e-mail está vazio",
"MFA phone is enabled but phone number is empty": "MFA por telefone está ativado, mas o número de telefone está vazio",
"New password cannot contain blank space.": "A nova senha não pode conter espaço em branco.",
"The new password must be different from your current password": "The new password must be different from your current password",
"the user's owner and name should not be empty": "o proprietário e o nome do usuário não devem estar vazios"
},
"util": {

View File

@@ -167,6 +167,7 @@
"MFA email is enabled but email is empty": "MFA по электронной почте включен, но электронная почта не указана",
"MFA phone is enabled but phone number is empty": "MFA по телефону включен, но номер телефона не указан",
"New password cannot contain blank space.": "Новый пароль не может содержать пробелы.",
"The new password must be different from your current password": "The new password must be different from your current password",
"the user's owner and name should not be empty": "владелец и имя пользователя не должны быть пустыми"
},
"util": {

View File

@@ -167,6 +167,7 @@
"MFA email is enabled but email is empty": "MFA e-mail je zapnutý, ale e-mail je prázdny",
"MFA phone is enabled but phone number is empty": "MFA telefón je zapnutý, ale telefónne číslo je prázdne",
"New password cannot contain blank space.": "Nové heslo nemôže obsahovať medzery.",
"The new password must be different from your current password": "The new password must be different from your current password",
"the user's owner and name should not be empty": "vlastník a meno používateľa nesmú byť prázdne"
},
"util": {

View File

@@ -167,6 +167,7 @@
"MFA email is enabled but email is empty": "MFA-e-post är aktiverat men e-post är tom",
"MFA phone is enabled but phone number is empty": "MFA-telefon är aktiverat men telefonnummer är tomt",
"New password cannot contain blank space.": "Nytt lösenord får inte innehålla mellanslag.",
"The new password must be different from your current password": "The new password must be different from your current password",
"the user's owner and name should not be empty": "användarens ägare och namn får inte vara tomma"
},
"util": {

View File

@@ -167,6 +167,7 @@
"MFA email is enabled but email is empty": "MFA e-postası etkin ancak e-posta boş",
"MFA phone is enabled but phone number is empty": "MFA telefonu etkin ancak telefon numarası boş",
"New password cannot contain blank space.": "Yeni şifre boşluk içeremez.",
"The new password must be different from your current password": "The new password must be different from your current password",
"the user's owner and name should not be empty": "kullanıcının sahibi ve adı boş olmamalıdır"
},
"util": {

View File

@@ -167,6 +167,7 @@
"MFA email is enabled but email is empty": "MFA email увімкнено, але email порожній",
"MFA phone is enabled but phone number is empty": "MFA телефон увімкнено, але номер телефону порожній",
"New password cannot contain blank space.": "Новий пароль не може містити пробіли.",
"The new password must be different from your current password": "The new password must be different from your current password",
"the user's owner and name should not be empty": "власник ім’я користувача не повинні бути порожніми"
},
"util": {

View File

@@ -167,6 +167,7 @@
"MFA email is enabled but email is empty": "MFA email đã bật nhưng email trống",
"MFA phone is enabled but phone number is empty": "MFA điện thoại đã bật nhưng số điện thoại trống",
"New password cannot contain blank space.": "Mật khẩu mới không thể chứa dấu trắng.",
"The new password must be different from your current password": "The new password must be different from your current password",
"the user's owner and name should not be empty": "chủ sở hữu và tên người dùng không được để trống"
},
"util": {

View File

@@ -167,6 +167,7 @@
"MFA email is enabled but email is empty": "MFA 电子邮件已启用,但电子邮件为空",
"MFA phone is enabled but phone number is empty": "MFA 电话已启用,但电话号码为空",
"New password cannot contain blank space.": "新密码不可以包含空格",
"The new password must be different from your current password": "新密码必须与您当前的密码不同",
"the user's owner and name should not be empty": "用户的组织和名称不能为空"
},
"util": {

View File

@@ -16,6 +16,8 @@ package object
import (
"regexp"
"github.com/casdoor/casdoor/cred"
)
type ValidatorFunc func(password string) string
@@ -96,3 +98,26 @@ func checkPasswordComplexity(password string, options []string) string {
}
return ""
}
// CheckPasswordNotSameAsCurrent checks if the new password is different from the current password
func CheckPasswordNotSameAsCurrent(user *User, newPassword string, organization *Organization) bool {
if user.Password == "" {
// User doesn't have a password set (e.g., OAuth-only users), allow any password
return true
}
credManager := cred.GetCredManager(organization.PasswordType)
if credManager == nil {
// If no credential manager is available, we can't compare passwords
return true
}
// Check if the new password is the same as the current password
// Try with both organization salt and user salt (like CheckPassword function does)
if credManager.IsPasswordCorrect(newPassword, user.Password, organization.PasswordSalt) ||
credManager.IsPasswordCorrect(newPassword, user.Password, user.PasswordSalt) {
return false
}
return true
}

View File

@@ -34,6 +34,8 @@ type Claims struct {
// the `azp` (Authorized Party) claim. Optional. See https://openid.net/specs/openid-connect-core-1_0.html#IDToken
Azp string `json:"azp,omitempty"`
Provider string `json:"provider,omitempty"`
SigninMethod string `json:"signinMethod,omitempty"`
jwt.RegisteredClaims
}
@@ -154,6 +156,8 @@ type ClaimsShort struct {
Scope string `json:"scope,omitempty"`
Azp string `json:"azp,omitempty"`
Provider string `json:"provider,omitempty"`
SigninMethod string `json:"signinMethod,omitempty"`
jwt.RegisteredClaims
}
@@ -174,6 +178,8 @@ type ClaimsWithoutThirdIdp struct {
Scope string `json:"scope,omitempty"`
Azp string `json:"azp,omitempty"`
Provider string `json:"provider,omitempty"`
SigninMethod string `json:"signinMethod,omitempty"`
jwt.RegisteredClaims
}
@@ -303,6 +309,7 @@ func getShortClaims(claims Claims) ClaimsShort {
Scope: claims.Scope,
RegisteredClaims: claims.RegisteredClaims,
Azp: claims.Azp,
SigninMethod: claims.SigninMethod,
Provider: claims.Provider,
}
return res
@@ -317,6 +324,7 @@ func getClaimsWithoutThirdIdp(claims Claims) ClaimsWithoutThirdIdp {
Scope: claims.Scope,
RegisteredClaims: claims.RegisteredClaims,
Azp: claims.Azp,
SigninMethod: claims.SigninMethod,
Provider: claims.Provider,
}
return res
@@ -339,6 +347,7 @@ func getClaimsCustom(claims Claims, tokenField []string) jwt.MapClaims {
res["tag"] = claims.Tag
res["scope"] = claims.Scope
res["azp"] = claims.Azp
res["signinMethod"] = claims.SigninMethod
res["provider"] = claims.Provider
for _, field := range tokenField {
@@ -395,7 +404,7 @@ func refineUser(user *User) *User {
return user
}
func generateJwtToken(application *Application, user *User, provider string, nonce string, scope string, host string) (string, string, string, error) {
func generateJwtToken(application *Application, user *User, provider string, signinMethod string, nonce string, scope string, host string) (string, string, string, error) {
nowTime := time.Now()
expireTime := nowTime.Add(time.Duration(application.ExpireInHours) * time.Hour)
refreshExpireTime := nowTime.Add(time.Duration(application.RefreshExpireInHours) * time.Hour)
@@ -423,10 +432,11 @@ func generateJwtToken(application *Application, user *User, provider string, non
TokenType: "access-token",
Nonce: nonce,
// FIXME: A workaround for custom claim by reusing `tag` in user info
Tag: user.Tag,
Scope: scope,
Azp: application.ClientId,
Provider: provider,
Tag: user.Tag,
Scope: scope,
Azp: application.ClientId,
Provider: provider,
SigninMethod: signinMethod,
RegisteredClaims: jwt.RegisteredClaims{
Issuer: originBackend,
Subject: user.Id,

View File

@@ -136,7 +136,7 @@ func CheckOAuthLogin(clientId string, responseType string, redirectUri string, s
return "", application, nil
}
func GetOAuthCode(userId string, clientId string, provider string, responseType string, redirectUri string, scope string, state string, nonce string, challenge string, host string, lang string) (*Code, error) {
func GetOAuthCode(userId string, clientId string, provider string, signinMethod string, responseType string, redirectUri string, scope string, state string, nonce string, challenge string, host string, lang string) (*Code, error) {
user, err := GetUser(userId)
if err != nil {
return nil, err
@@ -171,7 +171,7 @@ func GetOAuthCode(userId string, clientId string, provider string, responseType
if err != nil {
return nil, err
}
accessToken, refreshToken, tokenName, err := generateJwtToken(application, user, provider, nonce, scope, host)
accessToken, refreshToken, tokenName, err := generateJwtToken(application, user, provider, signinMethod, nonce, scope, host)
if err != nil {
return nil, err
}
@@ -379,7 +379,7 @@ func RefreshToken(grantType string, refreshToken string, scope string, clientId
return nil, err
}
newAccessToken, newRefreshToken, tokenName, err := generateJwtToken(application, user, "", "", scope, host)
newAccessToken, newRefreshToken, tokenName, err := generateJwtToken(application, user, "", "", "", scope, host)
if err != nil {
return &TokenError{
Error: EndpointError,
@@ -558,7 +558,7 @@ func GetPasswordToken(application *Application, username string, password string
return nil, nil, err
}
accessToken, refreshToken, tokenName, err := generateJwtToken(application, user, "", "", scope, host)
accessToken, refreshToken, tokenName, err := generateJwtToken(application, user, "", "", "", scope, host)
if err != nil {
return nil, &TokenError{
Error: EndpointError,
@@ -604,7 +604,7 @@ func GetClientCredentialsToken(application *Application, clientSecret string, sc
Type: "application",
}
accessToken, _, tokenName, err := generateJwtToken(application, nullUser, "", "", scope, host)
accessToken, _, tokenName, err := generateJwtToken(application, nullUser, "", "", "", scope, host)
if err != nil {
return nil, &TokenError{
Error: EndpointError,
@@ -668,7 +668,7 @@ func GetTokenByUser(application *Application, user *User, scope string, nonce st
return nil, err
}
accessToken, refreshToken, tokenName, err := generateJwtToken(application, user, "", nonce, scope, host)
accessToken, refreshToken, tokenName, err := generateJwtToken(application, user, "", "", nonce, scope, host)
if err != nil {
return nil, err
}
@@ -775,7 +775,7 @@ func GetWechatMiniProgramToken(application *Application, code string, host strin
return nil, nil, err
}
accessToken, refreshToken, tokenName, err := generateJwtToken(application, user, "", "", "", host)
accessToken, refreshToken, tokenName, err := generateJwtToken(application, user, "", "", "", "", host)
if err != nil {
return nil, &TokenError{
Error: EndpointError,

View File

@@ -89,7 +89,7 @@ func fastAutoSignin(ctx *context.Context) (string, error) {
return "", nil
}
code, err := object.GetOAuthCode(userId, clientId, "", responseType, redirectUri, scope, state, nonce, codeChallenge, ctx.Request.Host, getAcceptLanguage(ctx))
code, err := object.GetOAuthCode(userId, clientId, "", "autoSignin", responseType, redirectUri, scope, state, nonce, codeChallenge, ctx.Request.Host, getAcceptLanguage(ctx))
if err != nil {
return "", err
} else if code.Message != "" {