mirror of
https://github.com/casdoor/casdoor.git
synced 2025-07-15 07:23:50 +08:00
Compare commits
4 Commits
Author | SHA1 | Date | |
---|---|---|---|
835273576b | |||
7fdc264ff6 | |||
a120734bb1 | |||
edd0b30e08 |
@ -140,6 +140,9 @@ func (c *ApiController) SendEmail() {
|
||||
}
|
||||
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 {
|
||||
err = object.SendEmail(provider, emailForm.Title, content, receiver, emailForm.Sender)
|
||||
if err != nil {
|
||||
|
@ -258,7 +258,7 @@ func (c *ApiController) SendVerificationCode() {
|
||||
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:
|
||||
if vform.Method == LoginVerification || vform.Method == ForgetVerification {
|
||||
if user != nil && util.GetMaskedPhone(user.Phone) == vform.Dest {
|
||||
|
15
idp/lark.go
15
idp/lark.go
@ -29,14 +29,20 @@ import (
|
||||
type LarkIdProvider struct {
|
||||
Client *http.Client
|
||||
Config *oauth2.Config
|
||||
LarkDomain string
|
||||
}
|
||||
|
||||
func NewLarkIdProvider(clientId string, clientSecret string, redirectUrl string) *LarkIdProvider {
|
||||
func NewLarkIdProvider(clientId string, clientSecret string, redirectUrl string, useGlobalEndpoint bool) *LarkIdProvider {
|
||||
idp := &LarkIdProvider{}
|
||||
|
||||
if useGlobalEndpoint {
|
||||
idp.LarkDomain = "https://open.larksuite.com"
|
||||
} else {
|
||||
idp.LarkDomain = "https://open.feishu.cn"
|
||||
}
|
||||
|
||||
config := idp.getConfig(clientId, clientSecret, redirectUrl)
|
||||
idp.Config = config
|
||||
|
||||
return idp
|
||||
}
|
||||
|
||||
@ -47,7 +53,7 @@ func (idp *LarkIdProvider) SetHttpClient(client *http.Client) {
|
||||
// getConfig return a point of Config, which describes a typical 3-legged OAuth2 flow
|
||||
func (idp *LarkIdProvider) getConfig(clientId string, clientSecret string, redirectUrl string) *oauth2.Config {
|
||||
endpoint := oauth2.Endpoint{
|
||||
TokenURL: "https://open.feishu.cn/open-apis/auth/v3/tenant_access_token/internal",
|
||||
TokenURL: idp.LarkDomain + "/open-apis/auth/v3/tenant_access_token/internal",
|
||||
}
|
||||
|
||||
config := &oauth2.Config{
|
||||
@ -162,6 +168,7 @@ type LarkUserInfo struct {
|
||||
} `json:"data"`
|
||||
}
|
||||
|
||||
// GetUserInfo use LarkAccessToken gotten before return LinkedInUserInf
|
||||
// GetUserInfo use LarkAccessToken gotten before return LinkedInUserInfo
|
||||
// get more detail via: https://docs.microsoft.com/en-us/linkedin/consumer/integrations/self-serve/sign-in-with-linkedin?context=linkedin/consumer/context
|
||||
func (idp *LarkIdProvider) GetUserInfo(token *oauth2.Token) (*UserInfo, error) {
|
||||
@ -175,7 +182,7 @@ func (idp *LarkIdProvider) GetUserInfo(token *oauth2.Token) (*UserInfo, error) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
req, err := http.NewRequest("POST", "https://open.feishu.cn/open-apis/authen/v1/access_token", strings.NewReader(string(data)))
|
||||
req, err := http.NewRequest("POST", idp.LarkDomain+"/open-apis/authen/v1/access_token", strings.NewReader(string(data)))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -87,7 +87,7 @@ func GetIdProvider(idpInfo *ProviderInfo, redirectUrl string) (IdProvider, error
|
||||
return nil, fmt.Errorf("WeCom provider subType: %s is not supported", idpInfo.SubType)
|
||||
}
|
||||
case "Lark":
|
||||
return NewLarkIdProvider(idpInfo.ClientId, idpInfo.ClientSecret, redirectUrl), nil
|
||||
return NewLarkIdProvider(idpInfo.ClientId, idpInfo.ClientSecret, redirectUrl, idpInfo.DisableSsl), nil
|
||||
case "GitLab":
|
||||
return NewGitlabIdProvider(idpInfo.ClientId, idpInfo.ClientSecret, redirectUrl), nil
|
||||
case "ADFS":
|
||||
|
@ -252,7 +252,7 @@ func CheckPassword(user *User, password string, lang string, options ...bool) er
|
||||
|
||||
credManager := cred.GetCredManager(passwordType)
|
||||
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 != "" {
|
||||
@ -265,6 +265,16 @@ func CheckPassword(user *User, password string, lang string, options ...bool) er
|
||||
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)
|
||||
}
|
||||
|
||||
|
@ -19,6 +19,8 @@ import (
|
||||
"fmt"
|
||||
"math"
|
||||
"math/rand"
|
||||
"net/url"
|
||||
"regexp"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
@ -33,6 +35,8 @@ type VerifyResult struct {
|
||||
Msg string
|
||||
}
|
||||
|
||||
var ResetLinkReg *regexp.Regexp
|
||||
|
||||
const (
|
||||
VerificationSuccess = iota
|
||||
wrongCodeError
|
||||
@ -45,6 +49,10 @@ const (
|
||||
VerifyTypeEmail = "email"
|
||||
)
|
||||
|
||||
func init() {
|
||||
ResetLinkReg = regexp.MustCompile("(?s)<reset-link>(.*?)</reset-link>")
|
||||
}
|
||||
|
||||
type VerificationRecord struct {
|
||||
Owner string `xorm:"varchar(100) notnull pk" json:"owner"`
|
||||
Name string `xorm:"varchar(100) notnull pk" json:"name"`
|
||||
@ -81,7 +89,7 @@ func IsAllowSend(user *User, remoteAddr, recordType string) error {
|
||||
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
|
||||
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."
|
||||
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"
|
||||
if user != nil {
|
||||
userString = user.GetFriendlyName()
|
||||
|
@ -404,6 +404,7 @@ class App extends Component {
|
||||
account={this.state.account}
|
||||
theme={this.state.themeData}
|
||||
themeAlgorithm={this.state.themeAlgorithm}
|
||||
requiredEnableMfa={this.state.requiredEnableMfa}
|
||||
updateApplication={(application) => {
|
||||
this.setState({
|
||||
application: application,
|
||||
|
@ -931,10 +931,12 @@ class ProviderEditPage extends React.Component {
|
||||
)
|
||||
}
|
||||
{
|
||||
this.state.provider.type !== "Google" ? null : (
|
||||
this.state.provider.type !== "Google" && this.state.provider.type !== "Lark" ? null : (
|
||||
<Row style={{marginTop: "20px"}} >
|
||||
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||
{Setting.getLabel(i18next.t("provider:Get phone number"), i18next.t("provider:Get phone number - Tooltip"))} :
|
||||
{this.state.provider.type === "Google" ?
|
||||
Setting.getLabel(i18next.t("provider:Get phone number"), i18next.t("provider:Get phone number - Tooltip"))
|
||||
: Setting.getLabel(i18next.t("provider:Use global endpoint"), i18next.t("provider:Use global endpoint - Tooltip"))} :
|
||||
</Col>
|
||||
<Col span={1} >
|
||||
<Switch disabled={!this.state.provider.clientId} checked={this.state.provider.disableSsl} onChange={checked => {
|
||||
@ -1227,7 +1229,7 @@ class ProviderEditPage extends React.Component {
|
||||
</Col>
|
||||
<Col span={22} >
|
||||
<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")}
|
||||
</Button>
|
||||
<Button style={{marginLeft: "10px", marginBottom: "5px"}} type="primary" onClick={() => this.updateProviderField("content", Setting.getDefaultHtmlEmailContent())} >
|
||||
|
@ -1580,6 +1580,11 @@ export function getDefaultHtmlEmailContent() {
|
||||
<div class="code">
|
||||
%s
|
||||
</div>
|
||||
<reset-link>
|
||||
<div class="link">
|
||||
Or click this <a href="%link">link</a> to reset
|
||||
</div>
|
||||
</reset-link>
|
||||
<p>Thanks</p>
|
||||
<p>Casbin Team</p>
|
||||
<hr>
|
||||
|
@ -31,18 +31,21 @@ const {Option} = Select;
|
||||
class ForgetPage extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
const queryParams = new URLSearchParams(location.search);
|
||||
this.state = {
|
||||
classes: props,
|
||||
applicationName: props.applicationName ?? props.match.params?.applicationName,
|
||||
msg: null,
|
||||
name: props.account ? props.account.name : "",
|
||||
name: props.account ? props.account.name : queryParams.get("username"),
|
||||
username: props.account ? props.account.name : "",
|
||||
phone: "",
|
||||
email: "",
|
||||
dest: "",
|
||||
isVerifyTypeFixed: false,
|
||||
verifyType: "", // "email", "phone"
|
||||
current: 0,
|
||||
current: queryParams.get("code") ? 2 : 0,
|
||||
code: queryParams.get("code"),
|
||||
queryParams: queryParams,
|
||||
};
|
||||
this.form = React.createRef();
|
||||
}
|
||||
@ -148,9 +151,26 @@ class ForgetPage extends React.Component {
|
||||
}
|
||||
}
|
||||
|
||||
onFinish(values) {
|
||||
async onFinish(values) {
|
||||
values.username = this.state.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 => {
|
||||
if (res.status === "ok") {
|
||||
const linkInStorage = sessionStorage.getItem("signinUrl");
|
||||
|
@ -1040,6 +1040,10 @@ class LoginPage extends React.Component {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (this.props.requiredEnableMfa) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (this.state.userCode && this.state.userCodeStatus === "success") {
|
||||
return null;
|
||||
}
|
||||
|
@ -68,6 +68,7 @@ const authInfo = {
|
||||
Lark: {
|
||||
// scope: "email",
|
||||
endpoint: "https://open.feishu.cn/open-apis/authen/v1/index",
|
||||
endpoint2: "https://accounts.larksuite.com/open-apis/authen/v1/authorize",
|
||||
},
|
||||
GitLab: {
|
||||
scope: "read_user+profile",
|
||||
@ -406,6 +407,8 @@ export function getAuthUrl(application, provider, method, code) {
|
||||
if (provider.domain) {
|
||||
endpoint = `${provider.domain}/apps/oauth2/authorize`;
|
||||
}
|
||||
} else if (provider.type === "Lark" && provider.disableSsl) {
|
||||
endpoint = authInfo[provider.type].endpoint2;
|
||||
}
|
||||
|
||||
if (provider.type === "Google" || provider.type === "GitHub" || provider.type === "Facebook"
|
||||
@ -460,6 +463,9 @@ export function getAuthUrl(application, provider, method, code) {
|
||||
return `https://error:not-supported-provider-sub-type:${provider.subType}`;
|
||||
}
|
||||
} else if (provider.type === "Lark") {
|
||||
if (provider.disableSsl) {
|
||||
redirectUri = encodeURIComponent(redirectUri);
|
||||
}
|
||||
return `${endpoint}?app_id=${provider.clientId}&redirect_uri=${redirectUri}&state=${state}`;
|
||||
} else if (provider.type === "ADFS") {
|
||||
return `${provider.domain}/adfs/oauth2/authorize?client_id=${provider.clientId}&redirect_uri=${redirectUri}&state=${state}&response_type=code&nonce=casdoor&scope=openid`;
|
||||
|
Reference in New Issue
Block a user