diff --git a/internal/provider/providers/casdoor.go b/internal/provider/providers/casdoor.go new file mode 100644 index 0000000..56d9396 --- /dev/null +++ b/internal/provider/providers/casdoor.go @@ -0,0 +1,118 @@ +package providers + +import ( + "context" + "encoding/json" + "fmt" + "net/http" + "net/url" + + "github.com/synctv-org/synctv/internal/provider" + "github.com/synctv-org/synctv/internal/settings" + "golang.org/x/oauth2" +) + +// https://door.casdoor.com/.well-known/openid-configuration +type casdoorProvider struct { + config oauth2.Config + endpoint string +} + +func newCasdoorProvider() provider.Interface { + return &casdoorProvider{ + config: oauth2.Config{ + Scopes: []string{"profile", "email", "phone", "name", "openid"}, + }, + } +} + +func (p *casdoorProvider) Init(opt provider.Oauth2Option) { + p.config.ClientID = opt.ClientID + p.config.ClientSecret = opt.ClientSecret + p.config.RedirectURL = opt.RedirectURL +} + +func (p *casdoorProvider) NewAuthURL(ctx context.Context, state string) (string, error) { + return p.config.AuthCodeURL(state, oauth2.AccessTypeOnline), nil +} + +func (p *casdoorProvider) GetToken(ctx context.Context, code string) (*oauth2.Token, error) { + return p.config.Exchange(ctx, code) +} + +func (p *casdoorProvider) RefreshToken(ctx context.Context, token string) (*oauth2.Token, error) { + return p.config.TokenSource(ctx, &oauth2.Token{RefreshToken: token}).Token() +} + +func (p *casdoorProvider) GetUserInfo(ctx context.Context, code string) (*provider.UserInfo, error) { + tk, err := p.GetToken(ctx, code) + if err != nil { + return nil, err + } + client := p.config.Client(ctx, tk) + req, err := http.NewRequestWithContext(ctx, http.MethodGet, p.endpoint+"/api/userinfo", nil) + if err != nil { + return nil, err + } + resp, err := client.Do(req) + if err != nil { + return nil, err + } + defer resp.Body.Close() + var ui casdoorUserInfo + err = json.NewDecoder(resp.Body).Decode(&ui) + if err != nil { + return nil, err + } + un := ui.PreferredUsername + if un == "" { + un = ui.Name + } + return &provider.UserInfo{ + ProviderUserID: ui.Sub, + Username: un, + }, nil +} + +type casdoorUserInfo struct { + Sub string `json:"sub"` + PreferredUsername string `json:"preferred_username"` + Name string `json:"name"` + Email string `json:"email"` + Phone string `json:"phone"` +} + +func (p *casdoorProvider) RegistSetting(group string) { + settings.NewStringSetting( + group+"_endpoint", "", group, + settings.WithAfterInitString(func(ss settings.StringSetting, s string) { + p.endpoint = s + p.config.Endpoint = oauth2.Endpoint{ + AuthURL: s + "/login/oauth/authorize", + TokenURL: s + "/api/login/oauth/access_token", + } + }), + settings.WithBeforeSetString(func(ss settings.StringSetting, s string) (string, error) { + u, err := url.Parse(s) + if err != nil { + return "", err + } + return fmt.Sprintf("%s://%s", u.Scheme, u.Host), nil + }), + settings.WithAfterSetString(func(ss settings.StringSetting, s string) { + p.endpoint = s + p.config.Endpoint = oauth2.Endpoint{ + AuthURL: s + "/login/oauth/authorize", + TokenURL: s + "/api/login/oauth/access_token", + } + }), + ) +} + +func (p *casdoorProvider) Provider() provider.OAuth2Provider { + return "casdoor" +} + +func init() { + RegisterProvider(newCasdoorProvider()) +} diff --git a/internal/provider/providers/logto.go b/internal/provider/providers/logto.go index 04d2801..e74230b 100644 --- a/internal/provider/providers/logto.go +++ b/internal/provider/providers/logto.go @@ -3,8 +3,9 @@ package providers import ( "context" "encoding/json" + "fmt" "net/http" - "strings" + "net/url" "github.com/synctv-org/synctv/internal/provider" "github.com/synctv-org/synctv/internal/settings" @@ -86,17 +87,20 @@ func (p *logtoProvider) RegistSetting(group string) { settings.NewStringSetting( group+"_endpoint", "", group, settings.WithAfterInitString(func(ss settings.StringSetting, s string) { - s = strings.TrimSuffix(s, "/") - s = strings.TrimSuffix(s, "/oidc") p.endpoint = s p.config.Endpoint = oauth2.Endpoint{ AuthURL: s + "/oidc/auth", TokenURL: s + "/oidc/token", } }), + settings.WithBeforeSetString(func(ss settings.StringSetting, s string) (string, error) { + u, err := url.Parse(s) + if err != nil { + return "", err + } + return fmt.Sprintf("%s://%s", u.Scheme, u.Host), nil + }), settings.WithAfterSetString(func(ss settings.StringSetting, s string) { - s = strings.TrimSuffix(s, "/") - s = strings.TrimSuffix(s, "/oidc") p.endpoint = s p.config.Endpoint = oauth2.Endpoint{ AuthURL: s + "/oidc/auth",