Compare commits

..

11 Commits

Author SHA1 Message Date
cf4e76f9dc feat: add footer to door pages (#868) 2022-07-08 20:36:49 +08:00
81f2d01dc1 fix: fix dockerfile (#866) 2022-07-07 16:10:15 +08:00
61773d3173 fix: support user-defined clientId&Secret (#862) 2022-07-06 19:27:59 +08:00
ec29621547 feat: init from configuration file (#858)
* feat: init from configuration file

* Update init_data.json.template

* Update init_data.go

Co-authored-by: Yang Luo <hsluoyz@qq.com>
2022-07-05 00:36:22 +08:00
b8e324cadf fix: azurad provider (#855) 2022-07-04 16:40:23 +08:00
f37fd6ba87 Fix empty arg bug in getPermanentAvatarUrl(). 2022-07-03 19:31:12 +08:00
b4bf734fe8 fix: fix cors filter (#847)
* fix: fix cors filter

* Update cors_filter.go

Co-authored-by: Yang Luo <hsluoyz@qq.com>
2022-07-02 13:45:18 +08:00
f0431701c9 fix: fix OAuth error response (#835)
* fix: fix OAuth error response

* fix: provide more detailed error messages for TokenError
2022-07-01 14:53:34 +08:00
aa5078de15 fix: crowdin kept deleting translations (#843)
Signed-off-by: Yixiang Zhao <seriouszyx@foxmail.com>
2022-07-01 10:51:40 +08:00
9a324b2cca fix: Update Crowdin link (#841) 2022-06-30 22:05:20 +08:00
919eaf1df4 fix: fix CORS error after sucessful OPTION (#838) 2022-06-30 21:29:02 +08:00
25 changed files with 678 additions and 166 deletions

View File

@ -14,13 +14,12 @@ RUN ./build.sh
FROM alpine:latest AS STANDARD FROM alpine:latest AS STANDARD
LABEL MAINTAINER="https://casdoor.org/" LABEL MAINTAINER="https://casdoor.org/"
WORKDIR /app WORKDIR /
COPY --from=BACK /go/src/casdoor/server ./server COPY --from=BACK /go/src/casdoor/server ./server
COPY --from=BACK /go/src/casdoor/swagger ./swagger COPY --from=BACK /go/src/casdoor/swagger ./swagger
COPY --from=BACK /go/src/casdoor/conf/app.conf ./conf/app.conf COPY --from=BACK /go/src/casdoor/conf/app.conf ./conf/app.conf
COPY --from=FRONT /web/build ./web/build COPY --from=FRONT /web/build ./web/build
VOLUME /app/files /app/logs ENTRYPOINT ["/server"]
ENTRYPOINT ["/app/server"]
FROM debian:latest AS db FROM debian:latest AS db
@ -36,7 +35,7 @@ LABEL MAINTAINER="https://casdoor.org/"
ENV MYSQL_ROOT_PASSWORD=123456 ENV MYSQL_ROOT_PASSWORD=123456
WORKDIR /app WORKDIR /
COPY --from=BACK /go/src/casdoor/server ./server COPY --from=BACK /go/src/casdoor/server ./server
COPY --from=BACK /go/src/casdoor/swagger ./swagger COPY --from=BACK /go/src/casdoor/swagger ./swagger
COPY --from=BACK /go/src/casdoor/docker-entrypoint.sh /docker-entrypoint.sh COPY --from=BACK /go/src/casdoor/docker-entrypoint.sh /docker-entrypoint.sh

View File

@ -98,7 +98,7 @@ For casdoor, if you have any questions, you can give Issues, or you can also dir
### I18n translation ### I18n translation
If you are contributing to casdoor, please note that we use [Crowdin](https://crowdin.com/project/casdoor-web) as translating platform and i18next as translating tool. When you add some words using i18next in the ```web/``` directory, please remember to add what you have added to the ```web/src/locales/en/data.json``` file. If you are contributing to casdoor, please note that we use [Crowdin](https://crowdin.com/project/casdoor-site) as translating platform and i18next as translating tool. When you add some words using i18next in the ```web/``` directory, please remember to add what you have added to the ```web/src/locales/en/data.json``` file.

View File

@ -165,6 +165,8 @@ func (c *ApiController) GetOAuthCode() {
// @Param client_secret query string true "OAuth client secret" // @Param client_secret query string true "OAuth client secret"
// @Param code query string true "OAuth code" // @Param code query string true "OAuth code"
// @Success 200 {object} object.TokenWrapper The Response object // @Success 200 {object} object.TokenWrapper The Response object
// @Success 400 {object} object.TokenError The Response object
// @Success 401 {object} object.TokenError The Response object
// @router /login/oauth/access_token [post] // @router /login/oauth/access_token [post]
func (c *ApiController) GetOAuthToken() { func (c *ApiController) GetOAuthToken() {
grantType := c.Input().Get("grant_type") grantType := c.Input().Get("grant_type")
@ -200,6 +202,7 @@ func (c *ApiController) GetOAuthToken() {
host := c.Ctx.Request.Host host := c.Ctx.Request.Host
c.Data["json"] = object.GetOAuthToken(grantType, clientId, clientSecret, code, verifier, scope, username, password, host, tag, avatar) c.Data["json"] = object.GetOAuthToken(grantType, clientId, clientSecret, code, verifier, scope, username, password, host, tag, avatar)
c.SetTokenErrorHttpStatus()
c.ServeJSON() c.ServeJSON()
} }
@ -213,6 +216,8 @@ func (c *ApiController) GetOAuthToken() {
// @Param client_id query string true "OAuth client id" // @Param client_id query string true "OAuth client id"
// @Param client_secret query string false "OAuth client secret" // @Param client_secret query string false "OAuth client secret"
// @Success 200 {object} object.TokenWrapper The Response object // @Success 200 {object} object.TokenWrapper The Response object
// @Success 400 {object} object.TokenError The Response object
// @Success 401 {object} object.TokenError The Response object
// @router /login/oauth/refresh_token [post] // @router /login/oauth/refresh_token [post]
func (c *ApiController) RefreshToken() { func (c *ApiController) RefreshToken() {
grantType := c.Input().Get("grant_type") grantType := c.Input().Get("grant_type")
@ -235,6 +240,7 @@ func (c *ApiController) RefreshToken() {
} }
c.Data["json"] = object.RefreshToken(grantType, refreshToken, scope, clientId, clientSecret, host) c.Data["json"] = object.RefreshToken(grantType, refreshToken, scope, clientId, clientSecret, host)
c.SetTokenErrorHttpStatus()
c.ServeJSON() c.ServeJSON()
} }
@ -270,6 +276,8 @@ func (c *ApiController) TokenLogout() {
// @Param token formData string true "access_token's value or refresh_token's value" // @Param token formData string true "access_token's value or refresh_token's value"
// @Param token_type_hint formData string true "the token type access_token or refresh_token" // @Param token_type_hint formData string true "the token type access_token or refresh_token"
// @Success 200 {object} object.IntrospectionResponse The Response object // @Success 200 {object} object.IntrospectionResponse The Response object
// @Success 400 {object} object.TokenError The Response object
// @Success 401 {object} object.TokenError The Response object
// @router /login/oauth/introspect [post] // @router /login/oauth/introspect [post]
func (c *ApiController) IntrospectToken() { func (c *ApiController) IntrospectToken() {
tokenValue := c.Input().Get("token") tokenValue := c.Input().Get("token")
@ -279,12 +287,21 @@ func (c *ApiController) IntrospectToken() {
clientSecret = c.Input().Get("client_secret") clientSecret = c.Input().Get("client_secret")
if clientId == "" || clientSecret == "" { if clientId == "" || clientSecret == "" {
c.ResponseError("empty clientId or clientSecret") c.ResponseError("empty clientId or clientSecret")
c.Data["json"] = &object.TokenError{
Error: object.INVALID_REQUEST,
}
c.SetTokenErrorHttpStatus()
c.ServeJSON()
return return
} }
} }
application := object.GetApplicationByClientId(clientId) application := object.GetApplicationByClientId(clientId)
if application == nil || application.ClientSecret != clientSecret { if application == nil || application.ClientSecret != clientSecret {
c.ResponseError("invalid application or wrong clientSecret") c.ResponseError("invalid application or wrong clientSecret")
c.Data["json"] = &object.TokenError{
Error: object.INVALID_CLIENT,
}
c.SetTokenErrorHttpStatus()
return return
} }
token := object.GetTokenByTokenAndApplication(tokenValue, application.Name) token := object.GetTokenByTokenAndApplication(tokenValue, application.Name)

View File

@ -51,6 +51,23 @@ func (c *ApiController) ResponseError(error string, data ...interface{}) {
c.ServeJSON() c.ServeJSON()
} }
// SetTokenErrorHttpStatus ...
func (c *ApiController) SetTokenErrorHttpStatus() {
_, ok := c.Data["json"].(*object.TokenError)
if ok {
if c.Data["json"].(*object.TokenError).Error == object.INVALID_CLIENT {
c.Ctx.Output.SetStatus(401)
c.Ctx.Output.Header("WWW-Authenticate", "Basic realm=\"OAuth2\"")
} else {
c.Ctx.Output.SetStatus(400)
}
}
_, ok = c.Data["json"].(*object.TokenWrapper)
if ok {
c.Ctx.Output.SetStatus(200)
}
}
// RequireSignedIn ... // RequireSignedIn ...
func (c *ApiController) RequireSignedIn() (string, bool) { func (c *ApiController) RequireSignedIn() (string, bool) {
userId := c.GetSessionUsername() userId := c.GetSessionUsername()

View File

@ -4,4 +4,4 @@ service mariadb start
mysqladmin -u root password ${MYSQL_ROOT_PASSWORD} mysqladmin -u root password ${MYSQL_ROOT_PASSWORD}
exec /app/server --createDatabase=true exec /server --createDatabase=true

2
go.mod
View File

@ -11,7 +11,7 @@ require (
github.com/casbin/casbin/v2 v2.30.1 github.com/casbin/casbin/v2 v2.30.1
github.com/casbin/xorm-adapter/v2 v2.5.1 github.com/casbin/xorm-adapter/v2 v2.5.1
github.com/casdoor/go-sms-sender v0.2.0 github.com/casdoor/go-sms-sender v0.2.0
github.com/casdoor/goth v1.69.0-FIX1 github.com/casdoor/goth v1.69.0-FIX2
github.com/casdoor/oss v1.2.0 github.com/casdoor/oss v1.2.0
github.com/dchest/captcha v0.0.0-20200903113550-03f5f0333e1f github.com/dchest/captcha v0.0.0-20200903113550-03f5f0333e1f
github.com/go-gomail/gomail v0.0.0-20160411212932-81ebce5c23df github.com/go-gomail/gomail v0.0.0-20160411212932-81ebce5c23df

4
go.sum
View File

@ -100,8 +100,8 @@ github.com/casbin/xorm-adapter/v2 v2.5.1 h1:BkpIxRHKa0s3bSMx173PpuU7oTs+Zw7XmD0B
github.com/casbin/xorm-adapter/v2 v2.5.1/go.mod h1:AeH4dBKHC9/zYxzdPVHhPDzF8LYLqjDdb767CWJoV54= github.com/casbin/xorm-adapter/v2 v2.5.1/go.mod h1:AeH4dBKHC9/zYxzdPVHhPDzF8LYLqjDdb767CWJoV54=
github.com/casdoor/go-sms-sender v0.2.0 h1:52bin4EBOPzOee64s9UK7jxd22FODvT9/+Y/Z+PSHpg= github.com/casdoor/go-sms-sender v0.2.0 h1:52bin4EBOPzOee64s9UK7jxd22FODvT9/+Y/Z+PSHpg=
github.com/casdoor/go-sms-sender v0.2.0/go.mod h1:fsZsNnALvFIo+HFcE1U/oCQv4ZT42FdglXKMsEm3WSk= github.com/casdoor/go-sms-sender v0.2.0/go.mod h1:fsZsNnALvFIo+HFcE1U/oCQv4ZT42FdglXKMsEm3WSk=
github.com/casdoor/goth v1.69.0-FIX1 h1:24Y3tfaJxWGJbxickGe3F9y2c8X1PgsQynhxGXV1f9Q= github.com/casdoor/goth v1.69.0-FIX2 h1:RgfIMkL9kekylgxHHK2ZY8ASAwOGns2HVlaBwLu7Bcs=
github.com/casdoor/goth v1.69.0-FIX1/go.mod h1:Om55nRo8CkeDkPSNBbzXW4G5uI28ZUkSk5S69dPek3s= github.com/casdoor/goth v1.69.0-FIX2/go.mod h1:Om55nRo8CkeDkPSNBbzXW4G5uI28ZUkSk5S69dPek3s=
github.com/casdoor/oss v1.2.0 h1:ozLAE+nnNdFQBWbzH8U9spzaO8h8NrB57lBcdyMUUQ8= github.com/casdoor/oss v1.2.0 h1:ozLAE+nnNdFQBWbzH8U9spzaO8h8NrB57lBcdyMUUQ8=
github.com/casdoor/oss v1.2.0/go.mod h1:qii35VBuxnR/uEuYSKpS0aJ8htQFOcCVsZ4FHgHLuss= github.com/casdoor/oss v1.2.0/go.mod h1:qii35VBuxnR/uEuYSKpS0aJ8htQFOcCVsZ4FHgHLuss=
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=

160
init_data.json.template Normal file
View File

@ -0,0 +1,160 @@
{
"organizations": [
{
"owner": "",
"name": "",
"displayName": "",
"websiteUrl": "",
"favicon": "",
"passwordType": "",
"phonePrefix": "",
"defaultAvatar": "",
"tags": [""]
}
],
"applications": [
{
"owner": "",
"name": "",
"displayName": "",
"logo": "",
"homepageUrl": "",
"organization": "",
"cert": "",
"enablePassword": true,
"enableSignUp": true,
"clientId": "",
"clientSecret": "",
"providers": [
{
"name": "",
"canSignUp": true,
"canSignIn": true,
"canUnlink": false,
"prompted": false,
"alertType": "None"
}
],
"signupItems": [
{
"name": "ID",
"visible": false,
"required": true,
"prompted": false,
"rule": "Random"
},
{
"name": "Username",
"visible": true,
"required": true,
"prompted": false,
"rule": "None"
},
{
"name": "Display name",
"visible": true,
"required": true,
"prompted": false,
"rule": "None"
},
{
"name": "Password",
"visible": true,
"required": true,
"prompted": false,
"rule": "None"
},
{
"name": "Confirm password",
"visible": true,
"required": true,
"prompted": false,
"rule": "None"
},
{
"name": "Email",
"visible": true,
"required": true,
"prompted": false,
"rule": "None"
},
{
"name": "Phone",
"visible": true,
"required": true,
"prompted": false,
"rule": "None"
},
{
"name": "Agreement",
"visible": true,
"required": true,
"prompted": false,
"rule": "None"
}
],
"redirectUris": [""],
"expireInHours": 168
}
],
"users": [
{
"owner": "",
"name": "",
"type": "normal-user",
"password": "",
"displayName": "",
"avatar": "",
"email": "",
"phone": "",
"address": [],
"affiliation": "",
"tag": "",
"score": 2000,
"ranking": 1,
"isAdmin": true,
"isGlobalAdmin": true,
"isForbidden": false,
"isDeleted": false,
"signupApplication": "",
"createdIp": ""
}
],
"providers": [
{
"owner": "",
"name": "",
"displayName": "",
"category": "",
"type": ""
}
],
"certs": [
{
"owner": "",
"name": "",
"displayName": "",
"scope": "JWT",
"type": "x509",
"cryptoAlgorithm": "RS256",
"bitSize": 4096,
"expireInYears": 20,
"publicKey": "",
"privateKey": ""
}
],
"ldaps": [
{
"id": "",
"owner": "",
"serverName": "",
"host": "",
"port": 389,
"admin": "",
"passwd": "",
"baseDn": "",
"autoSync": 0,
"lastSync": ""
}
]
}

View File

@ -36,6 +36,7 @@ func main() {
object.InitAdapter(*createDatabase) object.InitAdapter(*createDatabase)
object.InitDb() object.InitDb()
object.InitFromFile()
object.InitDefaultStorageProvider() object.InitDefaultStorageProvider()
object.InitLdapAutoSynchronizer() object.InitLdapAutoSynchronizer()
proxy.InitHttpClient() proxy.InitHttpClient()

View File

@ -280,8 +280,12 @@ func UpdateApplication(id string, application *Application) bool {
} }
func AddApplication(application *Application) bool { func AddApplication(application *Application) bool {
if application.ClientId == "" {
application.ClientId = util.GenerateClientId() application.ClientId = util.GenerateClientId()
}
if application.ClientSecret == "" {
application.ClientSecret = util.GenerateClientSecret() application.ClientSecret = util.GenerateClientSecret()
}
for _, providerItem := range application.Providers { for _, providerItem := range application.Providers {
providerItem.Provider = nil providerItem.Provider = nil
} }

View File

@ -51,6 +51,10 @@ func downloadFile(url string) (*bytes.Buffer, error) {
} }
func getPermanentAvatarUrl(organization string, username string, url string) string { func getPermanentAvatarUrl(organization string, username string, url string) string {
if url == "" {
return ""
}
if defaultStorageProvider == nil { if defaultStorageProvider == nil {
return "" return ""
} }

146
object/init_data.go Normal file
View File

@ -0,0 +1,146 @@
// 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.
package object
import "github.com/casdoor/casdoor/util"
type InitData struct {
Organizations []*Organization `json:"organizations"`
Applications []*Application `json:"applications"`
Users []*User `json:"users"`
Certs []*Cert `json:"certs"`
Providers []*Provider `json:"providers"`
Ldaps []*Ldap `json:"ldaps"`
}
func InitFromFile() {
initData := readInitDataFromFile("./init_data.json")
if initData != nil {
for _, organization := range initData.Organizations {
initDefinedOrganization(organization)
}
for _, provider := range initData.Providers {
initDefinedProvider(provider)
}
for _, user := range initData.Users {
initDefinedUser(user)
}
for _, application := range initData.Applications {
initDefinedApplication(application)
}
for _, cert := range initData.Certs {
initDefinedCert(cert)
}
for _, ldap := range initData.Ldaps {
initDefinedLdap(ldap)
}
}
}
func readInitDataFromFile(filePath string) *InitData {
if !util.FileExist(filePath) {
return nil
}
s := util.ReadStringFromPath(filePath)
data := &InitData{}
err := util.JsonToStruct(s, data)
if err != nil {
panic(err)
}
return data
}
func initDefinedOrganization(organization *Organization) {
existed := getOrganization(organization.Owner, organization.Name)
if existed != nil {
return
}
organization.CreatedTime = util.GetCurrentTime()
organization.AccountItems = []*AccountItem{
{Name: "Organization", Visible: true, ViewRule: "Public", ModifyRule: "Admin"},
{Name: "ID", Visible: true, ViewRule: "Public", ModifyRule: "Immutable"},
{Name: "Name", Visible: true, ViewRule: "Public", ModifyRule: "Admin"},
{Name: "Display name", Visible: true, ViewRule: "Public", ModifyRule: "Self"},
{Name: "Avatar", Visible: true, ViewRule: "Public", ModifyRule: "Self"},
{Name: "User type", Visible: true, ViewRule: "Public", ModifyRule: "Admin"},
{Name: "Password", Visible: true, ViewRule: "Self", ModifyRule: "Self"},
{Name: "Email", Visible: true, ViewRule: "Public", ModifyRule: "Self"},
{Name: "Phone", Visible: true, ViewRule: "Public", ModifyRule: "Self"},
{Name: "Country/Region", Visible: true, ViewRule: "Public", ModifyRule: "Self"},
{Name: "Location", Visible: true, ViewRule: "Public", ModifyRule: "Self"},
{Name: "Affiliation", Visible: true, ViewRule: "Public", ModifyRule: "Self"},
{Name: "Title", Visible: true, ViewRule: "Public", ModifyRule: "Self"},
{Name: "Homepage", Visible: true, ViewRule: "Public", ModifyRule: "Self"},
{Name: "Bio", Visible: true, ViewRule: "Public", ModifyRule: "Self"},
{Name: "Tag", Visible: true, ViewRule: "Public", ModifyRule: "Admin"},
{Name: "Signup application", Visible: true, ViewRule: "Public", ModifyRule: "Admin"},
{Name: "3rd-party logins", Visible: true, ViewRule: "Self", ModifyRule: "Self"},
{Name: "Properties", Visible: false, ViewRule: "Admin", ModifyRule: "Admin"},
{Name: "Is admin", Visible: true, ViewRule: "Admin", ModifyRule: "Admin"},
{Name: "Is global admin", Visible: true, ViewRule: "Admin", ModifyRule: "Admin"},
{Name: "Is forbidden", Visible: true, ViewRule: "Admin", ModifyRule: "Admin"},
{Name: "Is deleted", Visible: true, ViewRule: "Admin", ModifyRule: "Admin"},
}
AddOrganization(organization)
}
func initDefinedApplication(application *Application) {
existed := getApplication(application.Owner, application.Name)
if existed != nil {
return
}
application.CreatedTime = util.GetCurrentTime()
AddApplication(application)
}
func initDefinedUser(user *User) {
existed := getUser(user.Owner, user.Name)
if existed != nil {
return
}
user.CreatedTime = util.GetCurrentTime()
user.Id = util.GenerateId()
user.Properties = make(map[string]string)
AddUser(user)
}
func initDefinedCert(cert *Cert) {
existed := getCert(cert.Owner, cert.Name)
if existed != nil {
return
}
cert.CreatedTime = util.GetCurrentTime()
AddCert(cert)
}
func initDefinedLdap(ldap *Ldap) {
existed := GetLdap(ldap.Id)
if existed != nil {
return
}
AddLdap(ldap)
}
func initDefinedProvider(provider *Provider) {
existed := GetProvider(provider.GetId())
if existed != nil {
return
}
AddProvider(provider)
}

View File

@ -17,7 +17,6 @@ package object
import ( import (
"crypto/sha256" "crypto/sha256"
"encoding/base64" "encoding/base64"
"errors"
"fmt" "fmt"
"strings" "strings"
"time" "time"
@ -29,6 +28,13 @@ import (
const ( const (
hourSeconds = 3600 hourSeconds = 3600
INVALID_REQUEST = "invalid_request"
INVALID_CLIENT = "invalid_client"
INVALID_GRANT = "invalid_grant"
UNAUTHORIZED_CLIENT = "unauthorized_client"
UNSUPPORTED_GRANT_TYPE = "unsupported_grant_type"
INVALID_SCOPE = "invalid_scope"
ENDPOINT_ERROR = "endpoint_error"
) )
type Code struct { type Code struct {
@ -63,7 +69,11 @@ type TokenWrapper struct {
TokenType string `json:"token_type"` TokenType string `json:"token_type"`
ExpiresIn int `json:"expires_in"` ExpiresIn int `json:"expires_in"`
Scope string `json:"scope"` Scope string `json:"scope"`
Error string `json:"error,omitempty"` }
type TokenError struct {
Error string `json:"error"`
ErrorDescription string `json:"error_description,omitempty"`
} }
type IntrospectionResponse struct { type IntrospectionResponse struct {
@ -311,59 +321,42 @@ func GetOAuthCode(userId string, clientId string, responseType string, redirectU
} }
} }
func GetOAuthToken(grantType string, clientId string, clientSecret string, code string, verifier string, scope string, username string, password string, host string, tag string, avatar string) interface{} {
func GetOAuthToken(grantType string, clientId string, clientSecret string, code string, verifier string, scope string, username string, password string, host string, tag string, avatar string) *TokenWrapper {
var errString string
application := GetApplicationByClientId(clientId) application := GetApplicationByClientId(clientId)
if application == nil { if application == nil {
errString = "error: invalid client_id" return &TokenError{
return &TokenWrapper{ Error: INVALID_CLIENT,
AccessToken: errString, ErrorDescription: "client_id is invalid",
TokenType: "",
ExpiresIn: 0,
Scope: "",
Error: errString,
} }
} }
//Check if grantType is allowed in the current application //Check if grantType is allowed in the current application
if !IsGrantTypeValid(grantType, application.GrantTypes) && tag == "" { if !IsGrantTypeValid(grantType, application.GrantTypes) && tag == "" {
errString = fmt.Sprintf("error: grant_type: %s is not supported in this application", grantType) return &TokenError{
return &TokenWrapper{ Error: UNSUPPORTED_GRANT_TYPE,
AccessToken: errString, ErrorDescription: fmt.Sprintf("grant_type: %s is not supported in this application", grantType),
TokenType: "",
ExpiresIn: 0,
Scope: "",
Error: errString,
} }
} }
var token *Token var token *Token
var err error var tokenError *TokenError
switch grantType { switch grantType {
case "authorization_code": // Authorization Code Grant case "authorization_code": // Authorization Code Grant
token, err = GetAuthorizationCodeToken(application, clientSecret, code, verifier) token, tokenError = GetAuthorizationCodeToken(application, clientSecret, code, verifier)
case "password": // Resource Owner Password Credentials Grant case "password": // Resource Owner Password Credentials Grant
token, err = GetPasswordToken(application, username, password, scope, host) token, tokenError = GetPasswordToken(application, username, password, scope, host)
case "client_credentials": // Client Credentials Grant case "client_credentials": // Client Credentials Grant
token, err = GetClientCredentialsToken(application, clientSecret, scope, host) token, tokenError = GetClientCredentialsToken(application, clientSecret, scope, host)
} }
if tag == "wechat_miniprogram" { if tag == "wechat_miniprogram" {
// Wechat Mini Program // Wechat Mini Program
token, err = GetWechatMiniProgramToken(application, code, host, username, avatar) token, tokenError = GetWechatMiniProgramToken(application, code, host, username, avatar)
} }
if err != nil { if tokenError != nil {
errString = err.Error() return tokenError
return &TokenWrapper{
AccessToken: errString,
TokenType: "",
ExpiresIn: 0,
Scope: "",
Error: errString,
}
} }
token.CodeIsUsed = true token.CodeIsUsed = true
@ -380,81 +373,59 @@ func GetOAuthToken(grantType string, clientId string, clientSecret string, code
return tokenWrapper return tokenWrapper
} }
func RefreshToken(grantType string, refreshToken string, scope string, clientId string, clientSecret string, host string) *TokenWrapper { func RefreshToken(grantType string, refreshToken string, scope string, clientId string, clientSecret string, host string) interface{} {
var errString string
// check parameters // check parameters
if grantType != "refresh_token" { if grantType != "refresh_token" {
errString = "error: grant_type should be \"refresh_token\"" return &TokenError{
return &TokenWrapper{ Error: UNSUPPORTED_GRANT_TYPE,
AccessToken: errString, ErrorDescription: "grant_type should be refresh_token",
TokenType: "",
ExpiresIn: 0,
Scope: "",
Error: errString,
} }
} }
application := GetApplicationByClientId(clientId) application := GetApplicationByClientId(clientId)
if application == nil { if application == nil {
errString = "error: invalid client_id" return &TokenError{
return &TokenWrapper{ Error: INVALID_CLIENT,
AccessToken: errString, ErrorDescription: "client_id is invalid",
TokenType: "",
ExpiresIn: 0,
Scope: "",
Error: errString,
} }
} }
if clientSecret != "" && application.ClientSecret != clientSecret { if clientSecret != "" && application.ClientSecret != clientSecret {
errString = "error: invalid client_secret" return &TokenError{
return &TokenWrapper{ Error: INVALID_CLIENT,
AccessToken: errString, ErrorDescription: "client_secret is invalid",
TokenType: "",
ExpiresIn: 0,
Scope: "",
Error: errString,
} }
} }
// check whether the refresh token is valid, and has not expired. // check whether the refresh token is valid, and has not expired.
token := Token{RefreshToken: refreshToken} token := Token{RefreshToken: refreshToken}
existed, err := adapter.Engine.Get(&token) existed, err := adapter.Engine.Get(&token)
if err != nil || !existed { if err != nil || !existed {
errString = "error: invalid refresh_token" return &TokenError{
return &TokenWrapper{ Error: INVALID_GRANT,
AccessToken: errString, ErrorDescription: "refresh token is invalid, expired or revoked",
TokenType: "",
ExpiresIn: 0,
Scope: "",
Error: errString,
} }
} }
cert := getCertByApplication(application) cert := getCertByApplication(application)
_, err = ParseJwtToken(refreshToken, cert) _, err = ParseJwtToken(refreshToken, cert)
if err != nil { if err != nil {
errString := fmt.Sprintf("error: %s", err.Error()) return &TokenError{
return &TokenWrapper{ Error: INVALID_GRANT,
AccessToken: errString, ErrorDescription: fmt.Sprintf("parse refresh token error: %s", err.Error()),
TokenType: "",
ExpiresIn: 0,
Scope: "",
Error: errString,
} }
} }
// generate a new token // generate a new token
user := getUser(application.Organization, token.User) user := getUser(application.Organization, token.User)
if user.IsForbidden { if user.IsForbidden {
errString = "error: the user is forbidden to sign in, please contact the administrator" return &TokenError{
return &TokenWrapper{ Error: INVALID_GRANT,
AccessToken: errString, ErrorDescription: "the user is forbidden to sign in, please contact the administrator",
TokenType: "",
ExpiresIn: 0,
Scope: "",
Error: errString,
} }
} }
newAccessToken, newRefreshToken, err := generateJwtToken(application, user, "", scope, host) newAccessToken, newRefreshToken, err := generateJwtToken(application, user, "", scope, host)
if err != nil { if err != nil {
panic(err) return &TokenError{
Error: ENDPOINT_ERROR,
ErrorDescription: fmt.Sprintf("generate jwt token error: %s", err.Error()),
}
} }
newToken := &Token{ newToken := &Token{
@ -508,63 +479,99 @@ func IsGrantTypeValid(method string, grantTypes []string) bool {
} }
// Authorization code flow // Authorization code flow
func GetAuthorizationCodeToken(application *Application, clientSecret string, code string, verifier string) (*Token, error) { func GetAuthorizationCodeToken(application *Application, clientSecret string, code string, verifier string) (*Token, *TokenError) {
if code == "" { if code == "" {
return nil, errors.New("error: authorization code should not be empty") return nil, &TokenError{
Error: INVALID_REQUEST,
ErrorDescription: "authorization code should not be empty",
}
} }
token := getTokenByCode(code) token := getTokenByCode(code)
if token == nil { if token == nil {
return nil, errors.New("error: invalid authorization code") return nil, &TokenError{
Error: INVALID_GRANT,
ErrorDescription: "authorization code is invalid",
}
} }
if token.CodeIsUsed { if token.CodeIsUsed {
// anti replay attacks // anti replay attacks
return nil, errors.New("error: authorization code has been used") return nil, &TokenError{
Error: INVALID_GRANT,
ErrorDescription: "authorization code has been used",
}
} }
if token.CodeChallenge != "" && pkceChallenge(verifier) != token.CodeChallenge { if token.CodeChallenge != "" && pkceChallenge(verifier) != token.CodeChallenge {
return nil, errors.New("error: incorrect code_verifier") return nil, &TokenError{
Error: INVALID_GRANT,
ErrorDescription: "verifier is invalid",
}
} }
if application.ClientSecret != clientSecret { if application.ClientSecret != clientSecret {
// when using PKCE, the Client Secret can be empty, // when using PKCE, the Client Secret can be empty,
// but if it is provided, it must be accurate. // but if it is provided, it must be accurate.
if token.CodeChallenge == "" { if token.CodeChallenge == "" {
return nil, errors.New("error: invalid client_secret") return nil, &TokenError{
Error: INVALID_CLIENT,
ErrorDescription: "client_secret is invalid",
}
} else { } else {
if clientSecret != "" { if clientSecret != "" {
return nil, errors.New("error: invalid client_secret") return nil, &TokenError{
Error: INVALID_CLIENT,
ErrorDescription: "client_secret is invalid",
}
} }
} }
} }
if application.Name != token.Application { if application.Name != token.Application {
return nil, errors.New("error: the token is for wrong application (client_id)") return nil, &TokenError{
Error: INVALID_GRANT,
ErrorDescription: "the token is for wrong application (client_id)",
}
} }
if time.Now().Unix() > token.CodeExpireIn { if time.Now().Unix() > token.CodeExpireIn {
// code must be used within 5 minutes // code must be used within 5 minutes
return nil, errors.New("error: authorization code has expired") return nil, &TokenError{
Error: INVALID_GRANT,
ErrorDescription: "authorization code has expired",
}
} }
return token, nil return token, nil
} }
// Resource Owner Password Credentials flow // Resource Owner Password Credentials flow
func GetPasswordToken(application *Application, username string, password string, scope string, host string) (*Token, error) { func GetPasswordToken(application *Application, username string, password string, scope string, host string) (*Token, *TokenError) {
user := getUser(application.Organization, username) user := getUser(application.Organization, username)
if user == nil { if user == nil {
return nil, errors.New("error: the user does not exist") return nil, &TokenError{
Error: INVALID_GRANT,
ErrorDescription: "the user does not exist",
}
} }
msg := CheckPassword(user, password) msg := CheckPassword(user, password)
if msg != "" { if msg != "" {
return nil, errors.New("error: invalid username or password") return nil, &TokenError{
Error: INVALID_GRANT,
ErrorDescription: "invalid username or password",
}
} }
if user.IsForbidden { if user.IsForbidden {
return nil, errors.New("error: the user is forbidden to sign in, please contact the administrator") return nil, &TokenError{
Error: INVALID_GRANT,
ErrorDescription: "the user is forbidden to sign in, please contact the administrator",
}
} }
accessToken, refreshToken, err := generateJwtToken(application, user, "", scope, host) accessToken, refreshToken, err := generateJwtToken(application, user, "", scope, host)
if err != nil { if err != nil {
return nil, err return nil, &TokenError{
Error: ENDPOINT_ERROR,
ErrorDescription: fmt.Sprintf("generate jwt token error: %s", err.Error()),
}
} }
token := &Token{ token := &Token{
Owner: application.Owner, Owner: application.Owner,
@ -586,9 +593,12 @@ func GetPasswordToken(application *Application, username string, password string
} }
// Client Credentials flow // Client Credentials flow
func GetClientCredentialsToken(application *Application, clientSecret string, scope string, host string) (*Token, error) { func GetClientCredentialsToken(application *Application, clientSecret string, scope string, host string) (*Token, *TokenError) {
if application.ClientSecret != clientSecret { if application.ClientSecret != clientSecret {
return nil, errors.New("error: invalid client_secret") return nil, &TokenError{
Error: INVALID_CLIENT,
ErrorDescription: "client_secret is invalid",
}
} }
nullUser := &User{ nullUser := &User{
Owner: application.Owner, Owner: application.Owner,
@ -597,7 +607,10 @@ func GetClientCredentialsToken(application *Application, clientSecret string, sc
} }
accessToken, _, err := generateJwtToken(application, nullUser, "", scope, host) accessToken, _, err := generateJwtToken(application, nullUser, "", scope, host)
if err != nil { if err != nil {
return nil, err return nil, &TokenError{
Error: ENDPOINT_ERROR,
ErrorDescription: fmt.Sprintf("generate jwt token error: %s", err.Error()),
}
} }
token := &Token{ token := &Token{
Owner: application.Owner, Owner: application.Owner,
@ -643,25 +656,37 @@ func GetTokenByUser(application *Application, user *User, scope string, host str
} }
// Wechat Mini Program flow // Wechat Mini Program flow
func GetWechatMiniProgramToken(application *Application, code string, host string, username string, avatar string) (*Token, error) { func GetWechatMiniProgramToken(application *Application, code string, host string, username string, avatar string) (*Token, *TokenError) {
mpProvider := GetWechatMiniProgramProvider(application) mpProvider := GetWechatMiniProgramProvider(application)
if mpProvider == nil { if mpProvider == nil {
return nil, errors.New("error: the application does not support wechat mini program") return nil, &TokenError{
Error: INVALID_CLIENT,
ErrorDescription: "the application does not support wechat mini program",
}
} }
provider := GetProvider(util.GetId(mpProvider.Name)) provider := GetProvider(util.GetId(mpProvider.Name))
mpIdp := idp.NewWeChatMiniProgramIdProvider(provider.ClientId, provider.ClientSecret) mpIdp := idp.NewWeChatMiniProgramIdProvider(provider.ClientId, provider.ClientSecret)
session, err := mpIdp.GetSessionByCode(code) session, err := mpIdp.GetSessionByCode(code)
if err != nil { if err != nil {
return nil, err return nil, &TokenError{
Error: INVALID_GRANT,
ErrorDescription: fmt.Sprintf("get wechat mini program session error: %s", err.Error()),
}
} }
openId, unionId := session.Openid, session.Unionid openId, unionId := session.Openid, session.Unionid
if openId == "" && unionId == "" { if openId == "" && unionId == "" {
return nil, errors.New("err: WeChat's openid and unionid are empty") return nil, &TokenError{
Error: INVALID_REQUEST,
ErrorDescription: "the wechat mini program session is invalid",
}
} }
user := getUserByWechatId(openId, unionId) user := getUserByWechatId(openId, unionId)
if user == nil { if user == nil {
if !application.EnableSignUp { if !application.EnableSignUp {
return nil, errors.New("err: the application does not allow to sign up new account") return nil, &TokenError{
Error: INVALID_GRANT,
ErrorDescription: "the application does not allow to sign up new account",
}
} }
//Add new user //Add new user
var name string var name string
@ -691,7 +716,10 @@ func GetWechatMiniProgramToken(application *Application, code string, host strin
accessToken, refreshToken, err := generateJwtToken(application, user, "", "", host) accessToken, refreshToken, err := generateJwtToken(application, user, "", "", host)
if err != nil { if err != nil {
return nil, err return nil, &TokenError{
Error: ENDPOINT_ERROR,
ErrorDescription: fmt.Sprintf("generate jwt token error: %s", err.Error()),
}
} }
token := &Token{ token := &Token{

View File

@ -1,9 +1,24 @@
// 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.
package routers package routers
import ( import (
"net/http" "net/http"
"github.com/astaxie/beego/context" "github.com/astaxie/beego/context"
"github.com/casdoor/casdoor/conf"
"github.com/casdoor/casdoor/object" "github.com/casdoor/casdoor/object"
) )
@ -15,17 +30,22 @@ const (
) )
func CorsFilter(ctx *context.Context) { func CorsFilter(ctx *context.Context) {
if ctx.Input.Method() == "OPTIONS" {
origin := ctx.Input.Header(headerOrigin) origin := ctx.Input.Header(headerOrigin)
originConf := conf.GetConfigString("origin")
if origin != "" && originConf != "" && origin != originConf {
if object.IsAllowOrigin(origin) { if object.IsAllowOrigin(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")
ctx.ResponseWriter.WriteHeader(http.StatusOK)
} else { } else {
ctx.ResponseWriter.WriteHeader(http.StatusForbidden) ctx.ResponseWriter.WriteHeader(http.StatusForbidden)
}
return return
} }
if ctx.Input.Method() == "OPTIONS" {
ctx.ResponseWriter.WriteHeader(http.StatusOK)
return
}
}
} }

View File

@ -2194,6 +2194,18 @@
"schema": { "schema": {
"$ref": "#/definitions/object.TokenWrapper" "$ref": "#/definitions/object.TokenWrapper"
} }
},
"400": {
"description": "The Response object",
"schema": {
"$ref": "#/definitions/object.TokenError"
}
},
"401": {
"description": "The Response object",
"schema": {
"$ref": "#/definitions/object.TokenError"
}
} }
} }
} }
@ -2285,6 +2297,18 @@
"schema": { "schema": {
"$ref": "#/definitions/object.IntrospectionResponse" "$ref": "#/definitions/object.IntrospectionResponse"
} }
},
"400": {
"description": "The Response object",
"schema": {
"$ref": "#/definitions/object.TokenError"
}
},
"401": {
"description": "The Response object",
"schema": {
"$ref": "#/definitions/object.TokenError"
}
} }
} }
} }
@ -2377,6 +2401,18 @@
"schema": { "schema": {
"$ref": "#/definitions/object.TokenWrapper" "$ref": "#/definitions/object.TokenWrapper"
} }
},
"400": {
"description": "The Response object",
"schema": {
"$ref": "#/definitions/object.TokenError"
}
},
"401": {
"description": "The Response object",
"schema": {
"$ref": "#/definitions/object.TokenError"
}
} }
} }
} }
@ -3063,11 +3099,11 @@
} }
}, },
"definitions": { "definitions": {
"2200.0xc0003c4b70.false": { "2127.0xc000398090.false": {
"title": "false", "title": "false",
"type": "object" "type": "object"
}, },
"2235.0xc0003c4ba0.false": { "2161.0xc0003980c0.false": {
"title": "false", "title": "false",
"type": "object" "type": "object"
}, },
@ -3082,6 +3118,9 @@
"content": { "content": {
"type": "string" "type": "string"
}, },
"provider": {
"type": "string"
},
"receivers": { "receivers": {
"type": "array", "type": "array",
"items": { "items": {
@ -3182,10 +3221,10 @@
"type": "object", "type": "object",
"properties": { "properties": {
"data": { "data": {
"$ref": "#/definitions/2200.0xc0003c4b70.false" "$ref": "#/definitions/2127.0xc000398090.false"
}, },
"data2": { "data2": {
"$ref": "#/definitions/2235.0xc0003c4ba0.false" "$ref": "#/definitions/2161.0xc0003980c0.false"
}, },
"msg": { "msg": {
"type": "string" "type": "string"
@ -4209,6 +4248,18 @@
} }
} }
}, },
"object.TokenError": {
"title": "TokenError",
"type": "object",
"properties": {
"error": {
"type": "string"
},
"error_description": {
"type": "string"
}
}
},
"object.TokenWrapper": { "object.TokenWrapper": {
"title": "TokenWrapper", "title": "TokenWrapper",
"type": "object", "type": "object",
@ -4216,9 +4267,6 @@
"access_token": { "access_token": {
"type": "string" "type": "string"
}, },
"error": {
"type": "string"
},
"expires_in": { "expires_in": {
"type": "integer", "type": "integer",
"format": "int64" "format": "int64"

View File

@ -1435,6 +1435,14 @@ paths:
description: The Response object description: The Response object
schema: schema:
$ref: '#/definitions/object.TokenWrapper' $ref: '#/definitions/object.TokenWrapper'
"400":
description: The Response object
schema:
$ref: '#/definitions/object.TokenError'
"401":
description: The Response object
schema:
$ref: '#/definitions/object.TokenError'
/api/login/oauth/code: /api/login/oauth/code:
post: post:
tags: tags:
@ -1497,6 +1505,14 @@ paths:
description: The Response object description: The Response object
schema: schema:
$ref: '#/definitions/object.IntrospectionResponse' $ref: '#/definitions/object.IntrospectionResponse'
"400":
description: The Response object
schema:
$ref: '#/definitions/object.TokenError'
"401":
description: The Response object
schema:
$ref: '#/definitions/object.TokenError'
/api/login/oauth/logout: /api/login/oauth/logout:
get: get:
tags: tags:
@ -1559,6 +1575,14 @@ paths:
description: The Response object description: The Response object
schema: schema:
$ref: '#/definitions/object.TokenWrapper' $ref: '#/definitions/object.TokenWrapper'
"400":
description: The Response object
schema:
$ref: '#/definitions/object.TokenError'
"401":
description: The Response object
schema:
$ref: '#/definitions/object.TokenError'
/api/logout: /api/logout:
post: post:
tags: tags:
@ -2005,10 +2029,10 @@ paths:
- Verification API - Verification API
operationId: ApiController.VerifyCaptcha operationId: ApiController.VerifyCaptcha
definitions: definitions:
2200.0xc0003c4b70.false: 2127.0xc000398090.false:
title: "false" title: "false"
type: object type: object
2235.0xc0003c4ba0.false: 2161.0xc0003980c0.false:
title: "false" title: "false"
type: object type: object
Response: Response:
@ -2020,6 +2044,8 @@ definitions:
properties: properties:
content: content:
type: string type: string
provider:
type: string
receivers: receivers:
type: array type: array
items: items:
@ -2087,9 +2113,9 @@ definitions:
type: object type: object
properties: properties:
data: data:
$ref: '#/definitions/2200.0xc0003c4b70.false' $ref: '#/definitions/2127.0xc000398090.false'
data2: data2:
$ref: '#/definitions/2235.0xc0003c4ba0.false' $ref: '#/definitions/2161.0xc0003980c0.false'
msg: msg:
type: string type: string
name: name:
@ -2776,14 +2802,20 @@ definitions:
type: string type: string
user: user:
type: string type: string
object.TokenError:
title: TokenError
type: object
properties:
error:
type: string
error_description:
type: string
object.TokenWrapper: object.TokenWrapper:
title: TokenWrapper title: TokenWrapper
type: object type: object
properties: properties:
access_token: access_token:
type: string type: string
error:
type: string
expires_in: expires_in:
type: integer type: integer
format: int64 format: int64

View File

@ -663,6 +663,7 @@ class App extends Component {
renderPage() { renderPage() {
if (this.isDoorPages()) { if (this.isDoorPages()) {
return ( return (
<div>
<Switch> <Switch>
<Route exact path="/signup" render={(props) => this.renderHomeIfLoggedIn(<SignupPage account={this.state.account} {...props} />)}/> <Route exact path="/signup" render={(props) => this.renderHomeIfLoggedIn(<SignupPage account={this.state.account} {...props} />)}/>
<Route exact path="/signup/:applicationName" render={(props) => this.renderHomeIfLoggedIn(<SignupPage account={this.state.account} {...props} onUpdateAccount={(account) => {this.onUpdateAccount(account)}} />)}/> <Route exact path="/signup/:applicationName" render={(props) => this.renderHomeIfLoggedIn(<SignupPage account={this.state.account} {...props} onUpdateAccount={(account) => {this.onUpdateAccount(account)}} />)}/>
@ -681,6 +682,10 @@ class App extends Component {
<Route path="" render={() => <Result status="404" title="404 NOT FOUND" subTitle={i18next.t("general:Sorry, the page you visited does not exist.")} <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>}/>} /> extra={<a href="/"><Button type="primary">{i18next.t("general:Back Home")}</Button></a>}/>} />
</Switch> </Switch>
{
this.renderFooter()
}
</div>
) )
} }

View File

@ -97,8 +97,8 @@ const authInfo = {
endpoint: "https://appleid.apple.com/auth/authorize", endpoint: "https://appleid.apple.com/auth/authorize",
}, },
AzureAD: { AzureAD: {
scope: "user_impersonation", scope: "user.read",
endpoint: "https://login.microsoftonline.com/common/oauth2/authorize", endpoint: "https://login.microsoftonline.com/common/oauth2/v2.0/authorize",
}, },
Slack: { Slack: {
scope: "users:read", scope: "users:read",
@ -236,7 +236,7 @@ export function getAuthUrl(application, provider, method) {
} else if (provider.type === "Apple") { } else if (provider.type === "Apple") {
return `${endpoint}?client_id=${provider.clientId}&redirect_uri=${redirectUri}&state=${state}&response_type=code&scope=${scope}&response_mode=form_post`; return `${endpoint}?client_id=${provider.clientId}&redirect_uri=${redirectUri}&state=${state}&response_type=code&scope=${scope}&response_mode=form_post`;
} else if (provider.type === "AzureAD") { } else if (provider.type === "AzureAD") {
return `${endpoint}?client_id=${provider.clientId}&redirect_uri=${redirectUri}&state=${state}&response_type=code&scope=${scope}&resource=https://graph.windows.net/`; return `${endpoint}?client_id=${provider.clientId}&redirect_uri=${redirectUri}&state=${state}&response_type=code&scope=${scope}`;
} else if (provider.type === "Slack") { } else if (provider.type === "Slack") {
return `${endpoint}?client_id=${provider.clientId}&redirect_uri=${redirectUri}&state=${state}&response_type=code&scope=${scope}`; return `${endpoint}?client_id=${provider.clientId}&redirect_uri=${redirectUri}&state=${state}&response_type=code&scope=${scope}`;
} else if (provider.type === "Steam") { } else if (provider.type === "Steam") {

View File

@ -6,10 +6,13 @@
"Sign Up": "Registrieren" "Sign Up": "Registrieren"
}, },
"application": { "application": {
"Copy SAML metadata URL": "Copy SAML metadata URL",
"Copy prompt page URL": "Copy prompt page URL", "Copy prompt page URL": "Copy prompt page URL",
"Copy signin page URL": "Copy signin page URL", "Copy signin page URL": "Copy signin page URL",
"Copy signup page URL": "Copy signup page URL", "Copy signup page URL": "Copy signup page URL",
"Edit Application": "Anwendung bearbeiten", "Edit Application": "Anwendung bearbeiten",
"Enable SAML compress": "Enable SAML compress",
"Enable SAML compress - Tooltip": "Enable SAML compress - Tooltip",
"Enable code signin": "Code-Anmeldung aktivieren", "Enable code signin": "Code-Anmeldung aktivieren",
"Enable code signin - Tooltip": "Aktiviere Codeanmeldung - Tooltip", "Enable code signin - Tooltip": "Aktiviere Codeanmeldung - Tooltip",
"Enable signin session - Tooltip": "Aktiviere Anmeldesession - Tooltip", "Enable signin session - Tooltip": "Aktiviere Anmeldesession - Tooltip",
@ -30,6 +33,7 @@
"Refresh token expire - Tooltip": "Aktualisierungs-Token läuft ab - Tooltip", "Refresh token expire - Tooltip": "Aktualisierungs-Token läuft ab - Tooltip",
"SAML metadata": "SAML metadata", "SAML metadata": "SAML metadata",
"SAML metadata - Tooltip": "SAML metadata - Tooltip", "SAML metadata - Tooltip": "SAML metadata - Tooltip",
"SAML metadata URL copied to clipboard successfully": "SAML metadata URL copied to clipboard successfully",
"Signin page URL copied to clipboard successfully, please paste it into the incognito window or another browser": "Signin page URL copied to clipboard successfully, please paste it into the incognito window or another browser", "Signin page URL copied to clipboard successfully, please paste it into the incognito window or another browser": "Signin page URL copied to clipboard successfully, please paste it into the incognito window or another browser",
"Signin session": "Anmeldesitzung", "Signin session": "Anmeldesitzung",
"Signup items": "Artikel registrieren", "Signup items": "Artikel registrieren",
@ -442,6 +446,8 @@
"SP ACS URL": "SP-ACS-URL", "SP ACS URL": "SP-ACS-URL",
"SP ACS URL - Tooltip": "SP ACS URL - Tooltip", "SP ACS URL - Tooltip": "SP ACS URL - Tooltip",
"SP Entity ID": "SP Entity ID", "SP Entity ID": "SP Entity ID",
"Scene": "Scene",
"Scene - Tooltip": "Scene - Tooltip",
"Scope": "Scope", "Scope": "Scope",
"Scope - Tooltip": "Scope - Tooltip", "Scope - Tooltip": "Scope - Tooltip",
"Secret access key": "Geheimer Zugangsschlüssel", "Secret access key": "Geheimer Zugangsschlüssel",
@ -533,7 +539,6 @@
"The input is not invoice title!": "The input is not invoice title!", "The input is not invoice title!": "The input is not invoice title!",
"The input is not valid Email!": "Die Eingabe ist ungültig!", "The input is not valid Email!": "Die Eingabe ist ungültig!",
"The input is not valid Phone!": "Die Eingabe ist nicht gültig!", "The input is not valid Phone!": "Die Eingabe ist nicht gültig!",
"Unknown Check Type": "Unbekannter Schecktyp",
"Username": "Benutzername", "Username": "Benutzername",
"Username - Tooltip": "Benutzername - Tooltip", "Username - Tooltip": "Benutzername - Tooltip",
"Your account has been created!": "Ihr Konto wurde erstellt!", "Your account has been created!": "Ihr Konto wurde erstellt!",

View File

@ -6,10 +6,13 @@
"Sign Up": "Sign Up" "Sign Up": "Sign Up"
}, },
"application": { "application": {
"Copy SAML metadata URL": "Copy SAML metadata URL",
"Copy prompt page URL": "Copy prompt page URL", "Copy prompt page URL": "Copy prompt page URL",
"Copy signin page URL": "Copy signin page URL", "Copy signin page URL": "Copy signin page URL",
"Copy signup page URL": "Copy signup page URL", "Copy signup page URL": "Copy signup page URL",
"Edit Application": "Edit Application", "Edit Application": "Edit Application",
"Enable SAML compress": "Enable SAML compress",
"Enable SAML compress - Tooltip": "Enable SAML compress - Tooltip",
"Enable code signin": "Enable code signin", "Enable code signin": "Enable code signin",
"Enable code signin - Tooltip": "Enable code signin - Tooltip", "Enable code signin - Tooltip": "Enable code signin - Tooltip",
"Enable signin session - Tooltip": "Enable signin session - Tooltip", "Enable signin session - Tooltip": "Enable signin session - Tooltip",
@ -30,6 +33,7 @@
"Refresh token expire - Tooltip": "Refresh token expire - Tooltip", "Refresh token expire - Tooltip": "Refresh token expire - Tooltip",
"SAML metadata": "SAML metadata", "SAML metadata": "SAML metadata",
"SAML metadata - Tooltip": "SAML metadata - Tooltip", "SAML metadata - Tooltip": "SAML metadata - Tooltip",
"SAML metadata URL copied to clipboard successfully": "SAML metadata URL copied to clipboard successfully",
"Signin page URL copied to clipboard successfully, please paste it into the incognito window or another browser": "Signin page URL copied to clipboard successfully, please paste it into the incognito window or another browser", "Signin page URL copied to clipboard successfully, please paste it into the incognito window or another browser": "Signin page URL copied to clipboard successfully, please paste it into the incognito window or another browser",
"Signin session": "Signin session", "Signin session": "Signin session",
"Signup items": "Signup items", "Signup items": "Signup items",
@ -407,9 +411,9 @@
"Domain": "Domain", "Domain": "Domain",
"Domain - Tooltip": "Domain - Tooltip", "Domain - Tooltip": "Domain - Tooltip",
"Edit Provider": "Edit Provider", "Edit Provider": "Edit Provider",
"Email Content": "Email content", "Email Content": "Email Content",
"Email Content - Tooltip": "Email Content - Tooltip", "Email Content - Tooltip": "Email Content - Tooltip",
"Email Title": "Email title", "Email Title": "Email Title",
"Email Title - Tooltip": "Email Title - Tooltip", "Email Title - Tooltip": "Email Title - Tooltip",
"Endpoint": "Endpoint", "Endpoint": "Endpoint",
"Endpoint (Intranet)": "Endpoint (Intranet)", "Endpoint (Intranet)": "Endpoint (Intranet)",
@ -442,6 +446,8 @@
"SP ACS URL": "SP ACS URL", "SP ACS URL": "SP ACS URL",
"SP ACS URL - Tooltip": "SP ACS URL - Tooltip", "SP ACS URL - Tooltip": "SP ACS URL - Tooltip",
"SP Entity ID": "SP Entity ID", "SP Entity ID": "SP Entity ID",
"Scene": "Scene",
"Scene - Tooltip": "Scene - Tooltip",
"Scope": "Scope", "Scope": "Scope",
"Scope - Tooltip": "Scope - Tooltip", "Scope - Tooltip": "Scope - Tooltip",
"Secret access key": "Secret access key", "Secret access key": "Secret access key",
@ -467,9 +473,9 @@
"Template Code - Tooltip": "Template Code - Tooltip", "Template Code - Tooltip": "Template Code - Tooltip",
"Terms of Use": "Terms of Use", "Terms of Use": "Terms of Use",
"Terms of Use - Tooltip": "Terms of Use - Tooltip", "Terms of Use - Tooltip": "Terms of Use - Tooltip",
"Test Connection": "Test Smtp Connection", "Test Connection": "Test Connection",
"Test Email": "Test email config", "Test Email": "Test Email",
"Test Email - Tooltip": "Email Address", "Test Email - Tooltip": "Test Email - Tooltip",
"Token URL": "Token URL", "Token URL": "Token URL",
"Token URL - Tooltip": "Token URL - Tooltip", "Token URL - Tooltip": "Token URL - Tooltip",
"Type": "Type", "Type": "Type",
@ -533,7 +539,6 @@
"The input is not invoice title!": "The input is not invoice title!", "The input is not invoice title!": "The input is not invoice title!",
"The input is not valid Email!": "The input is not valid Email!", "The input is not valid Email!": "The input is not valid Email!",
"The input is not valid Phone!": "The input is not valid Phone!", "The input is not valid Phone!": "The input is not valid Phone!",
"Unknown Check Type": "Unknown Check Type",
"Username": "Username", "Username": "Username",
"Username - Tooltip": "Username - Tooltip", "Username - Tooltip": "Username - Tooltip",
"Your account has been created!": "Your account has been created!", "Your account has been created!": "Your account has been created!",

View File

@ -6,10 +6,13 @@
"Sign Up": "S'inscrire" "Sign Up": "S'inscrire"
}, },
"application": { "application": {
"Copy SAML metadata URL": "Copy SAML metadata URL",
"Copy prompt page URL": "Copy prompt page URL", "Copy prompt page URL": "Copy prompt page URL",
"Copy signin page URL": "Copy signin page URL", "Copy signin page URL": "Copy signin page URL",
"Copy signup page URL": "Copy signup page URL", "Copy signup page URL": "Copy signup page URL",
"Edit Application": "Modifier l'application", "Edit Application": "Modifier l'application",
"Enable SAML compress": "Enable SAML compress",
"Enable SAML compress - Tooltip": "Enable SAML compress - Tooltip",
"Enable code signin": "Activer la connexion au code", "Enable code signin": "Activer la connexion au code",
"Enable code signin - Tooltip": "Activer la connexion au code - infobulle", "Enable code signin - Tooltip": "Activer la connexion au code - infobulle",
"Enable signin session - Tooltip": "Activer la session de connexion - infobulle", "Enable signin session - Tooltip": "Activer la session de connexion - infobulle",
@ -30,6 +33,7 @@
"Refresh token expire - Tooltip": "Expiration du jeton d'actualisation - infobulle", "Refresh token expire - Tooltip": "Expiration du jeton d'actualisation - infobulle",
"SAML metadata": "SAML metadata", "SAML metadata": "SAML metadata",
"SAML metadata - Tooltip": "SAML metadata - Tooltip", "SAML metadata - Tooltip": "SAML metadata - Tooltip",
"SAML metadata URL copied to clipboard successfully": "SAML metadata URL copied to clipboard successfully",
"Signin page URL copied to clipboard successfully, please paste it into the incognito window or another browser": "Signin page URL copied to clipboard successfully, please paste it into the incognito window or another browser", "Signin page URL copied to clipboard successfully, please paste it into the incognito window or another browser": "Signin page URL copied to clipboard successfully, please paste it into the incognito window or another browser",
"Signin session": "Connexion à la session", "Signin session": "Connexion à la session",
"Signup items": "Inscrire des éléments", "Signup items": "Inscrire des éléments",
@ -442,6 +446,8 @@
"SP ACS URL": "URL du SP ACS", "SP ACS URL": "URL du SP ACS",
"SP ACS URL - Tooltip": "URL SP ACS - infobulle", "SP ACS URL - Tooltip": "URL SP ACS - infobulle",
"SP Entity ID": "ID de l'entité SP", "SP Entity ID": "ID de l'entité SP",
"Scene": "Scene",
"Scene - Tooltip": "Scene - Tooltip",
"Scope": "Scope", "Scope": "Scope",
"Scope - Tooltip": "Scope - Tooltip", "Scope - Tooltip": "Scope - Tooltip",
"Secret access key": "Clé d'accès secrète", "Secret access key": "Clé d'accès secrète",
@ -533,7 +539,6 @@
"The input is not invoice title!": "The input is not invoice title!", "The input is not invoice title!": "The input is not invoice title!",
"The input is not valid Email!": "L'entrée n'est pas un email valide !", "The input is not valid Email!": "L'entrée n'est pas un email valide !",
"The input is not valid Phone!": "L'entrée n'est pas un téléphone valide !", "The input is not valid Phone!": "L'entrée n'est pas un téléphone valide !",
"Unknown Check Type": "Type de vérification inconnu",
"Username": "Nom d'utilisateur", "Username": "Nom d'utilisateur",
"Username - Tooltip": "Nom d'utilisateur - Info-bulle", "Username - Tooltip": "Nom d'utilisateur - Info-bulle",
"Your account has been created!": "Votre compte a été créé !", "Your account has been created!": "Votre compte a été créé !",

View File

@ -6,10 +6,13 @@
"Sign Up": "新規登録" "Sign Up": "新規登録"
}, },
"application": { "application": {
"Copy SAML metadata URL": "Copy SAML metadata URL",
"Copy prompt page URL": "Copy prompt page URL", "Copy prompt page URL": "Copy prompt page URL",
"Copy signin page URL": "Copy signin page URL", "Copy signin page URL": "Copy signin page URL",
"Copy signup page URL": "Copy signup page URL", "Copy signup page URL": "Copy signup page URL",
"Edit Application": "アプリケーションを編集", "Edit Application": "アプリケーションを編集",
"Enable SAML compress": "Enable SAML compress",
"Enable SAML compress - Tooltip": "Enable SAML compress - Tooltip",
"Enable code signin": "コードサインインを有効にする", "Enable code signin": "コードサインインを有効にする",
"Enable code signin - Tooltip": "Enable code signin - Tooltip", "Enable code signin - Tooltip": "Enable code signin - Tooltip",
"Enable signin session - Tooltip": "Enable signin session - Tooltip", "Enable signin session - Tooltip": "Enable signin session - Tooltip",
@ -30,6 +33,7 @@
"Refresh token expire - Tooltip": "トークンの有効期限を更新する - ツールチップ", "Refresh token expire - Tooltip": "トークンの有効期限を更新する - ツールチップ",
"SAML metadata": "SAML metadata", "SAML metadata": "SAML metadata",
"SAML metadata - Tooltip": "SAML metadata - Tooltip", "SAML metadata - Tooltip": "SAML metadata - Tooltip",
"SAML metadata URL copied to clipboard successfully": "SAML metadata URL copied to clipboard successfully",
"Signin page URL copied to clipboard successfully, please paste it into the incognito window or another browser": "Signin page URL copied to clipboard successfully, please paste it into the incognito window or another browser", "Signin page URL copied to clipboard successfully, please paste it into the incognito window or another browser": "Signin page URL copied to clipboard successfully, please paste it into the incognito window or another browser",
"Signin session": "サインインセッション", "Signin session": "サインインセッション",
"Signup items": "アイテムの登録", "Signup items": "アイテムの登録",
@ -442,6 +446,8 @@
"SP ACS URL": "SP ACS URL", "SP ACS URL": "SP ACS URL",
"SP ACS URL - Tooltip": "SP ACS URL - ツールチップ", "SP ACS URL - Tooltip": "SP ACS URL - ツールチップ",
"SP Entity ID": "SP ID", "SP Entity ID": "SP ID",
"Scene": "Scene",
"Scene - Tooltip": "Scene - Tooltip",
"Scope": "Scope", "Scope": "Scope",
"Scope - Tooltip": "Scope - Tooltip", "Scope - Tooltip": "Scope - Tooltip",
"Secret access key": "シークレットアクセスキー", "Secret access key": "シークレットアクセスキー",
@ -533,7 +539,6 @@
"The input is not invoice title!": "The input is not invoice title!", "The input is not invoice title!": "The input is not invoice title!",
"The input is not valid Email!": "入力されたメールアドレスが無効です!", "The input is not valid Email!": "入力されたメールアドレスが無効です!",
"The input is not valid Phone!": "入力された電話番号が正しくありません!", "The input is not valid Phone!": "入力された電話番号が正しくありません!",
"Unknown Check Type": "不明なチェックタイプ",
"Username": "ユーザー名", "Username": "ユーザー名",
"Username - Tooltip": "ユーザー名 - ツールチップ", "Username - Tooltip": "ユーザー名 - ツールチップ",
"Your account has been created!": "あなたのアカウントが作成されました!", "Your account has been created!": "あなたのアカウントが作成されました!",

View File

@ -6,10 +6,13 @@
"Sign Up": "Sign Up" "Sign Up": "Sign Up"
}, },
"application": { "application": {
"Copy SAML metadata URL": "Copy SAML metadata URL",
"Copy prompt page URL": "Copy prompt page URL", "Copy prompt page URL": "Copy prompt page URL",
"Copy signin page URL": "Copy signin page URL", "Copy signin page URL": "Copy signin page URL",
"Copy signup page URL": "Copy signup page URL", "Copy signup page URL": "Copy signup page URL",
"Edit Application": "Edit Application", "Edit Application": "Edit Application",
"Enable SAML compress": "Enable SAML compress",
"Enable SAML compress - Tooltip": "Enable SAML compress - Tooltip",
"Enable code signin": "Enable code signin", "Enable code signin": "Enable code signin",
"Enable code signin - Tooltip": "Enable code signin - Tooltip", "Enable code signin - Tooltip": "Enable code signin - Tooltip",
"Enable signin session - Tooltip": "Enable signin session - Tooltip", "Enable signin session - Tooltip": "Enable signin session - Tooltip",
@ -30,6 +33,7 @@
"Refresh token expire - Tooltip": "Refresh token expire - Tooltip", "Refresh token expire - Tooltip": "Refresh token expire - Tooltip",
"SAML metadata": "SAML metadata", "SAML metadata": "SAML metadata",
"SAML metadata - Tooltip": "SAML metadata - Tooltip", "SAML metadata - Tooltip": "SAML metadata - Tooltip",
"SAML metadata URL copied to clipboard successfully": "SAML metadata URL copied to clipboard successfully",
"Signin page URL copied to clipboard successfully, please paste it into the incognito window or another browser": "Signin page URL copied to clipboard successfully, please paste it into the incognito window or another browser", "Signin page URL copied to clipboard successfully, please paste it into the incognito window or another browser": "Signin page URL copied to clipboard successfully, please paste it into the incognito window or another browser",
"Signin session": "Signin session", "Signin session": "Signin session",
"Signup items": "Signup items", "Signup items": "Signup items",
@ -442,6 +446,8 @@
"SP ACS URL": "SP ACS URL", "SP ACS URL": "SP ACS URL",
"SP ACS URL - Tooltip": "SP ACS URL - Tooltip", "SP ACS URL - Tooltip": "SP ACS URL - Tooltip",
"SP Entity ID": "SP Entity ID", "SP Entity ID": "SP Entity ID",
"Scene": "Scene",
"Scene - Tooltip": "Scene - Tooltip",
"Scope": "Scope", "Scope": "Scope",
"Scope - Tooltip": "Scope - Tooltip", "Scope - Tooltip": "Scope - Tooltip",
"Secret access key": "Secret access key", "Secret access key": "Secret access key",
@ -533,7 +539,6 @@
"The input is not invoice title!": "The input is not invoice title!", "The input is not invoice title!": "The input is not invoice title!",
"The input is not valid Email!": "The input is not valid Email!", "The input is not valid Email!": "The input is not valid Email!",
"The input is not valid Phone!": "The input is not valid Phone!", "The input is not valid Phone!": "The input is not valid Phone!",
"Unknown Check Type": "Unknown Check Type",
"Username": "Username", "Username": "Username",
"Username - Tooltip": "Username - Tooltip", "Username - Tooltip": "Username - Tooltip",
"Your account has been created!": "Your account has been created!", "Your account has been created!": "Your account has been created!",

View File

@ -6,10 +6,13 @@
"Sign Up": "Регистрация" "Sign Up": "Регистрация"
}, },
"application": { "application": {
"Copy SAML metadata URL": "Copy SAML metadata URL",
"Copy prompt page URL": "Copy prompt page URL", "Copy prompt page URL": "Copy prompt page URL",
"Copy signin page URL": "Copy signin page URL", "Copy signin page URL": "Copy signin page URL",
"Copy signup page URL": "Copy signup page URL", "Copy signup page URL": "Copy signup page URL",
"Edit Application": "Изменить приложение", "Edit Application": "Изменить приложение",
"Enable SAML compress": "Enable SAML compress",
"Enable SAML compress - Tooltip": "Enable SAML compress - Tooltip",
"Enable code signin": "Включить кодовый вход", "Enable code signin": "Включить кодовый вход",
"Enable code signin - Tooltip": "Включить вход с кодом - Tooltip", "Enable code signin - Tooltip": "Включить вход с кодом - Tooltip",
"Enable signin session - Tooltip": "Включить сеанс входа - Подсказка", "Enable signin session - Tooltip": "Включить сеанс входа - Подсказка",
@ -30,6 +33,7 @@
"Refresh token expire - Tooltip": "Срок обновления токена истекает - Подсказка", "Refresh token expire - Tooltip": "Срок обновления токена истекает - Подсказка",
"SAML metadata": "SAML metadata", "SAML metadata": "SAML metadata",
"SAML metadata - Tooltip": "SAML metadata - Tooltip", "SAML metadata - Tooltip": "SAML metadata - Tooltip",
"SAML metadata URL copied to clipboard successfully": "SAML metadata URL copied to clipboard successfully",
"Signin page URL copied to clipboard successfully, please paste it into the incognito window or another browser": "Signin page URL copied to clipboard successfully, please paste it into the incognito window or another browser", "Signin page URL copied to clipboard successfully, please paste it into the incognito window or another browser": "Signin page URL copied to clipboard successfully, please paste it into the incognito window or another browser",
"Signin session": "Сессия входа", "Signin session": "Сессия входа",
"Signup items": "Элементы регистрации", "Signup items": "Элементы регистрации",
@ -442,6 +446,8 @@
"SP ACS URL": "SP ACS URL", "SP ACS URL": "SP ACS URL",
"SP ACS URL - Tooltip": "SP ACS URL - Подсказка", "SP ACS URL - Tooltip": "SP ACS URL - Подсказка",
"SP Entity ID": "Идентификатор сущности SP", "SP Entity ID": "Идентификатор сущности SP",
"Scene": "Scene",
"Scene - Tooltip": "Scene - Tooltip",
"Scope": "Scope", "Scope": "Scope",
"Scope - Tooltip": "Scope - Tooltip", "Scope - Tooltip": "Scope - Tooltip",
"Secret access key": "Секретный ключ доступа", "Secret access key": "Секретный ключ доступа",
@ -533,7 +539,6 @@
"The input is not invoice title!": "The input is not invoice title!", "The input is not invoice title!": "The input is not invoice title!",
"The input is not valid Email!": "Ввод не является допустимым Email!", "The input is not valid Email!": "Ввод не является допустимым Email!",
"The input is not valid Phone!": "Введен неверный телефон!", "The input is not valid Phone!": "Введен неверный телефон!",
"Unknown Check Type": "Неизвестный тип проверки",
"Username": "Имя пользователя", "Username": "Имя пользователя",
"Username - Tooltip": "Имя пользователя - Подсказка", "Username - Tooltip": "Имя пользователя - Подсказка",
"Your account has been created!": "Ваша учетная запись была создана!", "Your account has been created!": "Ваша учетная запись была создана!",

View File

@ -6,15 +6,15 @@
"Sign Up": "注册" "Sign Up": "注册"
}, },
"application": { "application": {
"Copy prompt page URL": "复制提醒页面URL",
"Copy SAML metadata URL": "复制SAML元数据URL", "Copy SAML metadata URL": "复制SAML元数据URL",
"Copy prompt page URL": "复制提醒页面URL",
"Copy signin page URL": "复制登录页面URL", "Copy signin page URL": "复制登录页面URL",
"Copy signup page URL": "复制注册页面URL", "Copy signup page URL": "复制注册页面URL",
"Edit Application": "编辑应用", "Edit Application": "编辑应用",
"Enable code signin": "启用验证码登录",
"Enable code signin - Tooltip": "是否允许用手机或邮箱验证码登录",
"Enable SAML compress": "压缩SAML响应", "Enable SAML compress": "压缩SAML响应",
"Enable SAML compress - Tooltip": "Casdoor作为SAML idp时是否压缩SAML响应信息", "Enable SAML compress - Tooltip": "Casdoor作为SAML idp时是否压缩SAML响应信息",
"Enable code signin": "启用验证码登录",
"Enable code signin - Tooltip": "是否允许用手机或邮箱验证码登录",
"Enable signin session - Tooltip": "从应用登录Casdoor后Casdoor是否保持会话", "Enable signin session - Tooltip": "从应用登录Casdoor后Casdoor是否保持会话",
"Enable signup": "启用注册", "Enable signup": "启用注册",
"Enable signup - Tooltip": "是否允许用户注册", "Enable signup - Tooltip": "是否允许用户注册",
@ -446,6 +446,8 @@
"SP ACS URL": "SP ACS URL", "SP ACS URL": "SP ACS URL",
"SP ACS URL - Tooltip": "SP ACS URL - 工具提示", "SP ACS URL - Tooltip": "SP ACS URL - 工具提示",
"SP Entity ID": "SP 实体 ID", "SP Entity ID": "SP 实体 ID",
"Scene": "Scene",
"Scene - Tooltip": "Scene - Tooltip",
"Scope": "Scope", "Scope": "Scope",
"Scope - Tooltip": "Scope - 工具提示", "Scope - Tooltip": "Scope - 工具提示",
"Secret access key": "秘密访问密钥", "Secret access key": "秘密访问密钥",
@ -537,7 +539,6 @@
"The input is not invoice title!": "您输入的发票抬头有误!", "The input is not invoice title!": "您输入的发票抬头有误!",
"The input is not valid Email!": "您输入的电子邮箱格式有误!", "The input is not valid Email!": "您输入的电子邮箱格式有误!",
"The input is not valid Phone!": "您输入的手机号格式有误!", "The input is not valid Phone!": "您输入的手机号格式有误!",
"Unknown Check Type": "未知的验证码类型",
"Username": "用户名", "Username": "用户名",
"Username - Tooltip": "允许字符包括字母、数字、下划线,不得以数字开头", "Username - Tooltip": "允许字符包括字母、数字、下划线,不得以数字开头",
"Your account has been created!": "您的账号已创建!", "Your account has been created!": "您的账号已创建!",