From 9703f3f712f0dc02513922632c065b9e65ed191b Mon Sep 17 00:00:00 2001 From: Yang Luo Date: Tue, 31 Oct 2023 23:09:49 +0800 Subject: [PATCH] Support Apple OAuth login now --- controllers/auth.go | 6 ++- idp/goth.go | 24 ++++++++++-- idp/provider.go | 77 +++++++++++++++++++------------------ object/provider.go | 24 ++++++------ web/src/ProviderEditPage.js | 26 ++++++++++++- 5 files changed, 103 insertions(+), 54 deletions(-) diff --git a/controllers/auth.go b/controllers/auth.go index aad69684..e11a5fb0 100644 --- a/controllers/auth.go +++ b/controllers/auth.go @@ -488,7 +488,11 @@ func (c *ApiController) Login() { } else if provider.Category == "OAuth" || provider.Category == "Web3" { // OAuth idpInfo := object.FromProviderToIdpInfo(c.Ctx, provider) - idProvider := idp.GetIdProvider(idpInfo, authForm.RedirectUri) + idProvider, err := idp.GetIdProvider(idpInfo, authForm.RedirectUri) + if err != nil { + c.ResponseError(err.Error()) + return + } if idProvider == nil { c.ResponseError(fmt.Sprintf(c.T("storage:The provider type: %s is not supported"), provider.Type)) return diff --git a/idp/goth.go b/idp/goth.go index 3ccd9e79..d81e73eb 100644 --- a/idp/goth.go +++ b/idp/goth.go @@ -89,7 +89,7 @@ type GothIdProvider struct { Session goth.Session } -func NewGothIdProvider(providerType string, clientId string, clientSecret string, redirectUrl string, hostUrl string) *GothIdProvider { +func NewGothIdProvider(providerType string, clientId string, clientSecret string, clientId2 string, clientSecret2 string, redirectUrl string, hostUrl string) (*GothIdProvider, error) { var idp GothIdProvider switch providerType { case "Amazon": @@ -101,8 +101,24 @@ func NewGothIdProvider(providerType string, clientId string, clientSecret string if !strings.Contains(redirectUrl, "/api/callback") { redirectUrl = strings.Replace(redirectUrl, "/callback", "/api/callback", 1) } + + iat := time.Now().Unix() + exp := iat + 60*60 + sp := apple.SecretParams{ + ClientId: clientId, + TeamId: clientSecret, + KeyId: clientId2, + PKCS8PrivateKey: clientSecret2, + Iat: int(iat), + Exp: int(exp), + } + secret, err := apple.MakeSecret(sp) + if err != nil { + return nil, err + } + idp = GothIdProvider{ - Provider: apple.New(clientId, clientSecret, redirectUrl, nil), + Provider: apple.New(clientId, *secret, redirectUrl, nil), Session: &apple.Session{}, } case "AzureAD": @@ -386,10 +402,10 @@ func NewGothIdProvider(providerType string, clientId string, clientSecret string Session: &zoom.Session{}, } default: - return nil + return nil, fmt.Errorf("OAuth Goth provider type: %s is not supported", providerType) } - return &idp + return &idp, nil } // SetHttpClient diff --git a/idp/provider.go b/idp/provider.go index dbcca0df..fc7e437a 100644 --- a/idp/provider.go +++ b/idp/provider.go @@ -15,6 +15,7 @@ package idp import ( + "fmt" "net/http" "strings" @@ -33,13 +34,15 @@ type UserInfo struct { } type ProviderInfo struct { - Type string - SubType string - ClientId string - ClientSecret string - AppId string - HostUrl string - RedirectUrl string + Type string + SubType string + ClientId string + ClientSecret string + ClientId2 string + ClientSecret2 string + AppId string + HostUrl string + RedirectUrl string TokenURL string AuthURL string @@ -53,71 +56,71 @@ type IdProvider interface { GetUserInfo(token *oauth2.Token) (*UserInfo, error) } -func GetIdProvider(idpInfo *ProviderInfo, redirectUrl string) IdProvider { +func GetIdProvider(idpInfo *ProviderInfo, redirectUrl string) (IdProvider, error) { switch idpInfo.Type { case "GitHub": - return NewGithubIdProvider(idpInfo.ClientId, idpInfo.ClientSecret, redirectUrl) + return NewGithubIdProvider(idpInfo.ClientId, idpInfo.ClientSecret, redirectUrl), nil case "Google": - return NewGoogleIdProvider(idpInfo.ClientId, idpInfo.ClientSecret, redirectUrl) + return NewGoogleIdProvider(idpInfo.ClientId, idpInfo.ClientSecret, redirectUrl), nil case "QQ": - return NewQqIdProvider(idpInfo.ClientId, idpInfo.ClientSecret, redirectUrl) + return NewQqIdProvider(idpInfo.ClientId, idpInfo.ClientSecret, redirectUrl), nil case "WeChat": - return NewWeChatIdProvider(idpInfo.ClientId, idpInfo.ClientSecret, redirectUrl) + return NewWeChatIdProvider(idpInfo.ClientId, idpInfo.ClientSecret, redirectUrl), nil case "Facebook": - return NewFacebookIdProvider(idpInfo.ClientId, idpInfo.ClientSecret, redirectUrl) + return NewFacebookIdProvider(idpInfo.ClientId, idpInfo.ClientSecret, redirectUrl), nil case "DingTalk": - return NewDingTalkIdProvider(idpInfo.ClientId, idpInfo.ClientSecret, redirectUrl) + return NewDingTalkIdProvider(idpInfo.ClientId, idpInfo.ClientSecret, redirectUrl), nil case "Weibo": - return NewWeiBoIdProvider(idpInfo.ClientId, idpInfo.ClientSecret, redirectUrl) + return NewWeiBoIdProvider(idpInfo.ClientId, idpInfo.ClientSecret, redirectUrl), nil case "Gitee": - return NewGiteeIdProvider(idpInfo.ClientId, idpInfo.ClientSecret, redirectUrl) + return NewGiteeIdProvider(idpInfo.ClientId, idpInfo.ClientSecret, redirectUrl), nil case "LinkedIn": - return NewLinkedInIdProvider(idpInfo.ClientId, idpInfo.ClientSecret, redirectUrl) + return NewLinkedInIdProvider(idpInfo.ClientId, idpInfo.ClientSecret, redirectUrl), nil case "WeCom": if idpInfo.SubType == "Internal" { - return NewWeComInternalIdProvider(idpInfo.ClientId, idpInfo.ClientSecret, redirectUrl) + return NewWeComInternalIdProvider(idpInfo.ClientId, idpInfo.ClientSecret, redirectUrl), nil } else if idpInfo.SubType == "Third-party" { - return NewWeComIdProvider(idpInfo.ClientId, idpInfo.ClientSecret, redirectUrl) + return NewWeComIdProvider(idpInfo.ClientId, idpInfo.ClientSecret, redirectUrl), nil } else { - return nil + return nil, fmt.Errorf("WeCom provider subType: %s is not supported", idpInfo.SubType) } case "Lark": - return NewLarkIdProvider(idpInfo.ClientId, idpInfo.ClientSecret, redirectUrl) + return NewLarkIdProvider(idpInfo.ClientId, idpInfo.ClientSecret, redirectUrl), nil case "GitLab": - return NewGitlabIdProvider(idpInfo.ClientId, idpInfo.ClientSecret, redirectUrl) + return NewGitlabIdProvider(idpInfo.ClientId, idpInfo.ClientSecret, redirectUrl), nil case "ADFS": - return NewAdfsIdProvider(idpInfo.ClientId, idpInfo.ClientSecret, redirectUrl, idpInfo.HostUrl) + return NewAdfsIdProvider(idpInfo.ClientId, idpInfo.ClientSecret, redirectUrl, idpInfo.HostUrl), nil case "Baidu": - return NewBaiduIdProvider(idpInfo.ClientId, idpInfo.ClientSecret, redirectUrl) + return NewBaiduIdProvider(idpInfo.ClientId, idpInfo.ClientSecret, redirectUrl), nil case "Alipay": - return NewAlipayIdProvider(idpInfo.ClientId, idpInfo.ClientSecret, redirectUrl) + return NewAlipayIdProvider(idpInfo.ClientId, idpInfo.ClientSecret, redirectUrl), nil case "Custom": - return NewCustomIdProvider(idpInfo, redirectUrl) + return NewCustomIdProvider(idpInfo, redirectUrl), nil case "Infoflow": if idpInfo.SubType == "Internal" { - return NewInfoflowInternalIdProvider(idpInfo.ClientId, idpInfo.ClientSecret, idpInfo.AppId, redirectUrl) + return NewInfoflowInternalIdProvider(idpInfo.ClientId, idpInfo.ClientSecret, idpInfo.AppId, redirectUrl), nil } else if idpInfo.SubType == "Third-party" { - return NewInfoflowIdProvider(idpInfo.ClientId, idpInfo.ClientSecret, idpInfo.AppId, redirectUrl) + return NewInfoflowIdProvider(idpInfo.ClientId, idpInfo.ClientSecret, idpInfo.AppId, redirectUrl), nil } else { - return nil + return nil, fmt.Errorf("Infoflow provider subType: %s is not supported", idpInfo.SubType) } case "Casdoor": - return NewCasdoorIdProvider(idpInfo.ClientId, idpInfo.ClientSecret, redirectUrl, idpInfo.HostUrl) + return NewCasdoorIdProvider(idpInfo.ClientId, idpInfo.ClientSecret, redirectUrl, idpInfo.HostUrl), nil case "Okta": - return NewOktaIdProvider(idpInfo.ClientId, idpInfo.ClientSecret, redirectUrl, idpInfo.HostUrl) + return NewOktaIdProvider(idpInfo.ClientId, idpInfo.ClientSecret, redirectUrl, idpInfo.HostUrl), nil case "Douyin": - return NewDouyinIdProvider(idpInfo.ClientId, idpInfo.ClientSecret, redirectUrl) + return NewDouyinIdProvider(idpInfo.ClientId, idpInfo.ClientSecret, redirectUrl), nil case "Bilibili": - return NewBilibiliIdProvider(idpInfo.ClientId, idpInfo.ClientSecret, redirectUrl) + return NewBilibiliIdProvider(idpInfo.ClientId, idpInfo.ClientSecret, redirectUrl), nil case "MetaMask": - return NewMetaMaskIdProvider() + return NewMetaMaskIdProvider(), nil case "Web3Onboard": - return NewWeb3OnboardIdProvider() + return NewWeb3OnboardIdProvider(), nil default: if isGothSupport(idpInfo.Type) { - return NewGothIdProvider(idpInfo.Type, idpInfo.ClientId, idpInfo.ClientSecret, redirectUrl, idpInfo.HostUrl) + return NewGothIdProvider(idpInfo.Type, idpInfo.ClientId, idpInfo.ClientSecret, idpInfo.ClientId2, idpInfo.ClientSecret2, redirectUrl, idpInfo.HostUrl) } - return nil + return nil, fmt.Errorf("OAuth provider type: %s is not supported", idpInfo.Type) } } diff --git a/object/provider.go b/object/provider.go index 8c87a7a9..e6f18cb1 100644 --- a/object/provider.go +++ b/object/provider.go @@ -39,7 +39,7 @@ type Provider struct { ClientId string `xorm:"varchar(200)" json:"clientId"` ClientSecret string `xorm:"varchar(2000)" json:"clientSecret"` ClientId2 string `xorm:"varchar(100)" json:"clientId2"` - ClientSecret2 string `xorm:"varchar(100)" json:"clientSecret2"` + ClientSecret2 string `xorm:"varchar(500)" json:"clientSecret2"` Cert string `xorm:"varchar(100)" json:"cert"` CustomAuthUrl string `xorm:"varchar(200)" json:"customAuthUrl"` CustomTokenUrl string `xorm:"varchar(200)" json:"customTokenUrl"` @@ -398,16 +398,18 @@ func providerChangeTrigger(oldName string, newName string) error { 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, + Type: provider.Type, + SubType: provider.SubType, + ClientId: provider.ClientId, + ClientSecret: provider.ClientSecret, + ClientId2: provider.ClientId2, + ClientSecret2: provider.ClientSecret2, + AppId: provider.AppId, + HostUrl: provider.Host, + TokenURL: provider.CustomTokenUrl, + AuthURL: provider.CustomAuthUrl, + UserInfoURL: provider.CustomUserInfoUrl, + UserMapping: provider.UserMapping, } if provider.Type == "WeChat" { diff --git a/web/src/ProviderEditPage.js b/web/src/ProviderEditPage.js index 86b3cf1c..ced36140 100644 --- a/web/src/ProviderEditPage.js +++ b/web/src/ProviderEditPage.js @@ -152,6 +152,12 @@ class ProviderEditPage extends React.Component { } getClientIdLabel(provider) { switch (provider.category) { + case "OAuth": + if (provider.type === "Apple") { + return Setting.getLabel(i18next.t("provider:Service ID identifier"), i18next.t("provider:Service ID identifier - Tooltip")); + } else { + return Setting.getLabel(i18next.t("provider:Client ID"), i18next.t("provider:Client ID - Tooltip")); + } case "Email": return Setting.getLabel(i18next.t("signup:Username"), i18next.t("signup:Username - Tooltip")); case "SMS": @@ -185,6 +191,12 @@ class ProviderEditPage extends React.Component { getClientSecretLabel(provider) { switch (provider.category) { + case "OAuth": + if (provider.type === "Apple") { + return Setting.getLabel(i18next.t("provider:Team ID"), i18next.t("provider:Team ID - Tooltip")); + } else { + return Setting.getLabel(i18next.t("provider:Client secret"), i18next.t("provider:Client secret - Tooltip")); + } case "Email": if (provider.type === "Azure ACS") { return Setting.getLabel(i18next.t("provider:Secret key"), i18next.t("provider:Secret key - Tooltip")); @@ -226,6 +238,12 @@ class ProviderEditPage extends React.Component { getClientId2Label(provider) { switch (provider.category) { + case "OAuth": + if (provider.type === "Apple") { + return Setting.getLabel(i18next.t("provider:Key ID"), i18next.t("provider:Key ID - Tooltip")); + } else { + return Setting.getLabel(i18next.t("provider:Client ID 2"), i18next.t("provider:Client ID 2 - Tooltip")); + } case "Email": return Setting.getLabel(i18next.t("provider:From address"), i18next.t("provider:From address - Tooltip")); default: @@ -241,6 +259,12 @@ class ProviderEditPage extends React.Component { getClientSecret2Label(provider) { switch (provider.category) { + case "OAuth": + if (provider.type === "Apple") { + return Setting.getLabel(i18next.t("provider:Key text"), i18next.t("provider:Key text - Tooltip")); + } else { + return Setting.getLabel(i18next.t("provider:Client secret 2"), i18next.t("provider:Client secret 2 - Tooltip")); + } case "Email": return Setting.getLabel(i18next.t("provider:From name"), i18next.t("provider:From name - Tooltip")); default: @@ -675,7 +699,7 @@ class ProviderEditPage extends React.Component { ) } { - this.state.provider.category !== "Email" && this.state.provider.type !== "WeChat" && this.state.provider.type !== "Aliyun Captcha" && this.state.provider.type !== "WeChat Pay" && this.state.provider.type !== "Twitter" && this.state.provider.type !== "Reddit" ? null : ( + this.state.provider.category !== "Email" && this.state.provider.type !== "WeChat" && this.state.provider.type !== "Apple" && this.state.provider.type !== "Aliyun Captcha" && this.state.provider.type !== "WeChat Pay" && this.state.provider.type !== "Twitter" && this.state.provider.type !== "Reddit" ? null : (