mirror of
https://github.com/casdoor/casdoor.git
synced 2025-08-13 09:37:46 +08:00
Compare commits
12 Commits
Author | SHA1 | Date | |
---|---|---|---|
![]() |
ba2e997d54 | ||
![]() |
0818de85d1 | ||
![]() |
457c6098a4 | ||
![]() |
60f979fbb5 | ||
![]() |
ff53e44fa6 | ||
![]() |
1832de47db | ||
![]() |
535eb0c465 | ||
![]() |
c190634cf3 | ||
![]() |
f7559aa040 | ||
![]() |
1e0b709c73 | ||
![]() |
c0800b7fb3 | ||
![]() |
6fcdad2100 |
@@ -854,6 +854,7 @@ func (c *ApiController) Login() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if authForm.Passcode != "" {
|
if authForm.Passcode != "" {
|
||||||
|
user.CountryCode = user.GetCountryCode(user.CountryCode)
|
||||||
mfaUtil := object.GetMfaUtil(authForm.MfaType, user.GetPreferredMfaProps(false))
|
mfaUtil := object.GetMfaUtil(authForm.MfaType, user.GetPreferredMfaProps(false))
|
||||||
if mfaUtil == nil {
|
if mfaUtil == nil {
|
||||||
c.ResponseError("Invalid multi-factor authentication type")
|
c.ResponseError("Invalid multi-factor authentication type")
|
||||||
|
@@ -294,6 +294,7 @@ func (c *ApiController) SendVerificationCode() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
vform.CountryCode = mfaProps.CountryCode
|
vform.CountryCode = mfaProps.CountryCode
|
||||||
|
vform.CountryCode = user.GetCountryCode(vform.CountryCode)
|
||||||
}
|
}
|
||||||
|
|
||||||
provider, err = application.GetSmsProvider(vform.Method, vform.CountryCode)
|
provider, err = application.GetSmsProvider(vform.Method, vform.CountryCode)
|
||||||
|
@@ -723,8 +723,15 @@ func (application *Application) GetId() string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (application *Application) IsRedirectUriValid(redirectUri string) bool {
|
func (application *Application) IsRedirectUriValid(redirectUri string) bool {
|
||||||
redirectUris := append([]string{"http://localhost:", "https://localhost:", "http://127.0.0.1:", "http://casdoor-app", ".chromiumapp.org"}, application.RedirectUris...)
|
isValid, err := util.IsValidOrigin(redirectUri)
|
||||||
for _, targetUri := range redirectUris {
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
if isValid {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, targetUri := range application.RedirectUris {
|
||||||
targetUriRegex := regexp.MustCompile(targetUri)
|
targetUriRegex := regexp.MustCompile(targetUri)
|
||||||
if targetUriRegex.MatchString(redirectUri) || strings.Contains(redirectUri, targetUri) {
|
if targetUriRegex.MatchString(redirectUri) || strings.Contains(redirectUri, targetUri) {
|
||||||
return true
|
return true
|
||||||
|
@@ -520,11 +520,46 @@ func CheckUsername(username string, lang string) string {
|
|||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func CheckUsernameWithEmail(username string, lang string) string {
|
||||||
|
if username == "" {
|
||||||
|
return i18n.Translate(lang, "check:Empty username.")
|
||||||
|
} else if len(username) > 39 {
|
||||||
|
return i18n.Translate(lang, "check:Username is too long (maximum is 39 characters).")
|
||||||
|
}
|
||||||
|
|
||||||
|
// https://stackoverflow.com/questions/58726546/github-username-convention-using-regex
|
||||||
|
|
||||||
|
if !util.ReUserNameWithEmail.MatchString(username) {
|
||||||
|
return i18n.Translate(lang, "check:Username supports email format. Also The username may only contain alphanumeric characters, underlines or hyphens, cannot have consecutive hyphens or underlines, and cannot begin or end with a hyphen or underline. Also pay attention to the email format.")
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
func CheckUpdateUser(oldUser, user *User, lang string) string {
|
func CheckUpdateUser(oldUser, user *User, lang string) string {
|
||||||
if oldUser.Name != user.Name {
|
if oldUser.Name != user.Name {
|
||||||
if msg := CheckUsername(user.Name, lang); msg != "" {
|
organizationName := oldUser.Owner
|
||||||
return msg
|
if organizationName == "" {
|
||||||
|
organizationName = user.Owner
|
||||||
}
|
}
|
||||||
|
|
||||||
|
organization, err := getOrganization("admin", organizationName)
|
||||||
|
if err != nil {
|
||||||
|
return err.Error()
|
||||||
|
}
|
||||||
|
if organization == nil {
|
||||||
|
return fmt.Sprintf(i18n.Translate(lang, "auth:The organization: %s does not exist"), organizationName)
|
||||||
|
}
|
||||||
|
|
||||||
|
if organization.UseEmailAsUsername {
|
||||||
|
if msg := CheckUsernameWithEmail(user.Name, lang); msg != "" {
|
||||||
|
return msg
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if msg := CheckUsername(user.Name, lang); msg != "" {
|
||||||
|
return msg
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if HasUserByField(user.Owner, "name", user.Name) {
|
if HasUserByField(user.Owner, "name", user.Name) {
|
||||||
return i18n.Translate(lang, "check:Username already exists")
|
return i18n.Translate(lang, "check:Username already exists")
|
||||||
}
|
}
|
||||||
|
@@ -43,6 +43,8 @@ func CheckEntryIp(clientIp string, user *User, application *Application, organiz
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
application.IpRestriction = err.Error() + application.Name
|
application.IpRestriction = err.Error() + application.Name
|
||||||
return fmt.Errorf(err.Error() + application.Name)
|
return fmt.Errorf(err.Error() + application.Name)
|
||||||
|
} else {
|
||||||
|
application.IpRestriction = ""
|
||||||
}
|
}
|
||||||
|
|
||||||
if organization == nil && application.OrganizationObj != nil {
|
if organization == nil && application.OrganizationObj != nil {
|
||||||
@@ -55,6 +57,8 @@ func CheckEntryIp(clientIp string, user *User, application *Application, organiz
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
organization.IpRestriction = err.Error() + organization.Name
|
organization.IpRestriction = err.Error() + organization.Name
|
||||||
return fmt.Errorf(err.Error() + organization.Name)
|
return fmt.Errorf(err.Error() + organization.Name)
|
||||||
|
} else {
|
||||||
|
organization.IpRestriction = ""
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -816,6 +816,10 @@ func AddUser(user *User) (bool, error) {
|
|||||||
user.UpdateUserPassword(organization)
|
user.UpdateUserPassword(organization)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if user.CreatedTime == "" {
|
||||||
|
user.CreatedTime = util.GetCurrentTime()
|
||||||
|
}
|
||||||
|
|
||||||
err = user.UpdateUserHash()
|
err = user.UpdateUserHash()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, err
|
return false, err
|
||||||
|
@@ -16,11 +16,11 @@ package routers
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"net/http"
|
"net/http"
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/beego/beego/context"
|
"github.com/beego/beego/context"
|
||||||
"github.com/casdoor/casdoor/conf"
|
"github.com/casdoor/casdoor/conf"
|
||||||
"github.com/casdoor/casdoor/object"
|
"github.com/casdoor/casdoor/object"
|
||||||
|
"github.com/casdoor/casdoor/util"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@@ -52,7 +52,13 @@ func CorsFilter(ctx *context.Context) {
|
|||||||
origin = ""
|
origin = ""
|
||||||
}
|
}
|
||||||
|
|
||||||
if strings.HasPrefix(origin, "http://localhost") || strings.HasPrefix(origin, "https://localhost") || strings.HasPrefix(origin, "http://127.0.0.1") || strings.HasPrefix(origin, "http://casdoor-app") || strings.Contains(origin, ".chromiumapp.org") {
|
isValid, err := util.IsValidOrigin(origin)
|
||||||
|
if err != nil {
|
||||||
|
ctx.ResponseWriter.WriteHeader(http.StatusForbidden)
|
||||||
|
responseError(ctx, err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if isValid {
|
||||||
setCorsHeaders(ctx, origin)
|
setCorsHeaders(ctx, origin)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@@ -17,6 +17,7 @@ package util
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/mail"
|
"net/mail"
|
||||||
|
"net/url"
|
||||||
"regexp"
|
"regexp"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
@@ -24,10 +25,11 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
rePhone *regexp.Regexp
|
rePhone *regexp.Regexp
|
||||||
ReWhiteSpace *regexp.Regexp
|
ReWhiteSpace *regexp.Regexp
|
||||||
ReFieldWhiteList *regexp.Regexp
|
ReFieldWhiteList *regexp.Regexp
|
||||||
ReUserName *regexp.Regexp
|
ReUserName *regexp.Regexp
|
||||||
|
ReUserNameWithEmail *regexp.Regexp
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
@@ -35,6 +37,7 @@ func init() {
|
|||||||
ReWhiteSpace, _ = regexp.Compile(`\s`)
|
ReWhiteSpace, _ = regexp.Compile(`\s`)
|
||||||
ReFieldWhiteList, _ = regexp.Compile(`^[A-Za-z0-9]+$`)
|
ReFieldWhiteList, _ = regexp.Compile(`^[A-Za-z0-9]+$`)
|
||||||
ReUserName, _ = regexp.Compile("^[a-zA-Z0-9]+([-._][a-zA-Z0-9]+)*$")
|
ReUserName, _ = regexp.Compile("^[a-zA-Z0-9]+([-._][a-zA-Z0-9]+)*$")
|
||||||
|
ReUserNameWithEmail, _ = regexp.Compile(`^([a-zA-Z0-9]+([-._][a-zA-Z0-9]+)*)|([a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,})$`) // Add support for email formats
|
||||||
}
|
}
|
||||||
|
|
||||||
func IsEmailValid(email string) bool {
|
func IsEmailValid(email string) bool {
|
||||||
@@ -100,3 +103,21 @@ func GetCountryCode(prefix string, phone string) (string, error) {
|
|||||||
func FilterField(field string) bool {
|
func FilterField(field string) bool {
|
||||||
return ReFieldWhiteList.MatchString(field)
|
return ReFieldWhiteList.MatchString(field)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func IsValidOrigin(origin string) (bool, error) {
|
||||||
|
urlObj, err := url.Parse(origin)
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
if urlObj == nil {
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
originHostOnly := ""
|
||||||
|
if urlObj.Host != "" {
|
||||||
|
originHostOnly = fmt.Sprintf("%s://%s", urlObj.Scheme, urlObj.Hostname())
|
||||||
|
}
|
||||||
|
|
||||||
|
res := originHostOnly == "http://localhost" || originHostOnly == "https://localhost" || originHostOnly == "http://127.0.0.1" || originHostOnly == "http://casdoor-app" || strings.HasSuffix(originHostOnly, ".chromiumapp.org")
|
||||||
|
return res, nil
|
||||||
|
}
|
||||||
|
@@ -603,7 +603,7 @@ class ApplicationEditPage extends React.Component {
|
|||||||
{Setting.getLabel(i18next.t("general:IP whitelist"), i18next.t("general:IP whitelist - Tooltip"))} :
|
{Setting.getLabel(i18next.t("general:IP whitelist"), i18next.t("general:IP whitelist - Tooltip"))} :
|
||||||
</Col>
|
</Col>
|
||||||
<Col span={22} >
|
<Col span={22} >
|
||||||
<Input placeholder = {this.state.application.organizationObj?.ipWhitelist} value={this.state.application.ipWhiteList} onChange={e => {
|
<Input placeholder = {this.state.application.organizationObj?.ipWhitelist} value={this.state.application.ipWhitelist} onChange={e => {
|
||||||
this.updateApplicationField("ipWhitelist", e.target.value);
|
this.updateApplicationField("ipWhitelist", e.target.value);
|
||||||
}} />
|
}} />
|
||||||
</Col>
|
</Col>
|
||||||
|
@@ -908,7 +908,7 @@ class ProviderEditPage extends React.Component {
|
|||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
)}
|
)}
|
||||||
{["Custom HTTP SMS", "Qiniu Cloud Kodo", "Synology", "Casdoor"].includes(this.state.provider.type) ? null : (
|
{["Custom HTTP SMS", "Synology", "Casdoor"].includes(this.state.provider.type) ? null : (
|
||||||
<Row style={{marginTop: "20px"}} >
|
<Row style={{marginTop: "20px"}} >
|
||||||
<Col style={{marginTop: "5px"}} span={2}>
|
<Col style={{marginTop: "5px"}} span={2}>
|
||||||
{Setting.getLabel(i18next.t("provider:Domain"), i18next.t("provider:Domain - Tooltip"))} :
|
{Setting.getLabel(i18next.t("provider:Domain"), i18next.t("provider:Domain - Tooltip"))} :
|
||||||
|
@@ -187,7 +187,7 @@ class RoleEditPage extends React.Component {
|
|||||||
{Setting.getLabel(i18next.t("role:Sub users"), i18next.t("role:Sub users - Tooltip"))} :
|
{Setting.getLabel(i18next.t("role:Sub users"), i18next.t("role:Sub users - Tooltip"))} :
|
||||||
</Col>
|
</Col>
|
||||||
<Col span={22} >
|
<Col span={22} >
|
||||||
<Select virtual={false} mode="multiple" style={{width: "100%"}} value={this.state.role.users}
|
<Select virtual={true} mode="multiple" style={{width: "100%"}} value={this.state.role.users}
|
||||||
onChange={(value => {this.updateRoleField("users", value);})}
|
onChange={(value => {this.updateRoleField("users", value);})}
|
||||||
options={this.state.users.map((user) => Setting.getOption(`${user.owner}/${user.name}`, `${user.owner}/${user.name}`))}
|
options={this.state.users.map((user) => Setting.getOption(`${user.owner}/${user.name}`, `${user.owner}/${user.name}`))}
|
||||||
/>
|
/>
|
||||||
|
@@ -227,6 +227,22 @@ class LoginPage extends React.Component {
|
|||||||
return "password";
|
return "password";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getCurrentLoginMethod() {
|
||||||
|
if (this.state.loginMethod === "password") {
|
||||||
|
return "Password";
|
||||||
|
} else if (this.state.loginMethod?.includes("verificationCode")) {
|
||||||
|
return "Verification code";
|
||||||
|
} else if (this.state.loginMethod === "webAuthn") {
|
||||||
|
return "WebAuthn";
|
||||||
|
} else if (this.state.loginMethod === "ldap") {
|
||||||
|
return "LDAP";
|
||||||
|
} else if (this.state.loginMethod === "faceId") {
|
||||||
|
return "Face ID";
|
||||||
|
} else {
|
||||||
|
return "Password";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
getPlaceholder() {
|
getPlaceholder() {
|
||||||
switch (this.state.loginMethod) {
|
switch (this.state.loginMethod) {
|
||||||
case "verificationCode": return i18next.t("login:Email or phone");
|
case "verificationCode": return i18next.t("login:Email or phone");
|
||||||
@@ -262,17 +278,7 @@ class LoginPage extends React.Component {
|
|||||||
values["organization"] = this.getApplicationObj().organization;
|
values["organization"] = this.getApplicationObj().organization;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.state.loginMethod === "password") {
|
values["signinMethod"] = this.getCurrentLoginMethod();
|
||||||
values["signinMethod"] = "Password";
|
|
||||||
} else if (this.state.loginMethod?.includes("verificationCode")) {
|
|
||||||
values["signinMethod"] = "Verification code";
|
|
||||||
} else if (this.state.loginMethod === "webAuthn") {
|
|
||||||
values["signinMethod"] = "WebAuthn";
|
|
||||||
} else if (this.state.loginMethod === "ldap") {
|
|
||||||
values["signinMethod"] = "LDAP";
|
|
||||||
} else if (this.state.loginMethod === "faceId") {
|
|
||||||
values["signinMethod"] = "Face ID";
|
|
||||||
}
|
|
||||||
const oAuthParams = Util.getOAuthGetParameters();
|
const oAuthParams = Util.getOAuthGetParameters();
|
||||||
|
|
||||||
values["type"] = oAuthParams?.responseType ?? this.state.type;
|
values["type"] = oAuthParams?.responseType ?? this.state.type;
|
||||||
@@ -409,6 +415,7 @@ class LoginPage extends React.Component {
|
|||||||
if (this.state.type === "cas") {
|
if (this.state.type === "cas") {
|
||||||
// CAS
|
// CAS
|
||||||
const casParams = Util.getCasParameters();
|
const casParams = Util.getCasParameters();
|
||||||
|
values["signinMethod"] = this.getCurrentLoginMethod();
|
||||||
values["type"] = this.state.type;
|
values["type"] = this.state.type;
|
||||||
AuthBackend.loginCas(values, casParams).then((res) => {
|
AuthBackend.loginCas(values, casParams).then((res) => {
|
||||||
const loginHandler = (res) => {
|
const loginHandler = (res) => {
|
||||||
@@ -437,8 +444,8 @@ class LoginPage extends React.Component {
|
|||||||
formValues={values}
|
formValues={values}
|
||||||
authParams={casParams}
|
authParams={casParams}
|
||||||
application={this.getApplicationObj()}
|
application={this.getApplicationObj()}
|
||||||
onFail={() => {
|
onFail={(errorMessage) => {
|
||||||
Setting.showMessage("error", i18next.t("mfa:Verification failed"));
|
Setting.showMessage("error", errorMessage);
|
||||||
}}
|
}}
|
||||||
onSuccess={(res) => loginHandler(res)}
|
onSuccess={(res) => loginHandler(res)}
|
||||||
/>);
|
/>);
|
||||||
@@ -506,8 +513,8 @@ class LoginPage extends React.Component {
|
|||||||
formValues={values}
|
formValues={values}
|
||||||
authParams={oAuthParams}
|
authParams={oAuthParams}
|
||||||
application={this.getApplicationObj()}
|
application={this.getApplicationObj()}
|
||||||
onFail={() => {
|
onFail={(errorMessage) => {
|
||||||
Setting.showMessage("error", i18next.t("mfa:Verification failed"));
|
Setting.showMessage("error", errorMessage);
|
||||||
}}
|
}}
|
||||||
onSuccess={(res) => loginHandler(res)}
|
onSuccess={(res) => loginHandler(res)}
|
||||||
/>);
|
/>);
|
||||||
|
@@ -37,7 +37,7 @@ class MfaSetupPage extends React.Component {
|
|||||||
this.state = {
|
this.state = {
|
||||||
account: props.account,
|
account: props.account,
|
||||||
application: null,
|
application: null,
|
||||||
applicationName: props.account.signupApplication ?? "",
|
applicationName: props.account.signupApplication ?? localStorage.getItem("applicationName") ?? "",
|
||||||
current: location.state?.from !== undefined ? 1 : 0,
|
current: location.state?.from !== undefined ? 1 : 0,
|
||||||
mfaProps: null,
|
mfaProps: null,
|
||||||
mfaType: params.get("mfaType") ?? SmsMfaType,
|
mfaType: params.get("mfaType") ?? SmsMfaType,
|
||||||
|
@@ -14,6 +14,7 @@
|
|||||||
|
|
||||||
import CryptoJS from "crypto-js";
|
import CryptoJS from "crypto-js";
|
||||||
import i18next from "i18next";
|
import i18next from "i18next";
|
||||||
|
import {Buffer} from "buffer";
|
||||||
|
|
||||||
export function getRandomKeyForObfuscator(obfuscatorType) {
|
export function getRandomKeyForObfuscator(obfuscatorType) {
|
||||||
if (obfuscatorType === "DES") {
|
if (obfuscatorType === "DES") {
|
||||||
|
Reference in New Issue
Block a user