Compare commits

...

9 Commits

Author SHA1 Message Date
wht
24a824d394 feat: return the correct error message in the Edit Model (#1504) 2023-01-30 22:19:42 +08:00
75b8357de8 Add properties to UserWithoutThirdIdp 2023-01-29 21:51:01 +08:00
087405dad2 Fix isAllowedInDemoMode() 2023-01-26 17:56:29 +08:00
6a6a1fa920 feat: fix missing phone number prefix in login screen (#1492)
fix: #1489
2023-01-24 23:19:44 +08:00
907d18d2e9 Fix missing roles and permissions in user table 2023-01-23 00:36:55 +08:00
a728e083eb feat: reduce the size of token's user object (#1487)
* fix: Reduce the size of token, especially the user object (#1170)

* fix: Reduce the size of token, especially the user object (#1170)

* fix: Reduce the size of token, especially the user object (#1170)

Co-authored-by: Zayn Xie <84443886+xiaoniuren99@users.noreply.github.com>
2023-01-21 09:30:23 +08:00
457e6208ad feat: terms of use auto selected (#1485) 2023-01-19 20:31:21 +08:00
d10b1347a8 feat: add terms of use in signin page (#1476)
* feat: extract terms of use renderer

* fix: layout

* fix: form styling

* fix: required state

* feat: application terms of use setting

* fix: refactor getTermsOfUseContent

* fix: refactor renderers
2023-01-19 18:39:24 +08:00
f5b7f8cb45 chore(frontend): remove import of the third-party js script (#1436)
Signed-off-by: qwqcode <qwqcode@gmail.com>

Signed-off-by: qwqcode <qwqcode@gmail.com>
2023-01-19 11:31:27 +08:00
10 changed files with 294 additions and 89 deletions

View File

@ -160,7 +160,7 @@ func IsAllowed(subOwner string, subName string, method string, urlPath string, o
func isAllowedInDemoMode(subOwner string, subName string, method string, urlPath string, objOwner string, objName string) bool {
if method == "POST" {
if strings.HasPrefix(urlPath, "/api/login") || urlPath == "/api/logout" || urlPath == "/api/signup" || urlPath == "/api/send-verification-code" {
if strings.HasPrefix(urlPath, "/api/login") || urlPath == "/api/logout" || urlPath == "/api/signup" || urlPath == "/api/send-verification-code" || urlPath == "/api/send-email" {
return true
} else if urlPath == "/api/update-user" {
// Allow ordinary users to update their own information

View File

@ -80,7 +80,7 @@ func (c *ApiController) UpdateModel() {
return
}
c.Data["json"] = wrapActionResponse(object.UpdateModel(id, &model))
c.Data["json"] = wrapErrorResponse(object.UpdateModelWithCheck(id, &model))
c.ServeJSON()
}

View File

@ -86,6 +86,17 @@ func GetModel(id string) *Model {
return getModel(owner, name)
}
func UpdateModelWithCheck(id string, modelObj *Model) error {
// check model grammar
_, err := model.NewModelFromString(modelObj.ModelText)
if err != nil {
return err
}
UpdateModel(id, modelObj)
return nil
}
func UpdateModel(id string, modelObj *Model) bool {
owner, name := util.GetOwnerAndNameFromId(id)
if getModel(owner, name) == nil {
@ -98,11 +109,6 @@ func UpdateModel(id string, modelObj *Model) bool {
return false
}
}
// check model grammar
_, err := model.NewModelFromString(modelObj.ModelText)
if err != nil {
panic(err)
}
affected, err := adapter.Engine.ID(core.PK{owner, name}).AllCols().Update(modelObj)
if err != nil {

View File

@ -36,6 +36,60 @@ type UserShort struct {
Name string `xorm:"varchar(100) notnull pk" json:"name"`
}
type UserWithoutThirdIdp struct {
Owner string `xorm:"varchar(100) notnull pk" json:"owner"`
Name string `xorm:"varchar(100) notnull pk" json:"name"`
CreatedTime string `xorm:"varchar(100)" json:"createdTime"`
UpdatedTime string `xorm:"varchar(100)" json:"updatedTime"`
Id string `xorm:"varchar(100) index" json:"id"`
Type string `xorm:"varchar(100)" json:"type"`
Password string `xorm:"varchar(100)" json:"password"`
PasswordSalt string `xorm:"varchar(100)" json:"passwordSalt"`
DisplayName string `xorm:"varchar(100)" json:"displayName"`
FirstName string `xorm:"varchar(100)" json:"firstName"`
LastName string `xorm:"varchar(100)" json:"lastName"`
Avatar string `xorm:"varchar(500)" json:"avatar"`
PermanentAvatar string `xorm:"varchar(500)" json:"permanentAvatar"`
Email string `xorm:"varchar(100) index" json:"email"`
EmailVerified bool `json:"emailVerified"`
Phone string `xorm:"varchar(100) index" json:"phone"`
Location string `xorm:"varchar(100)" json:"location"`
Address []string `json:"address"`
Affiliation string `xorm:"varchar(100)" json:"affiliation"`
Title string `xorm:"varchar(100)" json:"title"`
IdCardType string `xorm:"varchar(100)" json:"idCardType"`
IdCard string `xorm:"varchar(100) index" json:"idCard"`
Homepage string `xorm:"varchar(100)" json:"homepage"`
Bio string `xorm:"varchar(100)" json:"bio"`
Tag string `xorm:"varchar(100)" json:"tag"`
Region string `xorm:"varchar(100)" json:"region"`
Language string `xorm:"varchar(100)" json:"language"`
Gender string `xorm:"varchar(100)" json:"gender"`
Birthday string `xorm:"varchar(100)" json:"birthday"`
Education string `xorm:"varchar(100)" json:"education"`
Score int `json:"score"`
Karma int `json:"karma"`
Ranking int `json:"ranking"`
IsDefaultAvatar bool `json:"isDefaultAvatar"`
IsOnline bool `json:"isOnline"`
IsAdmin bool `json:"isAdmin"`
IsGlobalAdmin bool `json:"isGlobalAdmin"`
IsForbidden bool `json:"isForbidden"`
IsDeleted bool `json:"isDeleted"`
SignupApplication string `xorm:"varchar(100)" json:"signupApplication"`
Hash string `xorm:"varchar(100)" json:"hash"`
PreHash string `xorm:"varchar(100)" json:"preHash"`
CreatedIp string `xorm:"varchar(100)" json:"createdIp"`
LastSigninTime string `xorm:"varchar(100)" json:"lastSigninTime"`
LastSigninIp string `xorm:"varchar(100)" json:"lastSigninIp"`
Ldap string `xorm:"ldap varchar(100)" json:"ldap"`
Properties map[string]string `json:"properties"`
Roles []*Role `xorm:"-" json:"roles"`
Permissions []*Permission `xorm:"-" json:"permissions"`
LastSigninWrongTime string `xorm:"varchar(100)" json:"lastSigninWrongTime"`
SigninWrongTimes int `json:"signinWrongTimes"`
}
type ClaimsShort struct {
*UserShort
TokenType string `json:"tokenType,omitempty"`
@ -44,6 +98,15 @@ type ClaimsShort struct {
jwt.RegisteredClaims
}
type ClaimsWithoutThirdIdp struct {
*UserWithoutThirdIdp
TokenType string `json:"tokenType,omitempty"`
Nonce string `json:"nonce,omitempty"`
Tag string `json:"tag,omitempty"`
Scope string `json:"scope,omitempty"`
jwt.RegisteredClaims
}
func getShortUser(user *User) *UserShort {
res := &UserShort{
Owner: user.Owner,
@ -52,6 +115,68 @@ func getShortUser(user *User) *UserShort {
return res
}
func getUserWithoutThirdIdp(user *User) *UserWithoutThirdIdp {
res := &UserWithoutThirdIdp{
Owner: user.Owner,
Name: user.Name,
CreatedTime: user.CreatedTime,
UpdatedTime: user.UpdatedTime,
Id: user.Id,
Type: user.Type,
Password: user.Password,
PasswordSalt: user.PasswordSalt,
DisplayName: user.DisplayName,
FirstName: user.FirstName,
LastName: user.LastName,
Avatar: user.Avatar,
PermanentAvatar: user.PermanentAvatar,
Email: user.Email,
EmailVerified: user.EmailVerified,
Phone: user.Phone,
Location: user.Location,
Address: user.Address,
Affiliation: user.Affiliation,
Title: user.Title,
IdCardType: user.IdCardType,
IdCard: user.IdCard,
Homepage: user.Homepage,
Bio: user.Bio,
Tag: user.Tag,
Region: user.Region,
Language: user.Language,
Gender: user.Gender,
Birthday: user.Birthday,
Education: user.Education,
Score: user.Score,
Karma: user.Karma,
Ranking: user.Ranking,
IsDefaultAvatar: user.IsDefaultAvatar,
IsOnline: user.IsOnline,
IsAdmin: user.IsAdmin,
IsGlobalAdmin: user.IsGlobalAdmin,
IsForbidden: user.IsForbidden,
IsDeleted: user.IsDeleted,
SignupApplication: user.SignupApplication,
Hash: user.Hash,
PreHash: user.PreHash,
CreatedIp: user.CreatedIp,
LastSigninTime: user.LastSigninTime,
LastSigninIp: user.LastSigninIp,
Ldap: user.Ldap,
Properties: user.Properties,
Roles: user.Roles,
Permissions: user.Permissions,
LastSigninWrongTime: user.LastSigninWrongTime,
SigninWrongTimes: user.SigninWrongTimes,
}
return res
}
func getShortClaims(claims Claims) ClaimsShort {
res := ClaimsShort{
UserShort: getShortUser(claims.User),
@ -63,6 +188,18 @@ func getShortClaims(claims Claims) ClaimsShort {
return res
}
func getClaimsWithoutThirdIdp(claims Claims) ClaimsWithoutThirdIdp {
res := ClaimsWithoutThirdIdp{
UserWithoutThirdIdp: getUserWithoutThirdIdp(claims.User),
TokenType: claims.TokenType,
Nonce: claims.Nonce,
Tag: claims.Tag,
Scope: claims.Scope,
RegisteredClaims: claims.RegisteredClaims,
}
return res
}
func generateJwtToken(application *Application, user *User, nonce string, scope string, host string) (string, string, string, error) {
nowTime := time.Now()
expireTime := nowTime.Add(time.Duration(application.ExpireInHours) * time.Hour)
@ -104,10 +241,12 @@ func generateJwtToken(application *Application, user *User, nonce string, scope
claimsShort.TokenType = "refresh-token"
refreshToken = jwt.NewWithClaims(jwt.SigningMethodRS256, claimsShort)
} else {
token = jwt.NewWithClaims(jwt.SigningMethodRS256, claims)
claimsWithoutThirdIdp := getClaimsWithoutThirdIdp(claims)
token = jwt.NewWithClaims(jwt.SigningMethodRS256, claimsWithoutThirdIdp)
claims.ExpiresAt = jwt.NewNumericDate(refreshExpireTime)
claims.TokenType = "refresh-token"
refreshToken = jwt.NewWithClaims(jwt.SigningMethodRS256, claims)
refreshToken = jwt.NewWithClaims(jwt.SigningMethodRS256, claimsWithoutThirdIdp)
}
cert := getCertByApplication(application)

View File

@ -110,8 +110,8 @@ type User struct {
Ldap string `xorm:"ldap varchar(100)" json:"ldap"`
Properties map[string]string `json:"properties"`
Roles []*Role `xorm:"-" json:"roles"`
Permissions []*Permission `xorm:"-" json:"permissions"`
Roles []*Role `json:"roles"`
Permissions []*Permission `json:"permissions"`
LastSigninWrongTime string `xorm:"varchar(100)" json:"lastSigninWrongTime"`
SigninWrongTimes int `json:"signinWrongTimes"`

View File

@ -1,16 +1,6 @@
<!DOCTYPE html>
<html lang="en">
<head>
<script>
var _hmt = _hmt || [];
(function() {
var hm = document.createElement("script");
hm.src = "https://hm.baidu.com/hm.js?5998fcd123c220efc0936edf4f250504";
var s = document.getElementsByTagName("script")[0];
s.parentNode.insertBefore(hm, s);
})();
</script>
<meta charset="utf-8" />
<!-- <link rel="icon" href="%PUBLIC_URL%/favicon.png" />-->
<meta name="viewport" content="width=device-width, initial-scale=1" />

View File

@ -14,7 +14,7 @@
import React from "react";
import {Link} from "react-router-dom";
import {Tag, Tooltip, message, theme} from "antd";
import {Checkbox, Form, Modal, Tag, Tooltip, message, theme} from "antd";
import {QuestionCircleTwoTone} from "@ant-design/icons";
import {isMobile as isMobileDevice} from "react-device-detect";
import "./i18n";
@ -509,6 +509,78 @@ export function isMobile() {
return isMobileDevice;
}
export function getTermsOfUseContent(url, setTermsOfUseContent) {
fetch(url, {
method: "GET",
}).then(r => {
r.text().then(setTermsOfUseContent);
});
}
export function isAgreementRequired(application) {
if (application) {
const agreementItem = application.signupItems.find(item => item.name === "Agreement");
if (agreementItem.rule === "None" || !agreementItem.rule) {
return false;
}
if (agreementItem && agreementItem.required) {
return true;
}
}
return false;
}
export function isDefaultTrue(application) {
const agreementItem = application.signupItems.find(item => item.name === "Agreement");
if (isAgreementRequired(application) && agreementItem.rule === "Signin (Default True)") {
return true;
}
return false;
}
export function renderAgreement(required, onClick, noStyle, layout, initialValue) {
return (
<Form.Item
name="agreement"
key="agreement"
valuePropName="checked"
rules={[
{
required: required,
message: i18next.t("signup:Please accept the agreement!"),
},
]}
{...layout}
noStyle={noStyle}
initialValue={initialValue}
>
<Checkbox style={{float: "left"}}>
{i18next.t("signup:Accept")}&nbsp;
<a onClick={onClick}>
{i18next.t("signup:Terms of Use")}
</a>
</Checkbox>
</Form.Item>
);
}
export function renderModal(isOpen, onOk, onCancel, doc) {
return (
<Modal
title={i18next.t("signup:Terms of Use")}
open={isOpen}
width={"55vw"}
closable={false}
okText={i18next.t("signup:Accept")}
cancelText={i18next.t("signup:Decline")}
onOk={onOk}
onCancel={onCancel}
>
<iframe title={"terms"} style={{border: 0, width: "100%", height: "60vh"}} srcDoc={doc} />
</Modal>
);
}
export function getFormattedDate(date) {
if (date === undefined) {
return null;

View File

@ -186,6 +186,12 @@ class SignupTable extends React.Component {
{id: "Normal", name: "Normal"},
{id: "No verification", name: "No verification"},
];
} else if (record.name === "Agreement") {
options = [
{id: "None", name: "None"},
{id: "Signin", name: "Signin"},
{id: "Signin (Default True)", name: "Signin (Default True)"},
];
}
if (options.length === 0) {

View File

@ -53,12 +53,16 @@ class LoginPage extends React.Component {
samlResponse: "",
relayState: "",
redirectUrl: "",
isTermsOfUseVisible: false,
termsOfUseContent: "",
};
if (this.state.type === "cas" && props.match?.params.casApplicationName !== undefined) {
this.state.owner = props.match?.params.owner;
this.state.applicationName = props.match?.params.casApplicationName;
}
this.form = React.createRef();
}
componentDidMount() {
@ -122,7 +126,9 @@ class LoginPage extends React.Component {
this.onUpdateApplication(application);
this.setState({
application: application,
});
}, () => Setting.getTermsOfUseContent(this.state.application.termsOfUse, res => {
this.setState({termsOfUseContent: res});
}));
});
} else {
OrganizationBackend.getDefaultApplication("admin", this.state.owner)
@ -132,7 +138,9 @@ class LoginPage extends React.Component {
this.setState({
application: res.data,
applicationName: res.data.name,
});
}, () => Setting.getTermsOfUseContent(this.state.application.termsOfUse, res => {
this.setState({termsOfUseContent: res});
}));
} else {
this.onUpdateApplication(null);
Setting.showMessage("error", res.msg);
@ -383,6 +391,7 @@ class LoginPage extends React.Component {
onFinish={(values) => {this.onFinish(values);}}
style={{width: "300px"}}
size="large"
ref={this.form}
>
<Form.Item
hidden={true}
@ -456,11 +465,20 @@ class LoginPage extends React.Component {
}
</Row>
<Form.Item>
<Form.Item name="autoSignin" valuePropName="checked" noStyle>
<Checkbox style={{float: "left"}} disabled={!application.enablePassword}>
{i18next.t("login:Auto sign in")}
</Checkbox>
</Form.Item>
{
Setting.isAgreementRequired(application) ?
Setting.renderAgreement(true, () => {
this.setState({
isTermsOfUseVisible: true,
});
}, true, {}, Setting.isDefaultTrue(application)) : (
<Form.Item name="autoSignin" valuePropName="checked" noStyle>
<Checkbox style={{float: "left"}} disabled={!application.enablePassword}>
{i18next.t("login:Auto sign in")}
</Checkbox>
</Form.Item>
)
}
{
Setting.renderForgetLink(application, i18next.t("login:Forgot password?"))
}
@ -827,6 +845,19 @@ class LoginPage extends React.Component {
{
this.renderForm(application)
}
{
Setting.renderModal(this.state.isTermsOfUseVisible, () => {
this.form.current.setFieldsValue({agreement: true});
this.setState({
isTermsOfUseVisible: false,
});
}, () => {
this.form.current.setFieldsValue({agreement: false});
this.setState({
isTermsOfUseVisible: false,
});
}, this.state.termsOfUseContent)
}
</div>
</div>
</div>

View File

@ -13,8 +13,7 @@
// limitations under the License.
import React from "react";
import {Link} from "react-router-dom";
import {Button, Checkbox, Form, Input, Modal, Result} from "antd";
import {Button, Form, Input, Result} from "antd";
import * as Setting from "../Setting";
import * as AuthBackend from "./AuthBackend";
import * as ProviderButton from "./ProviderButton";
@ -113,7 +112,9 @@ class SignupPage extends React.Component {
});
if (application !== null && application !== undefined) {
this.getTermsofuseContent(application.termsOfUse);
Setting.getTermsOfUseContent(application.termsOfUse, res => {
this.setState({termsOfUseContent: res});
});
}
});
}
@ -134,16 +135,6 @@ class SignupPage extends React.Component {
return this.props.application ?? this.state.application;
}
getTermsofuseContent(url) {
fetch(url, {
method: "GET",
}).then(r => {
r.text().then(res => {
this.setState({termsOfUseContent: res});
});
});
}
onUpdateAccount(account) {
this.props.onUpdateAccount(account);
}
@ -413,7 +404,7 @@ class SignupPage extends React.Component {
style={{
width: "100%",
}}
addonBefore={`+${this.state.application?.organizationObj.phonePrefix}`}
addonBefore={`+${this.getApplicationObj()?.organizationObj.phonePrefix}`}
onChange={e => this.setState({phone: e.target.value})}
/>
</Form.Item>
@ -484,58 +475,28 @@ class SignupPage extends React.Component {
);
} else if (signupItem.name === "Agreement") {
return (
<Form.Item
name="agreement"
key="agreement"
valuePropName="checked"
rules={[
{
required: required,
message: i18next.t("signup:Please accept the agreement!"),
},
]}
{...tailFormItemLayout}
>
<Checkbox>
{i18next.t("signup:Accept")}&nbsp;
<Link onClick={() => {
this.setState({
isTermsOfUseVisible: true,
});
}}>
{i18next.t("signup:Terms of Use")}
</Link>
</Checkbox>
</Form.Item>
Setting.renderAgreement(Setting.isAgreementRequired(application), () => {
this.setState({
isTermsOfUseVisible: true,
});
}, false, tailFormItemLayout, Setting.isDefaultTrue(application))
);
}
}
renderModal() {
return (
<Modal
title={i18next.t("signup:Terms of Use")}
open={this.state.isTermsOfUseVisible}
width={"55vw"}
closable={false}
okText={i18next.t("signup:Accept")}
cancelText={i18next.t("signup:Decline")}
onOk={() => {
this.form.current.setFieldsValue({agreement: true});
this.setState({
isTermsOfUseVisible: false,
});
}}
onCancel={() => {
this.form.current.setFieldsValue({agreement: false});
this.setState({
isTermsOfUseVisible: false,
});
this.props.history.goBack();
}}
>
<iframe title={"terms"} style={{border: 0, width: "100%", height: "60vh"}} srcDoc={this.state.termsOfUseContent} />
</Modal>
Setting.renderModal(this.state.isTermsOfUseVisible, () => {
this.form.current.setFieldsValue({agreement: true});
this.setState({
isTermsOfUseVisible: false,
});
}, () => {
this.form.current.setFieldsValue({agreement: false});
this.setState({
isTermsOfUseVisible: false,
});
}, this.state.termsOfUseContent)
);
}