diff --git a/internal/bootstrap/provider.go b/internal/bootstrap/provider.go index 34dd87b..f233849 100644 --- a/internal/bootstrap/provider.go +++ b/internal/bootstrap/provider.go @@ -5,13 +5,20 @@ import ( "os" "path/filepath" + "github.com/hashicorp/go-hclog" log "github.com/sirupsen/logrus" + "github.com/synctv-org/synctv/cmd/flags" "github.com/synctv-org/synctv/internal/conf" "github.com/synctv-org/synctv/internal/provider" "github.com/synctv-org/synctv/utils" ) func InitProvider(ctx context.Context) error { + logOur := log.StandardLogger().Writer() + logLevle := hclog.Info + if flags.Dev { + logLevle = hclog.Debug + } for _, op := range conf.Conf.OAuth2.Plugins { utils.OptFilePath(&op.PluginFile) log.Infof("load oauth2 plugin: %s", op.PluginFile) @@ -20,7 +27,12 @@ func InitProvider(ctx context.Context) error { log.Errorf("create plugin dir: %s failed: %s", filepath.Dir(op.PluginFile), err) return err } - err = provider.InitProviderPlugins(op.PluginFile, op.Arges...) + err = provider.InitProviderPlugins(op.PluginFile, op.Arges, hclog.New(&hclog.LoggerOptions{ + Name: op.PluginFile, + Level: logLevle, + Output: logOur, + Color: hclog.ForceColor, + })) if err != nil { log.Errorf("load oauth2 plugin: %s failed: %s", op.PluginFile, err) return err diff --git a/internal/conf/oauth2.go b/internal/conf/oauth2.go index 99c03a8..13196fc 100644 --- a/internal/conf/oauth2.go +++ b/internal/conf/oauth2.go @@ -1,6 +1,9 @@ package conf -import "github.com/synctv-org/synctv/internal/provider" +import ( + "github.com/synctv-org/synctv/internal/provider" + "github.com/synctv-org/synctv/internal/provider/providers" +) type OAuth2Config struct { Providers map[provider.OAuth2Provider]OAuth2ProviderConfig `yaml:"providers"` @@ -21,7 +24,7 @@ type OAuth2ProviderConfig struct { func DefaultOAuth2Config() OAuth2Config { return OAuth2Config{ Providers: map[provider.OAuth2Provider]OAuth2ProviderConfig{ - (&provider.GithubProvider{}).Provider(): { + (&providers.GithubProvider{}).Provider(): { ClientID: "", ClientSecret: "", RedirectURL: "", diff --git a/internal/provider/client.go b/internal/provider/client.go new file mode 100644 index 0000000..5418c14 --- /dev/null +++ b/internal/provider/client.go @@ -0,0 +1,68 @@ +package provider + +import ( + "context" + "time" + + providerpb "github.com/synctv-org/synctv/proto/provider" + "golang.org/x/oauth2" +) + +type GRPCClient struct{ client providerpb.Oauth2PluginClient } + +var _ ProviderInterface = (*GRPCClient)(nil) + +func (c *GRPCClient) Init(o Oauth2Option) { + c.client.Init(context.Background(), &providerpb.InitReq{ + ClientId: o.ClientID, + ClientSecret: o.ClientSecret, + RedirectUrl: o.RedirectURL, + }) +} + +func (c *GRPCClient) Provider() OAuth2Provider { + resp, err := c.client.Provider(context.Background(), &providerpb.Enpty{}) + if err != nil { + return "" + } + return OAuth2Provider(resp.Name) +} + +func (c *GRPCClient) NewAuthURL(state string) string { + resp, err := c.client.NewAuthURL(context.Background(), &providerpb.NewAuthURLReq{State: state}) + if err != nil { + return "" + } + return resp.Url +} + +func (c *GRPCClient) GetToken(ctx context.Context, code string) (*oauth2.Token, error) { + resp, err := c.client.GetToken(ctx, &providerpb.GetTokenReq{Code: code}) + if err != nil { + return nil, err + } + return &oauth2.Token{ + AccessToken: resp.AccessToken, + TokenType: resp.TokenType, + RefreshToken: resp.RefreshToken, + Expiry: time.Unix(resp.Expiry, 0), + }, nil +} + +func (c *GRPCClient) GetUserInfo(ctx context.Context, tk *oauth2.Token) (*UserInfo, error) { + resp, err := c.client.GetUserInfo(ctx, &providerpb.GetUserInfoReq{ + Token: &providerpb.Token{ + AccessToken: tk.AccessToken, + TokenType: tk.TokenType, + RefreshToken: tk.RefreshToken, + Expiry: tk.Expiry.Unix(), + }, + }) + if err != nil { + return nil, err + } + return &UserInfo{ + Username: resp.Username, + ProviderUserID: uint(resp.ProviderUserId), + }, nil +} diff --git a/internal/provider/plugin.go b/internal/provider/plugin.go index 2cf8c73..ec33f78 100644 --- a/internal/provider/plugin.go +++ b/internal/provider/plugin.go @@ -4,148 +4,34 @@ import ( "context" "fmt" "os/exec" - "time" "github.com/hashicorp/go-hclog" - plugin "github.com/hashicorp/go-plugin" - log "github.com/sirupsen/logrus" + "github.com/hashicorp/go-plugin" sysnotify "github.com/synctv-org/synctv/internal/sysNotify" providerpb "github.com/synctv-org/synctv/proto/provider" - "golang.org/x/oauth2" "google.golang.org/grpc" ) -type ProviderPlugin struct { - plugin.Plugin - Impl ProviderInterface -} - -func (p *ProviderPlugin) GRPCServer(broker *plugin.GRPCBroker, s *grpc.Server) error { - providerpb.RegisterOauth2PluginServer(s, &GRPCServer{Impl: p.Impl}) - return nil -} - -func (p *ProviderPlugin) GRPCClient(ctx context.Context, broker *plugin.GRPCBroker, c *grpc.ClientConn) (interface{}, error) { - return &GRPCClient{client: providerpb.NewOauth2PluginClient(c)}, nil -} - -type GRPCServer struct { - providerpb.UnimplementedOauth2PluginServer - Impl ProviderInterface -} - -func (s *GRPCServer) Init(ctx context.Context, req *providerpb.InitReq) (*providerpb.Enpty, error) { - s.Impl.Init(Oauth2Option{ - ClientID: req.ClientId, - ClientSecret: req.ClientSecret, - RedirectURL: req.RedirectUrl, - }) - return &providerpb.Enpty{}, nil -} - -func (s *GRPCServer) Provider(ctx context.Context, req *providerpb.Enpty) (*providerpb.ProviderResp, error) { - return &providerpb.ProviderResp{Name: string(s.Impl.Provider())}, nil -} - -func (s *GRPCServer) NewAuthURL(ctx context.Context, req *providerpb.NewAuthURLReq) (*providerpb.NewAuthURLResp, error) { - return &providerpb.NewAuthURLResp{Url: s.Impl.NewAuthURL(req.State)}, nil -} - -func (s *GRPCServer) GetToken(ctx context.Context, req *providerpb.GetTokenReq) (*providerpb.Token, error) { - token, err := s.Impl.GetToken(ctx, req.Code) - if err != nil { - return nil, err - } - return &providerpb.Token{ - AccessToken: token.AccessToken, - TokenType: token.TokenType, - RefreshToken: token.RefreshToken, - Expiry: token.Expiry.Unix(), - }, nil -} - -func (s *GRPCServer) GetUserInfo(ctx context.Context, req *providerpb.GetUserInfoReq) (*providerpb.GetUserInfoResp, error) { - userInfo, err := s.Impl.GetUserInfo(ctx, &oauth2.Token{ - AccessToken: req.Token.AccessToken, - TokenType: req.Token.TokenType, - Expiry: time.Unix(req.Token.Expiry, 0), - RefreshToken: req.Token.RefreshToken, - }) - if err != nil { - return nil, err - } - resp := &providerpb.GetUserInfoResp{ - Username: userInfo.Username, - ProviderUserId: uint64(userInfo.ProviderUserID), - } - if userInfo.TokenRefreshed != nil { - resp.TokenRefreshed = &providerpb.Token{ - AccessToken: userInfo.TokenRefreshed.Token.AccessToken, - TokenType: userInfo.TokenRefreshed.Token.TokenType, - RefreshToken: userInfo.TokenRefreshed.Token.RefreshToken, - Expiry: userInfo.TokenRefreshed.Token.Expiry.Unix(), - } - } - return resp, nil -} - -type GRPCClient struct{ client providerpb.Oauth2PluginClient } - -var _ ProviderInterface = (*GRPCClient)(nil) - -func (c *GRPCClient) Init(o Oauth2Option) { - c.client.Init(context.Background(), &providerpb.InitReq{ - ClientId: o.ClientID, - ClientSecret: o.ClientSecret, - RedirectUrl: o.RedirectURL, - }) -} - -func (c *GRPCClient) Provider() OAuth2Provider { - resp, err := c.client.Provider(context.Background(), &providerpb.Enpty{}) - if err != nil { - return "" - } - return OAuth2Provider(resp.Name) -} - -func (c *GRPCClient) NewAuthURL(state string) string { - resp, err := c.client.NewAuthURL(context.Background(), &providerpb.NewAuthURLReq{State: state}) +func InitProviderPlugins(name string, arg []string, Logger hclog.Logger) error { + client := NewProviderPlugin(name, arg, Logger) + sysnotify.RegisterSysNotifyTask(0, sysnotify.NewSysNotifyTask("plugin", sysnotify.NotifyTypeEXIT, func() error { + client.Kill() + return nil + })) + c, err := client.Client() if err != nil { - return "" + return err } - return resp.Url -} - -func (c *GRPCClient) GetToken(ctx context.Context, code string) (*oauth2.Token, error) { - resp, err := c.client.GetToken(ctx, &providerpb.GetTokenReq{Code: code}) + i, err := c.Dispense("Provider") if err != nil { - return nil, err + return err } - return &oauth2.Token{ - AccessToken: resp.AccessToken, - TokenType: resp.TokenType, - RefreshToken: resp.RefreshToken, - Expiry: time.Unix(resp.Expiry, 0), - }, nil -} - -func (c *GRPCClient) GetUserInfo(ctx context.Context, tk *oauth2.Token) (*UserInfo, error) { - resp, err := c.client.GetUserInfo(ctx, &providerpb.GetUserInfoReq{ - Token: &providerpb.Token{ - AccessToken: tk.AccessToken, - TokenType: tk.TokenType, - RefreshToken: tk.RefreshToken, - Expiry: tk.Expiry.Unix(), - }, - }) - if err != nil { - return nil, err + provider, ok := i.(ProviderInterface) + if !ok { + return fmt.Errorf("%s not implement ProviderInterface", name) } - return &UserInfo{ - Username: resp.Username, - ProviderUserID: uint(resp.ProviderUserId), - }, nil + RegisterProvider(provider) + return nil } var HandshakeConfig = plugin.HandshakeConfig{ @@ -158,35 +44,27 @@ var pluginMap = map[string]plugin.Plugin{ "Provider": &ProviderPlugin{}, } -func InitProviderPlugins(name string, arg ...string) error { - client := plugin.NewClient(&plugin.ClientConfig{ +type ProviderPlugin struct { + plugin.Plugin + Impl ProviderInterface +} + +func (p *ProviderPlugin) GRPCServer(broker *plugin.GRPCBroker, s *grpc.Server) error { + providerpb.RegisterOauth2PluginServer(s, &GRPCServer{Impl: p.Impl}) + return nil +} + +func (p *ProviderPlugin) GRPCClient(ctx context.Context, broker *plugin.GRPCBroker, c *grpc.ClientConn) (interface{}, error) { + return &GRPCClient{client: providerpb.NewOauth2PluginClient(c)}, nil +} + +func NewProviderPlugin(name string, arg []string, Logger hclog.Logger) *plugin.Client { + return plugin.NewClient(&plugin.ClientConfig{ HandshakeConfig: HandshakeConfig, Plugins: pluginMap, Cmd: exec.Command(name, arg...), AllowedProtocols: []plugin.Protocol{ plugin.ProtocolGRPC}, - Logger: hclog.New(&hclog.LoggerOptions{ - Name: "plugin", - Output: log.StandardLogger().Writer(), - Level: hclog.Debug, - }), + Logger: Logger, }) - sysnotify.RegisterSysNotifyTask(0, sysnotify.NewSysNotifyTask("plugin", sysnotify.NotifyTypeEXIT, func() error { - client.Kill() - return nil - })) - c, err := client.Client() - if err != nil { - return err - } - i, err := c.Dispense("Provider") - if err != nil { - return err - } - provider, ok := i.(ProviderInterface) - if !ok { - return fmt.Errorf("%s not implement ProviderInterface", name) - } - registerProvider(provider) - return nil } diff --git a/internal/provider/plugins/example_gitee.go b/internal/provider/plugin/example_gitee.go similarity index 85% rename from internal/provider/plugins/example_gitee.go rename to internal/provider/plugin/example_gitee.go index e45b4c9..aa81089 100644 --- a/internal/provider/plugins/example_gitee.go +++ b/internal/provider/plugin/example_gitee.go @@ -10,6 +10,21 @@ import ( "golang.org/x/oauth2" ) +// go build -o gitee ./internal/provider/plugins/gitee.go +// +// mv gitee {data-dir}/plugins/oauth2/gitee +// +// config.yaml: +// +// oauth2: +// providers: +// gitee: +// client_id: xxx +// client_secret: xxx +// redirect_url: xxx +// plugins: +// - plugin_file: plugins/oauth2/gitee +// arges: [] type GiteeProvider struct { config oauth2.Config } diff --git a/internal/provider/provider.go b/internal/provider/provider.go index a364475..31e4d15 100644 --- a/internal/provider/provider.go +++ b/internal/provider/provider.go @@ -9,11 +9,6 @@ import ( type OAuth2Provider string -var ( - enabledProviders map[OAuth2Provider]ProviderInterface - allowedProviders = make(map[OAuth2Provider]ProviderInterface) -) - type TokenRefreshed struct { Refreshed bool Token *oauth2.Token @@ -39,6 +34,11 @@ type ProviderInterface interface { GetUserInfo(context.Context, *oauth2.Token) (*UserInfo, error) } +var ( + enabledProviders map[OAuth2Provider]ProviderInterface + allowedProviders = make(map[OAuth2Provider]ProviderInterface) +) + func InitProvider(p OAuth2Provider, c Oauth2Option) error { pi, ok := allowedProviders[p] if !ok { @@ -52,7 +52,7 @@ func InitProvider(p OAuth2Provider, c Oauth2Option) error { return nil } -func registerProvider(ps ...ProviderInterface) { +func RegisterProvider(ps ...ProviderInterface) { for _, p := range ps { allowedProviders[p.Provider()] = p } diff --git a/internal/provider/baidu-netdisk.go b/internal/provider/providers/baidu-netdisk.go similarity index 83% rename from internal/provider/baidu-netdisk.go rename to internal/provider/providers/baidu-netdisk.go index 805f8b7..3ba30ea 100644 --- a/internal/provider/baidu-netdisk.go +++ b/internal/provider/providers/baidu-netdisk.go @@ -1,4 +1,4 @@ -package provider +package providers import ( "context" @@ -6,6 +6,7 @@ import ( "net/http" json "github.com/json-iterator/go" + "github.com/synctv-org/synctv/internal/provider" "golang.org/x/oauth2" ) @@ -14,7 +15,7 @@ type BaiduNetDiskProvider struct { config oauth2.Config } -func (p *BaiduNetDiskProvider) Init(c Oauth2Option) { +func (p *BaiduNetDiskProvider) Init(c provider.Oauth2Option) { p.config.Scopes = []string{"basic", "netdisk"} p.config.Endpoint = oauth2.Endpoint{ AuthURL: "https://openapi.baidu.com/oauth/2.0/authorize", @@ -25,7 +26,7 @@ func (p *BaiduNetDiskProvider) Init(c Oauth2Option) { p.config.RedirectURL = c.RedirectURL } -func (p *BaiduNetDiskProvider) Provider() OAuth2Provider { +func (p *BaiduNetDiskProvider) Provider() provider.OAuth2Provider { return "baidu-netdisk" } @@ -36,7 +37,7 @@ func (p *BaiduNetDiskProvider) NewAuthURL(state string) string { func (p *BaiduNetDiskProvider) GetToken(ctx context.Context, code string) (*oauth2.Token, error) { return p.config.Exchange(ctx, code) } -func (p *BaiduNetDiskProvider) GetUserInfo(ctx context.Context, tk *oauth2.Token) (*UserInfo, error) { +func (p *BaiduNetDiskProvider) GetUserInfo(ctx context.Context, tk *oauth2.Token) (*provider.UserInfo, error) { client := p.config.Client(ctx, tk) req, err := http.NewRequestWithContext(ctx, http.MethodGet, fmt.Sprintf("https://pan.baidu.com/rest/2.0/xpan/nas?method=uinfo&access_token=%s", tk.AccessToken), nil) if err != nil { @@ -55,14 +56,14 @@ func (p *BaiduNetDiskProvider) GetUserInfo(ctx context.Context, tk *oauth2.Token if ui.Errno != 0 { return nil, fmt.Errorf("baidu oauth2 get user info error: %s", ui.Errmsg) } - return &UserInfo{ + return &provider.UserInfo{ Username: ui.BaiduName, ProviderUserID: ui.Uk, }, nil } func init() { - registerProvider(new(BaiduNetDiskProvider)) + provider.RegisterProvider(new(BaiduNetDiskProvider)) } type baiduNetDiskProviderUserInfo struct { diff --git a/internal/provider/baidu.go b/internal/provider/providers/baidu.go similarity index 83% rename from internal/provider/baidu.go rename to internal/provider/providers/baidu.go index 6b22103..1392e8c 100644 --- a/internal/provider/baidu.go +++ b/internal/provider/providers/baidu.go @@ -1,4 +1,4 @@ -package provider +package providers import ( "context" @@ -7,6 +7,7 @@ import ( "net/http" json "github.com/json-iterator/go" + "github.com/synctv-org/synctv/internal/provider" "github.com/zijiren233/stream" "golang.org/x/oauth2" ) @@ -16,7 +17,7 @@ type BaiduProvider struct { config oauth2.Config } -func (p *BaiduProvider) Init(c Oauth2Option) { +func (p *BaiduProvider) Init(c provider.Oauth2Option) { p.config.Scopes = []string{"basic"} p.config.Endpoint = oauth2.Endpoint{ AuthURL: "https://openapi.baidu.com/oauth/2.0/authorize", @@ -27,7 +28,7 @@ func (p *BaiduProvider) Init(c Oauth2Option) { p.config.RedirectURL = c.RedirectURL } -func (p *BaiduProvider) Provider() OAuth2Provider { +func (p *BaiduProvider) Provider() provider.OAuth2Provider { return "baidu" } @@ -39,7 +40,7 @@ func (p *BaiduProvider) GetToken(ctx context.Context, code string) (*oauth2.Toke return p.config.Exchange(ctx, code) } -func (p *BaiduProvider) GetUserInfo(ctx context.Context, tk *oauth2.Token) (*UserInfo, error) { +func (p *BaiduProvider) GetUserInfo(ctx context.Context, tk *oauth2.Token) (*provider.UserInfo, error) { client := p.config.Client(ctx, tk) req, err := http.NewRequestWithContext(ctx, http.MethodGet, fmt.Sprintf("https://openapi.baidu.com/rest/2.0/passport/users/getLoggedInUser?access_token=%s", tk.AccessToken), nil) if err != nil { @@ -55,14 +56,14 @@ func (p *BaiduProvider) GetUserInfo(ctx context.Context, tk *oauth2.Token) (*Use if err != nil { return nil, err } - return &UserInfo{ + return &provider.UserInfo{ Username: ui.Uname, ProviderUserID: uint(crc32.ChecksumIEEE(stream.StringToBytes(ui.Openid))), }, nil } func init() { - registerProvider(new(BaiduProvider)) + provider.RegisterProvider(new(BaiduProvider)) } type baiduProviderUserInfo struct { diff --git a/internal/provider/gitee.go b/internal/provider/providers/gitee.go similarity index 81% rename from internal/provider/gitee.go rename to internal/provider/providers/gitee.go index e9609b3..f920f76 100644 --- a/internal/provider/gitee.go +++ b/internal/provider/providers/gitee.go @@ -1,10 +1,11 @@ -package provider +package providers import ( "context" "net/http" json "github.com/json-iterator/go" + "github.com/synctv-org/synctv/internal/provider" "golang.org/x/oauth2" ) @@ -12,7 +13,7 @@ type GiteeProvider struct { config oauth2.Config } -func (p *GiteeProvider) Init(c Oauth2Option) { +func (p *GiteeProvider) Init(c provider.Oauth2Option) { p.config.Scopes = []string{"user_info"} p.config.Endpoint = oauth2.Endpoint{ AuthURL: "https://gitee.com/oauth/authorize", @@ -23,7 +24,7 @@ func (p *GiteeProvider) Init(c Oauth2Option) { p.config.RedirectURL = c.RedirectURL } -func (p *GiteeProvider) Provider() OAuth2Provider { +func (p *GiteeProvider) Provider() provider.OAuth2Provider { return "gitee" } @@ -35,7 +36,7 @@ func (p *GiteeProvider) GetToken(ctx context.Context, code string) (*oauth2.Toke return p.config.Exchange(ctx, code) } -func (p *GiteeProvider) GetUserInfo(ctx context.Context, tk *oauth2.Token) (*UserInfo, error) { +func (p *GiteeProvider) GetUserInfo(ctx context.Context, tk *oauth2.Token) (*provider.UserInfo, error) { client := p.config.Client(ctx, tk) req, err := http.NewRequestWithContext(ctx, http.MethodGet, "https://gitee.com/api/v5/user", nil) if err != nil { @@ -51,7 +52,7 @@ func (p *GiteeProvider) GetUserInfo(ctx context.Context, tk *oauth2.Token) (*Use if err != nil { return nil, err } - return &UserInfo{ + return &provider.UserInfo{ Username: ui.Login, ProviderUserID: ui.ID, }, nil @@ -63,5 +64,5 @@ type giteeUserInfo struct { } func init() { - registerProvider(new(GiteeProvider)) + provider.RegisterProvider(new(GiteeProvider)) } diff --git a/internal/provider/github.go b/internal/provider/providers/github.go similarity index 80% rename from internal/provider/github.go rename to internal/provider/providers/github.go index 62ffc66..3277cf5 100644 --- a/internal/provider/github.go +++ b/internal/provider/providers/github.go @@ -1,10 +1,11 @@ -package provider +package providers import ( "context" "net/http" json "github.com/json-iterator/go" + "github.com/synctv-org/synctv/internal/provider" "golang.org/x/oauth2" "golang.org/x/oauth2/github" ) @@ -13,7 +14,7 @@ type GithubProvider struct { config oauth2.Config } -func (p *GithubProvider) Init(c Oauth2Option) { +func (p *GithubProvider) Init(c provider.Oauth2Option) { p.config.Scopes = []string{"user"} p.config.Endpoint = github.Endpoint p.config.ClientID = c.ClientID @@ -21,7 +22,7 @@ func (p *GithubProvider) Init(c Oauth2Option) { p.config.RedirectURL = c.RedirectURL } -func (p *GithubProvider) Provider() OAuth2Provider { +func (p *GithubProvider) Provider() provider.OAuth2Provider { return "github" } @@ -33,7 +34,7 @@ func (p *GithubProvider) GetToken(ctx context.Context, code string) (*oauth2.Tok return p.config.Exchange(ctx, code) } -func (p *GithubProvider) GetUserInfo(ctx context.Context, tk *oauth2.Token) (*UserInfo, error) { +func (p *GithubProvider) GetUserInfo(ctx context.Context, tk *oauth2.Token) (*provider.UserInfo, error) { client := p.config.Client(ctx, tk) req, err := http.NewRequestWithContext(ctx, http.MethodGet, "https://api.github.com/user", nil) if err != nil { @@ -49,7 +50,7 @@ func (p *GithubProvider) GetUserInfo(ctx context.Context, tk *oauth2.Token) (*Us if err != nil { return nil, err } - return &UserInfo{ + return &provider.UserInfo{ Username: ui.Login, ProviderUserID: ui.ID, }, nil @@ -61,5 +62,5 @@ type githubUserInfo struct { } func init() { - registerProvider(new(GithubProvider)) + provider.RegisterProvider(new(GithubProvider)) } diff --git a/internal/provider/gitlab.go b/internal/provider/providers/gitlab.go similarity index 74% rename from internal/provider/gitlab.go rename to internal/provider/providers/gitlab.go index aa330e0..48ef75b 100644 --- a/internal/provider/gitlab.go +++ b/internal/provider/providers/gitlab.go @@ -1,9 +1,10 @@ -package provider +package providers import ( "context" "net/http" + "github.com/synctv-org/synctv/internal/provider" "golang.org/x/oauth2" "golang.org/x/oauth2/gitlab" ) @@ -12,7 +13,7 @@ type GitlabProvider struct { config oauth2.Config } -func (g *GitlabProvider) Init(c Oauth2Option) { +func (g *GitlabProvider) Init(c provider.Oauth2Option) { g.config.Scopes = []string{"read_user"} g.config.Endpoint = gitlab.Endpoint g.config.ClientID = c.ClientID @@ -20,7 +21,7 @@ func (g *GitlabProvider) Init(c Oauth2Option) { g.config.RedirectURL = c.RedirectURL } -func (g *GitlabProvider) Provider() OAuth2Provider { +func (g *GitlabProvider) Provider() provider.OAuth2Provider { return "gitlab" } @@ -32,7 +33,7 @@ func (g *GitlabProvider) GetToken(ctx context.Context, code string) (*oauth2.Tok return g.config.Exchange(ctx, code) } -func (g *GitlabProvider) GetUserInfo(ctx context.Context, tk *oauth2.Token) (*UserInfo, error) { +func (g *GitlabProvider) GetUserInfo(ctx context.Context, tk *oauth2.Token) (*provider.UserInfo, error) { client := g.config.Client(ctx, tk) req, err := http.NewRequestWithContext(ctx, http.MethodGet, "https://gitlab.com/api/v4/user", nil) if err != nil { @@ -43,9 +44,9 @@ func (g *GitlabProvider) GetUserInfo(ctx context.Context, tk *oauth2.Token) (*Us return nil, err } defer resp.Body.Close() - return nil, FormatErrNotImplemented("gitlab") + return nil, provider.FormatErrNotImplemented("gitlab") } func init() { - registerProvider(new(GitlabProvider)) + provider.RegisterProvider(new(GitlabProvider)) } diff --git a/internal/provider/google.go b/internal/provider/providers/google.go similarity index 80% rename from internal/provider/google.go rename to internal/provider/providers/google.go index 40c4a8f..608bd28 100644 --- a/internal/provider/google.go +++ b/internal/provider/providers/google.go @@ -1,10 +1,11 @@ -package provider +package providers import ( "context" "net/http" json "github.com/json-iterator/go" + "github.com/synctv-org/synctv/internal/provider" "golang.org/x/oauth2" "golang.org/x/oauth2/google" ) @@ -13,7 +14,7 @@ type GoogleProvider struct { config oauth2.Config } -func (g *GoogleProvider) Init(c Oauth2Option) { +func (g *GoogleProvider) Init(c provider.Oauth2Option) { g.config.Scopes = []string{"profile"} g.config.Endpoint = google.Endpoint g.config.ClientID = c.ClientID @@ -21,7 +22,7 @@ func (g *GoogleProvider) Init(c Oauth2Option) { g.config.RedirectURL = c.RedirectURL } -func (g *GoogleProvider) Provider() OAuth2Provider { +func (g *GoogleProvider) Provider() provider.OAuth2Provider { return "google" } @@ -33,7 +34,7 @@ func (g *GoogleProvider) GetToken(ctx context.Context, code string) (*oauth2.Tok return g.config.Exchange(ctx, code) } -func (g *GoogleProvider) GetUserInfo(ctx context.Context, tk *oauth2.Token) (*UserInfo, error) { +func (g *GoogleProvider) GetUserInfo(ctx context.Context, tk *oauth2.Token) (*provider.UserInfo, error) { client := g.config.Client(ctx, tk) req, err := http.NewRequestWithContext(ctx, http.MethodGet, "https://www.googleapis.com/oauth2/v2/userinfo", nil) if err != nil { @@ -49,14 +50,14 @@ func (g *GoogleProvider) GetUserInfo(ctx context.Context, tk *oauth2.Token) (*Us if err != nil { return nil, err } - return &UserInfo{ + return &provider.UserInfo{ Username: ui.Name, ProviderUserID: ui.ID, }, nil } func init() { - registerProvider(new(GoogleProvider)) + provider.RegisterProvider(new(GoogleProvider)) } type googleUserInfo struct { diff --git a/internal/provider/microsoft.go b/internal/provider/providers/microsoft.go similarity index 81% rename from internal/provider/microsoft.go rename to internal/provider/providers/microsoft.go index 2bf9773..788485f 100644 --- a/internal/provider/microsoft.go +++ b/internal/provider/providers/microsoft.go @@ -1,4 +1,4 @@ -package provider +package providers import ( "context" @@ -6,6 +6,7 @@ import ( "net/http" json "github.com/json-iterator/go" + "github.com/synctv-org/synctv/internal/provider" "github.com/zijiren233/stream" "golang.org/x/oauth2" "golang.org/x/oauth2/microsoft" @@ -15,7 +16,7 @@ type MicrosoftProvider struct { config oauth2.Config } -func (p *MicrosoftProvider) Init(c Oauth2Option) { +func (p *MicrosoftProvider) Init(c provider.Oauth2Option) { p.config.Scopes = []string{"user.read"} p.config.Endpoint = microsoft.LiveConnectEndpoint p.config.ClientID = c.ClientID @@ -23,7 +24,7 @@ func (p *MicrosoftProvider) Init(c Oauth2Option) { p.config.RedirectURL = c.RedirectURL } -func (p *MicrosoftProvider) Provider() OAuth2Provider { +func (p *MicrosoftProvider) Provider() provider.OAuth2Provider { return "microsoft" } @@ -35,7 +36,7 @@ func (p *MicrosoftProvider) GetToken(ctx context.Context, code string) (*oauth2. return p.config.Exchange(ctx, code) } -func (p *MicrosoftProvider) GetUserInfo(ctx context.Context, tk *oauth2.Token) (*UserInfo, error) { +func (p *MicrosoftProvider) GetUserInfo(ctx context.Context, tk *oauth2.Token) (*provider.UserInfo, error) { client := p.config.Client(ctx, tk) req, err := http.NewRequestWithContext(ctx, http.MethodGet, "https://graph.microsoft.com/v1.0/me", nil) if err != nil { @@ -51,7 +52,7 @@ func (p *MicrosoftProvider) GetUserInfo(ctx context.Context, tk *oauth2.Token) ( if err != nil { return nil, err } - return &UserInfo{ + return &provider.UserInfo{ Username: ui.DisplayName, ProviderUserID: uint(crc32.ChecksumIEEE(stream.StringToBytes(ui.ID))), }, nil @@ -63,5 +64,5 @@ type microsoftUserInfo struct { } func init() { - registerProvider(new(MicrosoftProvider)) + provider.RegisterProvider(new(MicrosoftProvider)) } diff --git a/internal/provider/server.go b/internal/provider/server.go new file mode 100644 index 0000000..0c61ac8 --- /dev/null +++ b/internal/provider/server.go @@ -0,0 +1,69 @@ +package provider + +import ( + "context" + "time" + + providerpb "github.com/synctv-org/synctv/proto/provider" + "golang.org/x/oauth2" +) + +type GRPCServer struct { + providerpb.UnimplementedOauth2PluginServer + Impl ProviderInterface +} + +func (s *GRPCServer) Init(ctx context.Context, req *providerpb.InitReq) (*providerpb.Enpty, error) { + s.Impl.Init(Oauth2Option{ + ClientID: req.ClientId, + ClientSecret: req.ClientSecret, + RedirectURL: req.RedirectUrl, + }) + return &providerpb.Enpty{}, nil +} + +func (s *GRPCServer) Provider(ctx context.Context, req *providerpb.Enpty) (*providerpb.ProviderResp, error) { + return &providerpb.ProviderResp{Name: string(s.Impl.Provider())}, nil +} + +func (s *GRPCServer) NewAuthURL(ctx context.Context, req *providerpb.NewAuthURLReq) (*providerpb.NewAuthURLResp, error) { + return &providerpb.NewAuthURLResp{Url: s.Impl.NewAuthURL(req.State)}, nil +} + +func (s *GRPCServer) GetToken(ctx context.Context, req *providerpb.GetTokenReq) (*providerpb.Token, error) { + token, err := s.Impl.GetToken(ctx, req.Code) + if err != nil { + return nil, err + } + return &providerpb.Token{ + AccessToken: token.AccessToken, + TokenType: token.TokenType, + RefreshToken: token.RefreshToken, + Expiry: token.Expiry.Unix(), + }, nil +} + +func (s *GRPCServer) GetUserInfo(ctx context.Context, req *providerpb.GetUserInfoReq) (*providerpb.GetUserInfoResp, error) { + userInfo, err := s.Impl.GetUserInfo(ctx, &oauth2.Token{ + AccessToken: req.Token.AccessToken, + TokenType: req.Token.TokenType, + Expiry: time.Unix(req.Token.Expiry, 0), + RefreshToken: req.Token.RefreshToken, + }) + if err != nil { + return nil, err + } + resp := &providerpb.GetUserInfoResp{ + Username: userInfo.Username, + ProviderUserId: uint64(userInfo.ProviderUserID), + } + if userInfo.TokenRefreshed != nil { + resp.TokenRefreshed = &providerpb.Token{ + AccessToken: userInfo.TokenRefreshed.Token.AccessToken, + TokenType: userInfo.TokenRefreshed.Token.TokenType, + RefreshToken: userInfo.TokenRefreshed.Token.RefreshToken, + Expiry: userInfo.TokenRefreshed.Token.Expiry.Unix(), + } + } + return resp, nil +}