Compare commits

...

2 Commits

7 changed files with 70 additions and 7 deletions

View File

@ -140,6 +140,9 @@ func (c *ApiController) SendEmail() {
} }
content = strings.Replace(content, "%{user.friendlyName}", userString, 1) content = strings.Replace(content, "%{user.friendlyName}", userString, 1)
matchContent := object.ResetLinkReg.Find([]byte(content))
content = strings.Replace(content, string(matchContent), "", -1)
for _, receiver := range emailForm.Receivers { for _, receiver := range emailForm.Receivers {
err = object.SendEmail(provider, emailForm.Title, content, receiver, emailForm.Sender) err = object.SendEmail(provider, emailForm.Title, content, receiver, emailForm.Sender)
if err != nil { if err != nil {

View File

@ -258,7 +258,7 @@ func (c *ApiController) SendVerificationCode() {
return return
} }
sendResp = object.SendVerificationCodeToEmail(organization, user, provider, clientIp, vform.Dest) sendResp = object.SendVerificationCodeToEmail(organization, user, provider, clientIp, vform.Dest, vform.Method, c.Ctx.Request.Host, application.Name)
case object.VerifyTypePhone: case object.VerifyTypePhone:
if vform.Method == LoginVerification || vform.Method == ForgetVerification { if vform.Method == LoginVerification || vform.Method == ForgetVerification {
if user != nil && util.GetMaskedPhone(user.Phone) == vform.Dest { if user != nil && util.GetMaskedPhone(user.Phone) == vform.Dest {

View File

@ -252,7 +252,7 @@ func CheckPassword(user *User, password string, lang string, options ...bool) er
credManager := cred.GetCredManager(passwordType) credManager := cred.GetCredManager(passwordType)
if credManager == nil { if credManager == nil {
return fmt.Errorf(i18n.Translate(lang, "check:unsupported password type: %s"), organization.PasswordType) return fmt.Errorf(i18n.Translate(lang, "check:unsupported password type: %s"), passwordType)
} }
if organization.MasterPassword != "" { if organization.MasterPassword != "" {
@ -265,6 +265,16 @@ func CheckPassword(user *User, password string, lang string, options ...bool) er
return recordSigninErrorInfo(user, lang, enableCaptcha) return recordSigninErrorInfo(user, lang, enableCaptcha)
} }
isOutdated := passwordType != organization.PasswordType
if isOutdated {
user.Password = password
user.UpdateUserPassword(organization)
_, err = UpdateUser(user.GetId(), user, []string{"password", "password_type", "password_salt"}, true)
if err != nil {
return err
}
}
return resetUserSigninErrorTimes(user) return resetUserSigninErrorTimes(user)
} }

View File

@ -19,6 +19,8 @@ import (
"fmt" "fmt"
"math" "math"
"math/rand" "math/rand"
"net/url"
"regexp"
"strings" "strings"
"time" "time"
@ -33,6 +35,8 @@ type VerifyResult struct {
Msg string Msg string
} }
var ResetLinkReg *regexp.Regexp
const ( const (
VerificationSuccess = iota VerificationSuccess = iota
wrongCodeError wrongCodeError
@ -45,6 +49,10 @@ const (
VerifyTypeEmail = "email" VerifyTypeEmail = "email"
) )
func init() {
ResetLinkReg = regexp.MustCompile("(?s)<reset-link>(.*?)</reset-link>")
}
type VerificationRecord struct { type VerificationRecord struct {
Owner string `xorm:"varchar(100) notnull pk" json:"owner"` Owner string `xorm:"varchar(100) notnull pk" json:"owner"`
Name string `xorm:"varchar(100) notnull pk" json:"name"` Name string `xorm:"varchar(100) notnull pk" json:"name"`
@ -81,7 +89,7 @@ func IsAllowSend(user *User, remoteAddr, recordType string) error {
return nil return nil
} }
func SendVerificationCodeToEmail(organization *Organization, user *User, provider *Provider, remoteAddr string, dest string) error { func SendVerificationCodeToEmail(organization *Organization, user *User, provider *Provider, remoteAddr string, dest string, method string, host string, applicationName string) error {
sender := organization.DisplayName sender := organization.DisplayName
title := provider.Title title := provider.Title
@ -93,6 +101,23 @@ func SendVerificationCodeToEmail(organization *Organization, user *User, provide
// "You have requested a verification code at Casdoor. Here is your code: %s, please enter in 5 minutes." // "You have requested a verification code at Casdoor. Here is your code: %s, please enter in 5 minutes."
content := strings.Replace(provider.Content, "%s", code, 1) content := strings.Replace(provider.Content, "%s", code, 1)
if method == "forget" {
originFrontend, _ := getOriginFromHost(host)
query := url.Values{}
query.Add("code", code)
query.Add("username", user.Name)
query.Add("dest", util.GetMaskedEmail(dest))
forgetURL := originFrontend + "/forget/" + applicationName + "?" + query.Encode()
content = strings.Replace(content, "%link", forgetURL, -1)
content = strings.Replace(content, "<reset-link>", "", -1)
content = strings.Replace(content, "</reset-link>", "", -1)
} else {
matchContent := ResetLinkReg.Find([]byte(content))
content = strings.Replace(content, string(matchContent), "", -1)
}
userString := "Hi" userString := "Hi"
if user != nil { if user != nil {
userString = user.GetFriendlyName() userString = user.GetFriendlyName()

View File

@ -1227,7 +1227,7 @@ class ProviderEditPage extends React.Component {
</Col> </Col>
<Col span={22} > <Col span={22} >
<Row style={{marginTop: "20px"}} > <Row style={{marginTop: "20px"}} >
<Button style={{marginLeft: "10px", marginBottom: "5px"}} onClick={() => this.updateProviderField("content", "You have requested a verification code at Casdoor. Here is your code: %s, please enter in 5 minutes.")} > <Button style={{marginLeft: "10px", marginBottom: "5px"}} onClick={() => this.updateProviderField("content", "You have requested a verification code at Casdoor. Here is your code: %s, please enter in 5 minutes. <reset-link>Or click %link to reset</reset-link>")} >
{i18next.t("provider:Reset to Default Text")} {i18next.t("provider:Reset to Default Text")}
</Button> </Button>
<Button style={{marginLeft: "10px", marginBottom: "5px"}} type="primary" onClick={() => this.updateProviderField("content", Setting.getDefaultHtmlEmailContent())} > <Button style={{marginLeft: "10px", marginBottom: "5px"}} type="primary" onClick={() => this.updateProviderField("content", Setting.getDefaultHtmlEmailContent())} >

View File

@ -1580,6 +1580,11 @@ export function getDefaultHtmlEmailContent() {
<div class="code"> <div class="code">
%s %s
</div> </div>
<reset-link>
<div class="link">
Or click this <a href="%link">link</a> to reset
</div>
</reset-link>
<p>Thanks</p> <p>Thanks</p>
<p>Casbin Team</p> <p>Casbin Team</p>
<hr> <hr>

View File

@ -31,18 +31,21 @@ const {Option} = Select;
class ForgetPage extends React.Component { class ForgetPage extends React.Component {
constructor(props) { constructor(props) {
super(props); super(props);
const queryParams = new URLSearchParams(location.search);
this.state = { this.state = {
classes: props, classes: props,
applicationName: props.applicationName ?? props.match.params?.applicationName, applicationName: props.applicationName ?? props.match.params?.applicationName,
msg: null, msg: null,
name: props.account ? props.account.name : "", name: props.account ? props.account.name : queryParams.get("username"),
username: props.account ? props.account.name : "", username: props.account ? props.account.name : "",
phone: "", phone: "",
email: "", email: "",
dest: "", dest: "",
isVerifyTypeFixed: false, isVerifyTypeFixed: false,
verifyType: "", // "email", "phone" verifyType: "", // "email", "phone"
current: 0, current: queryParams.get("code") ? 2 : 0,
code: queryParams.get("code"),
queryParams: queryParams,
}; };
this.form = React.createRef(); this.form = React.createRef();
} }
@ -148,9 +151,26 @@ class ForgetPage extends React.Component {
} }
} }
onFinish(values) { async onFinish(values) {
values.username = this.state.name; values.username = this.state.name;
values.userOwner = this.getApplicationObj()?.organizationObj.name; values.userOwner = this.getApplicationObj()?.organizationObj.name;
if (this.state.queryParams.get("code")) {
const res = await UserBackend.verifyCode({
application: this.getApplicationObj().name,
organization: values.userOwner,
username: this.state.queryParams.get("dest"),
name: this.state.name,
code: this.state.code,
type: "login",
});
if (res.status !== "ok") {
Setting.showMessage("error", res.msg);
return;
}
}
UserBackend.setPassword(values.userOwner, values.username, "", values?.newPassword, this.state.code).then(res => { UserBackend.setPassword(values.userOwner, values.username, "", values?.newPassword, this.state.code).then(res => {
if (res.status === "ok") { if (res.status === "ok") {
const linkInStorage = sessionStorage.getItem("signinUrl"); const linkInStorage = sessionStorage.getItem("signinUrl");