Support Apple OAuth login now

This commit is contained in:
Yang Luo 2023-10-31 23:09:49 +08:00
parent 140737b2f6
commit 9703f3f712
5 changed files with 103 additions and 54 deletions

View File

@ -488,7 +488,11 @@ func (c *ApiController) Login() {
} else if provider.Category == "OAuth" || provider.Category == "Web3" { } else if provider.Category == "OAuth" || provider.Category == "Web3" {
// OAuth // OAuth
idpInfo := object.FromProviderToIdpInfo(c.Ctx, provider) 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 { if idProvider == nil {
c.ResponseError(fmt.Sprintf(c.T("storage:The provider type: %s is not supported"), provider.Type)) c.ResponseError(fmt.Sprintf(c.T("storage:The provider type: %s is not supported"), provider.Type))
return return

View File

@ -89,7 +89,7 @@ type GothIdProvider struct {
Session goth.Session 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 var idp GothIdProvider
switch providerType { switch providerType {
case "Amazon": case "Amazon":
@ -101,8 +101,24 @@ func NewGothIdProvider(providerType string, clientId string, clientSecret string
if !strings.Contains(redirectUrl, "/api/callback") { if !strings.Contains(redirectUrl, "/api/callback") {
redirectUrl = strings.Replace(redirectUrl, "/callback", "/api/callback", 1) 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{ idp = GothIdProvider{
Provider: apple.New(clientId, clientSecret, redirectUrl, nil), Provider: apple.New(clientId, *secret, redirectUrl, nil),
Session: &apple.Session{}, Session: &apple.Session{},
} }
case "AzureAD": case "AzureAD":
@ -386,10 +402,10 @@ func NewGothIdProvider(providerType string, clientId string, clientSecret string
Session: &zoom.Session{}, Session: &zoom.Session{},
} }
default: default:
return nil return nil, fmt.Errorf("OAuth Goth provider type: %s is not supported", providerType)
} }
return &idp return &idp, nil
} }
// SetHttpClient // SetHttpClient

View File

@ -15,6 +15,7 @@
package idp package idp
import ( import (
"fmt"
"net/http" "net/http"
"strings" "strings"
@ -37,6 +38,8 @@ type ProviderInfo struct {
SubType string SubType string
ClientId string ClientId string
ClientSecret string ClientSecret string
ClientId2 string
ClientSecret2 string
AppId string AppId string
HostUrl string HostUrl string
RedirectUrl string RedirectUrl string
@ -53,71 +56,71 @@ type IdProvider interface {
GetUserInfo(token *oauth2.Token) (*UserInfo, error) GetUserInfo(token *oauth2.Token) (*UserInfo, error)
} }
func GetIdProvider(idpInfo *ProviderInfo, redirectUrl string) IdProvider { func GetIdProvider(idpInfo *ProviderInfo, redirectUrl string) (IdProvider, error) {
switch idpInfo.Type { switch idpInfo.Type {
case "GitHub": case "GitHub":
return NewGithubIdProvider(idpInfo.ClientId, idpInfo.ClientSecret, redirectUrl) return NewGithubIdProvider(idpInfo.ClientId, idpInfo.ClientSecret, redirectUrl), nil
case "Google": case "Google":
return NewGoogleIdProvider(idpInfo.ClientId, idpInfo.ClientSecret, redirectUrl) return NewGoogleIdProvider(idpInfo.ClientId, idpInfo.ClientSecret, redirectUrl), nil
case "QQ": case "QQ":
return NewQqIdProvider(idpInfo.ClientId, idpInfo.ClientSecret, redirectUrl) return NewQqIdProvider(idpInfo.ClientId, idpInfo.ClientSecret, redirectUrl), nil
case "WeChat": case "WeChat":
return NewWeChatIdProvider(idpInfo.ClientId, idpInfo.ClientSecret, redirectUrl) return NewWeChatIdProvider(idpInfo.ClientId, idpInfo.ClientSecret, redirectUrl), nil
case "Facebook": case "Facebook":
return NewFacebookIdProvider(idpInfo.ClientId, idpInfo.ClientSecret, redirectUrl) return NewFacebookIdProvider(idpInfo.ClientId, idpInfo.ClientSecret, redirectUrl), nil
case "DingTalk": case "DingTalk":
return NewDingTalkIdProvider(idpInfo.ClientId, idpInfo.ClientSecret, redirectUrl) return NewDingTalkIdProvider(idpInfo.ClientId, idpInfo.ClientSecret, redirectUrl), nil
case "Weibo": case "Weibo":
return NewWeiBoIdProvider(idpInfo.ClientId, idpInfo.ClientSecret, redirectUrl) return NewWeiBoIdProvider(idpInfo.ClientId, idpInfo.ClientSecret, redirectUrl), nil
case "Gitee": case "Gitee":
return NewGiteeIdProvider(idpInfo.ClientId, idpInfo.ClientSecret, redirectUrl) return NewGiteeIdProvider(idpInfo.ClientId, idpInfo.ClientSecret, redirectUrl), nil
case "LinkedIn": case "LinkedIn":
return NewLinkedInIdProvider(idpInfo.ClientId, idpInfo.ClientSecret, redirectUrl) return NewLinkedInIdProvider(idpInfo.ClientId, idpInfo.ClientSecret, redirectUrl), nil
case "WeCom": case "WeCom":
if idpInfo.SubType == "Internal" { 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" { } else if idpInfo.SubType == "Third-party" {
return NewWeComIdProvider(idpInfo.ClientId, idpInfo.ClientSecret, redirectUrl) return NewWeComIdProvider(idpInfo.ClientId, idpInfo.ClientSecret, redirectUrl), nil
} else { } else {
return nil return nil, fmt.Errorf("WeCom provider subType: %s is not supported", idpInfo.SubType)
} }
case "Lark": case "Lark":
return NewLarkIdProvider(idpInfo.ClientId, idpInfo.ClientSecret, redirectUrl) return NewLarkIdProvider(idpInfo.ClientId, idpInfo.ClientSecret, redirectUrl), nil
case "GitLab": case "GitLab":
return NewGitlabIdProvider(idpInfo.ClientId, idpInfo.ClientSecret, redirectUrl) return NewGitlabIdProvider(idpInfo.ClientId, idpInfo.ClientSecret, redirectUrl), nil
case "ADFS": case "ADFS":
return NewAdfsIdProvider(idpInfo.ClientId, idpInfo.ClientSecret, redirectUrl, idpInfo.HostUrl) return NewAdfsIdProvider(idpInfo.ClientId, idpInfo.ClientSecret, redirectUrl, idpInfo.HostUrl), nil
case "Baidu": case "Baidu":
return NewBaiduIdProvider(idpInfo.ClientId, idpInfo.ClientSecret, redirectUrl) return NewBaiduIdProvider(idpInfo.ClientId, idpInfo.ClientSecret, redirectUrl), nil
case "Alipay": case "Alipay":
return NewAlipayIdProvider(idpInfo.ClientId, idpInfo.ClientSecret, redirectUrl) return NewAlipayIdProvider(idpInfo.ClientId, idpInfo.ClientSecret, redirectUrl), nil
case "Custom": case "Custom":
return NewCustomIdProvider(idpInfo, redirectUrl) return NewCustomIdProvider(idpInfo, redirectUrl), nil
case "Infoflow": case "Infoflow":
if idpInfo.SubType == "Internal" { 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" { } 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 { } else {
return nil return nil, fmt.Errorf("Infoflow provider subType: %s is not supported", idpInfo.SubType)
} }
case "Casdoor": case "Casdoor":
return NewCasdoorIdProvider(idpInfo.ClientId, idpInfo.ClientSecret, redirectUrl, idpInfo.HostUrl) return NewCasdoorIdProvider(idpInfo.ClientId, idpInfo.ClientSecret, redirectUrl, idpInfo.HostUrl), nil
case "Okta": case "Okta":
return NewOktaIdProvider(idpInfo.ClientId, idpInfo.ClientSecret, redirectUrl, idpInfo.HostUrl) return NewOktaIdProvider(idpInfo.ClientId, idpInfo.ClientSecret, redirectUrl, idpInfo.HostUrl), nil
case "Douyin": case "Douyin":
return NewDouyinIdProvider(idpInfo.ClientId, idpInfo.ClientSecret, redirectUrl) return NewDouyinIdProvider(idpInfo.ClientId, idpInfo.ClientSecret, redirectUrl), nil
case "Bilibili": case "Bilibili":
return NewBilibiliIdProvider(idpInfo.ClientId, idpInfo.ClientSecret, redirectUrl) return NewBilibiliIdProvider(idpInfo.ClientId, idpInfo.ClientSecret, redirectUrl), nil
case "MetaMask": case "MetaMask":
return NewMetaMaskIdProvider() return NewMetaMaskIdProvider(), nil
case "Web3Onboard": case "Web3Onboard":
return NewWeb3OnboardIdProvider() return NewWeb3OnboardIdProvider(), nil
default: default:
if isGothSupport(idpInfo.Type) { 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)
} }
} }

View File

@ -39,7 +39,7 @@ type Provider struct {
ClientId string `xorm:"varchar(200)" json:"clientId"` ClientId string `xorm:"varchar(200)" json:"clientId"`
ClientSecret string `xorm:"varchar(2000)" json:"clientSecret"` ClientSecret string `xorm:"varchar(2000)" json:"clientSecret"`
ClientId2 string `xorm:"varchar(100)" json:"clientId2"` 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"` Cert string `xorm:"varchar(100)" json:"cert"`
CustomAuthUrl string `xorm:"varchar(200)" json:"customAuthUrl"` CustomAuthUrl string `xorm:"varchar(200)" json:"customAuthUrl"`
CustomTokenUrl string `xorm:"varchar(200)" json:"customTokenUrl"` CustomTokenUrl string `xorm:"varchar(200)" json:"customTokenUrl"`
@ -402,6 +402,8 @@ func FromProviderToIdpInfo(ctx *context.Context, provider *Provider) *idp.Provid
SubType: provider.SubType, SubType: provider.SubType,
ClientId: provider.ClientId, ClientId: provider.ClientId,
ClientSecret: provider.ClientSecret, ClientSecret: provider.ClientSecret,
ClientId2: provider.ClientId2,
ClientSecret2: provider.ClientSecret2,
AppId: provider.AppId, AppId: provider.AppId,
HostUrl: provider.Host, HostUrl: provider.Host,
TokenURL: provider.CustomTokenUrl, TokenURL: provider.CustomTokenUrl,

View File

@ -152,6 +152,12 @@ class ProviderEditPage extends React.Component {
} }
getClientIdLabel(provider) { getClientIdLabel(provider) {
switch (provider.category) { 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": case "Email":
return Setting.getLabel(i18next.t("signup:Username"), i18next.t("signup:Username - Tooltip")); return Setting.getLabel(i18next.t("signup:Username"), i18next.t("signup:Username - Tooltip"));
case "SMS": case "SMS":
@ -185,6 +191,12 @@ class ProviderEditPage extends React.Component {
getClientSecretLabel(provider) { getClientSecretLabel(provider) {
switch (provider.category) { 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": case "Email":
if (provider.type === "Azure ACS") { if (provider.type === "Azure ACS") {
return Setting.getLabel(i18next.t("provider:Secret key"), i18next.t("provider:Secret key - Tooltip")); 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) { getClientId2Label(provider) {
switch (provider.category) { 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": case "Email":
return Setting.getLabel(i18next.t("provider:From address"), i18next.t("provider:From address - Tooltip")); return Setting.getLabel(i18next.t("provider:From address"), i18next.t("provider:From address - Tooltip"));
default: default:
@ -241,6 +259,12 @@ class ProviderEditPage extends React.Component {
getClientSecret2Label(provider) { getClientSecret2Label(provider) {
switch (provider.category) { 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": case "Email":
return Setting.getLabel(i18next.t("provider:From name"), i18next.t("provider:From name - Tooltip")); return Setting.getLabel(i18next.t("provider:From name"), i18next.t("provider:From name - Tooltip"));
default: 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 : (
<React.Fragment> <React.Fragment>
<Row style={{marginTop: "20px"}} > <Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}> <Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>