diff --git a/controllers/user.go b/controllers/user.go index 693eb6d5..46a4343e 100644 --- a/controllers/user.go +++ b/controllers/user.go @@ -561,8 +561,9 @@ func (c *ApiController) SetPassword() { targetUser.Password = newPassword targetUser.UpdateUserPassword(organization) targetUser.NeedUpdatePassword = false + targetUser.LastChangePasswordTime = util.GetCurrentTime() - _, err = object.UpdateUser(userId, targetUser, []string{"password", "need_update_password", "password_type"}, false) + _, err = object.UpdateUser(userId, targetUser, []string{"password", "need_update_password", "password_type", "last_change_password_time"}, false) if err != nil { c.ResponseError(err.Error()) return diff --git a/object/check.go b/object/check.go index ac3425d5..6bf4ebd8 100644 --- a/object/check.go +++ b/object/check.go @@ -381,7 +381,13 @@ func CheckUserPassword(organization string, username string, password string, la if err != nil { return nil, err } + + err = checkPasswordExpired(user, lang) + if err != nil { + return nil, err + } } + return user, nil } diff --git a/object/check_password_expired.go b/object/check_password_expired.go new file mode 100644 index 00000000..3d5d2f98 --- /dev/null +++ b/object/check_password_expired.go @@ -0,0 +1,53 @@ +// Copyright 2024 The Casdoor Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package object + +import ( + "fmt" + "time" + + "github.com/casdoor/casdoor/i18n" + "github.com/casdoor/casdoor/util" +) + +func checkPasswordExpired(user *User, lang string) error { + organization, err := GetOrganizationByUser(user) + if err != nil { + return err + } + if organization == nil { + return fmt.Errorf(i18n.Translate(lang, "check:Organization does not exist")) + } + + passwordExpireDays := organization.PasswordExpireDays + if passwordExpireDays <= 0 { + return nil + } + + lastChangePasswordTime := user.LastChangePasswordTime + if lastChangePasswordTime == "" { + if user.CreatedTime == "" { + return fmt.Errorf(i18n.Translate(lang, "check:Your password has expired. Please reset your password by clicking \"Forgot password\"")) + } + lastChangePasswordTime = user.CreatedTime + } + + lastTime := util.String2Time(lastChangePasswordTime) + expireTime := lastTime.AddDate(0, 0, passwordExpireDays) + if time.Now().After(expireTime) { + return fmt.Errorf(i18n.Translate(lang, "check:Your password has expired. Please reset your password by clicking \"Forgot password\"")) + } + return nil +} diff --git a/object/user.go b/object/user.go index 3f8c52bf..2d44b3ce 100644 --- a/object/user.go +++ b/object/user.go @@ -200,8 +200,9 @@ type User struct { Permissions []*Permission `json:"permissions"` Groups []string `xorm:"groups varchar(1000)" json:"groups"` - LastSigninWrongTime string `xorm:"varchar(100)" json:"lastSigninWrongTime"` - SigninWrongTimes int `json:"signinWrongTimes"` + LastChangePasswordTime string `xorm:"varchar(100)" json:"lastChangePasswordTime"` + LastSigninWrongTime string `xorm:"varchar(100)" json:"lastSigninWrongTime"` + SigninWrongTimes int `json:"signinWrongTimes"` ManagedAccounts []ManagedAccount `xorm:"managedAccounts blob" json:"managedAccounts"` MfaAccounts []MfaAccount `xorm:"mfaAccounts blob" json:"mfaAccounts"` @@ -690,7 +691,7 @@ func UpdateUser(id string, user *User, columns []string, isAdmin bool) (bool, er "owner", "display_name", "avatar", "first_name", "last_name", "location", "address", "country_code", "region", "language", "affiliation", "title", "id_card_type", "id_card", "homepage", "bio", "tag", "language", "gender", "birthday", "education", "score", "karma", "ranking", "signup_application", "is_admin", "is_forbidden", "is_deleted", "hash", "is_default_avatar", "properties", "webauthnCredentials", "managedAccounts", "face_ids", "mfaAccounts", - "signin_wrong_times", "last_signin_wrong_time", "groups", "access_key", "access_secret", "mfa_phone_enabled", "mfa_email_enabled", + "signin_wrong_times", "last_change_password_time", "last_signin_wrong_time", "groups", "access_key", "access_secret", "mfa_phone_enabled", "mfa_email_enabled", "github", "google", "qq", "wechat", "facebook", "dingtalk", "weibo", "gitee", "linkedin", "wecom", "lark", "gitlab", "adfs", "baidu", "alipay", "casdoor", "infoflow", "apple", "azuread", "azureadb2c", "slack", "steam", "bilibili", "okta", "douyin", "line", "amazon", "auth0", "battlenet", "bitbucket", "box", "cloudfoundry", "dailymotion", "deezer", "digitalocean", "discord", "dropbox", diff --git a/web/src/UserEditPage.js b/web/src/UserEditPage.js index 47e855e3..420891c0 100644 --- a/web/src/UserEditPage.js +++ b/web/src/UserEditPage.js @@ -1009,6 +1009,19 @@ class UserEditPage extends React.Component { ); + } else if (accountItem.name === "Last change password time") { + return ( + + + {Setting.getLabel(i18next.t("user:Last change password time"), i18next.t("user:Last change password time"))} : + + + { + this.updateUserField("lastChangePasswordTime", e.target.value); + }} /> + + + ); } else if (accountItem.name === "Managed accounts") { return (