mirror of
https://github.com/casdoor/casdoor.git
synced 2025-08-24 06:02:34 +08:00
Compare commits
6 Commits
Author | SHA1 | Date | |
---|---|---|---|
![]() |
fe42b5e0ba | ||
![]() |
383bf44391 | ||
![]() |
36f5de3203 | ||
![]() |
eae69c41d7 | ||
![]() |
91057f54f3 | ||
![]() |
daa7b79915 |
@@ -47,6 +47,7 @@ p, *, *, GET, /api/get-app-login, *, *
|
|||||||
p, *, *, POST, /api/logout, *, *
|
p, *, *, POST, /api/logout, *, *
|
||||||
p, *, *, GET, /api/logout, *, *
|
p, *, *, GET, /api/logout, *, *
|
||||||
p, *, *, POST, /api/callback, *, *
|
p, *, *, POST, /api/callback, *, *
|
||||||
|
p, *, *, POST, /api/device-auth, *, *
|
||||||
p, *, *, GET, /api/get-account, *, *
|
p, *, *, GET, /api/get-account, *, *
|
||||||
p, *, *, GET, /api/userinfo, *, *
|
p, *, *, GET, /api/userinfo, *, *
|
||||||
p, *, *, GET, /api/user, *, *
|
p, *, *, GET, /api/user, *, *
|
||||||
|
@@ -32,6 +32,7 @@ const (
|
|||||||
ResponseTypeIdToken = "id_token"
|
ResponseTypeIdToken = "id_token"
|
||||||
ResponseTypeSaml = "saml"
|
ResponseTypeSaml = "saml"
|
||||||
ResponseTypeCas = "cas"
|
ResponseTypeCas = "cas"
|
||||||
|
ResponseTypeDevice = "device"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Response struct {
|
type Response struct {
|
||||||
|
@@ -25,6 +25,7 @@ import (
|
|||||||
"regexp"
|
"regexp"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/casdoor/casdoor/captcha"
|
"github.com/casdoor/casdoor/captcha"
|
||||||
"github.com/casdoor/casdoor/conf"
|
"github.com/casdoor/casdoor/conf"
|
||||||
@@ -169,6 +170,32 @@ func (c *ApiController) HandleLoggedIn(application *object.Application, user *ob
|
|||||||
|
|
||||||
resp.Data2 = user.NeedUpdatePassword
|
resp.Data2 = user.NeedUpdatePassword
|
||||||
}
|
}
|
||||||
|
} else if form.Type == ResponseTypeDevice {
|
||||||
|
authCache, ok := object.DeviceAuthMap.LoadAndDelete(form.UserCode)
|
||||||
|
if !ok {
|
||||||
|
c.ResponseError(c.T("auth:UserCode Expired"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
authCacheCast := authCache.(object.DeviceAuthCache)
|
||||||
|
if authCacheCast.RequestAt.Add(time.Second * 120).Before(time.Now()) {
|
||||||
|
c.ResponseError(c.T("auth:UserCode Expired"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
deviceAuthCacheDeviceCode, ok := object.DeviceAuthMap.Load(authCacheCast.UserName)
|
||||||
|
if !ok {
|
||||||
|
c.ResponseError(c.T("auth:DeviceCode Invalid"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
deviceAuthCacheDeviceCodeCast := deviceAuthCacheDeviceCode.(object.DeviceAuthCache)
|
||||||
|
deviceAuthCacheDeviceCodeCast.UserName = user.Name
|
||||||
|
deviceAuthCacheDeviceCodeCast.UserSignIn = true
|
||||||
|
|
||||||
|
object.DeviceAuthMap.Store(authCacheCast.UserName, deviceAuthCacheDeviceCodeCast)
|
||||||
|
|
||||||
|
resp = &Response{Status: "ok", Msg: "", Data: userId, Data2: user.NeedUpdatePassword}
|
||||||
} else if form.Type == ResponseTypeSaml { // saml flow
|
} else if form.Type == ResponseTypeSaml { // saml flow
|
||||||
res, redirectUrl, method, err := object.GetSamlResponse(application, user, form.SamlRequest, c.Ctx.Request.Host)
|
res, redirectUrl, method, err := object.GetSamlResponse(application, user, form.SamlRequest, c.Ctx.Request.Host)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -242,6 +269,7 @@ func (c *ApiController) GetApplicationLogin() {
|
|||||||
state := c.Input().Get("state")
|
state := c.Input().Get("state")
|
||||||
id := c.Input().Get("id")
|
id := c.Input().Get("id")
|
||||||
loginType := c.Input().Get("type")
|
loginType := c.Input().Get("type")
|
||||||
|
userCode := c.Input().Get("userCode")
|
||||||
|
|
||||||
var application *object.Application
|
var application *object.Application
|
||||||
var msg string
|
var msg string
|
||||||
@@ -268,6 +296,19 @@ func (c *ApiController) GetApplicationLogin() {
|
|||||||
c.ResponseError(err.Error())
|
c.ResponseError(err.Error())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
} else if loginType == "device" {
|
||||||
|
deviceAuthCache, ok := object.DeviceAuthMap.Load(userCode)
|
||||||
|
if !ok {
|
||||||
|
c.ResponseError(c.T("auth:UserCode Invalid"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
deviceAuthCacheCast := deviceAuthCache.(object.DeviceAuthCache)
|
||||||
|
application, err = object.GetApplication(deviceAuthCacheCast.ApplicationId)
|
||||||
|
if err != nil {
|
||||||
|
c.ResponseError(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
clientIp := util.GetClientIpFromRequest(c.Ctx.Request)
|
clientIp := util.GetClientIpFromRequest(c.Ctx.Request)
|
||||||
@@ -1215,3 +1256,75 @@ func (c *ApiController) Callback() {
|
|||||||
frontendCallbackUrl := fmt.Sprintf("/callback?code=%s&state=%s", code, state)
|
frontendCallbackUrl := fmt.Sprintf("/callback?code=%s&state=%s", code, state)
|
||||||
c.Ctx.Redirect(http.StatusFound, frontendCallbackUrl)
|
c.Ctx.Redirect(http.StatusFound, frontendCallbackUrl)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// DeviceAuth
|
||||||
|
// @Title DeviceAuth
|
||||||
|
// @Tag Device Authorization Endpoint
|
||||||
|
// @Description Endpoint for the device authorization flow
|
||||||
|
// @router /device-auth [post]
|
||||||
|
// @Success 200 {object} object.DeviceAuthResponse The Response object
|
||||||
|
func (c *ApiController) DeviceAuth() {
|
||||||
|
clientId := c.Input().Get("client_id")
|
||||||
|
scope := c.Input().Get("scope")
|
||||||
|
application, err := object.GetApplicationByClientId(clientId)
|
||||||
|
if err != nil {
|
||||||
|
c.Data["json"] = object.TokenError{
|
||||||
|
Error: err.Error(),
|
||||||
|
ErrorDescription: err.Error(),
|
||||||
|
}
|
||||||
|
c.ServeJSON()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if application == nil {
|
||||||
|
c.Data["json"] = object.TokenError{
|
||||||
|
Error: c.T("token:Invalid client_id"),
|
||||||
|
ErrorDescription: c.T("token:Invalid client_id"),
|
||||||
|
}
|
||||||
|
c.ServeJSON()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
deviceCode := util.GenerateId()
|
||||||
|
userCode := util.GetRandomName()
|
||||||
|
|
||||||
|
generateTime := 0
|
||||||
|
for {
|
||||||
|
if generateTime > 5 {
|
||||||
|
c.Data["json"] = object.TokenError{
|
||||||
|
Error: "userCode gen",
|
||||||
|
ErrorDescription: c.T("token:Invalid client_id"),
|
||||||
|
}
|
||||||
|
c.ServeJSON()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
_, ok := object.DeviceAuthMap.Load(userCode)
|
||||||
|
if !ok {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
generateTime++
|
||||||
|
}
|
||||||
|
|
||||||
|
deviceAuthCache := object.DeviceAuthCache{
|
||||||
|
UserSignIn: false,
|
||||||
|
UserName: "",
|
||||||
|
Scope: scope,
|
||||||
|
ApplicationId: application.GetId(),
|
||||||
|
RequestAt: time.Now(),
|
||||||
|
}
|
||||||
|
|
||||||
|
userAuthCache := object.DeviceAuthCache{
|
||||||
|
UserSignIn: false,
|
||||||
|
UserName: deviceCode,
|
||||||
|
Scope: scope,
|
||||||
|
ApplicationId: application.GetId(),
|
||||||
|
RequestAt: time.Now(),
|
||||||
|
}
|
||||||
|
|
||||||
|
object.DeviceAuthMap.Store(deviceCode, deviceAuthCache)
|
||||||
|
object.DeviceAuthMap.Store(userCode, userAuthCache)
|
||||||
|
|
||||||
|
c.Data["json"] = object.GetDeviceAuthResponse(deviceCode, userCode, c.Ctx.Request.Host)
|
||||||
|
c.ServeJSON()
|
||||||
|
}
|
||||||
|
@@ -16,6 +16,7 @@ package controllers
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/beego/beego/utils/pagination"
|
"github.com/beego/beego/utils/pagination"
|
||||||
"github.com/casdoor/casdoor/object"
|
"github.com/casdoor/casdoor/object"
|
||||||
@@ -170,12 +171,17 @@ func (c *ApiController) GetOAuthToken() {
|
|||||||
tag := c.Input().Get("tag")
|
tag := c.Input().Get("tag")
|
||||||
avatar := c.Input().Get("avatar")
|
avatar := c.Input().Get("avatar")
|
||||||
refreshToken := c.Input().Get("refresh_token")
|
refreshToken := c.Input().Get("refresh_token")
|
||||||
|
deviceCode := c.Input().Get("device_code")
|
||||||
|
|
||||||
if clientId == "" && clientSecret == "" {
|
if clientId == "" && clientSecret == "" {
|
||||||
clientId, clientSecret, _ = c.Ctx.Request.BasicAuth()
|
clientId, clientSecret, _ = c.Ctx.Request.BasicAuth()
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(c.Ctx.Input.RequestBody) != 0 {
|
if grantType == "urn:ietf:params:oauth:grant-type:device_code" {
|
||||||
|
clientId, clientSecret, _ = c.Ctx.Request.BasicAuth()
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(c.Ctx.Input.RequestBody) != 0 && grantType != "urn:ietf:params:oauth:grant-type:device_code" {
|
||||||
// If clientId is empty, try to read data from RequestBody
|
// If clientId is empty, try to read data from RequestBody
|
||||||
var tokenRequest TokenRequest
|
var tokenRequest TokenRequest
|
||||||
err := json.Unmarshal(c.Ctx.Input.RequestBody, &tokenRequest)
|
err := json.Unmarshal(c.Ctx.Input.RequestBody, &tokenRequest)
|
||||||
@@ -219,6 +225,40 @@ func (c *ApiController) GetOAuthToken() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if deviceCode != "" {
|
||||||
|
deviceAuthCache, ok := object.DeviceAuthMap.Load(deviceCode)
|
||||||
|
if !ok {
|
||||||
|
c.Data["json"] = object.TokenError{
|
||||||
|
Error: "expired_token",
|
||||||
|
ErrorDescription: "token is expired",
|
||||||
|
}
|
||||||
|
c.ServeJSON()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
deviceAuthCacheCast := deviceAuthCache.(object.DeviceAuthCache)
|
||||||
|
if !deviceAuthCacheCast.UserSignIn {
|
||||||
|
c.Data["json"] = object.TokenError{
|
||||||
|
Error: "authorization_pending",
|
||||||
|
ErrorDescription: "authorization pending",
|
||||||
|
}
|
||||||
|
c.ServeJSON()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if deviceAuthCacheCast.RequestAt.Add(time.Second * 120).Before(time.Now()) {
|
||||||
|
c.Data["json"] = object.TokenError{
|
||||||
|
Error: "expired_token",
|
||||||
|
ErrorDescription: "token is expired",
|
||||||
|
}
|
||||||
|
c.ServeJSON()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
object.DeviceAuthMap.Delete(deviceCode)
|
||||||
|
|
||||||
|
username = deviceAuthCacheCast.UserName
|
||||||
|
}
|
||||||
|
|
||||||
host := c.Ctx.Request.Host
|
host := c.Ctx.Request.Host
|
||||||
token, err := object.GetOAuthToken(grantType, clientId, clientSecret, code, verifier, scope, nonce, username, password, host, refreshToken, tag, avatar, c.GetAcceptLanguage())
|
token, err := object.GetOAuthToken(grantType, clientId, clientSecret, code, verifier, scope, nonce, username, password, host, refreshToken, tag, avatar, c.GetAcceptLanguage())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@@ -34,6 +34,8 @@ func GetCredManager(passwordType string) CredManager {
|
|||||||
return NewPbkdf2SaltCredManager()
|
return NewPbkdf2SaltCredManager()
|
||||||
} else if passwordType == "argon2id" {
|
} else if passwordType == "argon2id" {
|
||||||
return NewArgon2idCredManager()
|
return NewArgon2idCredManager()
|
||||||
|
} else if passwordType == "pbkdf2-django" {
|
||||||
|
return NewPbkdf2DjangoCredManager()
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
71
cred/pbkdf2_django.go
Normal file
71
cred/pbkdf2_django.go
Normal file
@@ -0,0 +1,71 @@
|
|||||||
|
// Copyright 2025 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.
|
||||||
|
|
||||||
|
package cred
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/sha256"
|
||||||
|
"encoding/base64"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"golang.org/x/crypto/pbkdf2"
|
||||||
|
)
|
||||||
|
|
||||||
|
// password type: pbkdf2-django
|
||||||
|
|
||||||
|
type Pbkdf2DjangoCredManager struct{}
|
||||||
|
|
||||||
|
func NewPbkdf2DjangoCredManager() *Pbkdf2DjangoCredManager {
|
||||||
|
cm := &Pbkdf2DjangoCredManager{}
|
||||||
|
return cm
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Pbkdf2DjangoCredManager) GetHashedPassword(password string, userSalt string, organizationSalt string) string {
|
||||||
|
iterations := 260000
|
||||||
|
salt := userSalt
|
||||||
|
if salt == "" {
|
||||||
|
salt = organizationSalt
|
||||||
|
}
|
||||||
|
|
||||||
|
saltBytes := []byte(salt)
|
||||||
|
passwordBytes := []byte(password)
|
||||||
|
computedHash := pbkdf2.Key(passwordBytes, saltBytes, iterations, sha256.Size, sha256.New)
|
||||||
|
hashBase64 := base64.StdEncoding.EncodeToString(computedHash)
|
||||||
|
return "pbkdf2_sha256$" + strconv.Itoa(iterations) + "$" + salt + "$" + hashBase64
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Pbkdf2DjangoCredManager) IsPasswordCorrect(password string, passwordHash string, userSalt string, organizationSalt string) bool {
|
||||||
|
parts := strings.Split(passwordHash, "$")
|
||||||
|
if len(parts) != 4 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
algorithm, iterations, salt, hash := parts[0], parts[1], parts[2], parts[3]
|
||||||
|
if algorithm != "pbkdf2_sha256" {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
iter, err := strconv.Atoi(iterations)
|
||||||
|
if err != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
saltBytes := []byte(salt)
|
||||||
|
passwordBytes := []byte(password)
|
||||||
|
computedHash := pbkdf2.Key(passwordBytes, saltBytes, iter, sha256.Size, sha256.New)
|
||||||
|
computedHashBase64 := base64.StdEncoding.EncodeToString(computedHash)
|
||||||
|
|
||||||
|
return computedHashBase64 == hash
|
||||||
|
}
|
@@ -70,6 +70,7 @@ type AuthForm struct {
|
|||||||
|
|
||||||
FaceId []float64 `json:"faceId"`
|
FaceId []float64 `json:"faceId"`
|
||||||
FaceIdImage []string `json:"faceIdImage"`
|
FaceIdImage []string `json:"faceIdImage"`
|
||||||
|
UserCode string `json:"userCode"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetAuthFormFieldValue(form *AuthForm, fieldName string) (bool, string) {
|
func GetAuthFormFieldValue(form *AuthForm, fieldName string) (bool, string) {
|
||||||
|
@@ -63,7 +63,11 @@ func GetCertCount(owner, field, value string) (int64, error) {
|
|||||||
|
|
||||||
func GetCerts(owner string) ([]*Cert, error) {
|
func GetCerts(owner string) ([]*Cert, error) {
|
||||||
certs := []*Cert{}
|
certs := []*Cert{}
|
||||||
err := ormer.Engine.Where("owner = ? or owner = ? ", "admin", owner).Desc("created_time").Find(&certs, &Cert{})
|
db := ormer.Engine.NewSession()
|
||||||
|
if owner != "" {
|
||||||
|
db = db.Where("owner = ? or owner = ? ", "admin", owner)
|
||||||
|
}
|
||||||
|
err := db.Desc("created_time").Find(&certs, &Cert{})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return certs, err
|
return certs, err
|
||||||
}
|
}
|
||||||
|
@@ -17,6 +17,7 @@ package object
|
|||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"github.com/casdoor/casdoor/conf"
|
"github.com/casdoor/casdoor/conf"
|
||||||
"github.com/casdoor/casdoor/util"
|
"github.com/casdoor/casdoor/util"
|
||||||
@@ -210,6 +211,12 @@ func DeleteGroup(group *Group) (bool, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func checkGroupName(name string) error {
|
func checkGroupName(name string) error {
|
||||||
|
if name == "" {
|
||||||
|
return errors.New("group name can't be empty")
|
||||||
|
}
|
||||||
|
if strings.Contains(name, "/") {
|
||||||
|
return errors.New("group name can't contain \"/\"")
|
||||||
|
}
|
||||||
exist, err := ormer.Engine.Exist(&Organization{Owner: "admin", Name: name})
|
exist, err := ormer.Engine.Exist(&Organization{Owner: "admin", Name: name})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
@@ -30,6 +30,7 @@ type OidcDiscovery struct {
|
|||||||
AuthorizationEndpoint string `json:"authorization_endpoint"`
|
AuthorizationEndpoint string `json:"authorization_endpoint"`
|
||||||
TokenEndpoint string `json:"token_endpoint"`
|
TokenEndpoint string `json:"token_endpoint"`
|
||||||
UserinfoEndpoint string `json:"userinfo_endpoint"`
|
UserinfoEndpoint string `json:"userinfo_endpoint"`
|
||||||
|
DeviceAuthorizationEndpoint string `json:"device_authorization_endpoint"`
|
||||||
JwksUri string `json:"jwks_uri"`
|
JwksUri string `json:"jwks_uri"`
|
||||||
IntrospectionEndpoint string `json:"introspection_endpoint"`
|
IntrospectionEndpoint string `json:"introspection_endpoint"`
|
||||||
ResponseTypesSupported []string `json:"response_types_supported"`
|
ResponseTypesSupported []string `json:"response_types_supported"`
|
||||||
@@ -119,6 +120,7 @@ func GetOidcDiscovery(host string) OidcDiscovery {
|
|||||||
AuthorizationEndpoint: fmt.Sprintf("%s/login/oauth/authorize", originFrontend),
|
AuthorizationEndpoint: fmt.Sprintf("%s/login/oauth/authorize", originFrontend),
|
||||||
TokenEndpoint: fmt.Sprintf("%s/api/login/oauth/access_token", originBackend),
|
TokenEndpoint: fmt.Sprintf("%s/api/login/oauth/access_token", originBackend),
|
||||||
UserinfoEndpoint: fmt.Sprintf("%s/api/userinfo", originBackend),
|
UserinfoEndpoint: fmt.Sprintf("%s/api/userinfo", originBackend),
|
||||||
|
DeviceAuthorizationEndpoint: fmt.Sprintf("%s/api/device-auth", originBackend),
|
||||||
JwksUri: fmt.Sprintf("%s/.well-known/jwks", originBackend),
|
JwksUri: fmt.Sprintf("%s/.well-known/jwks", originBackend),
|
||||||
IntrospectionEndpoint: fmt.Sprintf("%s/api/login/oauth/introspect", originBackend),
|
IntrospectionEndpoint: fmt.Sprintf("%s/api/login/oauth/introspect", originBackend),
|
||||||
ResponseTypesSupported: []string{"code", "token", "id_token", "code token", "code id_token", "token id_token", "code token id_token", "none"},
|
ResponseTypesSupported: []string{"code", "token", "id_token", "code token", "code id_token", "token id_token", "code token id_token", "none"},
|
||||||
@@ -138,7 +140,7 @@ func GetOidcDiscovery(host string) OidcDiscovery {
|
|||||||
|
|
||||||
func GetJsonWebKeySet() (jose.JSONWebKeySet, error) {
|
func GetJsonWebKeySet() (jose.JSONWebKeySet, error) {
|
||||||
jwks := jose.JSONWebKeySet{}
|
jwks := jose.JSONWebKeySet{}
|
||||||
certs, err := GetCerts("admin")
|
certs, err := GetCerts("")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return jwks, err
|
return jwks, err
|
||||||
}
|
}
|
||||||
@@ -213,3 +215,14 @@ func GetWebFinger(resource string, rels []string, host string) (WebFinger, error
|
|||||||
|
|
||||||
return wf, nil
|
return wf, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func GetDeviceAuthResponse(deviceCode string, userCode string, host string) DeviceAuthResponse {
|
||||||
|
originFrontend, _ := getOriginFromHost(host)
|
||||||
|
|
||||||
|
return DeviceAuthResponse{
|
||||||
|
DeviceCode: deviceCode,
|
||||||
|
UserCode: userCode,
|
||||||
|
VerificationUri: fmt.Sprintf("%s/login/oauth/device/%s", originFrontend, userCode),
|
||||||
|
ExpiresIn: 120,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@@ -263,6 +263,27 @@ func addWebhookRecord(webhook *Webhook, record *casvisorsdk.Record, statusCode i
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func filterRecordObject(object string, objectFields []string) string {
|
||||||
|
var rawObject map[string]interface{}
|
||||||
|
_ = json.Unmarshal([]byte(object), &rawObject)
|
||||||
|
|
||||||
|
if rawObject == nil {
|
||||||
|
return object
|
||||||
|
}
|
||||||
|
|
||||||
|
filteredObject := make(map[string]interface{})
|
||||||
|
|
||||||
|
for _, field := range objectFields {
|
||||||
|
fieldValue, ok := rawObject[field]
|
||||||
|
if !ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
filteredObject[field] = fieldValue
|
||||||
|
}
|
||||||
|
|
||||||
|
return util.StructToJson(filteredObject)
|
||||||
|
}
|
||||||
|
|
||||||
func SendWebhooks(record *casvisorsdk.Record) error {
|
func SendWebhooks(record *casvisorsdk.Record) error {
|
||||||
webhooks, err := getWebhooksByOrganization("")
|
webhooks, err := getWebhooksByOrganization("")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -271,7 +292,14 @@ func SendWebhooks(record *casvisorsdk.Record) error {
|
|||||||
|
|
||||||
errs := []error{}
|
errs := []error{}
|
||||||
webhooks = getFilteredWebhooks(webhooks, record.Organization, record.Action)
|
webhooks = getFilteredWebhooks(webhooks, record.Organization, record.Action)
|
||||||
|
|
||||||
|
record2 := *record
|
||||||
for _, webhook := range webhooks {
|
for _, webhook := range webhooks {
|
||||||
|
|
||||||
|
if len(webhook.ObjectFields) != 0 && webhook.ObjectFields[0] != "All" {
|
||||||
|
record2.Object = filterRecordObject(record.Object, webhook.ObjectFields)
|
||||||
|
}
|
||||||
|
|
||||||
var user *User
|
var user *User
|
||||||
if webhook.IsUserExtended {
|
if webhook.IsUserExtended {
|
||||||
user, err = getUser(record.Organization, record.User)
|
user, err = getUser(record.Organization, record.User)
|
||||||
@@ -287,12 +315,12 @@ func SendWebhooks(record *casvisorsdk.Record) error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
statusCode, respBody, err := sendWebhook(webhook, record, user)
|
statusCode, respBody, err := sendWebhook(webhook, &record2, user)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
errs = append(errs, err)
|
errs = append(errs, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
err = addWebhookRecord(webhook, record, statusCode, respBody, err)
|
err = addWebhookRecord(webhook, &record2, statusCode, respBody, err)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
errs = append(errs, err)
|
errs = append(errs, err)
|
||||||
}
|
}
|
||||||
|
@@ -18,6 +18,7 @@ import (
|
|||||||
"crypto/sha256"
|
"crypto/sha256"
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/casdoor/casdoor/i18n"
|
"github.com/casdoor/casdoor/i18n"
|
||||||
@@ -37,6 +38,8 @@ const (
|
|||||||
EndpointError = "endpoint_error"
|
EndpointError = "endpoint_error"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var DeviceAuthMap = sync.Map{}
|
||||||
|
|
||||||
type Code struct {
|
type Code struct {
|
||||||
Message string `xorm:"varchar(100)" json:"message"`
|
Message string `xorm:"varchar(100)" json:"message"`
|
||||||
Code string `xorm:"varchar(100)" json:"code"`
|
Code string `xorm:"varchar(100)" json:"code"`
|
||||||
@@ -71,6 +74,22 @@ type IntrospectionResponse struct {
|
|||||||
Jti string `json:"jti,omitempty"`
|
Jti string `json:"jti,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type DeviceAuthCache struct {
|
||||||
|
UserSignIn bool
|
||||||
|
UserName string
|
||||||
|
ApplicationId string
|
||||||
|
Scope string
|
||||||
|
RequestAt time.Time
|
||||||
|
}
|
||||||
|
|
||||||
|
type DeviceAuthResponse struct {
|
||||||
|
DeviceCode string `json:"device_code"`
|
||||||
|
UserCode string `json:"user_code"`
|
||||||
|
VerificationUri string `json:"verification_uri"`
|
||||||
|
ExpiresIn int `json:"expires_in"`
|
||||||
|
Interval int `json:"interval"`
|
||||||
|
}
|
||||||
|
|
||||||
func ExpireTokenByAccessToken(accessToken string) (bool, *Application, *Token, error) {
|
func ExpireTokenByAccessToken(accessToken string) (bool, *Application, *Token, error) {
|
||||||
token, err := GetTokenByAccessToken(accessToken)
|
token, err := GetTokenByAccessToken(accessToken)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -222,6 +241,8 @@ func GetOAuthToken(grantType string, clientId string, clientSecret string, code
|
|||||||
token, tokenError, err = GetClientCredentialsToken(application, clientSecret, scope, host)
|
token, tokenError, err = GetClientCredentialsToken(application, clientSecret, scope, host)
|
||||||
case "token", "id_token": // Implicit Grant
|
case "token", "id_token": // Implicit Grant
|
||||||
token, tokenError, err = GetImplicitToken(application, username, scope, nonce, host)
|
token, tokenError, err = GetImplicitToken(application, username, scope, nonce, host)
|
||||||
|
case "urn:ietf:params:oauth:grant-type:device_code":
|
||||||
|
token, tokenError, err = GetImplicitToken(application, username, scope, nonce, host)
|
||||||
case "refresh_token":
|
case "refresh_token":
|
||||||
refreshToken2, err := RefreshToken(grantType, refreshToken, scope, clientId, clientSecret, host)
|
refreshToken2, err := RefreshToken(grantType, refreshToken, scope, clientId, clientSecret, host)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@@ -39,6 +39,7 @@ type Webhook struct {
|
|||||||
Headers []*Header `xorm:"mediumtext" json:"headers"`
|
Headers []*Header `xorm:"mediumtext" json:"headers"`
|
||||||
Events []string `xorm:"varchar(1000)" json:"events"`
|
Events []string `xorm:"varchar(1000)" json:"events"`
|
||||||
TokenFields []string `xorm:"varchar(1000)" json:"tokenFields"`
|
TokenFields []string `xorm:"varchar(1000)" json:"tokenFields"`
|
||||||
|
ObjectFields []string `xorm:"varchar(1000)" json:"objectFields"`
|
||||||
IsUserExtended bool `json:"isUserExtended"`
|
IsUserExtended bool `json:"isUserExtended"`
|
||||||
SingleOrgOnly bool `json:"singleOrgOnly"`
|
SingleOrgOnly bool `json:"singleOrgOnly"`
|
||||||
IsEnabled bool `json:"isEnabled"`
|
IsEnabled bool `json:"isEnabled"`
|
||||||
|
@@ -66,6 +66,7 @@ func initAPI() {
|
|||||||
beego.Router("/api/get-webhook-event", &controllers.ApiController{}, "GET:GetWebhookEventType")
|
beego.Router("/api/get-webhook-event", &controllers.ApiController{}, "GET:GetWebhookEventType")
|
||||||
beego.Router("/api/get-captcha-status", &controllers.ApiController{}, "GET:GetCaptchaStatus")
|
beego.Router("/api/get-captcha-status", &controllers.ApiController{}, "GET:GetCaptchaStatus")
|
||||||
beego.Router("/api/callback", &controllers.ApiController{}, "POST:Callback")
|
beego.Router("/api/callback", &controllers.ApiController{}, "POST:Callback")
|
||||||
|
beego.Router("/api/device-auth", &controllers.ApiController{}, "POST:DeviceAuth")
|
||||||
|
|
||||||
beego.Router("/api/get-organizations", &controllers.ApiController{}, "GET:GetOrganizations")
|
beego.Router("/api/get-organizations", &controllers.ApiController{}, "GET:GetOrganizations")
|
||||||
beego.Router("/api/get-organization", &controllers.ApiController{}, "GET:GetOrganization")
|
beego.Router("/api/get-organization", &controllers.ApiController{}, "GET:GetOrganization")
|
||||||
|
@@ -726,6 +726,7 @@ class ApplicationEditPage extends React.Component {
|
|||||||
{id: "token", name: "Token"},
|
{id: "token", name: "Token"},
|
||||||
{id: "id_token", name: "ID Token"},
|
{id: "id_token", name: "ID Token"},
|
||||||
{id: "refresh_token", name: "Refresh Token"},
|
{id: "refresh_token", name: "Refresh Token"},
|
||||||
|
{id: "urn:ietf:params:oauth:grant-type:device_code", name: "Device Code"},
|
||||||
].map((item, index) => <Option key={index} value={item.id}>{item.name}</Option>)
|
].map((item, index) => <Option key={index} value={item.id}>{item.name}</Option>)
|
||||||
}
|
}
|
||||||
</Select>
|
</Select>
|
||||||
|
@@ -119,6 +119,7 @@ class EntryPage extends React.Component {
|
|||||||
<Route exact path="/login/:owner" 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="/signup/oauth/authorize" render={(props) => <SignupPage {...this.props} application={this.state.application} 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/oauth/authorize" render={(props) => <LoginPage {...this.props} application={this.state.application} type={"code"} mode={"signin"} onUpdateApplication={onUpdateApplication} {...props} />} />
|
||||||
|
<Route exact path="/login/oauth/device/:userCode" render={(props) => <LoginPage {...this.props} application={this.state.application} type={"device"} 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="/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) => <SelfForgetPage {...this.props} account={this.props.account} application={this.state.application} onUpdateApplication={onUpdateApplication} {...props} />} />
|
<Route exact path="/forget" render={(props) => <SelfForgetPage {...this.props} account={this.props.account} application={this.state.application} onUpdateApplication={onUpdateApplication} {...props} />} />
|
||||||
<Route exact path="/forget/:applicationName" render={(props) => <ForgetPage {...this.props} account={this.props.account} application={this.state.application} onUpdateApplication={onUpdateApplication} {...props} />} />
|
<Route exact path="/forget/:applicationName" render={(props) => <ForgetPage {...this.props} account={this.props.account} application={this.state.application} onUpdateApplication={onUpdateApplication} {...props} />} />
|
||||||
|
@@ -276,7 +276,7 @@ class OrganizationEditPage extends React.Component {
|
|||||||
</Col>
|
</Col>
|
||||||
<Col span={22} >
|
<Col span={22} >
|
||||||
<Select virtual={false} style={{width: "100%"}} value={this.state.organization.passwordType} onChange={(value => {this.updateOrganizationField("passwordType", value);})}
|
<Select virtual={false} style={{width: "100%"}} value={this.state.organization.passwordType} onChange={(value => {this.updateOrganizationField("passwordType", value);})}
|
||||||
options={["plain", "salt", "sha512-salt", "md5-salt", "bcrypt", "pbkdf2-salt", "argon2id"].map(item => Setting.getOption(item, item))}
|
options={["plain", "salt", "sha512-salt", "md5-salt", "bcrypt", "pbkdf2-salt", "argon2id", "pbkdf2-django"].map(item => Setting.getOption(item, item))}
|
||||||
/>
|
/>
|
||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
|
@@ -144,6 +144,9 @@ class WebhookEditPage extends React.Component {
|
|||||||
if (["port"].includes(key)) {
|
if (["port"].includes(key)) {
|
||||||
value = Setting.myParseInt(value);
|
value = Setting.myParseInt(value);
|
||||||
}
|
}
|
||||||
|
if (key === "objectFields") {
|
||||||
|
value = value.includes("All") ? ["All"] : value;
|
||||||
|
}
|
||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -294,6 +297,19 @@ class WebhookEditPage extends React.Component {
|
|||||||
</Select>
|
</Select>
|
||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
|
<Row style={{marginTop: "20px"}} >
|
||||||
|
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||||
|
{Setting.getLabel(i18next.t("webhook:Object fields"), i18next.t("webhook:Object fields - Tooltip"))} :
|
||||||
|
</Col>
|
||||||
|
<Col span={22} >
|
||||||
|
<Select virtual={false} mode="tags" showSearch style={{width: "100%"}} value={this.state.webhook.objectFields} onChange={(value => {this.updateWebhookField("objectFields", value);})}>
|
||||||
|
<Option key="All" value="All">{i18next.t("general:All")}</Option>
|
||||||
|
{
|
||||||
|
["owner", "name", "createdTime", "updatedTime", "deletedTime", "id", "displayName"].map((item, index) => <Option key={index} value={item}>{item}</Option>)
|
||||||
|
}
|
||||||
|
</Select>
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
<Row style={{marginTop: "20px"}} >
|
<Row style={{marginTop: "20px"}} >
|
||||||
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 19 : 2}>
|
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 19 : 2}>
|
||||||
{Setting.getLabel(i18next.t("webhook:Is user extended"), i18next.t("webhook:Is user extended - Tooltip"))} :
|
{Setting.getLabel(i18next.t("webhook:Is user extended"), i18next.t("webhook:Is user extended - Tooltip"))} :
|
||||||
|
@@ -61,7 +61,14 @@ export function oAuthParamsToQuery(oAuthParams) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function getApplicationLogin(params) {
|
export function getApplicationLogin(params) {
|
||||||
const queryParams = (params?.type === "cas") ? casLoginParamsToQuery(params) : oAuthParamsToQuery(params);
|
let queryParams = "";
|
||||||
|
if (params?.type === "cas") {
|
||||||
|
queryParams = casLoginParamsToQuery(params);
|
||||||
|
} else if (params?.type === "device") {
|
||||||
|
queryParams = `?userCode=${params.userCode}&type=device`;
|
||||||
|
} else {
|
||||||
|
queryParams = oAuthParamsToQuery(params);
|
||||||
|
}
|
||||||
return fetch(`${authConfig.serverUrl}/api/get-app-login${queryParams}`, {
|
return fetch(`${authConfig.serverUrl}/api/get-app-login${queryParams}`, {
|
||||||
method: "GET",
|
method: "GET",
|
||||||
credentials: "include",
|
credentials: "include",
|
||||||
|
@@ -65,6 +65,8 @@ class LoginPage extends React.Component {
|
|||||||
orgChoiceMode: new URLSearchParams(props.location?.search).get("orgChoiceMode") ?? null,
|
orgChoiceMode: new URLSearchParams(props.location?.search).get("orgChoiceMode") ?? null,
|
||||||
userLang: null,
|
userLang: null,
|
||||||
loginLoading: false,
|
loginLoading: false,
|
||||||
|
userCode: props.userCode ?? (props.match?.params?.userCode ?? null),
|
||||||
|
userCodeStatus: "",
|
||||||
};
|
};
|
||||||
|
|
||||||
if (this.state.type === "cas" && props.match?.params.casApplicationName !== undefined) {
|
if (this.state.type === "cas" && props.match?.params.casApplicationName !== undefined) {
|
||||||
@@ -81,7 +83,7 @@ class LoginPage extends React.Component {
|
|||||||
if (this.getApplicationObj() === undefined) {
|
if (this.getApplicationObj() === undefined) {
|
||||||
if (this.state.type === "login" || this.state.type === "saml") {
|
if (this.state.type === "login" || this.state.type === "saml") {
|
||||||
this.getApplication();
|
this.getApplication();
|
||||||
} else if (this.state.type === "code" || this.state.type === "cas") {
|
} else if (this.state.type === "code" || this.state.type === "cas" || this.state.type === "device") {
|
||||||
this.getApplicationLogin();
|
this.getApplicationLogin();
|
||||||
} else {
|
} else {
|
||||||
Setting.showMessage("error", `Unknown authentication type: ${this.state.type}`);
|
Setting.showMessage("error", `Unknown authentication type: ${this.state.type}`);
|
||||||
@@ -155,13 +157,25 @@ class LoginPage extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
getApplicationLogin() {
|
getApplicationLogin() {
|
||||||
const loginParams = (this.state.type === "cas") ? Util.getCasLoginParameters("admin", this.state.applicationName) : Util.getOAuthGetParameters();
|
let loginParams;
|
||||||
|
if (this.state.type === "cas") {
|
||||||
|
loginParams = Util.getCasLoginParameters("admin", this.state.applicationName);
|
||||||
|
} else if (this.state.type === "device") {
|
||||||
|
loginParams = {userCode: this.state.userCode, type: this.state.type};
|
||||||
|
} else {
|
||||||
|
loginParams = Util.getOAuthGetParameters();
|
||||||
|
}
|
||||||
AuthBackend.getApplicationLogin(loginParams)
|
AuthBackend.getApplicationLogin(loginParams)
|
||||||
.then((res) => {
|
.then((res) => {
|
||||||
if (res.status === "ok") {
|
if (res.status === "ok") {
|
||||||
const application = res.data;
|
const application = res.data;
|
||||||
this.onUpdateApplication(application);
|
this.onUpdateApplication(application);
|
||||||
} else {
|
} else {
|
||||||
|
if (this.state.type === "device") {
|
||||||
|
this.setState({
|
||||||
|
userCodeStatus: "expired",
|
||||||
|
});
|
||||||
|
}
|
||||||
this.onUpdateApplication(null);
|
this.onUpdateApplication(null);
|
||||||
this.setState({
|
this.setState({
|
||||||
msg: res.msg,
|
msg: res.msg,
|
||||||
@@ -266,6 +280,9 @@ class LoginPage extends React.Component {
|
|||||||
|
|
||||||
onUpdateApplication(application) {
|
onUpdateApplication(application) {
|
||||||
this.props.onUpdateApplication(application);
|
this.props.onUpdateApplication(application);
|
||||||
|
if (application === null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
for (const idx in application.providers) {
|
for (const idx in application.providers) {
|
||||||
const provider = application.providers[idx];
|
const provider = application.providers[idx];
|
||||||
if (provider.provider?.category === "Face ID") {
|
if (provider.provider?.category === "Face ID") {
|
||||||
@@ -296,6 +313,9 @@ class LoginPage extends React.Component {
|
|||||||
const oAuthParams = Util.getOAuthGetParameters();
|
const oAuthParams = Util.getOAuthGetParameters();
|
||||||
|
|
||||||
values["type"] = oAuthParams?.responseType ?? this.state.type;
|
values["type"] = oAuthParams?.responseType ?? this.state.type;
|
||||||
|
if (this.state.userCode) {
|
||||||
|
values["userCode"] = this.state.userCode;
|
||||||
|
}
|
||||||
|
|
||||||
if (oAuthParams?.samlRequest) {
|
if (oAuthParams?.samlRequest) {
|
||||||
values["samlRequest"] = oAuthParams.samlRequest;
|
values["samlRequest"] = oAuthParams.samlRequest;
|
||||||
@@ -479,6 +499,11 @@ class LoginPage extends React.Component {
|
|||||||
this.props.onLoginSuccess();
|
this.props.onLoginSuccess();
|
||||||
} else if (responseType === "code") {
|
} else if (responseType === "code") {
|
||||||
this.postCodeLoginAction(res);
|
this.postCodeLoginAction(res);
|
||||||
|
} else if (responseType === "device") {
|
||||||
|
Setting.showMessage("success", "Successful login");
|
||||||
|
this.setState({
|
||||||
|
userCodeStatus: "success",
|
||||||
|
});
|
||||||
} else if (responseType === "token" || responseType === "id_token") {
|
} else if (responseType === "token" || responseType === "id_token") {
|
||||||
if (res.data2) {
|
if (res.data2) {
|
||||||
sessionStorage.setItem("signinUrl", window.location.pathname + window.location.search);
|
sessionStorage.setItem("signinUrl", window.location.pathname + window.location.search);
|
||||||
@@ -826,6 +851,16 @@ class LoginPage extends React.Component {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (this.state.userCode && this.state.userCodeStatus === "success") {
|
||||||
|
return (
|
||||||
|
<Result
|
||||||
|
status="success"
|
||||||
|
title={i18next.t("application:Logged in successfully")}
|
||||||
|
>
|
||||||
|
</Result>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
const showForm = Setting.isPasswordEnabled(application) || Setting.isCodeSigninEnabled(application) || Setting.isWebAuthnEnabled(application) || Setting.isLdapEnabled(application) || Setting.isFaceIdEnabled(application);
|
const showForm = Setting.isPasswordEnabled(application) || Setting.isCodeSigninEnabled(application) || Setting.isWebAuthnEnabled(application) || Setting.isLdapEnabled(application) || Setting.isFaceIdEnabled(application);
|
||||||
if (showForm) {
|
if (showForm) {
|
||||||
let loginWidth = 320;
|
let loginWidth = 320;
|
||||||
@@ -986,6 +1021,10 @@ class LoginPage extends React.Component {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (this.state.userCode && this.state.userCodeStatus === "success") {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<div style={{fontSize: 16, textAlign: "left"}}>
|
<div style={{fontSize: 16, textAlign: "left"}}>
|
||||||
@@ -1070,6 +1109,8 @@ class LoginPage extends React.Component {
|
|||||||
.catch(error => {
|
.catch(error => {
|
||||||
Setting.showMessage("error", `${i18next.t("general:Failed to connect to server")}${error}`);
|
Setting.showMessage("error", `${i18next.t("general:Failed to connect to server")}${error}`);
|
||||||
});
|
});
|
||||||
|
}).catch(error => {
|
||||||
|
Setting.showMessage("error", `${error}`);
|
||||||
}).finally(() => {
|
}).finally(() => {
|
||||||
this.setState({
|
this.setState({
|
||||||
loginLoading: false,
|
loginLoading: false,
|
||||||
@@ -1266,6 +1307,15 @@ class LoginPage extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
|
if (this.state.userCodeStatus === "expired") {
|
||||||
|
return <Result
|
||||||
|
style={{width: "100%"}}
|
||||||
|
status="error"
|
||||||
|
title={`Code ${i18next.t("subscription:Expired")}`}
|
||||||
|
>
|
||||||
|
</Result>;
|
||||||
|
}
|
||||||
|
|
||||||
const application = this.getApplicationObj();
|
const application = this.getApplicationObj();
|
||||||
if (application === undefined) {
|
if (application === undefined) {
|
||||||
return null;
|
return null;
|
||||||
|
Reference in New Issue
Block a user