Compare commits

..

7 Commits

Author SHA1 Message Date
Luckery
0818de85d1 feat: fix username checks when organization.UseEmailAsUsername is enabled (#3329)
* feat: Username support email format

* feat: Only fulfill the first requirement

* fix: Improve code robustness
2024-11-05 20:38:47 +08:00
Yang Luo
457c6098a4 feat: fix MFA empty CountryCode bug and show MFA error better in frontend 2024-11-04 16:17:24 +08:00
Yang Luo
60f979fbb5 feat: fix MfaSetupPage empty bug when user's signup application is empty 2024-11-04 00:04:47 +08:00
Luckery
ff53e44fa6 feat: use virtual select UI in role edit page (#3322) 2024-11-03 20:05:34 +08:00
Yang Luo
1832de47db feat: fix bug in CheckEntryIp() 2024-11-03 20:00:52 +08:00
Yang Luo
535eb0c465 fix: fix IP Whitelist field bug in application edit page 2024-11-03 19:55:59 +08:00
ithilelda
c190634cf3 feat: show Domain field for Qiniu storage provider (#3318)
allow Qiniu Provider to edit the Domain property in the edit page.
2024-10-27 14:10:58 +08:00
10 changed files with 52 additions and 14 deletions

View File

@@ -854,6 +854,7 @@ func (c *ApiController) Login() {
}
if authForm.Passcode != "" {
user.CountryCode = user.GetCountryCode(user.CountryCode)
mfaUtil := object.GetMfaUtil(authForm.MfaType, user.GetPreferredMfaProps(false))
if mfaUtil == nil {
c.ResponseError("Invalid multi-factor authentication type")

View File

@@ -294,6 +294,7 @@ func (c *ApiController) SendVerificationCode() {
}
vform.CountryCode = mfaProps.CountryCode
vform.CountryCode = user.GetCountryCode(vform.CountryCode)
}
provider, err = application.GetSmsProvider(vform.Method, vform.CountryCode)

View File

@@ -520,11 +520,41 @@ func CheckUsername(username string, lang string) string {
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 {
if oldUser.Name != user.Name {
if msg := CheckUsername(user.Name, lang); msg != "" {
return msg
// Check if the organization uses email as the username
organization, err := GetOrganizationByUser(oldUser)
if err != nil {
return err.Error()
}
if organization == nil {
return fmt.Sprintf(i18n.Translate(lang, "The organization: %s does not exist"), oldUser.Owner)
}
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) {
return i18n.Translate(lang, "check:Username already exists")
}

View File

@@ -43,6 +43,8 @@ func CheckEntryIp(clientIp string, user *User, application *Application, organiz
if err != nil {
application.IpRestriction = err.Error() + application.Name
return fmt.Errorf(err.Error() + application.Name)
} else {
application.IpRestriction = ""
}
if organization == nil && application.OrganizationObj != nil {
@@ -55,6 +57,8 @@ func CheckEntryIp(clientIp string, user *User, application *Application, organiz
if err != nil {
organization.IpRestriction = err.Error() + organization.Name
return fmt.Errorf(err.Error() + organization.Name)
} else {
organization.IpRestriction = ""
}
}

View File

@@ -25,10 +25,11 @@ import (
)
var (
rePhone *regexp.Regexp
ReWhiteSpace *regexp.Regexp
ReFieldWhiteList *regexp.Regexp
ReUserName *regexp.Regexp
rePhone *regexp.Regexp
ReWhiteSpace *regexp.Regexp
ReFieldWhiteList *regexp.Regexp
ReUserName *regexp.Regexp
ReUserNameWithEmail *regexp.Regexp
)
func init() {
@@ -36,6 +37,7 @@ func init() {
ReWhiteSpace, _ = regexp.Compile(`\s`)
ReFieldWhiteList, _ = regexp.Compile(`^[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 {

View File

@@ -603,7 +603,7 @@ class ApplicationEditPage extends React.Component {
{Setting.getLabel(i18next.t("general:IP whitelist"), i18next.t("general:IP whitelist - Tooltip"))} :
</Col>
<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);
}} />
</Col>

View File

@@ -908,7 +908,7 @@ class ProviderEditPage extends React.Component {
</Col>
</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"}} >
<Col style={{marginTop: "5px"}} span={2}>
{Setting.getLabel(i18next.t("provider:Domain"), i18next.t("provider:Domain - Tooltip"))} :

View File

@@ -187,7 +187,7 @@ class RoleEditPage extends React.Component {
{Setting.getLabel(i18next.t("role:Sub users"), i18next.t("role:Sub users - Tooltip"))} :
</Col>
<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);})}
options={this.state.users.map((user) => Setting.getOption(`${user.owner}/${user.name}`, `${user.owner}/${user.name}`))}
/>

View File

@@ -444,8 +444,8 @@ class LoginPage extends React.Component {
formValues={values}
authParams={casParams}
application={this.getApplicationObj()}
onFail={() => {
Setting.showMessage("error", i18next.t("mfa:Verification failed"));
onFail={(errorMessage) => {
Setting.showMessage("error", errorMessage);
}}
onSuccess={(res) => loginHandler(res)}
/>);
@@ -513,8 +513,8 @@ class LoginPage extends React.Component {
formValues={values}
authParams={oAuthParams}
application={this.getApplicationObj()}
onFail={() => {
Setting.showMessage("error", i18next.t("mfa:Verification failed"));
onFail={(errorMessage) => {
Setting.showMessage("error", errorMessage);
}}
onSuccess={(res) => loginHandler(res)}
/>);

View File

@@ -37,7 +37,7 @@ class MfaSetupPage extends React.Component {
this.state = {
account: props.account,
application: null,
applicationName: props.account.signupApplication ?? "",
applicationName: props.account.signupApplication ?? localStorage.getItem("applicationName") ?? "",
current: location.state?.from !== undefined ? 1 : 0,
mfaProps: null,
mfaType: params.get("mfaType") ?? SmsMfaType,