Compare commits

...

6 Commits

Author SHA1 Message Date
da69d94445 feat: fix the bug that spin in oauth is always showing (#1421) 2022-12-23 15:06:51 +08:00
b8b915abe1 feat: check AccessPermission in multiple permissions (#1420) 2022-12-23 14:06:02 +08:00
5d1548e989 feat: fix absolute URL redirection (#1419)
* fix: redirect to absolute url

* fix: original jump
2022-12-23 11:05:15 +08:00
a0dc6e06cd feat: add EntryPage for login, signup pages to fix background flashing issue (#1416)
* feat: fix flush in login Pages

* fix: code format

* fix: improve code

* Update App.js

* Update EntryPage.js

* fix: optimize api request

* Update App.js

* Update App.js

* fix: fix css

* fix: css and getApllicationObj

Co-authored-by: hsluoyz <hsluoyz@qq.com>
2022-12-22 23:39:02 +08:00
ae130788ec feat: add Line support as OAuth 3rd-party login (#1413) 2022-12-21 02:25:58 +08:00
f075d0fd74 Refactor out application.IsRedirectUriValid() 2022-12-21 00:35:33 +08:00
34 changed files with 319 additions and 215 deletions

View File

@ -170,7 +170,7 @@ func (c *ApiController) GetApplicationLogin() {
} }
func setHttpClient(idProvider idp.IdProvider, providerType string) { func setHttpClient(idProvider idp.IdProvider, providerType string) {
if providerType == "GitHub" || providerType == "Google" || providerType == "Facebook" || providerType == "LinkedIn" || providerType == "Steam" { if providerType == "GitHub" || providerType == "Google" || providerType == "Facebook" || providerType == "LinkedIn" || providerType == "Steam" || providerType == "Line" {
idProvider.SetHttpClient(proxy.ProxyHttpClient) idProvider.SetHttpClient(proxy.ProxyHttpClient)
} else { } else {
idProvider.SetHttpClient(proxy.DefaultHttpClient) idProvider.SetHttpClient(proxy.DefaultHttpClient)

View File

@ -261,7 +261,7 @@ func (c *ApiController) TokenLogout() {
flag, application := object.DeleteTokenByAccessToken(token) flag, application := object.DeleteTokenByAccessToken(token)
redirectUri := c.Input().Get("post_logout_redirect_uri") redirectUri := c.Input().Get("post_logout_redirect_uri")
state := c.Input().Get("state") state := c.Input().Get("state")
if application != nil && object.CheckRedirectUriValid(application, redirectUri) { if application != nil && application.IsRedirectUriValid(redirectUri) {
c.Ctx.Redirect(http.StatusFound, redirectUri+"?state="+state) c.Ctx.Redirect(http.StatusFound, redirectUri+"?state="+state)
return return
} }

View File

@ -98,7 +98,7 @@ func GetIdProvider(typ string, subType string, clientId string, clientSecret str
return nil return nil
} }
var gothList = []string{"Apple", "AzureAD", "Slack", "Steam"} var gothList = []string{"Apple", "AzureAD", "Slack", "Steam", "Line"}
func isGothSupport(provider string) bool { func isGothSupport(provider string) bool {
for _, value := range gothList { for _, value := range gothList {

View File

@ -16,7 +16,6 @@ package object
import ( import (
"fmt" "fmt"
"net/url"
"regexp" "regexp"
"strings" "strings"
@ -354,52 +353,26 @@ func (application *Application) GetId() string {
return fmt.Sprintf("%s/%s", application.Owner, application.Name) return fmt.Sprintf("%s/%s", application.Owner, application.Name)
} }
func CheckRedirectUriValid(application *Application, redirectUri string) bool { func (application *Application) IsRedirectUriValid(redirectUri string) bool {
validUri := false isValid := false
for _, tmpUri := range application.RedirectUris { for _, targetUri := range application.RedirectUris {
tmpUriRegex := regexp.MustCompile(tmpUri) targetUriRegex := regexp.MustCompile(targetUri)
if tmpUriRegex.MatchString(redirectUri) || strings.Contains(redirectUri, tmpUri) { if targetUriRegex.MatchString(redirectUri) || strings.Contains(redirectUri, targetUri) {
validUri = true isValid = true
break break
} }
} }
return validUri return isValid
} }
func IsAllowOrigin(origin string) bool { func IsOriginAllowed(origin string) bool {
allowOrigin := false applications := GetApplications("")
originUrl, err := url.Parse(origin) for _, application := range applications {
if err != nil { if application.IsRedirectUriValid(origin) {
return false return true
}
rows, err := adapter.Engine.Cols("redirect_uris").Rows(&Application{})
if err != nil {
panic(err)
}
application := Application{}
for rows.Next() {
err := rows.Scan(&application)
if err != nil {
panic(err)
}
for _, tmpRedirectUri := range application.RedirectUris {
u1, err := url.Parse(tmpRedirectUri)
if err != nil {
continue
}
if u1.Scheme == originUrl.Scheme && u1.Host == originUrl.Host {
allowOrigin = true
break
}
}
if allowOrigin {
break
} }
} }
return false
return allowOrigin
} }
func getApplicationMap(organization string) map[string]*Application { func getApplicationMap(organization string) map[string]*Application {

View File

@ -313,8 +313,9 @@ func CheckAccessPermission(userId string, application *Application) (bool, error
return true, err return true, err
} }
enforcer := getEnforcer(permission) enforcer := getEnforcer(permission)
allowed, err = enforcer.Enforce(userId, application.Name, "read") if allowed, err = enforcer.Enforce(userId, application.Name, "read"); allowed {
break return allowed, err
}
} }
} }
return allowed, err return allowed, err

View File

@ -240,8 +240,8 @@ func GetSamlResponse(application *Application, user *User, samlRequest string, h
} }
// verify samlRequest // verify samlRequest
if valid := CheckRedirectUriValid(application, authnRequest.Issuer.Url); !valid { if isValid := application.IsRedirectUriValid(authnRequest.Issuer.Url); !isValid {
return "", "", fmt.Errorf("err: invalid issuer url") return "", "", fmt.Errorf("err: Issuer URI: %s doesn't exist in the allowed Redirect URI list", authnRequest.Issuer.Url)
} }
// get certificate string // get certificate string

View File

@ -18,7 +18,6 @@ import (
"crypto/sha256" "crypto/sha256"
"encoding/base64" "encoding/base64"
"fmt" "fmt"
"strings"
"time" "time"
"github.com/casdoor/casdoor/i18n" "github.com/casdoor/casdoor/i18n"
@ -253,14 +252,7 @@ func CheckOAuthLogin(clientId string, responseType string, redirectUri string, s
return i18n.Translate(lang, "token:Invalid client_id"), nil return i18n.Translate(lang, "token:Invalid client_id"), nil
} }
validUri := false if !application.IsRedirectUriValid(redirectUri) {
for _, tmpUri := range application.RedirectUris {
if strings.Contains(redirectUri, tmpUri) {
validUri = true
break
}
}
if !validUri {
return fmt.Sprintf(i18n.Translate(lang, "token:Redirect URI: %s doesn't exist in the allowed Redirect URI list"), redirectUri), application return fmt.Sprintf(i18n.Translate(lang, "token:Redirect URI: %s doesn't exist in the allowed Redirect URI list"), redirectUri), application
} }

View File

@ -102,6 +102,7 @@ type User struct {
Bilibili string `xorm:"bilibili varchar(100)" json:"bilibili"` Bilibili string `xorm:"bilibili varchar(100)" json:"bilibili"`
Okta string `xorm:"okta varchar(100)" json:"okta"` Okta string `xorm:"okta varchar(100)" json:"okta"`
Douyin string `xorm:"douyin varchar(100)" json:"douyin"` Douyin string `xorm:"douyin varchar(100)" json:"douyin"`
Line string `xorm:"line varchar(100)" json:"line"`
Custom string `xorm:"custom varchar(100)" json:"custom"` Custom string `xorm:"custom varchar(100)" json:"custom"`
WebauthnCredentials []webauthn.Credential `xorm:"webauthnCredentials blob" json:"webauthnCredentials"` WebauthnCredentials []webauthn.Credential `xorm:"webauthnCredentials blob" json:"webauthnCredentials"`

View File

@ -34,7 +34,7 @@ func CorsFilter(ctx *context.Context) {
originConf := conf.GetConfigString("origin") originConf := conf.GetConfigString("origin")
if origin != "" && originConf != "" && origin != originConf { if origin != "" && originConf != "" && origin != originConf {
if object.IsAllowOrigin(origin) { if object.IsOriginAllowed(origin) {
ctx.Output.Header(headerAllowOrigin, origin) ctx.Output.Header(headerAllowOrigin, origin)
ctx.Output.Header(headerAllowMethods, "POST, GET, OPTIONS") ctx.Output.Header(headerAllowMethods, "POST, GET, OPTIONS")
ctx.Output.Header(headerAllowHeaders, "Content-Type, Authorization") ctx.Output.Header(headerAllowHeaders, "Content-Type, Authorization")

View File

@ -55,20 +55,14 @@ import CustomGithubCorner from "./CustomGithubCorner";
import * as Conf from "./Conf"; import * as Conf from "./Conf";
import * as Auth from "./auth/Auth"; import * as Auth from "./auth/Auth";
import SignupPage from "./auth/SignupPage"; import EntryPage from "./EntryPage";
import ResultPage from "./auth/ResultPage"; import ResultPage from "./auth/ResultPage";
import LoginPage from "./auth/LoginPage";
import SelfLoginPage from "./auth/SelfLoginPage";
import SelfForgetPage from "./auth/SelfForgetPage";
import ForgetPage from "./auth/ForgetPage";
import * as AuthBackend from "./auth/AuthBackend"; import * as AuthBackend from "./auth/AuthBackend";
import AuthCallback from "./auth/AuthCallback"; import AuthCallback from "./auth/AuthCallback";
import SelectLanguageBox from "./SelectLanguageBox"; import SelectLanguageBox from "./SelectLanguageBox";
import i18next from "i18next"; import i18next from "i18next";
import PromptPage from "./auth/PromptPage";
import OdicDiscoveryPage from "./auth/OidcDiscoveryPage"; import OdicDiscoveryPage from "./auth/OidcDiscoveryPage";
import SamlCallback from "./auth/SamlCallback"; import SamlCallback from "./auth/SamlCallback";
import CasLogout from "./auth/CasLogout";
import ModelListPage from "./ModelListPage"; import ModelListPage from "./ModelListPage";
import ModelEditPage from "./ModelEditPage"; import ModelEditPage from "./ModelEditPage";
import SystemInfo from "./SystemInfo"; import SystemInfo from "./SystemInfo";
@ -619,7 +613,7 @@ class App extends Component {
// https://www.freecodecamp.org/neyarnws/how-to-keep-your-footer-where-it-belongs-59c6aa05c59c/ // https://www.freecodecamp.org/neyarnws/how-to-keep-your-footer-where-it-belongs-59c6aa05c59c/
return ( return (
<> <React.Fragment>
{!this.state.account ? null : <div style={{display: "none"}} id="CasdoorApplicationName" value={this.state.account.signupApplication} />} {!this.state.account ? null : <div style={{display: "none"}} id="CasdoorApplicationName" value={this.state.account.signupApplication} />}
<Footer id="footer" style={ <Footer id="footer" style={
{ {
@ -630,63 +624,57 @@ class App extends Component {
}> }>
Powered by <a target="_blank" href="https://casdoor.org" rel="noreferrer"><img style={{paddingBottom: "3px"}} height={"20px"} alt={"Casdoor"} src={`${Setting.StaticBaseUrl}/img/casdoor-logo_1185x256.png`} /></a> Powered by <a target="_blank" href="https://casdoor.org" rel="noreferrer"><img style={{paddingBottom: "3px"}} height={"20px"} alt={"Casdoor"} src={`${Setting.StaticBaseUrl}/img/casdoor-logo_1185x256.png`} /></a>
</Footer> </Footer>
</> </React.Fragment>
); );
} }
isDoorPages() { isDoorPages() {
return this.isEntryPages() || window.location.pathname.startsWith("/callback");
}
isEntryPages() {
return window.location.pathname.startsWith("/signup") || return window.location.pathname.startsWith("/signup") ||
window.location.pathname.startsWith("/login") || window.location.pathname.startsWith("/login") ||
window.location.pathname.startsWith("/callback") ||
window.location.pathname.startsWith("/prompt") ||
window.location.pathname.startsWith("/forget") || window.location.pathname.startsWith("/forget") ||
window.location.pathname.startsWith("/cas"); window.location.pathname.startsWith("/prompt") ||
window.location.pathname.startsWith("/cas") ||
window.location.pathname.startsWith("/auto-signup");
} }
renderPage() { renderPage() {
if (this.isDoorPages()) { if (this.isDoorPages()) {
return ( return (
<> <React.Fragment>
<Layout id="parent-area"> <Layout id="parent-area">
<Content style={{display: "flex", justifyContent: "center"}}> <Content style={{display: "flex", justifyContent: "center"}}>
<Switch> {
<Route exact path="/signup" render={(props) => this.renderHomeIfLoggedIn(<SignupPage account={this.state.account} {...props} />)} /> this.isEntryPages() ?
<Route exact path="/signup/:applicationName" render={(props) => this.renderHomeIfLoggedIn(<SignupPage account={this.state.account} {...props} onUpdateAccount={(account) => {this.onUpdateAccount(account);}} />)} /> <EntryPage account={this.state.account} onUpdateAccount={(account) => {this.onUpdateAccount(account);}} />
<Route exact path="/login" render={(props) => this.renderHomeIfLoggedIn(<SelfLoginPage account={this.state.account} {...props} />)} /> :
<Route exact path="/login/:owner" render={(props) => this.renderHomeIfLoggedIn(<SelfLoginPage account={this.state.account} {...props} />)} /> <Switch>
<Route exact path="/auto-signup/oauth/authorize" render={(props) => <LoginPage account={this.state.account} type={"code"} mode={"signup"} {...props} onUpdateAccount={(account) => {this.onUpdateAccount(account);}} />} /> <Route exact path="/callback" component={AuthCallback} />
<Route exact path="/signup/oauth/authorize" render={(props) => <SignupPage account={this.state.account} {...props} onUpdateAccount={(account) => {this.onUpdateAccount(account);}} />} /> <Route exact path="/callback/saml" component={SamlCallback} />
<Route exact path="/login/oauth/authorize" render={(props) => <LoginPage account={this.state.account} type={"code"} mode={"signin"} {...props} onUpdateAccount={(account) => {this.onUpdateAccount(account);}} />} /> <Route path="" render={() => <Result status="404" title="404 NOT FOUND" subTitle={i18next.t("general:Sorry, the page you visited does not exist.")}
<Route exact path="/login/saml/authorize/:owner/:applicationName" render={(props) => <LoginPage account={this.state.account} type={"saml"} mode={"signin"} {...props} onUpdateAccount={(account) => {this.onUpdateAccount(account);}} />} /> extra={<a href="/"><Button type="primary">{i18next.t("general:Back Home")}</Button></a>} />} />
<Route exact path="/cas/:owner/:casApplicationName/logout" render={(props) => this.renderHomeIfLoggedIn(<CasLogout clearAccount={() => this.setState({account: null})} {...props} />)} /> </Switch>
<Route exact path="/cas/:owner/:casApplicationName/login" render={(props) => {return (<LoginPage type={"cas"} mode={"signup"} account={this.state.account} {...props} />);}} /> }
<Route exact path="/callback" component={AuthCallback} />
<Route exact path="/callback/saml" component={SamlCallback} />
<Route exact path="/forget" render={(props) => this.renderHomeIfLoggedIn(<SelfForgetPage {...props} />)} />
<Route exact path="/forget/:applicationName" render={(props) => this.renderHomeIfLoggedIn(<ForgetPage {...props} />)} />
<Route exact path="/prompt" render={(props) => this.renderLoginIfNotLoggedIn(<PromptPage account={this.state.account} {...props} />)} />
<Route exact path="/prompt/:applicationName" render={(props) => this.renderLoginIfNotLoggedIn(<PromptPage account={this.state.account} onUpdateAccount={(account) => {this.onUpdateAccount(account);}} {...props} />)} />
<Route exact path="/sysinfo" render={(props) => this.renderLoginIfNotLoggedIn(<SystemInfo {...props} />)} />
<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>
</Content> </Content>
{ {
this.renderFooter() this.renderFooter()
} }
</Layout> </Layout>
</> </React.Fragment>
); );
} }
return ( return (
<> <React.Fragment>
<FloatButton.BackTop /> <FloatButton.BackTop />
<CustomGithubCorner /> <CustomGithubCorner />
{ {
this.renderContent() this.renderContent()
} }
</> </React.Fragment>
); );
} }

84
web/src/EntryPage.js Normal file
View File

@ -0,0 +1,84 @@
// Copyright 2022 The Casdoor Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
import React from "react";
import {Redirect, Route, Switch} from "react-router-dom";
import {Spin} from "antd";
import i18next from "i18next";
import * as Setting from "./Setting";
import SignupPage from "./auth/SignupPage";
import SelfLoginPage from "./auth/SelfLoginPage";
import LoginPage from "./auth/LoginPage";
import SelfForgetPage from "./auth/SelfForgetPage";
import ForgetPage from "./auth/ForgetPage";
import PromptPage from "./auth/PromptPage";
import CasLogout from "./auth/CasLogout";
class EntryPage extends React.Component {
constructor(props) {
super(props);
this.state = {
application: undefined,
};
}
renderHomeIfLoggedIn(component) {
if (this.props.account !== null && this.props.account !== undefined) {
return <Redirect to="/" />;
} else {
return component;
}
}
renderLoginIfNotLoggedIn(component) {
if (this.props.account === null) {
sessionStorage.setItem("from", window.location.pathname);
return <Redirect to="/login" />;
} else if (this.props.account === undefined) {
return null;
} else {
return component;
}
}
render() {
const onUpdateApplication = (application) => {
this.setState({
application: application,
});
};
return <div className="loginBackground" style={{backgroundImage: Setting.inIframe() || Setting.isMobile() ? null : `url(${this.state.application?.formBackgroundUrl})`}}>
<Spin spinning={this.state.application === undefined} tip={i18next.t("login:Loading")} style={{margin: "0 auto"}} />
<Switch>
<Route exact path="/signup" render={(props) => this.renderHomeIfLoggedIn(<SignupPage {...this.props} application={this.state.application} onUpdateApplication={onUpdateApplication} {...props} />)} />
<Route exact path="/signup/:applicationName" render={(props) => this.renderHomeIfLoggedIn(<SignupPage {...this.props} application={this.state.application} onUpdateApplication={onUpdateApplication} {...props} />)} />
<Route exact path="/login" render={(props) => this.renderHomeIfLoggedIn(<SelfLoginPage {...this.props} application={this.state.application} onUpdateApplication={onUpdateApplication} {...props} />)} />
<Route exact path="/login/:owner" render={(props) => this.renderHomeIfLoggedIn(<SelfLoginPage {...this.props} application={this.state.application} onUpdateApplication={onUpdateApplication} {...props} />)} />
<Route exact path="/auto-signup/oauth/authorize" render={(props) => <LoginPage {...this.props} application={this.state.application} type={"code"} mode={"signup"} onUpdateApplication={onUpdateApplication}{...props} />} />
<Route exact path="/signup/oauth/authorize" render={(props) => <SignupPage {...this.props} application={this.state.application} onUpdateApplication={onUpdateApplication} {...props} />} />
<Route exact path="/login/oauth/authorize" render={(props) => <LoginPage {...this.props} application={this.state.application} type={"code"} mode={"signin"} onUpdateApplication={onUpdateApplication} {...props} />} />
<Route exact path="/login/saml/authorize/:owner/:applicationName" render={(props) => <LoginPage {...this.props} application={this.state.application} type={"saml"} mode={"signin"} onUpdateApplication={onUpdateApplication} {...props} />} />
<Route exact path="/forget" render={(props) => this.renderHomeIfLoggedIn(<SelfForgetPage {...this.props} application={this.state.application} onUpdateApplication={onUpdateApplication} {...props} />)} />
<Route exact path="/forget/:applicationName" render={(props) => this.renderHomeIfLoggedIn(<ForgetPage {...this.props} application={this.state.application} onUpdateApplication={onUpdateApplication} {...props} />)} />
<Route exact path="/prompt" render={(props) => this.renderLoginIfNotLoggedIn(<PromptPage {...this.props} application={this.state.application} onUpdateApplication={onUpdateApplication} {...props} />)} />
<Route exact path="/prompt/:applicationName" render={(props) => this.renderLoginIfNotLoggedIn(<PromptPage {...this.props} application={this.state.application} onUpdateApplication={onUpdateApplication} {...props} />)} />
<Route exact path="/cas/:owner/:casApplicationName/logout" render={(props) => this.renderHomeIfLoggedIn(<CasLogout {...this.props} application={this.state.application} {...props} />)} />
<Route exact path="/cas/:owner/:casApplicationName/login" render={(props) => {return (<LoginPage {...this.props} application={this.state.application} type={"cas"} mode={"signup"} {...props} />);}} />
</Switch>
</div>;
}
}
export default EntryPage;

View File

@ -681,6 +681,7 @@ export function getProviderTypeOptions(category) {
{id: "Bilibili", name: "Bilibili"}, {id: "Bilibili", name: "Bilibili"},
{id: "Okta", name: "Okta"}, {id: "Okta", name: "Okta"},
{id: "Douyin", name: "Douyin"}, {id: "Douyin", name: "Douyin"},
{id: "Line", name: "Line"},
{id: "Custom", name: "Custom"}, {id: "Custom", name: "Custom"},
] ]
); );
@ -798,6 +799,9 @@ export function renderLoginLink(application, text) {
export function redirectToLoginPage(application, history) { export function redirectToLoginPage(application, history) {
const loginLink = getLoginLink(application); const loginLink = getLoginLink(application);
if (loginLink.indexOf("http") === 0 || loginLink.indexOf("https") === 0) {
openLink(loginLink);
}
history.push(loginLink); history.push(loginLink);
} }

View File

@ -178,7 +178,7 @@ class AuthCallback extends React.Component {
render() { render() {
return ( return (
<div style={{textAlign: "center"}}> <div style={{display: "flex", justifyContent: "center", alignItems: "center"}}>
{ {
(this.state.msg === null) ? ( (this.state.msg === null) ? (
<Spin size="large" tip={i18next.t("login:Signing in...")} style={{paddingTop: "10%"}} /> <Spin size="large" tip={i18next.t("login:Signing in...")} style={{paddingTop: "10%"}} />

View File

@ -13,7 +13,7 @@
// limitations under the License. // limitations under the License.
import React from "react"; import React from "react";
import {Spin} from "antd"; import {Card, Spin} from "antd";
import {withRouter} from "react-router-dom"; import {withRouter} from "react-router-dom";
import * as AuthBackend from "./AuthBackend"; import * as AuthBackend from "./AuthBackend";
import * as Setting from "../Setting"; import * as Setting from "../Setting";
@ -39,7 +39,7 @@ class CasLogout extends React.Component {
.then((res) => { .then((res) => {
if (res.status === "ok") { if (res.status === "ok") {
Setting.showMessage("success", "Logged out successfully"); Setting.showMessage("success", "Logged out successfully");
this.props.clearAccount(); this.props.onUpdateAccount(null);
const redirectUri = res.data2; const redirectUri = res.data2;
if (redirectUri !== null && redirectUri !== undefined && redirectUri !== "") { if (redirectUri !== null && redirectUri !== undefined && redirectUri !== "") {
Setting.goToLink(redirectUri); Setting.goToLink(redirectUri);
@ -57,11 +57,13 @@ class CasLogout extends React.Component {
render() { render() {
return ( return (
<div style={{textAlign: "center"}}> <Card>
{ <div style={{display: "flex", justifyContent: "center", alignItems: "center"}}>
<Spin size="large" tip={i18next.t("login:Logging out...")} style={{paddingTop: "10%"}} /> {
} <Spin size="large" tip={i18next.t("login:Logging out...")} style={{paddingTop: "10%"}} />
</div> }
</div>
</Card>
); );
} }
} }

View File

@ -34,12 +34,7 @@ class ForgetPage extends React.Component {
this.state = { this.state = {
classes: props, classes: props,
account: props.account, account: props.account,
applicationName: applicationName: props.applicationName ?? props.match.params?.applicationName,
props.applicationName !== undefined
? props.applicationName
: props.match === undefined
? null
: props.match.params.applicationName,
application: null, application: null,
msg: null, msg: null,
userId: "", userId: "",
@ -57,34 +52,36 @@ class ForgetPage extends React.Component {
}; };
} }
UNSAFE_componentWillMount() { componentDidMount() {
if (this.state.applicationName !== undefined) { if (this.getApplicationObj() === null) {
this.getApplication(); if (this.state.applicationName !== undefined) {
} else { this.getApplication();
Setting.showMessage("error", i18next.t("forget:Unknown forget type: ") + this.state.type); } else {
Setting.showMessage("error", i18next.t("forget:Unknown forget type: ") + this.state.type);
}
} }
} }
getApplication() { getApplication() {
if (this.state.applicationName === null) { if (this.state.applicationName === undefined) {
return; return;
} }
ApplicationBackend.getApplication("admin", this.state.applicationName).then( ApplicationBackend.getApplication("admin", this.state.applicationName)
(application) => { .then((application) => {
this.onUpdateApplication(application);
this.setState({ this.setState({
application: application, application: application,
}); });
} });
);
} }
getApplicationObj() { getApplicationObj() {
if (this.props.application !== undefined) { return this.props.application ?? this.state.application;
return this.props.application; }
} else {
return this.state.application; onUpdateApplication(application) {
} this.props.onUpdateApplication(application);
} }
onFormFinish(name, info, forms) { onFormFinish(name, info, forms) {
@ -143,7 +140,7 @@ class ForgetPage extends React.Component {
username: this.state.username, username: this.state.username,
name: this.state.name, name: this.state.name,
code: forms.step2.getFieldValue("emailCode"), code: forms.step2.getFieldValue("emailCode"),
phonePrefix: this.state.application?.organizationObj.phonePrefix, phonePrefix: this.getApplicationObj()?.organizationObj.phonePrefix,
type: "login", type: "login",
}, oAuthParams).then(res => { }, oAuthParams).then(res => {
if (res.status === "ok") { if (res.status === "ok") {
@ -166,10 +163,10 @@ class ForgetPage extends React.Component {
onFinish(values) { onFinish(values) {
values.username = this.state.username; values.username = this.state.username;
values.userOwner = this.state.application?.organizationObj.name; values.userOwner = this.getApplicationObj()?.organizationObj.name;
UserBackend.setPassword(values.userOwner, values.username, "", values?.newPassword).then(res => { UserBackend.setPassword(values.userOwner, values.username, "", values?.newPassword).then(res => {
if (res.status === "ok") { if (res.status === "ok") {
Setting.redirectToLoginPage(this.state.application, this.props.history); Setting.redirectToLoginPage(this.getApplicationObj(), this.props.history);
} else { } else {
Setting.showMessage("error", i18next.t(`signup:${res.msg}`)); Setting.showMessage("error", i18next.t(`signup:${res.msg}`));
} }
@ -356,14 +353,14 @@ class ForgetPage extends React.Component {
<CountDownInput <CountDownInput
disabled={this.state.username === "" || this.state.verifyType === ""} disabled={this.state.username === "" || this.state.verifyType === ""}
method={"forget"} method={"forget"}
onButtonClickArgs={[this.state.email, "email", Setting.getApplicationName(this.state.application), this.state.name]} onButtonClickArgs={[this.state.email, "email", Setting.getApplicationName(this.getApplicationObj()), this.state.name]}
application={application} application={application}
/> />
) : ( ) : (
<CountDownInput <CountDownInput
disabled={this.state.username === "" || this.state.verifyType === ""} disabled={this.state.username === "" || this.state.verifyType === ""}
method={"forget"} method={"forget"}
onButtonClickArgs={[this.state.phone, "phone", Setting.getApplicationName(this.state.application), this.state.name]} onButtonClickArgs={[this.state.phone, "phone", Setting.getApplicationName(this.getApplicationObj()), this.state.name]}
application={application} application={application}
/> />
)} )}
@ -492,7 +489,7 @@ class ForgetPage extends React.Component {
} }
return ( return (
<div className="loginBackground" style={{backgroundImage: Setting.inIframe() || Setting.isMobile() ? null : `url(${application.formBackgroundUrl})`}}> <React.Fragment>
<CustomGithubCorner /> <CustomGithubCorner />
<div className="forget-content" style={{padding: Setting.isMobile() ? "0" : null, boxShadow: Setting.isMobile() ? "none" : null}}> <div className="forget-content" style={{padding: Setting.isMobile() ? "0" : null, boxShadow: Setting.isMobile() ? "none" : null}}>
<Row> <Row>
@ -550,7 +547,7 @@ class ForgetPage extends React.Component {
</Col> </Col>
</Row> </Row>
</div> </div>
</div> </React.Fragment>
); );
} }
} }

View File

@ -0,0 +1,32 @@
// Copyright 2021 The Casdoor Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
import {createButton} from "react-social-login-buttons";
import {StaticBaseUrl} from "../Setting";
function Icon({width = 24, height = 24, color}) {
return <img src={`${StaticBaseUrl}/buttons/line.svg`} alt="Sign in with Line" style={{width: 24, height: 24}} />;
}
const config = {
text: "Sign in with Line",
icon: Icon,
iconFormat: name => `fa fa-${name}`,
style: {background: "#ffffff", color: "#000000"},
activeStyle: {background: "#ededee"},
};
const LineLoginButton = createButton(config);
export default LineLoginButton;

View File

@ -61,19 +61,19 @@ class LoginPage extends React.Component {
} }
} }
UNSAFE_componentWillMount() {
if (this.state.type === "login" || this.state.type === "cas") {
this.getApplication();
} else if (this.state.type === "code") {
this.getApplicationLogin();
} else if (this.state.type === "saml") {
this.getSamlApplication();
} else {
Setting.showMessage("error", `Unknown authentication type: ${this.state.type}`);
}
}
componentDidMount() { componentDidMount() {
if (this.getApplicationObj() === null) {
if (this.state.type === "login" || this.state.type === "cas") {
this.getApplication();
} else if (this.state.type === "code") {
this.getApplicationLogin();
} else if (this.state.type === "saml") {
this.getSamlApplication();
} else {
Setting.showMessage("error", `Unknown authentication type: ${this.state.type}`);
}
}
Setting.Countries.forEach((country) => { Setting.Countries.forEach((country) => {
new Image().src = `${Setting.StaticBaseUrl}/flag-icons/${country.country}.svg`; new Image().src = `${Setting.StaticBaseUrl}/flag-icons/${country.country}.svg`;
}); });
@ -96,6 +96,7 @@ class LoginPage extends React.Component {
AuthBackend.getApplicationLogin(oAuthParams) AuthBackend.getApplicationLogin(oAuthParams)
.then((res) => { .then((res) => {
if (res.status === "ok") { if (res.status === "ok") {
this.onUpdateApplication(res.data);
this.setState({ this.setState({
application: res.data, application: res.data,
}); });
@ -117,6 +118,7 @@ class LoginPage extends React.Component {
if (this.state.owner === null || this.state.owner === undefined || this.state.owner === "") { if (this.state.owner === null || this.state.owner === undefined || this.state.owner === "") {
ApplicationBackend.getApplication("admin", this.state.applicationName) ApplicationBackend.getApplication("admin", this.state.applicationName)
.then((application) => { .then((application) => {
this.onUpdateApplication(application);
this.setState({ this.setState({
application: application, application: application,
}); });
@ -125,6 +127,7 @@ class LoginPage extends React.Component {
OrganizationBackend.getDefaultApplication("admin", this.state.owner) OrganizationBackend.getDefaultApplication("admin", this.state.owner)
.then((res) => { .then((res) => {
if (res.status === "ok") { if (res.status === "ok") {
this.onUpdateApplication(res.data);
this.setState({ this.setState({
application: res.data, application: res.data,
applicationName: res.data.name, applicationName: res.data.name,
@ -142,25 +145,25 @@ class LoginPage extends React.Component {
} }
ApplicationBackend.getApplication(this.state.owner, this.state.applicationName) ApplicationBackend.getApplication(this.state.owner, this.state.applicationName)
.then((application) => { .then((application) => {
this.onUpdateApplication(application);
this.setState({ this.setState({
application: application, application: application,
}); });
} });
);
} }
getApplicationObj() { getApplicationObj() {
if (this.props.application !== undefined) { return this.props.application ?? this.state.application;
return this.props.application;
} else {
return this.state.application;
}
} }
onUpdateAccount(account) { onUpdateAccount(account) {
this.props.onUpdateAccount(account); this.props.onUpdateAccount(account);
} }
onUpdateApplication(application) {
this.props.onUpdateApplication(application);
}
parseOffset(offset) { parseOffset(offset) {
if (offset === 2 || offset === 4 || Setting.inIframe() || Setting.isMobile()) { if (offset === 2 || offset === 4 || Setting.inIframe() || Setting.isMobile()) {
return "0 auto"; return "0 auto";
@ -191,8 +194,8 @@ class LoginPage extends React.Component {
values["relayState"] = oAuthParams.relayState; values["relayState"] = oAuthParams.relayState;
} }
if (this.state.application.organization !== null && this.state.application.organization !== undefined) { if (this.getApplicationObj()?.organization) {
values["organization"] = this.state.application.organization; values["organization"] = this.getApplicationObj().organization;
} }
} }
postCodeLoginAction(res) { postCodeLoginAction(res) {
@ -573,12 +576,12 @@ class LoginPage extends React.Component {
<span style={{float: "right"}}> <span style={{float: "right"}}>
{ {
!application.enableSignUp ? null : ( !application.enableSignUp ? null : (
<> <React.Fragment>
{i18next.t("login:No account?")}&nbsp; {i18next.t("login:No account?")}&nbsp;
{ {
Setting.renderSignupLink(application, i18next.t("login:sign up now")) Setting.renderSignupLink(application, i18next.t("login:sign up now"))
} }
</> </React.Fragment>
) )
} }
</span> </span>
@ -788,14 +791,14 @@ class LoginPage extends React.Component {
if (this.props.application === undefined && !application.enablePassword && visibleOAuthProviderItems.length === 1) { if (this.props.application === undefined && !application.enablePassword && visibleOAuthProviderItems.length === 1) {
Setting.goToLink(Provider.getAuthUrl(application, visibleOAuthProviderItems[0].provider, "signup")); Setting.goToLink(Provider.getAuthUrl(application, visibleOAuthProviderItems[0].provider, "signup"));
return ( return (
<div style={{textAlign: "center"}}> <div style={{display: "flex", justifyContent: "center", alignItems: "center"}}>
<Spin size="large" tip={i18next.t("login:Signing in...")} style={{paddingTop: "10%"}} /> <Spin size="large" tip={i18next.t("login:Signing in...")} style={{paddingTop: "10%"}} />
</div> </div>
); );
} }
return ( return (
<div className="loginBackground" style={{backgroundImage: Setting.inIframe() || Setting.isMobile() ? null : `url(${application.formBackgroundUrl})`}}> <React.Fragment>
<CustomGithubCorner /> <CustomGithubCorner />
<div className="login-content" style={{margin: this.parseOffset(application.formOffset)}}> <div className="login-content" style={{margin: this.parseOffset(application.formOffset)}}>
{Setting.inIframe() || Setting.isMobile() ? null : <div dangerouslySetInnerHTML={{__html: application.formCss}} />} {Setting.inIframe() || Setting.isMobile() ? null : <div dangerouslySetInnerHTML={{__html: application.formCss}} />}
@ -827,7 +830,7 @@ class LoginPage extends React.Component {
</div> </div>
</div> </div>
</div> </div>
</div> </React.Fragment>
); );
} }
} }

View File

@ -13,7 +13,7 @@
// limitations under the License. // limitations under the License.
import React from "react"; import React from "react";
import {Button, Col, Result, Row} from "antd"; import {Button, Card, Col, Result, Row} from "antd";
import * as ApplicationBackend from "../backend/ApplicationBackend"; import * as ApplicationBackend from "../backend/ApplicationBackend";
import * as UserBackend from "../backend/UserBackend"; import * as UserBackend from "../backend/UserBackend";
import * as AuthBackend from "./AuthBackend"; import * as AuthBackend from "./AuthBackend";
@ -30,7 +30,7 @@ class PromptPage extends React.Component {
this.state = { this.state = {
classes: props, classes: props,
type: props.type, type: props.type,
applicationName: props.applicationName !== undefined ? props.applicationName : (props.match === undefined ? null : props.match.params.applicationName), applicationName: props.applicationName ?? (props.match === undefined ? null : props.match.params.applicationName),
application: null, application: null,
user: null, user: null,
}; };
@ -38,7 +38,9 @@ class PromptPage extends React.Component {
UNSAFE_componentWillMount() { UNSAFE_componentWillMount() {
this.getUser(); this.getUser();
this.getApplication(); if (this.getApplicationObj() === null) {
this.getApplication();
}
} }
getUser() { getUser() {
@ -59,6 +61,7 @@ class PromptPage extends React.Component {
ApplicationBackend.getApplication("admin", this.state.applicationName) ApplicationBackend.getApplication("admin", this.state.applicationName)
.then((application) => { .then((application) => {
this.onUpdateApplication(application);
this.setState({ this.setState({
application: application, application: application,
}); });
@ -66,11 +69,11 @@ class PromptPage extends React.Component {
} }
getApplicationObj() { getApplicationObj() {
if (this.props.application !== undefined) { return this.props.application ?? this.state.application;
return this.props.application; }
} else {
return this.state.application; onUpdateApplication(application) {
} this.props.onUpdateApplication(application);
} }
parseUserField(key, value) { parseUserField(key, value) {
@ -122,7 +125,7 @@ class PromptPage extends React.Component {
renderContent(application) { renderContent(application) {
return ( return (
<div style={{width: "400px"}}> <div style={{width: "500px"}}>
{ {
this.renderAffiliation(application) this.renderAffiliation(application)
} }
@ -247,9 +250,9 @@ class PromptPage extends React.Component {
} }
return ( return (
<Row> <div style={{display: "flex", flex: "1", justifyContent: "center"}}>
<Col span={24} style={{display: "flex", justifyContent: "center"}}> <Card>
<div style={{marginTop: "80px", marginBottom: "50px", textAlign: "center"}}> <div style={{marginTop: "30px", marginBottom: "30px", textAlign: "center"}}>
{ {
Setting.renderHelmet(application) Setting.renderHelmet(application)
} }
@ -259,16 +262,12 @@ class PromptPage extends React.Component {
{ {
this.renderContent(application) this.renderContent(application)
} }
<Row style={{margin: 10}}>
<Col span={18}>
</Col>
</Row>
<div style={{marginTop: "50px"}}> <div style={{marginTop: "50px"}}>
<Button disabled={!Setting.isPromptAnswered(this.state.user, application)} type="primary" size="large" onClick={() => {this.submitUserEdit(true);}}>{i18next.t("code:Submit and complete")}</Button> <Button disabled={!Setting.isPromptAnswered(this.state.user, application)} type="primary" size="large" onClick={() => {this.submitUserEdit(true);}}>{i18next.t("code:Submit and complete")}</Button>
</div> </div>
</div> </div>
</Col> </Card>
</Row> </div>
); );
} }
} }

View File

@ -121,6 +121,10 @@ const authInfo = {
Bilibili: { Bilibili: {
endpoint: "https://passport.bilibili.com/register/pc_oauth2.html", endpoint: "https://passport.bilibili.com/register/pc_oauth2.html",
}, },
Line: {
scope: "profile%20openid%20email",
endpoint: "https://access.line.me/oauth2/v2.1/authorize",
},
}; };
export function getProviderUrl(provider) { export function getProviderUrl(provider) {
@ -256,5 +260,7 @@ export function getAuthUrl(application, provider, method) {
return `${provider.customAuthUrl}?client_id=${provider.clientId}&redirect_uri=${redirectUri}&scope=${provider.customScope}&response_type=code&state=${state}`; return `${provider.customAuthUrl}?client_id=${provider.clientId}&redirect_uri=${redirectUri}&scope=${provider.customScope}&response_type=code&state=${state}`;
} else if (provider.type === "Bilibili") { } else if (provider.type === "Bilibili") {
return `${endpoint}#/?client_id=${provider.clientId}&return_url=${redirectUri}&state=${state}&response_type=code`; return `${endpoint}#/?client_id=${provider.clientId}&return_url=${redirectUri}&state=${state}&response_type=code`;
} else if (provider.type === "Line") {
return `${endpoint}?client_id=${provider.clientId}&redirect_uri=${redirectUri}&state=${state}&response_type=code&scope=${scope}`;
} }
} }

View File

@ -39,6 +39,7 @@ import SteamLoginButton from "./SteamLoginButton";
import BilibiliLoginButton from "./BilibiliLoginButton"; import BilibiliLoginButton from "./BilibiliLoginButton";
import OktaLoginButton from "./OktaLoginButton"; import OktaLoginButton from "./OktaLoginButton";
import DouyinLoginButton from "./DouyinLoginButton"; import DouyinLoginButton from "./DouyinLoginButton";
import LineLoginButton from "./LineLoginButton";
import * as AuthBackend from "./AuthBackend"; import * as AuthBackend from "./AuthBackend";
import {getEvent} from "./Util"; import {getEvent} from "./Util";
import {Modal} from "antd"; import {Modal} from "antd";
@ -93,6 +94,8 @@ function getSigninButton(type) {
return <OktaLoginButton text={text} align={"center"} />; return <OktaLoginButton text={text} align={"center"} />;
} else if (type === "Douyin") { } else if (type === "Douyin") {
return <DouyinLoginButton text={text} align={"center"} />; return <DouyinLoginButton text={text} align={"center"} />;
} else if (type === "Line") {
return <LineLoginButton text={text} align={"center"} />;
} }
return text; return text;

View File

@ -95,7 +95,7 @@ class SamlCallback extends React.Component {
render() { render() {
return ( return (
<div style={{textAlign: "center"}}> <div style={{display: "flex", justifyContent: "center", alignItems: "center"}}>
{ {
(this.state.msg === null) ? ( (this.state.msg === null) ? (
<Spin size="large" tip={i18next.t("login:Signing in...")} style={{paddingTop: "10%"}} /> <Spin size="large" tip={i18next.t("login:Signing in...")} style={{paddingTop: "10%"}} />

View File

@ -22,7 +22,6 @@ class SelfForgetPage extends React.Component {
<ForgetPage <ForgetPage
type={"forgotPassword"} type={"forgotPassword"}
applicationName={authConfig.appName} applicationName={authConfig.appName}
account={this.props.account}
{...this.props} {...this.props}
/> />
); );

View File

@ -19,7 +19,7 @@ import {authConfig} from "./Auth";
class SelfLoginPage extends React.Component { class SelfLoginPage extends React.Component {
render() { render() {
return ( return (
<LoginPage type={"login"} mode={"signin"} applicationName={authConfig.appName} account={this.props.account} {...this.props} /> <LoginPage type={"login"} mode={"signin"} applicationName={authConfig.appName} {...this.props} />
); );
} }
} }

View File

@ -65,7 +65,7 @@ class SignupPage extends React.Component {
super(props); super(props);
this.state = { this.state = {
classes: props, classes: props,
applicationName: props.match?.params.applicationName !== undefined ? props.match.params.applicationName : authConfig.appName, applicationName: props.match.params?.applicationName ?? authConfig.appName,
application: null, application: null,
email: "", email: "",
phone: "", phone: "",
@ -91,10 +91,12 @@ class SignupPage extends React.Component {
sessionStorage.setItem("signinUrl", signinUrl); sessionStorage.setItem("signinUrl", signinUrl);
} }
if (applicationName !== undefined) { if (this.getApplicationObj() === null) {
this.getApplication(applicationName); if (applicationName !== undefined) {
} else { this.getApplication(applicationName);
Setting.showMessage("error", `Unknown application name: ${applicationName}`); } else {
Setting.showMessage("error", `Unknown application name: ${applicationName}`);
}
} }
} }
@ -105,6 +107,7 @@ class SignupPage extends React.Component {
ApplicationBackend.getApplication("admin", applicationName) ApplicationBackend.getApplication("admin", applicationName)
.then((application) => { .then((application) => {
this.onUpdateApplication(application);
this.setState({ this.setState({
application: application, application: application,
}); });
@ -128,11 +131,7 @@ class SignupPage extends React.Component {
} }
getApplicationObj() { getApplicationObj() {
if (this.props.application !== undefined) { return this.props.application ?? this.state.application;
return this.props.application;
} else {
return this.state.application;
}
} }
getTermsofuseContent(url) { getTermsofuseContent(url) {
@ -149,6 +148,10 @@ class SignupPage extends React.Component {
this.props.onUpdateAccount(account); this.props.onUpdateAccount(account);
} }
onUpdateApplication(application) {
this.props.onUpdateApplication(application);
}
parseOffset(offset) { parseOffset(offset) {
if (offset === 2 || offset === 4 || Setting.inIframe() || Setting.isMobile()) { if (offset === 2 || offset === 4 || Setting.inIframe() || Setting.isMobile()) {
return "0 auto"; return "0 auto";
@ -632,7 +635,7 @@ class SignupPage extends React.Component {
} }
return ( return (
<div className="loginBackground" style={{backgroundImage: Setting.inIframe() || Setting.isMobile() ? null : `url(${application.formBackgroundUrl})`}}> <React.Fragment>
<CustomGithubCorner /> <CustomGithubCorner />
<div className="login-content" style={{margin: this.parseOffset(application.formOffset)}}> <div className="login-content" style={{margin: this.parseOffset(application.formOffset)}}>
{Setting.inIframe() || Setting.isMobile() ? null : <div dangerouslySetInnerHTML={{__html: application.formCss}} />} {Setting.inIframe() || Setting.isMobile() ? null : <div dangerouslySetInnerHTML={{__html: application.formCss}} />}
@ -657,7 +660,7 @@ class SignupPage extends React.Component {
{ {
this.renderModal() this.renderModal()
} }
</div> </React.Fragment>
); );
} }
} }

View File

@ -147,7 +147,7 @@ class OAuthWidget extends React.Component {
</Col> </Col>
<Col span={24 - this.props.labelSpan} > <Col span={24 - this.props.labelSpan} >
<img style={{marginRight: "10px"}} width={30} height={30} src={avatarUrl} alt={name} referrerPolicy="no-referrer" /> <img style={{marginRight: "10px"}} width={30} height={30} src={avatarUrl} alt={name} referrerPolicy="no-referrer" />
<span style={{width: this.props.labelSpan === 3 ? "300px" : "130px", display: (Setting.isMobile()) ? "inline" : "inline-block"}}> <span style={{width: this.props.labelSpan === 3 ? "300px" : "200px", display: (Setting.isMobile()) ? "inline" : "inline-block"}}>
{ {
linkedValue === "" ? ( linkedValue === "" ? (
"(empty)" "(empty)"

View File

@ -293,14 +293,15 @@ class PolicyTable extends React.Component {
} }
render() { render() {
return (<> return (
<Button type="primary" disabled={this.state.editingIndex !== ""} onClick={() => {this.synPolicies();}}> <React.Fragment>
{i18next.t("adapter:Sync")} <Button type="primary" disabled={this.state.editingIndex !== ""} onClick={() => {this.synPolicies();}}>
</Button> {i18next.t("adapter:Sync")}
{ </Button>
this.renderTable(this.state.policyLists) {
} this.renderTable(this.state.policyLists)
</> }
</React.Fragment>
); );
} }
} }

View File

@ -13,6 +13,7 @@
// limitations under the License. // limitations under the License.
import React, {useEffect} from "react"; import React, {useEffect} from "react";
import i18next from "i18next";
export const RedirectForm = (props) => { export const RedirectForm = (props) => {
@ -20,23 +21,24 @@ export const RedirectForm = (props) => {
document.getElementById("saml").submit(); document.getElementById("saml").submit();
}, []); }, []);
return (<> return (
<p>Redirecting, please wait.</p> <React.Fragment>
<form id="saml" method="post" action={props.redirectUrl}> <p>{i18next.t("login:Redirecting, please wait.")}</p>
<input <form id="saml" method="post" action={props.redirectUrl}>
type="hidden" <input
name="SAMLResponse" type="hidden"
id="samlResponse" name="SAMLResponse"
value={props.samlResponse} id="samlResponse"
/> value={props.samlResponse}
<input />
type="hidden" <input
name="RelayState" type="hidden"
id="relayState" name="RelayState"
value={props.relayState} id="relayState"
/> value={props.relayState}
</form> />
</> </form>
</React.Fragment>
); );
}; };

View File

@ -299,6 +299,7 @@
"Continue with": "Weiter mit", "Continue with": "Weiter mit",
"Email or phone": "E-Mail oder Telefon", "Email or phone": "E-Mail oder Telefon",
"Forgot password?": "Passwort vergessen?", "Forgot password?": "Passwort vergessen?",
"Loading": "Loading",
"Logging out...": "Logging out...", "Logging out...": "Logging out...",
"No account?": "Kein Konto?", "No account?": "Kein Konto?",
"Or sign in with another account": "Oder melden Sie sich mit einem anderen Konto an", "Or sign in with another account": "Oder melden Sie sich mit einem anderen Konto an",
@ -308,6 +309,7 @@
"Please input your password!": "Bitte geben Sie Ihr Passwort ein!", "Please input your password!": "Bitte geben Sie Ihr Passwort ein!",
"Please input your password, at least 6 characters!": "Bitte geben Sie Ihr Passwort ein, mindestens 6 Zeichen!", "Please input your password, at least 6 characters!": "Bitte geben Sie Ihr Passwort ein, mindestens 6 Zeichen!",
"Please input your username, Email or phone!": "Bitte geben Sie Ihren Benutzernamen, E-Mail oder Telefon ein!", "Please input your username, Email or phone!": "Bitte geben Sie Ihren Benutzernamen, E-Mail oder Telefon ein!",
"Redirecting, please wait.": "Redirecting, please wait.",
"Sign In": "Anmelden", "Sign In": "Anmelden",
"Sign in with WebAuthn": "Sign in with WebAuthn", "Sign in with WebAuthn": "Sign in with WebAuthn",
"Sign in with {type}": "Mit {type} anmelden", "Sign in with {type}": "Mit {type} anmelden",

View File

@ -299,6 +299,7 @@
"Continue with": "Continue with", "Continue with": "Continue with",
"Email or phone": "Email or phone", "Email or phone": "Email or phone",
"Forgot password?": "Forgot password?", "Forgot password?": "Forgot password?",
"Loading": "Loading",
"Logging out...": "Logging out...", "Logging out...": "Logging out...",
"No account?": "No account?", "No account?": "No account?",
"Or sign in with another account": "Or sign in with another account", "Or sign in with another account": "Or sign in with another account",
@ -308,6 +309,7 @@
"Please input your password!": "Please input your password!", "Please input your password!": "Please input your password!",
"Please input your password, at least 6 characters!": "Please input your password, at least 6 characters!", "Please input your password, at least 6 characters!": "Please input your password, at least 6 characters!",
"Please input your username, Email or phone!": "Please input your username, Email or phone!", "Please input your username, Email or phone!": "Please input your username, Email or phone!",
"Redirecting, please wait.": "Redirecting, please wait.",
"Sign In": "Sign In", "Sign In": "Sign In",
"Sign in with WebAuthn": "Sign in with WebAuthn", "Sign in with WebAuthn": "Sign in with WebAuthn",
"Sign in with {type}": "Sign in with {type}", "Sign in with {type}": "Sign in with {type}",

View File

@ -299,6 +299,7 @@
"Continue with": "Continuer avec", "Continue with": "Continuer avec",
"Email or phone": "Courriel ou téléphone", "Email or phone": "Courriel ou téléphone",
"Forgot password?": "Mot de passe oublié ?", "Forgot password?": "Mot de passe oublié ?",
"Loading": "Loading",
"Logging out...": "Logging out...", "Logging out...": "Logging out...",
"No account?": "Pas de compte ?", "No account?": "Pas de compte ?",
"Or sign in with another account": "Ou connectez-vous avec un autre compte", "Or sign in with another account": "Ou connectez-vous avec un autre compte",
@ -308,6 +309,7 @@
"Please input your password!": "Veuillez saisir votre mot de passe !", "Please input your password!": "Veuillez saisir votre mot de passe !",
"Please input your password, at least 6 characters!": "Veuillez entrer votre mot de passe, au moins 6 caractères !", "Please input your password, at least 6 characters!": "Veuillez entrer votre mot de passe, au moins 6 caractères !",
"Please input your username, Email or phone!": "Veuillez entrer votre nom d'utilisateur, votre e-mail ou votre téléphone!", "Please input your username, Email or phone!": "Veuillez entrer votre nom d'utilisateur, votre e-mail ou votre téléphone!",
"Redirecting, please wait.": "Redirecting, please wait.",
"Sign In": "Se connecter", "Sign In": "Se connecter",
"Sign in with WebAuthn": "Sign in with WebAuthn", "Sign in with WebAuthn": "Sign in with WebAuthn",
"Sign in with {type}": "Se connecter avec {type}", "Sign in with {type}": "Se connecter avec {type}",

View File

@ -299,6 +299,7 @@
"Continue with": "次で続ける", "Continue with": "次で続ける",
"Email or phone": "Eメールまたは電話番号", "Email or phone": "Eメールまたは電話番号",
"Forgot password?": "パスワードを忘れましたか?", "Forgot password?": "パスワードを忘れましたか?",
"Loading": "Loading",
"Logging out...": "Logging out...", "Logging out...": "Logging out...",
"No account?": "アカウントがありませんか?", "No account?": "アカウントがありませんか?",
"Or sign in with another account": "または別のアカウントでサインイン", "Or sign in with another account": "または別のアカウントでサインイン",
@ -308,6 +309,7 @@
"Please input your password!": "パスワードを入力してください!", "Please input your password!": "パスワードを入力してください!",
"Please input your password, at least 6 characters!": "6文字以上でパスワードを入力してください", "Please input your password, at least 6 characters!": "6文字以上でパスワードを入力してください",
"Please input your username, Email or phone!": "ユーザー名、メールアドレスまたは電話番号を入力してください。", "Please input your username, Email or phone!": "ユーザー名、メールアドレスまたは電話番号を入力してください。",
"Redirecting, please wait.": "Redirecting, please wait.",
"Sign In": "サインイン", "Sign In": "サインイン",
"Sign in with WebAuthn": "Sign in with WebAuthn", "Sign in with WebAuthn": "Sign in with WebAuthn",
"Sign in with {type}": "{type} でサインイン", "Sign in with {type}": "{type} でサインイン",

View File

@ -299,6 +299,7 @@
"Continue with": "Continue with", "Continue with": "Continue with",
"Email or phone": "Email or phone", "Email or phone": "Email or phone",
"Forgot password?": "Forgot password?", "Forgot password?": "Forgot password?",
"Loading": "Loading",
"Logging out...": "Logging out...", "Logging out...": "Logging out...",
"No account?": "No account?", "No account?": "No account?",
"Or sign in with another account": "Or sign in with another account", "Or sign in with another account": "Or sign in with another account",
@ -308,6 +309,7 @@
"Please input your password!": "Please input your password!", "Please input your password!": "Please input your password!",
"Please input your password, at least 6 characters!": "Please input your password, at least 6 characters!", "Please input your password, at least 6 characters!": "Please input your password, at least 6 characters!",
"Please input your username, Email or phone!": "Please input your username, Email or phone!", "Please input your username, Email or phone!": "Please input your username, Email or phone!",
"Redirecting, please wait.": "Redirecting, please wait.",
"Sign In": "Sign In", "Sign In": "Sign In",
"Sign in with WebAuthn": "Sign in with WebAuthn", "Sign in with WebAuthn": "Sign in with WebAuthn",
"Sign in with {type}": "Sign in with {type}", "Sign in with {type}": "Sign in with {type}",

View File

@ -299,6 +299,7 @@
"Continue with": "Продолжить с", "Continue with": "Продолжить с",
"Email or phone": "Электронная почта или телефон", "Email or phone": "Электронная почта или телефон",
"Forgot password?": "Забыли пароль?", "Forgot password?": "Забыли пароль?",
"Loading": "Loading",
"Logging out...": "Выход...", "Logging out...": "Выход...",
"No account?": "Нет учетной записи?", "No account?": "Нет учетной записи?",
"Or sign in with another account": "Или войти с помощью другой учетной записи", "Or sign in with another account": "Или войти с помощью другой учетной записи",
@ -308,6 +309,7 @@
"Please input your password!": "Пожалуйста, введите ваш пароль!", "Please input your password!": "Пожалуйста, введите ваш пароль!",
"Please input your password, at least 6 characters!": "Пожалуйста, введите ваш пароль, по крайней мере 6 символов!", "Please input your password, at least 6 characters!": "Пожалуйста, введите ваш пароль, по крайней мере 6 символов!",
"Please input your username, Email or phone!": "Пожалуйста, введите ваше имя пользователя, адрес электронной почты или телефон!", "Please input your username, Email or phone!": "Пожалуйста, введите ваше имя пользователя, адрес электронной почты или телефон!",
"Redirecting, please wait.": "Redirecting, please wait.",
"Sign In": "Войти", "Sign In": "Войти",
"Sign in with WebAuthn": "Sign in with WebAuthn", "Sign in with WebAuthn": "Sign in with WebAuthn",
"Sign in with {type}": "Войти с помощью {type}", "Sign in with {type}": "Войти с помощью {type}",

View File

@ -299,6 +299,7 @@
"Continue with": "使用以下账号继续", "Continue with": "使用以下账号继续",
"Email or phone": "Email或手机号", "Email or phone": "Email或手机号",
"Forgot password?": "忘记密码?", "Forgot password?": "忘记密码?",
"Loading": "加载中",
"Logging out...": "正在退出登录...", "Logging out...": "正在退出登录...",
"No account?": "没有账号?", "No account?": "没有账号?",
"Or sign in with another account": "或者,登录其他账号", "Or sign in with another account": "或者,登录其他账号",
@ -308,6 +309,7 @@
"Please input your password!": "请输入您的密码!", "Please input your password!": "请输入您的密码!",
"Please input your password, at least 6 characters!": "请输入您的密码不少于6位", "Please input your password, at least 6 characters!": "请输入您的密码不少于6位",
"Please input your username, Email or phone!": "请输入您的用户名、Email或手机号", "Please input your username, Email or phone!": "请输入您的用户名、Email或手机号",
"Redirecting, please wait.": "正在跳转, 请稍等.",
"Sign In": "登录", "Sign In": "登录",
"Sign in with WebAuthn": "WebAuthn登录", "Sign in with WebAuthn": "WebAuthn登录",
"Sign in with {type}": "{type}登录", "Sign in with {type}": "{type}登录",