mirror of
https://github.com/casdoor/casdoor.git
synced 2025-07-17 15:23:49 +08:00
Compare commits
6 Commits
Author | SHA1 | Date | |
---|---|---|---|
558b168477 | |||
802b6812a9 | |||
a5a627f92e | |||
9701818a6e | |||
06986fbd41 | |||
3d12ac8dc2 |
@ -306,6 +306,35 @@ func isProxyProviderType(providerType string) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func checkMfaEnable(c *ApiController, user *object.User, organization *object.Organization, verificationType string) bool {
|
||||
if object.IsNeedPromptMfa(organization, user) {
|
||||
// The prompt page needs the user to be srigned in
|
||||
c.SetSessionUsername(user.GetId())
|
||||
c.ResponseOk(object.RequiredMfa)
|
||||
return true
|
||||
}
|
||||
|
||||
if user.IsMfaEnabled() {
|
||||
c.setMfaUserSession(user.GetId())
|
||||
mfaList := object.GetAllMfaProps(user, true)
|
||||
mfaAllowList := []*object.MfaProps{}
|
||||
for _, prop := range mfaList {
|
||||
if prop.MfaType == verificationType || !prop.Enabled {
|
||||
continue
|
||||
}
|
||||
mfaAllowList = append(mfaAllowList, prop)
|
||||
}
|
||||
if len(mfaAllowList) >= 1 {
|
||||
c.SetSession("verificationCodeType", verificationType)
|
||||
c.Ctx.Input.CruSession.SessionRelease(c.Ctx.ResponseWriter)
|
||||
c.ResponseOk(object.NextMfa, mfaAllowList)
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// Login ...
|
||||
// @Title Login
|
||||
// @Tag Login API
|
||||
@ -523,30 +552,10 @@ func (c *ApiController) Login() {
|
||||
c.ResponseError(err.Error())
|
||||
}
|
||||
|
||||
if object.IsNeedPromptMfa(organization, user) {
|
||||
// The prompt page needs the user to be signed in
|
||||
c.SetSessionUsername(user.GetId())
|
||||
c.ResponseOk(object.RequiredMfa)
|
||||
if checkMfaEnable(c, user, organization, verificationType) {
|
||||
return
|
||||
}
|
||||
|
||||
if user.IsMfaEnabled() {
|
||||
c.setMfaUserSession(user.GetId())
|
||||
mfaList := object.GetAllMfaProps(user, true)
|
||||
mfaAllowList := []*object.MfaProps{}
|
||||
for _, prop := range mfaList {
|
||||
if prop.MfaType == verificationType || !prop.Enabled {
|
||||
continue
|
||||
}
|
||||
mfaAllowList = append(mfaAllowList, prop)
|
||||
}
|
||||
if len(mfaAllowList) >= 1 {
|
||||
c.SetSession("verificationCodeType", verificationType)
|
||||
c.ResponseOk(object.NextMfa, mfaAllowList)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
resp = c.HandleLoggedIn(application, user, &authForm)
|
||||
|
||||
c.Ctx.Input.SetParam("recordUserId", user.GetId())
|
||||
@ -679,6 +688,11 @@ func (c *ApiController) Login() {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
if checkMfaEnable(c, user, organization, verificationType) {
|
||||
return
|
||||
}
|
||||
|
||||
resp = c.HandleLoggedIn(application, user, &authForm)
|
||||
|
||||
c.Ctx.Input.SetParam("recordUserId", user.GetId())
|
||||
@ -914,7 +928,11 @@ func (c *ApiController) Login() {
|
||||
}
|
||||
|
||||
var application *object.Application
|
||||
application, err = object.GetApplication(fmt.Sprintf("admin/%s", authForm.Application))
|
||||
if authForm.ClientId == "" {
|
||||
application, err = object.GetApplication(fmt.Sprintf("admin/%s", authForm.Application))
|
||||
} else {
|
||||
application, err = object.GetApplicationByClientId(authForm.ClientId)
|
||||
}
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
@ -944,6 +962,10 @@ func (c *ApiController) Login() {
|
||||
return
|
||||
}
|
||||
|
||||
if authForm.Provider == "" {
|
||||
authForm.Provider = authForm.ProviderBack
|
||||
}
|
||||
|
||||
user := c.getCurrentUser()
|
||||
resp = c.HandleLoggedIn(application, user, &authForm)
|
||||
|
||||
|
@ -70,15 +70,33 @@ func (c *ApiController) GetGroups() {
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
} else {
|
||||
err = object.ExtendGroupsWithUsers(groups)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
groupsHaveChildrenMap, err := object.GetGroupsHaveChildrenMap(groups)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
for _, group := range groups {
|
||||
_, ok := groupsHaveChildrenMap[group.Name]
|
||||
if ok {
|
||||
group.HaveChildren = true
|
||||
}
|
||||
|
||||
c.ResponseOk(groups, paginator.Nums())
|
||||
parent, ok := groupsHaveChildrenMap[group.ParentId]
|
||||
if ok {
|
||||
group.ParentName = parent.DisplayName
|
||||
}
|
||||
}
|
||||
|
||||
err = object.ExtendGroupsWithUsers(groups)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
c.ResponseOk(groups, paginator.Nums())
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -21,6 +21,11 @@ import (
|
||||
)
|
||||
|
||||
func (c *RootController) HandleScim() {
|
||||
_, ok := c.RequireAdmin()
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
path := c.Ctx.Request.URL.Path
|
||||
c.Ctx.Request.URL.Path = strings.TrimPrefix(path, "/scim")
|
||||
scim.Server.ServeHTTP(c.Ctx.ResponseWriter, c.Ctx.Request)
|
||||
|
15
form/auth.go
15
form/auth.go
@ -37,13 +37,14 @@ type AuthForm struct {
|
||||
Region string `json:"region"`
|
||||
InvitationCode string `json:"invitationCode"`
|
||||
|
||||
Application string `json:"application"`
|
||||
ClientId string `json:"clientId"`
|
||||
Provider string `json:"provider"`
|
||||
Code string `json:"code"`
|
||||
State string `json:"state"`
|
||||
RedirectUri string `json:"redirectUri"`
|
||||
Method string `json:"method"`
|
||||
Application string `json:"application"`
|
||||
ClientId string `json:"clientId"`
|
||||
Provider string `json:"provider"`
|
||||
ProviderBack string `json:"providerBack"`
|
||||
Code string `json:"code"`
|
||||
State string `json:"state"`
|
||||
RedirectUri string `json:"redirectUri"`
|
||||
Method string `json:"method"`
|
||||
|
||||
EmailCode string `json:"emailCode"`
|
||||
PhoneCode string `json:"phoneCode"`
|
||||
|
@ -481,7 +481,10 @@ func GetApplicationByClientId(clientId string) (*Application, error) {
|
||||
}
|
||||
|
||||
func GetApplication(id string) (*Application, error) {
|
||||
owner, name := util.GetOwnerAndNameFromId(id)
|
||||
owner, name, err := util.GetOwnerAndNameFromIdWithError(id)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return getApplication(owner, name)
|
||||
}
|
||||
|
||||
|
@ -17,7 +17,6 @@ package object
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"sync"
|
||||
|
||||
"github.com/casdoor/casdoor/conf"
|
||||
"github.com/casdoor/casdoor/util"
|
||||
@ -36,12 +35,14 @@ type Group struct {
|
||||
ContactEmail string `xorm:"varchar(100)" json:"contactEmail"`
|
||||
Type string `xorm:"varchar(100)" json:"type"`
|
||||
ParentId string `xorm:"varchar(100)" json:"parentId"`
|
||||
ParentName string `xorm:"-" json:"parentName"`
|
||||
IsTopGroup bool `xorm:"bool" json:"isTopGroup"`
|
||||
Users []string `xorm:"-" json:"users"`
|
||||
|
||||
Title string `json:"title,omitempty"`
|
||||
Key string `json:"key,omitempty"`
|
||||
Children []*Group `json:"children,omitempty"`
|
||||
Title string `json:"title,omitempty"`
|
||||
Key string `json:"key,omitempty"`
|
||||
HaveChildren bool `xorm:"-" json:"haveChildren"`
|
||||
Children []*Group `json:"children,omitempty"`
|
||||
|
||||
IsEnabled bool `json:"isEnabled"`
|
||||
}
|
||||
@ -79,6 +80,26 @@ func GetPaginationGroups(owner string, offset, limit int, field, value, sortFiel
|
||||
return groups, nil
|
||||
}
|
||||
|
||||
func GetGroupsHaveChildrenMap(groups []*Group) (map[string]*Group, error) {
|
||||
groupsHaveChildren := []*Group{}
|
||||
resultMap := make(map[string]*Group)
|
||||
|
||||
groupIds := []string{}
|
||||
for _, group := range groups {
|
||||
groupIds = append(groupIds, group.Name)
|
||||
groupIds = append(groupIds, group.ParentId)
|
||||
}
|
||||
|
||||
err := ormer.Engine.Cols("owner", "name", "parent_id", "display_name").Distinct("parent_id").In("parent_id", groupIds).Find(&groupsHaveChildren)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, group := range groups {
|
||||
resultMap[group.Name] = group
|
||||
}
|
||||
return resultMap, nil
|
||||
}
|
||||
|
||||
func getGroup(owner string, name string) (*Group, error) {
|
||||
if owner == "" || name == "" {
|
||||
return nil, nil
|
||||
@ -298,17 +319,11 @@ func ExtendGroupWithUsers(group *Group) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
users, err := GetUsers(group.Owner)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
groupId := group.GetId()
|
||||
userIds := []string{}
|
||||
for _, user := range users {
|
||||
if util.InSlice(user.Groups, groupId) {
|
||||
userIds = append(userIds, user.GetId())
|
||||
}
|
||||
userIds, err := userEnforcer.GetAllUsersByGroup(groupId)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
group.Users = userIds
|
||||
@ -316,29 +331,14 @@ func ExtendGroupWithUsers(group *Group) error {
|
||||
}
|
||||
|
||||
func ExtendGroupsWithUsers(groups []*Group) error {
|
||||
var wg sync.WaitGroup
|
||||
errChan := make(chan error, len(groups))
|
||||
|
||||
for _, group := range groups {
|
||||
wg.Add(1)
|
||||
go func(group *Group) {
|
||||
defer wg.Done()
|
||||
err := ExtendGroupWithUsers(group)
|
||||
if err != nil {
|
||||
errChan <- err
|
||||
}
|
||||
}(group)
|
||||
}
|
||||
|
||||
wg.Wait()
|
||||
close(errChan)
|
||||
|
||||
for err := range errChan {
|
||||
users, err := userEnforcer.GetAllUsersByGroup(group.GetId())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
group.Users = users
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
|
@ -151,7 +151,10 @@ func getOrganization(owner string, name string) (*Organization, error) {
|
||||
}
|
||||
|
||||
func GetOrganization(id string) (*Organization, error) {
|
||||
owner, name := util.GetOwnerAndNameFromId(id)
|
||||
owner, name, err := util.GetOwnerAndNameFromIdWithError(id)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return getOrganization(owner, name)
|
||||
}
|
||||
|
||||
|
@ -965,6 +965,14 @@ func DeleteUser(user *User) (bool, error) {
|
||||
return false, err
|
||||
}
|
||||
|
||||
ok, err := userEnforcer.DeleteGroupsForUser(user.GetId())
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
if !ok {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
organization, err := GetOrganizationByUser(user)
|
||||
if err != nil {
|
||||
return false, err
|
||||
|
@ -133,6 +133,9 @@ func StaticFilter(ctx *context.Context) {
|
||||
path += urlPath
|
||||
}
|
||||
|
||||
// Preventing synchronization problems from concurrency
|
||||
ctx.Input.CruSession = nil
|
||||
|
||||
organizationThemeCookie, err := appendThemeCookie(ctx, urlPath)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
|
@ -32,94 +32,79 @@ type OrganizationThemeCookie struct {
|
||||
}
|
||||
|
||||
func appendThemeCookie(ctx *context.Context, urlPath string) (*OrganizationThemeCookie, error) {
|
||||
organizationThemeCookie, err := getOrganizationThemeCookieFromUrlPath(ctx, urlPath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if organizationThemeCookie != nil {
|
||||
return organizationThemeCookie, setThemeDataCookie(ctx, organizationThemeCookie)
|
||||
}
|
||||
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func getOrganizationThemeCookieFromUrlPath(ctx *context.Context, urlPath string) (*OrganizationThemeCookie, error) {
|
||||
var application *object.Application
|
||||
var organization *object.Organization
|
||||
var err error
|
||||
if urlPath == "/login" {
|
||||
application, err := object.GetDefaultApplication(fmt.Sprintf("admin/built-in"))
|
||||
application, err = object.GetDefaultApplication(fmt.Sprintf("admin/built-in"))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
organization := application.OrganizationObj
|
||||
if organization == nil {
|
||||
organization, err = object.GetOrganization(fmt.Sprintf("admin/built-in"))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
if organization != nil {
|
||||
organizationThemeCookie := &OrganizationThemeCookie{
|
||||
application.ThemeData,
|
||||
application.Logo,
|
||||
application.FooterHtml,
|
||||
organization.Favicon,
|
||||
organization.DisplayName,
|
||||
}
|
||||
|
||||
if application.ThemeData != nil {
|
||||
organizationThemeCookie.ThemeData = organization.ThemeData
|
||||
}
|
||||
return organizationThemeCookie, setThemeDataCookie(ctx, organizationThemeCookie)
|
||||
}
|
||||
} else if strings.HasPrefix(urlPath, "/login/oauth/authorize") {
|
||||
clientId := ctx.Input.Query("client_id")
|
||||
if clientId == "" {
|
||||
return nil, nil
|
||||
}
|
||||
application, err := object.GetApplicationByClientId(clientId)
|
||||
application, err = object.GetApplicationByClientId(clientId)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if application != nil {
|
||||
organization := application.OrganizationObj
|
||||
if organization == nil {
|
||||
organization, err = object.GetOrganization(fmt.Sprintf("admin/%s", application.Owner))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
organizationThemeCookie := &OrganizationThemeCookie{
|
||||
application.ThemeData,
|
||||
application.Logo,
|
||||
application.FooterHtml,
|
||||
organization.Favicon,
|
||||
organization.DisplayName,
|
||||
}
|
||||
|
||||
if application.ThemeData != nil {
|
||||
organizationThemeCookie.ThemeData = organization.ThemeData
|
||||
}
|
||||
return organizationThemeCookie, setThemeDataCookie(ctx, organizationThemeCookie)
|
||||
} else if strings.HasPrefix(urlPath, "/login/saml") {
|
||||
owner, _ := strings.CutPrefix(urlPath, "/login/saml/authorize/")
|
||||
application, err = object.GetApplication(owner)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
} else if strings.HasPrefix(urlPath, "/login/") {
|
||||
owner := strings.Replace(urlPath, "/login/", "", -1)
|
||||
if owner != "undefined" && owner != "oauth/undefined" {
|
||||
application, err := object.GetDefaultApplication(fmt.Sprintf("admin/%s", owner))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
organization := application.OrganizationObj
|
||||
if organization == nil {
|
||||
organization, err = object.GetOrganization(fmt.Sprintf("admin/%s", owner))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
if organization != nil {
|
||||
organizationThemeCookie := &OrganizationThemeCookie{
|
||||
application.ThemeData,
|
||||
application.Logo,
|
||||
application.FooterHtml,
|
||||
organization.Favicon,
|
||||
organization.DisplayName,
|
||||
}
|
||||
|
||||
if application.ThemeData != nil {
|
||||
organizationThemeCookie.ThemeData = organization.ThemeData
|
||||
}
|
||||
return organizationThemeCookie, setThemeDataCookie(ctx, organizationThemeCookie)
|
||||
}
|
||||
owner, _ := strings.CutPrefix(urlPath, "/login/")
|
||||
if owner == "undefined" || strings.Count(owner, "/") > 0 {
|
||||
return nil, nil
|
||||
}
|
||||
application, err = object.GetDefaultApplication(fmt.Sprintf("admin/%s", owner))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
} else if strings.HasPrefix(urlPath, "/cas/") && strings.HasSuffix(urlPath, "/login") {
|
||||
owner, _ := strings.CutPrefix(urlPath, "/cas/")
|
||||
owner, _ = strings.CutSuffix(owner, "/login")
|
||||
application, err = object.GetApplication(owner)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return nil, nil
|
||||
if application == nil {
|
||||
return nil, nil
|
||||
}
|
||||
organization = application.OrganizationObj
|
||||
if organization == nil {
|
||||
organization, err = object.GetOrganization(fmt.Sprintf("admin/%s", application.Organization))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
organizationThemeCookie := &OrganizationThemeCookie{
|
||||
application.ThemeData,
|
||||
application.Logo,
|
||||
application.FooterHtml,
|
||||
organization.Favicon,
|
||||
organization.DisplayName,
|
||||
}
|
||||
|
||||
return organizationThemeCookie, nil
|
||||
}
|
||||
|
||||
func setThemeDataCookie(ctx *context.Context, organizationThemeCookie *OrganizationThemeCookie) error {
|
||||
|
@ -361,6 +361,14 @@ class App extends Component {
|
||||
}
|
||||
};
|
||||
|
||||
onLoginSuccess(redirectUrl) {
|
||||
window.google?.accounts?.id?.cancel();
|
||||
if (redirectUrl) {
|
||||
localStorage.setItem("mfaRedirectUrl", redirectUrl);
|
||||
}
|
||||
this.getAccount();
|
||||
}
|
||||
|
||||
renderPage() {
|
||||
if (this.isDoorPages()) {
|
||||
let themeData = this.state.themeData;
|
||||
@ -401,19 +409,13 @@ class App extends Component {
|
||||
application: application,
|
||||
});
|
||||
}}
|
||||
onLoginSuccess={(redirectUrl) => {
|
||||
window.google?.accounts?.id?.cancel();
|
||||
if (redirectUrl) {
|
||||
localStorage.setItem("mfaRedirectUrl", redirectUrl);
|
||||
}
|
||||
this.getAccount();
|
||||
}}
|
||||
onLoginSuccess={(redirectUrl) => {this.onLoginSuccess(redirectUrl);}}
|
||||
onUpdateAccount={(account) => this.onUpdateAccount(account)}
|
||||
updataThemeData={this.setTheme}
|
||||
/> :
|
||||
<Switch>
|
||||
<Route exact path="/callback" component={AuthCallback} />
|
||||
<Route exact path="/callback/saml" component={SamlCallback} />
|
||||
<Route exact path="/callback" render={(props) => <AuthCallback {...props} {...this.props} application={this.state.application} onLoginSuccess={(redirectUrl) => {this.onLoginSuccess(redirectUrl);}} />} />
|
||||
<Route exact path="/callback/saml" render={(props) => <SamlCallback {...props} {...this.props} application={this.state.application} onLoginSuccess={(redirectUrl) => {this.onLoginSuccess(redirectUrl);}} />} />
|
||||
<Route path="" render={() => <Result status="404" title="404 NOT FOUND" subTitle={i18next.t("general:Sorry, the page you visited does not exist.")}
|
||||
extra={<a href="/"><Button type="primary">{i18next.t("general:Back Home")}</Button></a>} />} />
|
||||
</Switch>
|
||||
|
@ -33,18 +33,6 @@ class GroupListPage extends BaseListPage {
|
||||
}
|
||||
UNSAFE_componentWillMount() {
|
||||
super.UNSAFE_componentWillMount();
|
||||
this.getGroups(this.state.owner);
|
||||
}
|
||||
|
||||
getGroups(organizationName) {
|
||||
GroupBackend.getGroups(organizationName)
|
||||
.then((res) => {
|
||||
if (res.status === "ok") {
|
||||
this.setState({
|
||||
groups: res.data,
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
newGroup() {
|
||||
@ -188,12 +176,8 @@ class GroupListPage extends BaseListPage {
|
||||
{record.parentId}
|
||||
</Link>;
|
||||
}
|
||||
const parentGroup = this.state.groups.find((group) => group.name === text);
|
||||
if (parentGroup === undefined) {
|
||||
return "";
|
||||
}
|
||||
return <Link to={`/groups/${parentGroup.owner}/${parentGroup.name}`}>
|
||||
{parentGroup?.displayName}
|
||||
return <Link to={`/groups/${record.owner}/${record.parentId}`}>
|
||||
{record?.parentName}
|
||||
</Link>;
|
||||
},
|
||||
},
|
||||
@ -215,12 +199,11 @@ class GroupListPage extends BaseListPage {
|
||||
width: "180px",
|
||||
fixed: (Setting.isMobile()) ? "false" : "right",
|
||||
render: (text, record, index) => {
|
||||
const haveChildren = this.state.groups.find((group) => group.parentId === record.id) !== undefined;
|
||||
return (
|
||||
<div>
|
||||
<Button style={{marginTop: "10px", marginBottom: "10px", marginRight: "10px"}} type="primary" onClick={() => this.props.history.push(`/groups/${record.owner}/${record.name}`)}>{i18next.t("general:Edit")}</Button>
|
||||
<PopconfirmModal
|
||||
disabled={haveChildren}
|
||||
disabled={record.haveChildren}
|
||||
title={i18next.t("general:Sure to delete") + `: ${record.name} ?`}
|
||||
onConfirm={() => this.deleteGroup(index)}
|
||||
>
|
||||
|
@ -14,7 +14,7 @@
|
||||
|
||||
import React from "react";
|
||||
import {Link} from "react-router-dom";
|
||||
import {Select, Tag, Tooltip, message, theme} from "antd";
|
||||
import {Button, Select, Tag, Tooltip, message, theme} from "antd";
|
||||
import {QuestionCircleTwoTone} from "@ant-design/icons";
|
||||
import {isMobile as isMobileDevice} from "react-device-detect";
|
||||
import "./i18n";
|
||||
@ -25,6 +25,8 @@ import {Helmet} from "react-helmet";
|
||||
import * as Conf from "./Conf";
|
||||
import * as phoneNumber from "libphonenumber-js";
|
||||
import moment from "moment";
|
||||
import {MfaAuthVerifyForm, NextMfa, RequiredMfa} from "./auth/mfa/MfaAuthVerifyForm";
|
||||
import {EmailMfaType, SmsMfaType, TotpMfaType} from "./auth/MfaSetupPage";
|
||||
|
||||
const {Option} = Select;
|
||||
|
||||
@ -1588,3 +1590,114 @@ export function getCurrencyText(product) {
|
||||
export function isDarkTheme(themeAlgorithm) {
|
||||
return themeAlgorithm && themeAlgorithm.includes("dark");
|
||||
}
|
||||
|
||||
function getPreferredMfaProp(mfaProps) {
|
||||
for (const i in mfaProps) {
|
||||
if (mfaProps[i].isPreffered) {
|
||||
return mfaProps[i];
|
||||
}
|
||||
}
|
||||
return mfaProps[0];
|
||||
}
|
||||
|
||||
export function checkLoginMfa(res, body, params, handleLogin, componentThis, requireRedirect = null) {
|
||||
if (res.data === RequiredMfa) {
|
||||
if (!requireRedirect) {
|
||||
componentThis.props.onLoginSuccess(window.location.href);
|
||||
} else {
|
||||
componentThis.props.onLoginSuccess(requireRedirect);
|
||||
}
|
||||
} else if (res.data === NextMfa) {
|
||||
componentThis.setState({
|
||||
mfaProps: res.data2,
|
||||
selectedMfaProp: getPreferredMfaProp(res.data2),
|
||||
}, () => {
|
||||
body["providerBack"] = body["provider"];
|
||||
body["provider"] = "";
|
||||
componentThis.setState({
|
||||
getVerifyTotp: () => renderMfaAuthVerifyForm(body, params, handleLogin, componentThis),
|
||||
});
|
||||
});
|
||||
} else if (res.data === "SelectPlan") {
|
||||
// paid-user does not have active or pending subscription, go to application default pricing page to select-plan
|
||||
const pricing = res.data2;
|
||||
goToLink(`/select-plan/${pricing.owner}/${pricing.name}?user=${body.username}`);
|
||||
} else if (res.data === "BuyPlanResult") {
|
||||
// paid-user has pending subscription, go to buy-plan/result apge to notify payment result
|
||||
const sub = res.data2;
|
||||
goToLink(`/buy-plan/${sub.owner}/${sub.pricing}/result?subscription=${sub.name}`);
|
||||
} else {
|
||||
handleLogin(res);
|
||||
}
|
||||
}
|
||||
|
||||
export function getApplicationObj(componentThis) {
|
||||
return componentThis.props.application;
|
||||
}
|
||||
|
||||
export function parseOffset(offset) {
|
||||
if (offset === 2 || offset === 4 || inIframe() || isMobile()) {
|
||||
return "0 auto";
|
||||
}
|
||||
if (offset === 1) {
|
||||
return "0 10%";
|
||||
}
|
||||
if (offset === 3) {
|
||||
return "0 60%";
|
||||
}
|
||||
}
|
||||
|
||||
function renderMfaAuthVerifyForm(values, authParams, onSuccess, componentThis) {
|
||||
return (
|
||||
<div>
|
||||
<MfaAuthVerifyForm
|
||||
mfaProps={componentThis.state.selectedMfaProp}
|
||||
formValues={values}
|
||||
authParams={authParams}
|
||||
application={getApplicationObj(componentThis)}
|
||||
onFail={(errorMessage) => {
|
||||
showMessage("error", errorMessage);
|
||||
}}
|
||||
onSuccess={(res) => onSuccess(res)}
|
||||
/>
|
||||
<div>
|
||||
{
|
||||
componentThis.state.mfaProps.map((mfa) => {
|
||||
if (componentThis.state.selectedMfaProp.mfaType === mfa.mfaType) {return null;}
|
||||
let mfaI18n = "";
|
||||
switch (mfa.mfaType) {
|
||||
case SmsMfaType: mfaI18n = i18next.t("mfa:Use SMS"); break;
|
||||
case TotpMfaType: mfaI18n = i18next.t("mfa:Use Authenticator App"); break ;
|
||||
case EmailMfaType: mfaI18n = i18next.t("mfa:Use Email") ;break;
|
||||
}
|
||||
return <div key={mfa.mfaType}><Button type={"link"} onClick={() => {
|
||||
componentThis.setState({
|
||||
selectedMfaProp: mfa,
|
||||
});
|
||||
}}>{mfaI18n}</Button></div>;
|
||||
})
|
||||
}
|
||||
</div>
|
||||
</div>);
|
||||
}
|
||||
|
||||
export function renderLoginPanel(application, getInnerComponent, componentThis) {
|
||||
return (
|
||||
<div className="login-content" style={{margin: componentThis.props.preview ?? parseOffset(application.formOffset)}}>
|
||||
{inIframe() || isMobile() ? null : <div dangerouslySetInnerHTML={{__html: application.formCss}} />}
|
||||
{inIframe() || !isMobile() ? null : <div dangerouslySetInnerHTML={{__html: application.formCssMobile}} />}
|
||||
<div className={isDarkTheme(componentThis.props.themeAlgorithm) ? "login-panel-dark" : "login-panel"}>
|
||||
<div className="side-image" style={{display: application.formOffset !== 4 ? "none" : null}}>
|
||||
<div dangerouslySetInnerHTML={{__html: application.formSideHtml}} />
|
||||
</div>
|
||||
<div className="login-form">
|
||||
<div>
|
||||
{
|
||||
getInnerComponent()
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
@ -21,6 +21,7 @@ import {authConfig} from "./Auth";
|
||||
import * as Setting from "../Setting";
|
||||
import i18next from "i18next";
|
||||
import RedirectForm from "../common/RedirectForm";
|
||||
import {renderLoginPanel} from "../Setting";
|
||||
|
||||
class AuthCallback extends React.Component {
|
||||
constructor(props) {
|
||||
@ -131,19 +132,23 @@ class AuthCallback extends React.Component {
|
||||
// user is using casdoor as cas sso server, and wants the ticket to be acquired
|
||||
AuthBackend.loginCas(body, {"service": casService}).then((res) => {
|
||||
if (res.status === "ok") {
|
||||
let msg = "Logged in successfully.";
|
||||
if (casService === "") {
|
||||
// If service was not specified, Casdoor must display a message notifying the client that it has successfully initiated a single sign-on session.
|
||||
msg += "Now you can visit apps protected by Casdoor.";
|
||||
}
|
||||
Setting.showMessage("success", msg);
|
||||
const handleCasLogin = (res) => {
|
||||
let msg = "Logged in successfully.";
|
||||
if (casService === "") {
|
||||
// If service was not specified, Casdoor must display a message notifying the client that it has successfully initiated a single sign-on session.
|
||||
msg += "Now you can visit apps protected by Casdoor.";
|
||||
}
|
||||
Setting.showMessage("success", msg);
|
||||
|
||||
if (casService !== "") {
|
||||
const st = res.data;
|
||||
const newUrl = new URL(casService);
|
||||
newUrl.searchParams.append("ticket", st);
|
||||
window.location.href = newUrl.toString();
|
||||
}
|
||||
if (casService !== "") {
|
||||
const st = res.data;
|
||||
const newUrl = new URL(casService);
|
||||
newUrl.searchParams.append("ticket", st);
|
||||
window.location.href = newUrl.toString();
|
||||
}
|
||||
};
|
||||
|
||||
Setting.checkLoginMfa(res, body, {"service": casService}, handleCasLogin, this);
|
||||
} else {
|
||||
Setting.showMessage("error", `${i18next.t("application:Failed to sign in")}: ${res.msg}`);
|
||||
}
|
||||
@ -159,54 +164,58 @@ class AuthCallback extends React.Component {
|
||||
.then((res) => {
|
||||
if (res.status === "ok") {
|
||||
const responseType = this.getResponseType();
|
||||
if (responseType === "login") {
|
||||
if (res.data2) {
|
||||
sessionStorage.setItem("signinUrl", signinUrl);
|
||||
Setting.goToLinkSoft(this, `/forget/${applicationName}`);
|
||||
return;
|
||||
}
|
||||
Setting.showMessage("success", "Logged in successfully");
|
||||
// Setting.goToLinkSoft(this, "/");
|
||||
const link = Setting.getFromLink();
|
||||
Setting.goToLink(link);
|
||||
} else if (responseType === "code") {
|
||||
if (res.data2) {
|
||||
sessionStorage.setItem("signinUrl", signinUrl);
|
||||
Setting.goToLinkSoft(this, `/forget/${applicationName}`);
|
||||
return;
|
||||
}
|
||||
const code = res.data;
|
||||
Setting.goToLink(`${oAuthParams.redirectUri}${concatChar}code=${code}&state=${oAuthParams.state}`);
|
||||
// Setting.showMessage("success", `Authorization code: ${res.data}`);
|
||||
} else if (responseType === "token" || responseType === "id_token") {
|
||||
if (res.data2) {
|
||||
sessionStorage.setItem("signinUrl", signinUrl);
|
||||
Setting.goToLinkSoft(this, `/forget/${applicationName}`);
|
||||
return;
|
||||
}
|
||||
const token = res.data;
|
||||
Setting.goToLink(`${oAuthParams.redirectUri}${concatChar}${responseType}=${token}&state=${oAuthParams.state}&token_type=bearer`);
|
||||
} else if (responseType === "link") {
|
||||
const from = innerParams.get("from");
|
||||
Setting.goToLinkSoftOrJumpSelf(this, from);
|
||||
} else if (responseType === "saml") {
|
||||
if (res.data2.method === "POST") {
|
||||
this.setState({
|
||||
samlResponse: res.data,
|
||||
redirectUrl: res.data2.redirectUrl,
|
||||
relayState: oAuthParams.relayState,
|
||||
});
|
||||
} else {
|
||||
if (res.data2.needUpdatePassword) {
|
||||
const handleLogin = (res) => {
|
||||
if (responseType === "login") {
|
||||
if (res.data2) {
|
||||
sessionStorage.setItem("signinUrl", signinUrl);
|
||||
Setting.goToLinkSoft(this, `/forget/${applicationName}`);
|
||||
return;
|
||||
}
|
||||
const SAMLResponse = res.data;
|
||||
const redirectUri = res.data2.redirectUrl;
|
||||
Setting.goToLink(`${redirectUri}${redirectUri.includes("?") ? "&" : "?"}SAMLResponse=${encodeURIComponent(SAMLResponse)}&RelayState=${oAuthParams.relayState}`);
|
||||
Setting.showMessage("success", "Logged in successfully");
|
||||
// Setting.goToLinkSoft(this, "/");
|
||||
const link = Setting.getFromLink();
|
||||
Setting.goToLink(link);
|
||||
} else if (responseType === "code") {
|
||||
if (res.data2) {
|
||||
sessionStorage.setItem("signinUrl", signinUrl);
|
||||
Setting.goToLinkSoft(this, `/forget/${applicationName}`);
|
||||
return;
|
||||
}
|
||||
const code = res.data;
|
||||
Setting.goToLink(`${oAuthParams.redirectUri}${concatChar}code=${code}&state=${oAuthParams.state}`);
|
||||
// Setting.showMessage("success", `Authorization code: ${res.data}`);
|
||||
} else if (responseType === "token" || responseType === "id_token") {
|
||||
if (res.data2) {
|
||||
sessionStorage.setItem("signinUrl", signinUrl);
|
||||
Setting.goToLinkSoft(this, `/forget/${applicationName}`);
|
||||
return;
|
||||
}
|
||||
const token = res.data;
|
||||
Setting.goToLink(`${oAuthParams.redirectUri}${concatChar}${responseType}=${token}&state=${oAuthParams.state}&token_type=bearer`);
|
||||
} else if (responseType === "link") {
|
||||
const from = innerParams.get("from");
|
||||
Setting.goToLinkSoftOrJumpSelf(this, from);
|
||||
} else if (responseType === "saml") {
|
||||
if (res.data2.method === "POST") {
|
||||
this.setState({
|
||||
samlResponse: res.data,
|
||||
redirectUrl: res.data2.redirectUrl,
|
||||
relayState: oAuthParams.relayState,
|
||||
});
|
||||
} else {
|
||||
if (res.data2.needUpdatePassword) {
|
||||
sessionStorage.setItem("signinUrl", signinUrl);
|
||||
Setting.goToLinkSoft(this, `/forget/${applicationName}`);
|
||||
return;
|
||||
}
|
||||
const SAMLResponse = res.data;
|
||||
const redirectUri = res.data2.redirectUrl;
|
||||
Setting.goToLink(`${redirectUri}${redirectUri.includes("?") ? "&" : "?"}SAMLResponse=${encodeURIComponent(SAMLResponse)}&RelayState=${oAuthParams.relayState}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
Setting.checkLoginMfa(res, body, oAuthParams, handleLogin, this, window.location.origin);
|
||||
} else {
|
||||
this.setState({
|
||||
msg: res.msg,
|
||||
@ -220,6 +229,11 @@ class AuthCallback extends React.Component {
|
||||
return <RedirectForm samlResponse={this.state.samlResponse} redirectUrl={this.state.redirectUrl} relayState={this.state.relayState} />;
|
||||
}
|
||||
|
||||
if (this.state.getVerifyTotp !== undefined) {
|
||||
const application = Setting.getApplicationObj(this);
|
||||
return renderLoginPanel(application, this.state.getVerifyTotp, this);
|
||||
}
|
||||
|
||||
return (
|
||||
<div style={{display: "flex", justifyContent: "center", alignItems: "center"}}>
|
||||
{
|
||||
|
@ -264,6 +264,9 @@ class ForgetPage extends React.Component {
|
||||
)
|
||||
}
|
||||
onValuesChange={(changedValues, allValues) => {
|
||||
if (!changedValues.dest) {
|
||||
return;
|
||||
}
|
||||
const verifyType = changedValues.dest?.indexOf("@") === -1 ? "phone" : "email";
|
||||
this.setState({
|
||||
dest: changedValues.dest,
|
||||
|
@ -34,10 +34,9 @@ import {SendCodeInput} from "../common/SendCodeInput";
|
||||
import LanguageSelect from "../common/select/LanguageSelect";
|
||||
import {CaptchaModal, CaptchaRule} from "../common/modal/CaptchaModal";
|
||||
import RedirectForm from "../common/RedirectForm";
|
||||
import {MfaAuthVerifyForm, NextMfa, RequiredMfa} from "./mfa/MfaAuthVerifyForm";
|
||||
import {RequiredMfa} from "./mfa/MfaAuthVerifyForm";
|
||||
import {GoogleOneTapLoginVirtualButton} from "./GoogleLoginButton";
|
||||
import * as ProviderButton from "./ProviderButton";
|
||||
import {EmailMfaType, SmsMfaType, TotpMfaType} from "./MfaSetupPage";
|
||||
const FaceRecognitionModal = lazy(() => import("../common/modal/FaceRecognitionModal"));
|
||||
|
||||
class LoginPage extends React.Component {
|
||||
@ -439,18 +438,7 @@ class LoginPage extends React.Component {
|
||||
};
|
||||
|
||||
if (res.status === "ok") {
|
||||
if (res.data === NextMfa) {
|
||||
this.setState({
|
||||
mfaProps: res.data2,
|
||||
selectedMfaProp: this.getPreferredMfaProp(res.data2),
|
||||
}, () => {
|
||||
this.setState({
|
||||
getVerifyTotp: () => this.renderMfaAuthVerifyForm(values, casParams, loginHandler),
|
||||
});
|
||||
});
|
||||
} else {
|
||||
loginHandler(res);
|
||||
}
|
||||
Setting.checkLoginMfa(res, values, casParams, loginHandler, this);
|
||||
} else {
|
||||
Setting.showMessage("error", `${i18next.t("application:Failed to sign in")}: ${res.msg}`);
|
||||
}
|
||||
@ -505,26 +493,7 @@ class LoginPage extends React.Component {
|
||||
};
|
||||
|
||||
if (res.status === "ok") {
|
||||
if (res.data === NextMfa) {
|
||||
this.setState({
|
||||
mfaProps: res.data2,
|
||||
selectedMfaProp: this.getPreferredMfaProp(res.data2),
|
||||
}, () => {
|
||||
this.setState({
|
||||
getVerifyTotp: () => this.renderMfaAuthVerifyForm(values, oAuthParams, loginHandler),
|
||||
});
|
||||
});
|
||||
} else if (res.data === "SelectPlan") {
|
||||
// paid-user does not have active or pending subscription, go to application default pricing page to select-plan
|
||||
const pricing = res.data2;
|
||||
Setting.goToLink(`/select-plan/${pricing.owner}/${pricing.name}?user=${values.username}`);
|
||||
} else if (res.data === "BuyPlanResult") {
|
||||
// paid-user has pending subscription, go to buy-plan/result apge to notify payment result
|
||||
const sub = res.data2;
|
||||
Setting.goToLink(`/buy-plan/${sub.owner}/${sub.pricing}/result?subscription=${sub.name}`);
|
||||
} else {
|
||||
loginHandler(res);
|
||||
}
|
||||
Setting.checkLoginMfa(res, values, oAuthParams, loginHandler, this);
|
||||
} else {
|
||||
Setting.showMessage("error", `${i18next.t("application:Failed to sign in")}: ${res.msg}`);
|
||||
}
|
||||
@ -532,49 +501,6 @@ class LoginPage extends React.Component {
|
||||
}
|
||||
}
|
||||
|
||||
renderMfaAuthVerifyForm(values, authParams, onSuccess) {
|
||||
return (
|
||||
<div>
|
||||
<MfaAuthVerifyForm
|
||||
mfaProps={this.state.selectedMfaProp}
|
||||
formValues={values}
|
||||
authParams={authParams}
|
||||
application={this.getApplicationObj()}
|
||||
onFail={(errorMessage) => {
|
||||
Setting.showMessage("error", errorMessage);
|
||||
}}
|
||||
onSuccess={(res) => onSuccess(res)}
|
||||
/>
|
||||
<div>
|
||||
{
|
||||
this.state.mfaProps.map((mfa) => {
|
||||
if (this.state.selectedMfaProp.mfaType === mfa.mfaType) {return null;}
|
||||
let mfaI18n = "";
|
||||
switch (mfa.mfaType) {
|
||||
case SmsMfaType: mfaI18n = i18next.t("mfa:Use SMS"); break;
|
||||
case TotpMfaType: mfaI18n = i18next.t("mfa:Use Authenticator App"); break ;
|
||||
case EmailMfaType: mfaI18n = i18next.t("mfa:Use Email") ;break;
|
||||
}
|
||||
return <div key={mfa.mfaType}><Button type={"link"} onClick={() => {
|
||||
this.setState({
|
||||
selectedMfaProp: mfa,
|
||||
});
|
||||
}}>{mfaI18n}</Button></div>;
|
||||
})
|
||||
}
|
||||
</div>
|
||||
</div>);
|
||||
}
|
||||
|
||||
getPreferredMfaProp(mfaProps) {
|
||||
for (const i in mfaProps) {
|
||||
if (mfaProps[i].isPreffered) {
|
||||
return mfaProps[i];
|
||||
}
|
||||
}
|
||||
return mfaProps[0];
|
||||
}
|
||||
|
||||
isProviderVisible(providerItem) {
|
||||
if (this.state.mode === "signup") {
|
||||
return Setting.isProviderVisibleForSignUp(providerItem);
|
||||
|
@ -20,6 +20,7 @@ import * as Util from "./Util";
|
||||
import * as Setting from "../Setting";
|
||||
import i18next from "i18next";
|
||||
import {authConfig} from "./Auth";
|
||||
import {renderLoginPanel} from "../Setting";
|
||||
|
||||
class SamlCallback extends React.Component {
|
||||
constructor(props) {
|
||||
@ -81,13 +82,26 @@ class SamlCallback extends React.Component {
|
||||
.then((res) => {
|
||||
if (res.status === "ok") {
|
||||
const responseType = this.getResponseType(redirectUri);
|
||||
if (responseType === "login") {
|
||||
Setting.showMessage("success", "Logged in successfully");
|
||||
Setting.goToLink("/");
|
||||
} else if (responseType === "code") {
|
||||
const code = res.data;
|
||||
Setting.goToLink(`${redirectUri}?code=${code}&state=${state}`);
|
||||
}
|
||||
const handleLogin = (res2) => {
|
||||
if (responseType === "login") {
|
||||
Setting.showMessage("success", "Logged in successfully");
|
||||
Setting.goToLink("/");
|
||||
} else if (responseType === "code") {
|
||||
const code = res2.data;
|
||||
Setting.goToLink(`${redirectUri}?code=${code}&state=${state}`);
|
||||
}
|
||||
};
|
||||
Setting.checkLoginMfa(res, body, {
|
||||
clientId: clientId,
|
||||
responseType: responseType,
|
||||
redirectUri: messages[3],
|
||||
state: state,
|
||||
nonce: "",
|
||||
scope: "read",
|
||||
challengeMethod: "",
|
||||
codeChallenge: "",
|
||||
type: "code",
|
||||
}, handleLogin, this);
|
||||
} else {
|
||||
this.setState({
|
||||
msg: res.msg,
|
||||
@ -97,6 +111,11 @@ class SamlCallback extends React.Component {
|
||||
}
|
||||
|
||||
render() {
|
||||
if (this.state.getVerifyTotp !== undefined) {
|
||||
const application = Setting.getApplicationObj(this);
|
||||
return renderLoginPanel(application, this.state.getVerifyTotp, this, window.location.origin);
|
||||
}
|
||||
|
||||
return (
|
||||
<div style={{display: "flex", justifyContent: "center", alignItems: "center"}}>
|
||||
{
|
||||
|
Reference in New Issue
Block a user