From 3d4ca1adb17ce1b7d799a2965e4f1ac99a3d994e Mon Sep 17 00:00:00 2001 From: Yaodong Yu <2814461814@qq.com> Date: Wed, 5 Jul 2023 20:35:02 +0800 Subject: [PATCH] feat: support custom user mapping (#2029) * feat: support custom user mapping * fix: parse id to string * Update data.json * Update data.json --------- Co-authored-by: hsluoyz --- controllers/auth.go | 11 +-- go.mod | 1 + idp/custom.go | 82 ++++++++++++++-------- idp/provider.go | 123 +++++++++++++++++++-------------- object/provider.go | 58 ++++++++++++---- swagger/swagger.json | 2 +- swagger/swagger.yml | 2 +- util/string.go | 15 ++++ util/string_test.go | 20 ++++++ web/src/ProviderEditPage.js | 68 ++++++++++++++---- web/src/auth/Provider.js | 2 +- web/src/locales/de/data.json | 4 +- web/src/locales/en/data.json | 2 + web/src/locales/es/data.json | 2 + web/src/locales/fr/data.json | 2 + web/src/locales/id/data.json | 2 + web/src/locales/ja/data.json | 2 + web/src/locales/ko/data.json | 2 + web/src/locales/pt/data.json | 2 + web/src/locales/ru/data.json | 2 + web/src/locales/vi/data.json | 2 + web/src/locales/zh/data.json | 2 +- web/src/pricing/PricingPage.js | 4 +- web/yarn.lock | 12 +++- 24 files changed, 297 insertions(+), 127 deletions(-) diff --git a/controllers/auth.go b/controllers/auth.go index 86e697cc..cff1cd4e 100644 --- a/controllers/auth.go +++ b/controllers/auth.go @@ -416,15 +416,8 @@ func (c *ApiController) Login() { } } else if provider.Category == "OAuth" { // OAuth - - clientId := provider.ClientId - clientSecret := provider.ClientSecret - if provider.Type == "WeChat" && strings.Contains(c.Ctx.Request.UserAgent(), "MicroMessenger") { - clientId = provider.ClientId2 - clientSecret = provider.ClientSecret2 - } - - idProvider := idp.GetIdProvider(provider.Type, provider.SubType, clientId, clientSecret, provider.AppId, authForm.RedirectUri, provider.Domain, provider.CustomAuthUrl, provider.CustomTokenUrl, provider.CustomUserInfoUrl) + idpInfo := object.FromProviderToIdpInfo(c.Ctx, provider) + idProvider := idp.GetIdProvider(idpInfo, authForm.RedirectUri) if idProvider == nil { c.ResponseError(fmt.Sprintf(c.T("storage:The provider type: %s is not supported"), provider.Type)) return diff --git a/go.mod b/go.mod index c1342ef6..93d7e295 100644 --- a/go.mod +++ b/go.mod @@ -39,6 +39,7 @@ require ( github.com/lib/pq v1.10.2 github.com/lor00x/goldap v0.0.0-20180618054307-a546dffdd1a3 github.com/markbates/goth v1.75.2 + github.com/mitchellh/mapstructure v1.5.0 github.com/nu7hatch/gouuid v0.0.0-20131221200532-179d4d0c4d8d // indirect github.com/nyaruka/phonenumbers v1.1.5 github.com/pkoukk/tiktoken-go v0.1.1 diff --git a/idp/custom.go b/idp/custom.go index 5447272b..8f99ccb0 100644 --- a/idp/custom.go +++ b/idp/custom.go @@ -20,32 +20,37 @@ import ( "fmt" "io" "net/http" - _ "net/url" - _ "time" + "github.com/casdoor/casdoor/util" + "github.com/mitchellh/mapstructure" "golang.org/x/oauth2" ) type CustomIdProvider struct { - Client *http.Client - Config *oauth2.Config - UserInfoUrl string + Client *http.Client + Config *oauth2.Config + + UserInfoURL string + TokenURL string + AuthURL string + UserMapping map[string]string + Scopes []string } -func NewCustomIdProvider(clientId string, clientSecret string, redirectUrl string, authUrl string, tokenUrl string, userInfoUrl string) *CustomIdProvider { +func NewCustomIdProvider(idpInfo *ProviderInfo, redirectUrl string) *CustomIdProvider { idp := &CustomIdProvider{} - idp.UserInfoUrl = userInfoUrl - config := &oauth2.Config{ - ClientID: clientId, - ClientSecret: clientSecret, + idp.Config = &oauth2.Config{ + ClientID: idpInfo.ClientId, + ClientSecret: idpInfo.ClientSecret, RedirectURL: redirectUrl, Endpoint: oauth2.Endpoint{ - AuthURL: authUrl, - TokenURL: tokenUrl, + AuthURL: idpInfo.AuthURL, + TokenURL: idpInfo.TokenURL, }, } - idp.Config = config + idp.UserInfoURL = idpInfo.UserInfoURL + idp.UserMapping = idpInfo.UserMapping return idp } @@ -60,22 +65,20 @@ func (idp *CustomIdProvider) GetToken(code string) (*oauth2.Token, error) { } type CustomUserInfo struct { - Id string `json:"sub"` - Name string `json:"preferred_username,omitempty"` - DisplayName string `json:"name"` - Email string `json:"email"` - AvatarUrl string `json:"picture"` - Status string `json:"status"` - Msg string `json:"msg"` + Id string `mapstructure:"id"` + Username string `mapstructure:"username"` + DisplayName string `mapstructure:"displayName"` + Email string `mapstructure:"email"` + AvatarUrl string `mapstructure:"avatarUrl"` } func (idp *CustomIdProvider) GetUserInfo(token *oauth2.Token) (*UserInfo, error) { - ctUserinfo := &CustomUserInfo{} accessToken := token.AccessToken - request, err := http.NewRequest("GET", idp.UserInfoUrl, nil) + request, err := http.NewRequest("GET", idp.UserInfoURL, nil) if err != nil { return nil, err } + // add accessToken to request header request.Header.Add("Authorization", fmt.Sprintf("Bearer %s", accessToken)) resp, err := idp.Client.Do(request) @@ -89,21 +92,40 @@ func (idp *CustomIdProvider) GetUserInfo(token *oauth2.Token) (*UserInfo, error) return nil, err } - err = json.Unmarshal(data, ctUserinfo) + var dataMap map[string]interface{} + err = json.Unmarshal(data, &dataMap) if err != nil { return nil, err } - if ctUserinfo.Status != "" { - return nil, fmt.Errorf("err: %s", ctUserinfo.Msg) + // map user info + for k, v := range idp.UserMapping { + _, ok := dataMap[v] + if !ok { + return nil, fmt.Errorf("cannot find %s in user from castom provider", v) + } + dataMap[k] = dataMap[v] + } + + // try to parse id to string + id, err := util.ParseIdToString(dataMap["id"]) + if err != nil { + return nil, err + } + dataMap["id"] = id + + customUserinfo := &CustomUserInfo{} + err = mapstructure.Decode(dataMap, customUserinfo) + if err != nil { + return nil, err } userInfo := &UserInfo{ - Id: ctUserinfo.Id, - Username: ctUserinfo.Name, - DisplayName: ctUserinfo.DisplayName, - Email: ctUserinfo.Email, - AvatarUrl: ctUserinfo.AvatarUrl, + Id: customUserinfo.Id, + Username: customUserinfo.Username, + DisplayName: customUserinfo.DisplayName, + Email: customUserinfo.Email, + AvatarUrl: customUserinfo.AvatarUrl, } return userInfo, nil } diff --git a/idp/provider.go b/idp/provider.go index e563f399..9a9dadd1 100644 --- a/idp/provider.go +++ b/idp/provider.go @@ -32,72 +32,89 @@ type UserInfo struct { AvatarUrl string } +type ProviderInfo struct { + Type string + SubType string + ClientId string + ClientSecret string + AppId string + HostUrl string + RedirectUrl string + + TokenURL string + AuthURL string + UserInfoURL string + UserMapping map[string]string +} + type IdProvider interface { SetHttpClient(client *http.Client) GetToken(code string) (*oauth2.Token, error) GetUserInfo(token *oauth2.Token) (*UserInfo, error) } -func GetIdProvider(typ string, subType string, clientId string, clientSecret string, appId string, redirectUrl string, hostUrl string, authUrl string, tokenUrl string, userInfoUrl string) IdProvider { - if typ == "GitHub" { - return NewGithubIdProvider(clientId, clientSecret, redirectUrl) - } else if typ == "Google" { - return NewGoogleIdProvider(clientId, clientSecret, redirectUrl) - } else if typ == "QQ" { - return NewQqIdProvider(clientId, clientSecret, redirectUrl) - } else if typ == "WeChat" { - return NewWeChatIdProvider(clientId, clientSecret, redirectUrl) - } else if typ == "Facebook" { - return NewFacebookIdProvider(clientId, clientSecret, redirectUrl) - } else if typ == "DingTalk" { - return NewDingTalkIdProvider(clientId, clientSecret, redirectUrl) - } else if typ == "Weibo" { - return NewWeiBoIdProvider(clientId, clientSecret, redirectUrl) - } else if typ == "Gitee" { - return NewGiteeIdProvider(clientId, clientSecret, redirectUrl) - } else if typ == "LinkedIn" { - return NewLinkedInIdProvider(clientId, clientSecret, redirectUrl) - } else if typ == "WeCom" { - if subType == "Internal" { - return NewWeComInternalIdProvider(clientId, clientSecret, redirectUrl) - } else if subType == "Third-party" { - return NewWeComIdProvider(clientId, clientSecret, redirectUrl) +func GetIdProvider(idpInfo *ProviderInfo, redirectUrl string) IdProvider { + switch idpInfo.Type { + case "GitHub": + return NewGithubIdProvider(idpInfo.ClientId, idpInfo.ClientSecret, redirectUrl) + case "Google": + return NewGoogleIdProvider(idpInfo.ClientId, idpInfo.ClientSecret, redirectUrl) + case "QQ": + return NewQqIdProvider(idpInfo.ClientId, idpInfo.ClientSecret, redirectUrl) + case "WeChat": + return NewWeChatIdProvider(idpInfo.ClientId, idpInfo.ClientSecret, redirectUrl) + case "Facebook": + return NewFacebookIdProvider(idpInfo.ClientId, idpInfo.ClientSecret, redirectUrl) + case "DingTalk": + return NewDingTalkIdProvider(idpInfo.ClientId, idpInfo.ClientSecret, redirectUrl) + case "Weibo": + return NewWeiBoIdProvider(idpInfo.ClientId, idpInfo.ClientSecret, redirectUrl) + case "Gitee": + return NewGiteeIdProvider(idpInfo.ClientId, idpInfo.ClientSecret, redirectUrl) + case "LinkedIn": + return NewLinkedInIdProvider(idpInfo.ClientId, idpInfo.ClientSecret, redirectUrl) + case "WeCom": + if idpInfo.SubType == "Internal" { + return NewWeComInternalIdProvider(idpInfo.ClientId, idpInfo.ClientSecret, redirectUrl) + } else if idpInfo.SubType == "Third-party" { + return NewWeComIdProvider(idpInfo.ClientId, idpInfo.ClientSecret, redirectUrl) } else { return nil } - } else if typ == "Lark" { - return NewLarkIdProvider(clientId, clientSecret, redirectUrl) - } else if typ == "GitLab" { - return NewGitlabIdProvider(clientId, clientSecret, redirectUrl) - } else if typ == "Adfs" { - return NewAdfsIdProvider(clientId, clientSecret, redirectUrl, hostUrl) - } else if typ == "Baidu" { - return NewBaiduIdProvider(clientId, clientSecret, redirectUrl) - } else if typ == "Alipay" { - return NewAlipayIdProvider(clientId, clientSecret, redirectUrl) - } else if typ == "Custom" { - return NewCustomIdProvider(clientId, clientSecret, redirectUrl, authUrl, tokenUrl, userInfoUrl) - } else if typ == "Infoflow" { - if subType == "Internal" { - return NewInfoflowInternalIdProvider(clientId, clientSecret, appId, redirectUrl) - } else if subType == "Third-party" { - return NewInfoflowIdProvider(clientId, clientSecret, appId, redirectUrl) + case "Lark": + return NewLarkIdProvider(idpInfo.ClientId, idpInfo.ClientSecret, redirectUrl) + case "GitLab": + return NewGitlabIdProvider(idpInfo.ClientId, idpInfo.ClientSecret, redirectUrl) + case "Adfs": + return NewAdfsIdProvider(idpInfo.ClientId, idpInfo.ClientSecret, redirectUrl, idpInfo.HostUrl) + case "Baidu": + return NewBaiduIdProvider(idpInfo.ClientId, idpInfo.ClientSecret, redirectUrl) + case "Alipay": + return NewAlipayIdProvider(idpInfo.ClientId, idpInfo.ClientSecret, redirectUrl) + case "Custom": + return NewCustomIdProvider(idpInfo, redirectUrl) + case "Infoflow": + if idpInfo.SubType == "Internal" { + return NewInfoflowInternalIdProvider(idpInfo.ClientId, idpInfo.ClientSecret, idpInfo.AppId, redirectUrl) + } else if idpInfo.SubType == "Third-party" { + return NewInfoflowIdProvider(idpInfo.ClientId, idpInfo.ClientSecret, idpInfo.AppId, redirectUrl) } else { return nil } - } else if typ == "Casdoor" { - return NewCasdoorIdProvider(clientId, clientSecret, redirectUrl, hostUrl) - } else if typ == "Okta" { - return NewOktaIdProvider(clientId, clientSecret, redirectUrl, hostUrl) - } else if typ == "Douyin" { - return NewDouyinIdProvider(clientId, clientSecret, redirectUrl) - } else if isGothSupport(typ) { - return NewGothIdProvider(typ, clientId, clientSecret, redirectUrl, hostUrl) - } else if typ == "Bilibili" { - return NewBilibiliIdProvider(clientId, clientSecret, redirectUrl) + case "Casdoor": + return NewCasdoorIdProvider(idpInfo.ClientId, idpInfo.ClientSecret, redirectUrl, idpInfo.HostUrl) + case "Okta": + return NewOktaIdProvider(idpInfo.ClientId, idpInfo.ClientSecret, redirectUrl, idpInfo.HostUrl) + case "Douyin": + return NewDouyinIdProvider(idpInfo.ClientId, idpInfo.ClientSecret, redirectUrl) + case "Bilibili": + return NewBilibiliIdProvider(idpInfo.ClientId, idpInfo.ClientSecret, redirectUrl) + default: + if isGothSupport(idpInfo.Type) { + return NewGothIdProvider(idpInfo.Type, idpInfo.ClientId, idpInfo.ClientSecret, redirectUrl, idpInfo.HostUrl) + } + return nil } - - return nil } var gothList = []string{ diff --git a/object/provider.go b/object/provider.go index 20cbc3c1..616eab7a 100644 --- a/object/provider.go +++ b/object/provider.go @@ -16,8 +16,11 @@ package object import ( "fmt" + "strings" + "github.com/beego/beego/context" "github.com/casdoor/casdoor/i18n" + "github.com/casdoor/casdoor/idp" "github.com/casdoor/casdoor/pp" "github.com/casdoor/casdoor/util" "github.com/xorm-io/core" @@ -28,21 +31,22 @@ type Provider struct { Name string `xorm:"varchar(100) notnull pk unique" json:"name"` CreatedTime string `xorm:"varchar(100)" json:"createdTime"` - DisplayName string `xorm:"varchar(100)" json:"displayName"` - Category string `xorm:"varchar(100)" json:"category"` - Type string `xorm:"varchar(100)" json:"type"` - SubType string `xorm:"varchar(100)" json:"subType"` - Method string `xorm:"varchar(100)" json:"method"` - ClientId string `xorm:"varchar(100)" json:"clientId"` - ClientSecret string `xorm:"varchar(2000)" json:"clientSecret"` - ClientId2 string `xorm:"varchar(100)" json:"clientId2"` - ClientSecret2 string `xorm:"varchar(100)" json:"clientSecret2"` - Cert string `xorm:"varchar(100)" json:"cert"` - CustomAuthUrl string `xorm:"varchar(200)" json:"customAuthUrl"` - CustomScope string `xorm:"varchar(200)" json:"customScope"` - CustomTokenUrl string `xorm:"varchar(200)" json:"customTokenUrl"` - CustomUserInfoUrl string `xorm:"varchar(200)" json:"customUserInfoUrl"` - CustomLogo string `xorm:"varchar(200)" json:"customLogo"` + DisplayName string `xorm:"varchar(100)" json:"displayName"` + Category string `xorm:"varchar(100)" json:"category"` + Type string `xorm:"varchar(100)" json:"type"` + SubType string `xorm:"varchar(100)" json:"subType"` + Method string `xorm:"varchar(100)" json:"method"` + ClientId string `xorm:"varchar(100)" json:"clientId"` + ClientSecret string `xorm:"varchar(2000)" json:"clientSecret"` + ClientId2 string `xorm:"varchar(100)" json:"clientId2"` + ClientSecret2 string `xorm:"varchar(100)" json:"clientSecret2"` + Cert string `xorm:"varchar(100)" json:"cert"` + CustomAuthUrl string `xorm:"varchar(200)" json:"customAuthUrl"` + CustomTokenUrl string `xorm:"varchar(200)" json:"customTokenUrl"` + CustomUserInfoUrl string `xorm:"varchar(200)" json:"customUserInfoUrl"` + CustomLogo string `xorm:"varchar(200)" json:"customLogo"` + Scopes string `xorm:"varchar(100)" json:"scopes"` + UserMapping map[string]string `xorm:"varchar(500)" json:"userMapping"` Host string `xorm:"varchar(100)" json:"host"` Port int `json:"port"` @@ -365,3 +369,27 @@ func providerChangeTrigger(oldName string, newName string) error { return session.Commit() } + +func FromProviderToIdpInfo(ctx *context.Context, provider *Provider) *idp.ProviderInfo { + providerInfo := &idp.ProviderInfo{ + Type: provider.Type, + SubType: provider.SubType, + ClientId: provider.ClientId, + ClientSecret: provider.ClientSecret, + AppId: provider.AppId, + HostUrl: provider.Host, + TokenURL: provider.CustomTokenUrl, + AuthURL: provider.CustomAuthUrl, + UserInfoURL: provider.CustomUserInfoUrl, + UserMapping: provider.UserMapping, + } + + if provider.Type == "WeChat" { + if ctx != nil && strings.Contains(ctx.Request.UserAgent(), "MicroMessenger") { + providerInfo.ClientId = provider.ClientId2 + providerInfo.ClientSecret = provider.ClientSecret2 + } + } + + return providerInfo +} diff --git a/swagger/swagger.json b/swagger/swagger.json index e6a6b17e..d20ddb91 100644 --- a/swagger/swagger.json +++ b/swagger/swagger.json @@ -6126,7 +6126,7 @@ "customLogo": { "type": "string" }, - "customScope": { + "scopes": { "type": "string" }, "customTokenUrl": { diff --git a/swagger/swagger.yml b/swagger/swagger.yml index 78e4785e..31d9623a 100644 --- a/swagger/swagger.yml +++ b/swagger/swagger.yml @@ -4026,7 +4026,7 @@ definitions: type: string customLogo: type: string - customScope: + scopes: type: string customTokenUrl: type: string diff --git a/util/string.go b/util/string.go index e78c777f..2df12e1f 100644 --- a/util/string.go +++ b/util/string.go @@ -289,3 +289,18 @@ func HasString(strs []string, str string) bool { } return false } + +func ParseIdToString(input interface{}) (string, error) { + switch v := input.(type) { + case string: + return v, nil + case int: + return strconv.Itoa(v), nil + case int64: + return strconv.FormatInt(v, 10), nil + case float64: + return strconv.FormatFloat(v, 'f', -1, 64), nil + default: + return "", fmt.Errorf("unsupported id type: %T", input) + } +} diff --git a/util/string_test.go b/util/string_test.go index 720dbba8..c3027e77 100644 --- a/util/string_test.go +++ b/util/string_test.go @@ -246,3 +246,23 @@ func TestSnakeString(t *testing.T) { }) } } + +func TestParseId(t *testing.T) { + scenarios := []struct { + description string + input interface{} + expected interface{} + }{ + {"Should be return 123456", "123456", "123456"}, + {"Should be return 123456", 123456, "123456"}, + {"Should be return 123456", int64(123456), "123456"}, + {"Should be return 123456", float64(123456), "123456"}, + } + for _, scenery := range scenarios { + t.Run(scenery.description, func(t *testing.T) { + actual, err := ParseIdToString(scenery.input) + assert.Nil(t, err, "The returned value not is expected") + assert.Equal(t, scenery.expected, actual, "The returned value not is expected") + }) + } +} diff --git a/web/src/ProviderEditPage.js b/web/src/ProviderEditPage.js index 9bb3aae6..b3677951 100644 --- a/web/src/ProviderEditPage.js +++ b/web/src/ProviderEditPage.js @@ -56,8 +56,10 @@ class ProviderEditPage extends React.Component { } if (res.status === "ok") { + const provider = res.data; + provider.userMapping = provider.userMapping || {}; this.setState({ - provider: res.data, + provider: provider, }); } else { Setting.showMessage("error", res.msg); @@ -93,6 +95,40 @@ class ProviderEditPage extends React.Component { }); } + updateUserMappingField(key, value) { + const provider = this.state.provider; + provider.userMapping[key] = value; + this.setState({ + provider: provider, + }); + } + + renderUserMappingInput() { + return ( + + {Setting.getLabel(i18next.t("general:ID"), i18next.t("general:ID - Tooltip"))} : + { + this.updateUserMappingField("id", e.target.value); + }} /> + {Setting.getLabel(i18next.t("signup:Username"), i18next.t("signup:Username - Tooltip"))} : + { + this.updateUserMappingField("username", e.target.value); + }} /> + {Setting.getLabel(i18next.t("general:Display name"), i18next.t("general:Display name - Tooltip"))} : + { + this.updateUserMappingField("displayName", e.target.value); + }} /> + {Setting.getLabel(i18next.t("general:Email"), i18next.t("general:Email - Tooltip"))} : + { + this.updateUserMappingField("email", e.target.value); + }} /> + {Setting.getLabel(i18next.t("general:Avatar"), i18next.t("general:Avatar - Tooltip"))} : + { + this.updateUserMappingField("avatarUrl", e.target.value); + }} /> + + ); + } getClientIdLabel(provider) { switch (provider.category) { case "Email": @@ -350,7 +386,7 @@ class ProviderEditPage extends React.Component { } if (value === "Custom") { this.updateProviderField("customAuthUrl", "https://door.casdoor.com/login/oauth/authorize"); - this.updateProviderField("customScope", "openid profile email"); + this.updateProviderField("scopes", "openid profile email"); this.updateProviderField("customTokenUrl", "https://door.casdoor.com/api/login/oauth/access_token"); this.updateProviderField("customUserInfoUrl", "https://door.casdoor.com/api/userinfo"); } @@ -416,16 +452,6 @@ class ProviderEditPage extends React.Component { }} /> - - - {Setting.getLabel(i18next.t("provider:Scope"), i18next.t("provider:Scope - Tooltip"))} - - - { - this.updateProviderField("customScope", e.target.value); - }} /> - - {Setting.getLabel(i18next.t("provider:Token URL"), i18next.t("provider:Token URL - Tooltip"))} @@ -436,6 +462,16 @@ class ProviderEditPage extends React.Component { }} /> + + + {Setting.getLabel(i18next.t("provider:Scope"), i18next.t("provider:Scope - Tooltip"))} + + + { + this.updateProviderField("scopes", e.target.value); + }} /> + + {Setting.getLabel(i18next.t("provider:UserInfo URL"), i18next.t("provider:UserInfo URL - Tooltip"))} @@ -446,6 +482,14 @@ class ProviderEditPage extends React.Component { }} /> + + + {Setting.getLabel(i18next.t("provider:User mapping"), i18next.t("provider:User mapping - Tooltip"))} : + + + {this.renderUserMappingInput()} + + {Setting.getLabel(i18next.t("general:Favicon"), i18next.t("general:Favicon - Tooltip"))} : diff --git a/web/src/auth/Provider.js b/web/src/auth/Provider.js index b1d84ba2..64cd0b44 100644 --- a/web/src/auth/Provider.js +++ b/web/src/auth/Provider.js @@ -448,7 +448,7 @@ export function getAuthUrl(application, provider, method) { } else if (provider.type === "Douyin" || provider.type === "TikTok") { return `${endpoint}?client_key=${provider.clientId}&redirect_uri=${redirectUri}&state=${state}&response_type=code&scope=${scope}`; } else if (provider.type === "Custom") { - return `${provider.customAuthUrl}?client_id=${provider.clientId}&redirect_uri=${redirectUri}&scope=${provider.customScope}&response_type=code&state=${state}`; + return `${provider.customAuthUrl}?client_id=${provider.clientId}&redirect_uri=${redirectUri}&scope=${provider.scopes}&response_type=code&state=${state}`; } else if (provider.type === "Bilibili") { return `${endpoint}#/?client_id=${provider.clientId}&return_url=${redirectUri}&state=${state}&response_type=code`; } else if (provider.type === "Deezer") { diff --git a/web/src/locales/de/data.json b/web/src/locales/de/data.json index c28b5d2c..2b4f5ab2 100644 --- a/web/src/locales/de/data.json +++ b/web/src/locales/de/data.json @@ -570,8 +570,8 @@ "pricing": { "Copy pricing page URL": "Preisseite URL kopieren", "Edit Pricing": "Edit Pricing", - "Free": "Kostenlos", "Failed to get plans": "Es konnten keine Pläne abgerufen werden", + "Free": "Kostenlos", "Getting started": "Loslegen", "New Pricing": "New Pricing", "Trial duration": "Testphase Dauer", @@ -739,6 +739,8 @@ "Token URL - Tooltip": "Token-URL", "Type": "Typ", "Type - Tooltip": "Wählen Sie einen Typ aus", + "User mapping": "User mapping", + "User mapping - Tooltip": "User mapping - Tooltip", "UserInfo URL": "UserInfo-URL", "UserInfo URL - Tooltip": "UserInfo-URL", "admin (Shared)": "admin (Shared)" diff --git a/web/src/locales/en/data.json b/web/src/locales/en/data.json index 9614254f..8b116f5a 100644 --- a/web/src/locales/en/data.json +++ b/web/src/locales/en/data.json @@ -739,6 +739,8 @@ "Token URL - Tooltip": "Token URL", "Type": "Type", "Type - Tooltip": "Select a type", + "User mapping": "User mapping", + "User mapping - Tooltip": "User mapping - Tooltip", "UserInfo URL": "UserInfo URL", "UserInfo URL - Tooltip": "UserInfo URL", "admin (Shared)": "admin (Shared)" diff --git a/web/src/locales/es/data.json b/web/src/locales/es/data.json index 9303daea..b469cf15 100644 --- a/web/src/locales/es/data.json +++ b/web/src/locales/es/data.json @@ -739,6 +739,8 @@ "Token URL - Tooltip": "URL de token", "Type": "Tipo", "Type - Tooltip": "Seleccionar un tipo", + "User mapping": "User mapping", + "User mapping - Tooltip": "User mapping - Tooltip", "UserInfo URL": "URL de información del usuario", "UserInfo URL - Tooltip": "URL de información de usuario", "admin (Shared)": "administrador (compartido)" diff --git a/web/src/locales/fr/data.json b/web/src/locales/fr/data.json index 2f029445..dc2e05f4 100644 --- a/web/src/locales/fr/data.json +++ b/web/src/locales/fr/data.json @@ -739,6 +739,8 @@ "Token URL - Tooltip": "URL de jeton", "Type": "Type", "Type - Tooltip": "Sélectionnez un type", + "User mapping": "User mapping", + "User mapping - Tooltip": "User mapping - Tooltip", "UserInfo URL": "URL d'informations utilisateur", "UserInfo URL - Tooltip": "URL d'informations sur l'utilisateur", "admin (Shared)": "admin (Partagé)" diff --git a/web/src/locales/id/data.json b/web/src/locales/id/data.json index a1aef057..d2d04fc8 100644 --- a/web/src/locales/id/data.json +++ b/web/src/locales/id/data.json @@ -739,6 +739,8 @@ "Token URL - Tooltip": "Token URL: URL Token", "Type": "Jenis", "Type - Tooltip": "Pilih tipe", + "User mapping": "User mapping", + "User mapping - Tooltip": "User mapping - Tooltip", "UserInfo URL": "URL UserInfo", "UserInfo URL - Tooltip": "URL Informasi Pengguna", "admin (Shared)": "Admin (Berbagi)" diff --git a/web/src/locales/ja/data.json b/web/src/locales/ja/data.json index c531a142..4986f3eb 100644 --- a/web/src/locales/ja/data.json +++ b/web/src/locales/ja/data.json @@ -739,6 +739,8 @@ "Token URL - Tooltip": "トークンURL", "Type": "タイプ", "Type - Tooltip": "タイプを選択してください", + "User mapping": "User mapping", + "User mapping - Tooltip": "User mapping - Tooltip", "UserInfo URL": "UserInfo URLを日本語に翻訳すると、「ユーザー情報のURL」となります", "UserInfo URL - Tooltip": "ユーザー情報URL", "admin (Shared)": "管理者(共有)" diff --git a/web/src/locales/ko/data.json b/web/src/locales/ko/data.json index 62d40868..6d1f54b8 100644 --- a/web/src/locales/ko/data.json +++ b/web/src/locales/ko/data.json @@ -739,6 +739,8 @@ "Token URL - Tooltip": "토큰 URL", "Type": "타입", "Type - Tooltip": "유형을 선택하세요", + "User mapping": "User mapping", + "User mapping - Tooltip": "User mapping - Tooltip", "UserInfo URL": "사용자 정보 URL", "UserInfo URL - Tooltip": "UserInfo URL: 사용자 정보 URL", "admin (Shared)": "관리자 (공유)" diff --git a/web/src/locales/pt/data.json b/web/src/locales/pt/data.json index e6ea369d..b40cff9e 100644 --- a/web/src/locales/pt/data.json +++ b/web/src/locales/pt/data.json @@ -739,6 +739,8 @@ "Token URL - Tooltip": "URL do Token", "Type": "Tipo", "Type - Tooltip": "Selecione um tipo", + "User mapping": "User mapping", + "User mapping - Tooltip": "User mapping - Tooltip", "UserInfo URL": "URL do UserInfo", "UserInfo URL - Tooltip": "URL do UserInfo", "admin (Shared)": "admin (Compartilhado)" diff --git a/web/src/locales/ru/data.json b/web/src/locales/ru/data.json index 482f393d..edbb8044 100644 --- a/web/src/locales/ru/data.json +++ b/web/src/locales/ru/data.json @@ -739,6 +739,8 @@ "Token URL - Tooltip": "Токен URL", "Type": "Тип", "Type - Tooltip": "Выберите тип", + "User mapping": "User mapping", + "User mapping - Tooltip": "User mapping - Tooltip", "UserInfo URL": "URL информации о пользователе", "UserInfo URL - Tooltip": "URL пользовательской информации (URL информации о пользователе)", "admin (Shared)": "администратор (общий)" diff --git a/web/src/locales/vi/data.json b/web/src/locales/vi/data.json index 180255c0..e92699f0 100644 --- a/web/src/locales/vi/data.json +++ b/web/src/locales/vi/data.json @@ -739,6 +739,8 @@ "Token URL - Tooltip": "Địa chỉ URL của Token", "Type": "Kiểu", "Type - Tooltip": "Chọn loại", + "User mapping": "User mapping", + "User mapping - Tooltip": "User mapping - Tooltip", "UserInfo URL": "Đường dẫn UserInfo", "UserInfo URL - Tooltip": "Địa chỉ URL của Thông tin người dùng", "admin (Shared)": "quản trị viên (Chung)" diff --git a/web/src/locales/zh/data.json b/web/src/locales/zh/data.json index cae0e6a5..5c57c95d 100644 --- a/web/src/locales/zh/data.json +++ b/web/src/locales/zh/data.json @@ -570,8 +570,8 @@ "pricing": { "Copy pricing page URL": "复制定价页面链接", "Edit Pricing": "编辑定价", - "Free": "免费", "Failed to get plans": "未能获取计划", + "Free": "免费", "Getting started": "开始使用", "New Pricing": "添加定价", "Trial duration": "试用期时长", diff --git a/web/src/pricing/PricingPage.js b/web/src/pricing/PricingPage.js index 9299f4ab..49ee6337 100644 --- a/web/src/pricing/PricingPage.js +++ b/web/src/pricing/PricingPage.js @@ -66,7 +66,7 @@ class PricingPage extends React.Component { .then(results => { const hasError = results.some(result => result.status === "error"); if (hasError) { - Setting.showMessage("error", `${i18next.t("Failed to get plans")}`); + Setting.showMessage("error", i18next.t("pricing:Failed to get plans")); return; } this.setState({ @@ -75,7 +75,7 @@ class PricingPage extends React.Component { }); }) .catch(error => { - Setting.showMessage("error", `Failed to get plans: ${error}`); + Setting.showMessage("error", i18next.t("pricing:Failed to get plans") + `: ${error}`); }); } diff --git a/web/yarn.lock b/web/yarn.lock index f198b70b..1e5aa7a8 100644 --- a/web/yarn.lock +++ b/web/yarn.lock @@ -173,7 +173,7 @@ lru-cache "^5.1.1" semver "^6.3.0" -"@babel/helper-create-class-features-plugin@^7.18.6", "@babel/helper-create-class-features-plugin@^7.22.5": +"@babel/helper-create-class-features-plugin@^7.18.6", "@babel/helper-create-class-features-plugin@^7.21.0", "@babel/helper-create-class-features-plugin@^7.22.5": version "7.22.5" resolved "https://registry.yarnpkg.com/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.22.5.tgz#2192a1970ece4685fbff85b48da2c32fcb130b7c" integrity sha512-xkb58MyOYIslxu3gKmVXmjTtUPvBU4odYzbiIQbWwLKIHCsx6UGZGX6F1IznMFVnDdirseUZopzN+ZRt8Xb33Q== @@ -433,6 +433,16 @@ resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-private-property-in-object/-/plugin-proposal-private-property-in-object-7.21.0-placeholder-for-preset-env.2.tgz#7844f9289546efa9febac2de4cfe358a050bd703" integrity sha512-SOSkfJDddaM7mak6cPEpswyTRnuRltl429hMraQEglW+OkovnCzsiszTmsrlY//qLFjCpQDFRvjdm2wA5pPm9w== +"@babel/plugin-proposal-private-property-in-object@^7.21.11": + version "7.21.11" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-private-property-in-object/-/plugin-proposal-private-property-in-object-7.21.11.tgz#69d597086b6760c4126525cfa154f34631ff272c" + integrity sha512-0QZ8qP/3RLDVBwBFoWAwCtgcDZJVwA5LUJRZU8x2YFfKNuFq161wK3cuGrALu5yiPu+vzwTAg/sMWVNeWeNyaw== + dependencies: + "@babel/helper-annotate-as-pure" "^7.18.6" + "@babel/helper-create-class-features-plugin" "^7.21.0" + "@babel/helper-plugin-utils" "^7.20.2" + "@babel/plugin-syntax-private-property-in-object" "^7.14.5" + "@babel/plugin-proposal-unicode-property-regex@^7.4.4": version "7.18.6" resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-unicode-property-regex/-/plugin-proposal-unicode-property-regex-7.18.6.tgz#af613d2cd5e643643b65cded64207b15c85cb78e"