mirror of
https://github.com/casdoor/casdoor.git
synced 2025-07-09 17:33:45 +08:00
Compare commits
12 Commits
Author | SHA1 | Date | |
---|---|---|---|
2f9f946c87 | |||
d8b60f838e | |||
7599e2715a | |||
35676455bc | |||
8128671c8c | |||
ee54dec3b3 | |||
d278bc9651 | |||
b23bd0b189 | |||
409be85264 | |||
0395b7e1a9 | |||
4536fd0636 | |||
af9ae7dbb7 |
@ -128,6 +128,12 @@ p, *, *, GET, /api/get-release, *, *
|
||||
}
|
||||
|
||||
func IsAllowed(subOwner string, subName string, method string, urlPath string, objOwner string, objName string) bool {
|
||||
if conf.IsDemoMode() {
|
||||
if !isAllowedInDemoMode(subOwner, subName, method, urlPath, objOwner, objName) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
res, err := Enforcer.Enforce(subOwner, subName, method, urlPath, objOwner, objName)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
@ -135,3 +141,22 @@ func IsAllowed(subOwner string, subName string, method string, urlPath string, o
|
||||
|
||||
return res
|
||||
}
|
||||
|
||||
func isAllowedInDemoMode(subOwner string, subName string, method string, urlPath string, objOwner string, objName string) bool {
|
||||
if method == "POST" {
|
||||
if urlPath == "/api/login" || urlPath == "/api/logout" || urlPath == "/api/signup" || urlPath == "/api/send-verification-code" {
|
||||
return true
|
||||
} else if urlPath == "/api/update-user" {
|
||||
// Allow ordinary users to update their own information
|
||||
if subOwner == objOwner && subName == objName && !(subOwner == "built-in" && subName == "admin") {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// If method equals GET
|
||||
return true
|
||||
}
|
||||
|
4
build.sh
4
build.sh
@ -6,6 +6,6 @@ then
|
||||
echo "Successfully connected to Google, no need to use Go proxy"
|
||||
else
|
||||
echo "Google is blocked, Go proxy is enabled: GOPROXY=https://goproxy.cn,direct"
|
||||
GO_PROXY_SETTING=GOPROXY=https://goproxy.cn,direct
|
||||
export GOPROXY="https://goproxy.cn,direct"
|
||||
fi
|
||||
CGO_ENABLED=0 GOOS=linux GOARCH=amd64 $GO_PROXY_SETTING go build -ldflags="-w -s" -o server .
|
||||
CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -ldflags="-w -s" -o server .
|
||||
|
@ -16,4 +16,5 @@ verificationCodeTimeout = 10
|
||||
initScore = 2000
|
||||
logPostOnly = true
|
||||
origin =
|
||||
staticBaseUrl = "https://cdn.casbin.org"
|
||||
staticBaseUrl = "https://cdn.casbin.org"
|
||||
isDemoMode = false
|
||||
|
@ -80,3 +80,7 @@ func GetBeegoConfDataSourceName() string {
|
||||
|
||||
return dataSourceName
|
||||
}
|
||||
|
||||
func IsDemoMode() bool {
|
||||
return strings.ToLower(GetConfigString("isDemoMode")) == "true"
|
||||
}
|
||||
|
@ -16,10 +16,28 @@
|
||||
|
||||
package object
|
||||
|
||||
import "github.com/go-gomail/gomail"
|
||||
import (
|
||||
"crypto/tls"
|
||||
|
||||
"github.com/go-gomail/gomail"
|
||||
)
|
||||
|
||||
func getDialer(provider *Provider) *gomail.Dialer {
|
||||
dialer := &gomail.Dialer{}
|
||||
if provider.Type == "SUBMAIL" {
|
||||
dialer = gomail.NewDialer(provider.Host, provider.Port, provider.AppId, provider.ClientSecret)
|
||||
dialer.TLSConfig = &tls.Config{InsecureSkipVerify: true}
|
||||
} else {
|
||||
dialer = gomail.NewDialer(provider.Host, provider.Port, provider.ClientId, provider.ClientSecret)
|
||||
}
|
||||
|
||||
dialer.SSL = !provider.DisableSsl
|
||||
|
||||
return dialer
|
||||
}
|
||||
|
||||
func SendEmail(provider *Provider, title string, content string, dest string, sender string) error {
|
||||
dialer := gomail.NewDialer(provider.Host, provider.Port, provider.ClientId, provider.ClientSecret)
|
||||
dialer := getDialer(provider)
|
||||
|
||||
message := gomail.NewMessage()
|
||||
message.SetAddressHeader("From", provider.ClientId, sender)
|
||||
@ -32,8 +50,7 @@ func SendEmail(provider *Provider, title string, content string, dest string, se
|
||||
|
||||
// DailSmtpServer Dail Smtp server
|
||||
func DailSmtpServer(provider *Provider) error {
|
||||
dialer := gomail.NewDialer(provider.Host, provider.Port, provider.ClientId, provider.ClientSecret)
|
||||
dialer.SSL = !provider.DisableSsl
|
||||
dialer := getDialer(provider)
|
||||
|
||||
sender, err := dialer.Dial()
|
||||
if err != nil {
|
||||
|
@ -25,6 +25,8 @@ import (
|
||||
)
|
||||
|
||||
func InitDb() {
|
||||
MigratePermissionRule()
|
||||
|
||||
existed := initBuiltInOrganization()
|
||||
if !existed {
|
||||
initBuiltInModel()
|
||||
|
@ -43,6 +43,11 @@ type OidcDiscovery struct {
|
||||
}
|
||||
|
||||
func getOriginFromHost(host string) (string, string) {
|
||||
origin := conf.GetConfigString("origin")
|
||||
if origin != "" {
|
||||
return origin, origin
|
||||
}
|
||||
|
||||
protocol := "https://"
|
||||
if strings.HasPrefix(host, "localhost") {
|
||||
protocol = "http://"
|
||||
@ -58,12 +63,6 @@ func getOriginFromHost(host string) (string, string) {
|
||||
func GetOidcDiscovery(host string) OidcDiscovery {
|
||||
originFrontend, originBackend := getOriginFromHost(host)
|
||||
|
||||
origin := conf.GetConfigString("origin")
|
||||
if origin != "" {
|
||||
originFrontend = origin
|
||||
originBackend = origin
|
||||
}
|
||||
|
||||
// Examples:
|
||||
// https://login.okta.com/.well-known/openid-configuration
|
||||
// https://auth0.auth0.com/.well-known/openid-configuration
|
||||
|
@ -16,6 +16,7 @@ package object
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/casdoor/casdoor/util"
|
||||
"xorm.io/core"
|
||||
@ -208,6 +209,33 @@ func GetPermissionsBySubmitter(owner string, submitter string) []*Permission {
|
||||
return permissions
|
||||
}
|
||||
|
||||
func MigratePermissionRule() {
|
||||
models := []*Model{}
|
||||
err := adapter.Engine.Find(&models, &Model{})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
isHit := false
|
||||
for _, model := range models {
|
||||
if strings.Contains(model.ModelText, "permission") {
|
||||
// update model table
|
||||
model.ModelText = strings.Replace(model.ModelText, "permission,", "", -1)
|
||||
UpdateModel(model.GetId(), model)
|
||||
isHit = true
|
||||
}
|
||||
}
|
||||
|
||||
if isHit {
|
||||
// update permission_rule table
|
||||
sql := "UPDATE `permission_rule`SET V0 = V1, V1 = V2, V2 = V3, V3 = V4, V4 = V5 WHERE V0 IN (SELECT CONCAT(owner, '/', name) AS permission_id FROM `permission`)"
|
||||
_, err = adapter.Engine.Exec(sql)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func ContainsAsterisk(userId string, users []string) bool {
|
||||
containsAsterisk := false
|
||||
group, _ := util.GetOwnerAndNameFromId(userId)
|
||||
|
@ -48,6 +48,7 @@ type Provider struct {
|
||||
DisableSsl bool `json:"disableSsl"`
|
||||
Title string `xorm:"varchar(100)" json:"title"`
|
||||
Content string `xorm:"varchar(1000)" json:"content"`
|
||||
Receiver string `xorm:"varchar(100)" json:"receiver"`
|
||||
|
||||
RegionId string `xorm:"varchar(100)" json:"regionId"`
|
||||
SignName string `xorm:"varchar(100)" json:"signName"`
|
||||
|
@ -29,7 +29,6 @@ import (
|
||||
|
||||
"github.com/RobotsAndPencils/go-saml"
|
||||
"github.com/beevik/etree"
|
||||
"github.com/casdoor/casdoor/conf"
|
||||
"github.com/golang-jwt/jwt/v4"
|
||||
dsig "github.com/russellhaering/goxmldsig"
|
||||
uuid "github.com/satori/go.uuid"
|
||||
@ -176,16 +175,12 @@ type Attribute struct {
|
||||
}
|
||||
|
||||
func GetSamlMeta(application *Application, host string) (*IdpEntityDescriptor, error) {
|
||||
//_, originBackend := getOriginFromHost(host)
|
||||
cert := getCertByApplication(application)
|
||||
block, _ := pem.Decode([]byte(cert.Certificate))
|
||||
certificate := base64.StdEncoding.EncodeToString(block.Bytes)
|
||||
|
||||
origin := conf.GetConfigString("origin")
|
||||
originFrontend, originBackend := getOriginFromHost(host)
|
||||
if origin != "" {
|
||||
originBackend = origin
|
||||
}
|
||||
|
||||
d := IdpEntityDescriptor{
|
||||
XMLName: xml.Name{
|
||||
Local: "md:EntityDescriptor",
|
||||
|
@ -70,10 +70,12 @@ func GenerateSamlLoginUrl(id, relayState string) (string, string, error) {
|
||||
}
|
||||
|
||||
func buildSp(provider *Provider, samlResponse string) (*saml2.SAMLServiceProvider, error) {
|
||||
origin := conf.GetConfigString("origin")
|
||||
|
||||
certStore := dsig.MemoryX509CertificateStore{
|
||||
Roots: []*x509.Certificate{},
|
||||
}
|
||||
origin := conf.GetConfigString("origin")
|
||||
|
||||
certEncodedData := ""
|
||||
if samlResponse != "" {
|
||||
certEncodedData = parseSamlResponse(samlResponse, provider.Type)
|
||||
|
@ -18,7 +18,6 @@ import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/casdoor/casdoor/conf"
|
||||
"github.com/casdoor/casdoor/util"
|
||||
"github.com/golang-jwt/jwt/v4"
|
||||
)
|
||||
@ -67,11 +66,7 @@ func generateJwtToken(application *Application, user *User, nonce string, scope
|
||||
refreshExpireTime := nowTime.Add(time.Duration(application.RefreshExpireInHours) * time.Hour)
|
||||
|
||||
user.Password = ""
|
||||
origin := conf.GetConfigString("origin")
|
||||
_, originBackend := getOriginFromHost(host)
|
||||
if origin != "" {
|
||||
originBackend = origin
|
||||
}
|
||||
|
||||
name := util.GenerateId()
|
||||
jti := fmt.Sprintf("%s/%s", application.Owner, name)
|
||||
|
@ -18,7 +18,6 @@ import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/casdoor/casdoor/conf"
|
||||
"github.com/casdoor/casdoor/util"
|
||||
"github.com/duo-labs/webauthn/webauthn"
|
||||
"xorm.io/core"
|
||||
@ -527,11 +526,8 @@ func GetUserInfo(userId string, scope string, aud string, host string) (*Userinf
|
||||
if user == nil {
|
||||
return nil, fmt.Errorf("the user: %s doesn't exist", userId)
|
||||
}
|
||||
origin := conf.GetConfigString("origin")
|
||||
|
||||
_, originBackend := getOriginFromHost(host)
|
||||
if origin != "" {
|
||||
originBackend = origin
|
||||
}
|
||||
|
||||
resp := Userinfo{
|
||||
Sub: user.Id,
|
||||
|
@ -27,12 +27,9 @@ import (
|
||||
func GetWebAuthnObject(host string) *webauthn.WebAuthn {
|
||||
var err error
|
||||
|
||||
origin := conf.GetConfigString("origin")
|
||||
if origin == "" {
|
||||
_, origin = getOriginFromHost(host)
|
||||
}
|
||||
_, originBackend := getOriginFromHost(host)
|
||||
|
||||
localUrl, err := url.Parse(origin)
|
||||
localUrl, err := url.Parse(originBackend)
|
||||
if err != nil {
|
||||
panic("error when parsing origin:" + err.Error())
|
||||
}
|
||||
@ -40,7 +37,7 @@ func GetWebAuthnObject(host string) *webauthn.WebAuthn {
|
||||
webAuthn, err := webauthn.New(&webauthn.Config{
|
||||
RPDisplayName: conf.GetConfigString("appname"), // Display Name for your site
|
||||
RPID: strings.Split(localUrl.Host, ":")[0], // Generally the domain name for your site, it's ok because splits cannot return empty array
|
||||
RPOrigin: origin, // The origin URL for WebAuthn requests
|
||||
RPOrigin: originBackend, // The origin URL for WebAuthn requests
|
||||
// RPIcon: "https://duo.com/logo.png", // Optional icon URL for your site
|
||||
})
|
||||
if err != nil {
|
||||
|
@ -47,7 +47,7 @@ func SendVerificationCodeToEmail(organization *Organization, user *User, provide
|
||||
|
||||
sender := organization.DisplayName
|
||||
title := provider.Title
|
||||
code := getRandomCode(5)
|
||||
code := getRandomCode(6)
|
||||
// "You have requested a verification code at Casdoor. Here is your code: %s, please enter in 5 minutes."
|
||||
content := fmt.Sprintf(provider.Content, code)
|
||||
|
||||
@ -63,7 +63,7 @@ func SendVerificationCodeToPhone(organization *Organization, user *User, provide
|
||||
return errors.New("please set a SMS provider first")
|
||||
}
|
||||
|
||||
code := getRandomCode(5)
|
||||
code := getRandomCode(6)
|
||||
if err := SendSms(provider, code, dest); err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -97,6 +97,7 @@
|
||||
"react/jsx-key": "error",
|
||||
"no-console": "error",
|
||||
"eqeqeq": "error",
|
||||
"keyword-spacing": "error",
|
||||
|
||||
"react/prop-types": "off",
|
||||
"react/display-name": "off",
|
||||
|
@ -527,7 +527,7 @@ class App extends Component {
|
||||
}
|
||||
|
||||
renderRouter() {
|
||||
return(
|
||||
return (
|
||||
<div>
|
||||
<Switch>
|
||||
<Route exact path="/result" render={(props) => this.renderHomeIfLoggedIn(<ResultPage {...props} />)} />
|
||||
@ -618,7 +618,7 @@ class App extends Component {
|
||||
</div>
|
||||
);
|
||||
} else {
|
||||
return(
|
||||
return (
|
||||
<div>
|
||||
<Header style={{padding: "0", marginBottom: "3px"}}>
|
||||
{
|
||||
|
@ -34,7 +34,6 @@ class ProviderEditPage extends React.Component {
|
||||
providerName: props.match.params.providerName,
|
||||
provider: null,
|
||||
mode: props.location.mode !== undefined ? props.location.mode : "edit",
|
||||
testEmail: this.props.account["email"] !== undefined ? this.props.account["email"] : "",
|
||||
};
|
||||
}
|
||||
|
||||
@ -131,6 +130,9 @@ class ProviderEditPage extends React.Component {
|
||||
} else if (this.state.provider.category === "SMS" && this.state.provider.type === "Huawei Cloud SMS") {
|
||||
text = i18next.t("provider:Channel No.");
|
||||
tooltip = i18next.t("provider:Channel No. - Tooltip");
|
||||
} else if (this.state.provider.category === "Email" && this.state.provider.type === "SUBMAIL") {
|
||||
text = i18next.t("provider:App ID");
|
||||
tooltip = i18next.t("provider:App ID - Tooltip");
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
@ -199,9 +201,12 @@ class ProviderEditPage extends React.Component {
|
||||
this.updateProviderField("type", "GitHub");
|
||||
} else if (value === "Email") {
|
||||
this.updateProviderField("type", "Default");
|
||||
this.updateProviderField("host", "smtp.example.com");
|
||||
this.updateProviderField("port", 465);
|
||||
this.updateProviderField("disableSsl", false);
|
||||
this.updateProviderField("title", "Casdoor Verification Code");
|
||||
this.updateProviderField("content", "You have requested a verification code at Casdoor. Here is your code: %s, please enter in 5 minutes.");
|
||||
this.updateProviderField("receiver", this.props.account.email);
|
||||
} else if (value === "SMS") {
|
||||
this.updateProviderField("type", "Aliyun SMS");
|
||||
} else if (value === "Storage") {
|
||||
@ -536,7 +541,7 @@ class ProviderEditPage extends React.Component {
|
||||
{Setting.getLabel(i18next.t("provider:Email Content"), i18next.t("provider:Email Content - Tooltip"))} :
|
||||
</Col>
|
||||
<Col span={22} >
|
||||
<TextArea autoSize={{minRows: 1, maxRows: 100}} value={this.state.provider.content} onChange={e => {
|
||||
<TextArea autoSize={{minRows: 3, maxRows: 100}} value={this.state.provider.content} onChange={e => {
|
||||
this.updateProviderField("content", e.target.value);
|
||||
}} />
|
||||
</Col>
|
||||
@ -546,19 +551,16 @@ class ProviderEditPage extends React.Component {
|
||||
{Setting.getLabel(i18next.t("provider:Test Email"), i18next.t("provider:Test Email - Tooltip"))} :
|
||||
</Col>
|
||||
<Col span={4} >
|
||||
<Input value={this.state.testEmail}
|
||||
placeHolder = {i18next.t("user:Input your email")}
|
||||
onChange={e => {
|
||||
this.setState({testEmail: e.target.value});
|
||||
}} />
|
||||
<Input value={this.state.provider.receiver} placeholder = {i18next.t("user:Input your email")} onChange={e => {
|
||||
this.updateProviderField("receiver", e.target.value);
|
||||
}} />
|
||||
</Col>
|
||||
<Button style={{marginLeft: "10px", marginBottom: "5px"}} type="primary"
|
||||
onClick={() => ProviderEditTestEmail.connectSmtpServer(this.state.provider)} >
|
||||
<Button style={{marginLeft: "10px", marginBottom: "5px"}} type="primary" onClick={() => ProviderEditTestEmail.connectSmtpServer(this.state.provider)} >
|
||||
{i18next.t("provider:Test Connection")}
|
||||
</Button>
|
||||
<Button style={{marginLeft: "10px", marginBottom: "5px"}} type="primary"
|
||||
disabled={!Setting.isValidEmail(this.state.testEmail)}
|
||||
onClick={() => ProviderEditTestEmail.sendTestEmail(this.state.provider, this.state.testEmail)} >
|
||||
disabled={!Setting.isValidEmail(this.state.provider.receiver)}
|
||||
onClick={() => ProviderEditTestEmail.sendTestEmail(this.state.provider, this.state.provider.receiver)} >
|
||||
{i18next.t("provider:Send Test Email")}
|
||||
</Button>
|
||||
</Row>
|
||||
|
@ -56,8 +56,12 @@ export const ResetModal = (props) => {
|
||||
});
|
||||
};
|
||||
|
||||
let placeHolder = "";
|
||||
if (destType === "email") {placeHolder = i18next.t("user:Input your email");} else if (destType === "phone") {placeHolder = i18next.t("user:Input your phone number");}
|
||||
let placeholder = "";
|
||||
if (destType === "email") {
|
||||
placeholder = i18next.t("user:Input your email");
|
||||
} else if (destType === "phone") {
|
||||
placeholder = i18next.t("user:Input your phone number");
|
||||
}
|
||||
|
||||
return (
|
||||
<Row>
|
||||
@ -80,7 +84,7 @@ export const ResetModal = (props) => {
|
||||
<Input
|
||||
addonBefore={destType === "email" ? i18next.t("user:New Email") : i18next.t("user:New phone")}
|
||||
prefix={destType === "email" ? <MailOutlined /> : <PhoneOutlined />}
|
||||
placeholder={placeHolder}
|
||||
placeholder={placeholder}
|
||||
onChange={e => setDest(e.target.value)}
|
||||
/>
|
||||
</Row>
|
||||
|
@ -450,9 +450,9 @@ export function trim(str, ch) {
|
||||
let start = 0;
|
||||
let end = str.length;
|
||||
|
||||
while(start < end && str[start] === ch) {++start;}
|
||||
while (start < end && str[start] === ch) {++start;}
|
||||
|
||||
while(end > start && str[end - 1] === ch) {--end;}
|
||||
while (end > start && str[end - 1] === ch) {--end;}
|
||||
|
||||
return (start > 0 || end < str.length) ? str.substring(start, end) : str;
|
||||
}
|
||||
@ -633,6 +633,7 @@ export function getProviderTypeOptions(category) {
|
||||
return (
|
||||
[
|
||||
{id: "Default", name: "Default"},
|
||||
{id: "SUBMAIL", name: "SUBMAIL"},
|
||||
]
|
||||
);
|
||||
} else if (category === "SMS") {
|
||||
|
@ -50,8 +50,12 @@ class AuthCallback extends React.Component {
|
||||
// Casdoor's own login page, so "code" is not necessary
|
||||
if (realRedirectUri === null) {
|
||||
const samlRequest = innerParams.get("SAMLRequest");
|
||||
// cas don't use 'redirect_url', it is called 'service'
|
||||
const casService = innerParams.get("service");
|
||||
if (samlRequest !== null && samlRequest !== undefined && samlRequest !== "") {
|
||||
return "saml";
|
||||
} else if (casService !== null && casService !== undefined && casService !== "") {
|
||||
return "cas";
|
||||
}
|
||||
return "login";
|
||||
}
|
||||
@ -97,6 +101,7 @@ class AuthCallback extends React.Component {
|
||||
const providerName = innerParams.get("provider");
|
||||
const method = innerParams.get("method");
|
||||
const samlRequest = innerParams.get("SAMLRequest");
|
||||
const casService = innerParams.get("service");
|
||||
|
||||
const redirectUri = `${window.location.origin}/callback`;
|
||||
|
||||
@ -111,6 +116,31 @@ class AuthCallback extends React.Component {
|
||||
redirectUri: redirectUri,
|
||||
method: method,
|
||||
};
|
||||
|
||||
if (this.getResponseType() === "cas") {
|
||||
// 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.";
|
||||
}
|
||||
Util.showMessage("success", msg);
|
||||
|
||||
if (casService !== "") {
|
||||
const st = res.data;
|
||||
const newUrl = new URL(casService);
|
||||
newUrl.searchParams.append("ticket", st);
|
||||
window.location.href = newUrl.toString();
|
||||
}
|
||||
} else {
|
||||
Util.showMessage("error", `Failed to log in: ${res.msg}`);
|
||||
}
|
||||
});
|
||||
return;
|
||||
}
|
||||
// OAuth
|
||||
const oAuthParams = Util.getOAuthGetParameters(innerParams);
|
||||
const concatChar = oAuthParams?.redirectUri?.includes("?") ? "&" : "?";
|
||||
AuthBackend.login(body, oAuthParams)
|
||||
|
@ -584,9 +584,9 @@ class SignupPage extends React.Component {
|
||||
{i18next.t("signup:Have account?")}
|
||||
<a onClick={() => {
|
||||
const linkInStorage = sessionStorage.getItem("signinUrl");
|
||||
if(linkInStorage !== null && linkInStorage !== "") {
|
||||
if (linkInStorage !== null && linkInStorage !== "") {
|
||||
Setting.goToLink(linkInStorage);
|
||||
}else{
|
||||
} else {
|
||||
Setting.goToLogin(this, application);
|
||||
}
|
||||
}}>
|
||||
|
Reference in New Issue
Block a user