Compare commits

..

9 Commits

12 changed files with 157 additions and 13 deletions

View File

@@ -719,7 +719,8 @@ func (c *ApiController) Login() {
setHttpClient(idProvider, provider.Type)
if authForm.State != conf.GetConfigString("authState") && authForm.State != application.Name {
stateApplicationName := strings.Split(authForm.State, "-org-")[0]
if authForm.State != conf.GetConfigString("authState") && stateApplicationName != application.Name {
c.ResponseError(fmt.Sprintf(c.T("auth:State expected: %s, but got: %s"), conf.GetConfigString("authState"), authForm.State))
return
}

View File

@@ -435,9 +435,15 @@ func (c *ApiController) ResetEmailOrPhone() {
switch destType {
case object.VerifyTypeEmail:
id := user.GetId()
user.Email = dest
user.EmailVerified = true
_, err = object.UpdateUser(user.GetId(), user, []string{"email", "email_verified"}, false)
columns := []string{"email", "email_verified"}
if organization.UseEmailAsUsername {
user.Name = user.Email
columns = append(columns, "name")
}
_, err = object.UpdateUser(id, user, columns, false)
case object.VerifyTypePhone:
user.Phone = dest
_, err = object.SetUserField(user, "phone", user.Phone)
@@ -449,6 +455,9 @@ func (c *ApiController) ResetEmailOrPhone() {
c.ResponseError(err.Error())
return
}
if organization.UseEmailAsUsername {
c.SetSessionUsername(user.GetId())
}
err = object.DisableVerificationCode(checkDest)
if err != nil {

View File

@@ -42,10 +42,11 @@ class BaseListPage extends React.Component {
handleOrganizationChange = () => {
this.setState({
organizationName: this.props.match?.params.organizationName || Setting.getRequestOrganization(this.props.account),
},
() => {
const {pagination} = this.state;
this.fetch({pagination});
});
const {pagination} = this.state;
this.fetch({pagination});
};
handleTourChange = () => {

View File

@@ -157,7 +157,7 @@ export const OtherProviderInfo = {
url: "https://control.msg91.com/app/",
},
"OSON SMS": {
logo: "https://osonsms.com/images/osonsms-logo.svg",
logo: `${StaticBaseUrl}/img/social_osonsms.svg`,
url: "https://osonsms.com/",
},
"Custom HTTP SMS": {

View File

@@ -326,7 +326,7 @@ class UserEditPage extends React.Component {
</Col>
<Col span={22} >
<Select virtual={false} mode="multiple" style={{width: "100%"}} disabled={disabled} value={this.state.user.groups ?? []} onChange={(value => {
if (this.state.groups?.filter(group => value.includes(group.name))
if (this.state.groups?.filter(group => value.includes(`${group.owner}/${group.name}`))
.filter(group => group.type === "Physical").length > 1) {
Setting.showMessage("error", i18next.t("general:You can only select one physical group"));
return;
@@ -1117,6 +1117,32 @@ class UserEditPage extends React.Component {
</Col>
</Row>
);
} else if (accountItem.name === "First name") {
return (
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{Setting.getLabel(i18next.t("general:First name"), i18next.t("general:First name - Tooltip"))} :
</Col>
<Col span={22}>
<Input value={this.state.user.firstName} onChange={e => {
this.updateUserField("firstName", e.target.value);
}} />
</Col>
</Row>
);
} else if (accountItem.name === "Last name") {
return (
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{Setting.getLabel(i18next.t("general:Last name"), i18next.t("general:Last name - Tooltip"))} :
</Col>
<Col span={22}>
<Input value={this.state.user.lastName} onChange={e => {
this.updateUserField("lastName", e.target.value);
}} />
</Col>
</Row>
);
}
}

View File

@@ -39,6 +39,7 @@ import {GoogleOneTapLoginVirtualButton} from "./GoogleLoginButton";
import * as ProviderButton from "./ProviderButton";
import {createFormAndSubmit, goToLink} from "../Setting";
import WeChatLoginPanel from "./WeChatLoginPanel";
import {CountryCodeSelect} from "../common/select/CountryCodeSelect";
const FaceRecognitionCommonModal = lazy(() => import("../common/modal/FaceRecognitionCommonModal"));
const FaceRecognitionModal = lazy(() => import("../common/modal/FaceRecognitionModal"));
@@ -677,6 +678,62 @@ class LoginPage extends React.Component {
if (this.state.loginMethod === "wechat") {
return (<WeChatLoginPanel application={application} loginMethod={this.state.loginMethod} />);
}
if (this.state.loginMethod === "verificationCodePhone") {
return <Form.Item className="signin-phone" required={true}>
<Input.Group compact>
<Form.Item
name="countryCode"
noStyle
rules={[
{
required: true,
message: i18next.t("signup:Please select your country code!"),
},
]}
>
<CountryCodeSelect
style={{width: "35%"}}
countryCodes={this.getApplicationObj().organizationObj.countryCodes}
/>
</Form.Item>
<Form.Item
name="username"
dependencies={["countryCode"]}
noStyle
rules={[
{
required: true,
message: i18next.t("signup:Please input your phone number!"),
},
({getFieldValue}) => ({
validator: (_, value) => {
if (!value) {
return Promise.resolve();
}
if (value && !Setting.isValidPhone(value, getFieldValue("countryCode"))) {
this.setState({validEmailOrPhone: false});
return Promise.reject(i18next.t("signup:The input is not valid Phone!"));
}
this.setState({validEmailOrPhone: true});
return Promise.resolve();
},
}),
]}
>
<Input
className="signup-phone-input"
placeholder={signinItem.placeholder}
style={{width: "65%", textAlign: "left"}}
onChange={e => this.setState({username: e.target.value})}
/>
</Form.Item>
</Input.Group>
</Form.Item>;
}
return (
<div key={resultItemKey}>
<div dangerouslySetInnerHTML={{__html: ("<style>" + signinItem.customCss?.replaceAll("<style>", "").replaceAll("</style>", "") + "</style>")}} />
@@ -1122,11 +1179,13 @@ class LoginPage extends React.Component {
{i18next.t("login:Continue with")}&nbsp;:
</div>
<br />
<SelfLoginButton account={this.props.account} onClick={() => {
<div onClick={() => {
const values = {};
values["application"] = application.name;
this.login(values);
}} />
}}>
<SelfLoginButton account={this.props.account} />
</div>
<br />
<br />
<div style={{fontSize: 16, textAlign: "left"}}>

View File

@@ -392,7 +392,11 @@ export function getAuthUrl(application, provider, method, code) {
let redirectUri = `${redirectOrigin}/callback`;
let scope = authInfo[provider.type].scope;
const isShortState = (provider.type === "WeChat" && navigator.userAgent.includes("MicroMessenger")) || (provider.type === "Twitter");
const state = Util.getStateFromQueryParams(application.name, provider.name, method, isShortState);
let applicationName = application.name;
if (application?.isShared) {
applicationName = `${application.name}-org-${application.organization}`;
}
const state = Util.getStateFromQueryParams(applicationName, provider.name, method, isShortState);
const codeChallenge = "P3S-a7dr8bgM4bF6vOyiKkKETDl16rcAzao9F8UIL1Y"; // SHA256(Base64-URL-encode("casdoor-verifier"))
if (provider.type === "AzureAD") {

View File

@@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
import React from "react";
import React, {memo} from "react";
import {createButton} from "react-social-login-buttons";
class SelfLoginButton extends React.Component {
@@ -44,4 +44,4 @@ class SelfLoginButton extends React.Component {
}
}
export default SelfLoginButton;
export default memo(SelfLoginButton);

View File

@@ -364,6 +364,40 @@ class SignupPage extends React.Component {
<Input className="signup-name-input" placeholder={signupItem.placeholder} />
</Form.Item>
);
} else if (signupItem.name === "First name" && this.state?.displayNameRule !== "First, last") {
return (
<Form.Item
name="firstName"
className="signup-first-name"
label={signupItem.label ? signupItem.label : i18next.t("general:First name")}
rules={[
{
required: required,
message: i18next.t("signup:Please input your first name!"),
whitespace: true,
},
]}
>
<Input className="signup-first-name-input" placeholder={signupItem.placeholder} />
</Form.Item>
);
} else if (signupItem.name === "Last name" && this.state?.displayNameRule !== "First, last") {
return (
<Form.Item
name="lastName"
className="signup-last-name"
label={signupItem.label ? signupItem.label : i18next.t("general:Last name")}
rules={[
{
required: required,
message: i18next.t("signup:Please input your last name!"),
whitespace: true,
},
]}
>
<Input className="signup-last-name-input" placeholder={signupItem.placeholder} />
</Form.Item>
);
} else if (signupItem.name === "Affiliation") {
return (
<Form.Item
@@ -776,6 +810,12 @@ class SignupPage extends React.Component {
this.form.current?.setFieldValue("invitationCode", this.state.invitationCode);
}
}
const displayNameItem = application.signupItems?.find(item => item.name === "Display name");
if (displayNameItem && !this.state.displayNameRule) {
this.setState({displayNameRule: displayNameItem.rule});
}
return (
<Form
{...formItemLayout}

View File

@@ -83,7 +83,7 @@ export const CaptchaModal = (props) => {
const renderDefaultCaptcha = () => {
if (noModal) {
return (
<Row style={{textAlign: "center"}}>
<Row style={{textAlign: "center"}} gutter={10}>
<Col
style={{flex: noModal ? "70%" : "100%"}}>
<Input

View File

@@ -67,6 +67,8 @@ class AccountTable extends React.Component {
{name: "ID", label: i18next.t("general:ID")},
{name: "Name", label: i18next.t("general:Name")},
{name: "Display name", label: i18next.t("general:Display name")},
{name: "First name", label: i18next.t("general:First name")},
{name: "Last name", label: i18next.t("general:Last name")},
{name: "Avatar", label: i18next.t("general:Avatar")},
{name: "User type", label: i18next.t("general:User type")},
{name: "Password", label: i18next.t("general:Password")},

View File

@@ -94,6 +94,8 @@ class SignupTable extends React.Component {
{name: "Username", displayName: i18next.t("signup:Username")},
{name: "ID", displayName: i18next.t("general:ID")},
{name: "Display name", displayName: i18next.t("general:Display name")},
{name: "First name", displayName: i18next.t("general:First name")},
{name: "Last name", displayName: i18next.t("general:Last name")},
{name: "Affiliation", displayName: i18next.t("user:Affiliation")},
{name: "Gender", displayName: i18next.t("user:Gender")},
{name: "Bio", displayName: i18next.t("user:Bio")},