diff --git a/config/flipt.schema.cue b/config/flipt.schema.cue index 56cea0eb93..da17b9c654 100644 --- a/config/flipt.schema.cue +++ b/config/flipt.schema.cue @@ -65,6 +65,14 @@ import "strings" service_account_token_path: string cleanup?: #authentication.#authentication_cleanup } + + github?: { + enabled?: bool | *false + client_secret?: string + client_id?: string + redirect_address?: string + scopes?: [...string] + } } #authentication_cleanup: { @@ -79,7 +87,7 @@ import "strings" client_id?: string client_secret?: string redirect_address?: string - scopes?: [...string] + scopes?: [...string] } } @@ -131,10 +139,10 @@ import "strings" object?: { type: "s3" | *"" s3?: { - region: string - bucket: string - prefix?: string - endpoint?: string + region: string + bucket: string + prefix?: string + endpoint?: string poll_interval?: =~#duration | *"1m" } } diff --git a/config/flipt.schema.json b/config/flipt.schema.json index c7ae8a4b02..a744082792 100644 --- a/config/flipt.schema.json +++ b/config/flipt.schema.json @@ -171,6 +171,30 @@ "required": [], "title": "Kubernetes", "additionalProperties": false + }, + "github": { + "type": "object", + "properties": { + "enabled": { + "type": "boolean", + "default": false + }, + "client_secret": { + "type": "string" + }, + "client_id": { + "type": "string" + }, + "redirect_address": { + "type": "string" + }, + "scopes": { + "type": ["array", "null"], + "items": { "type": "string" } + } + }, + "title": "Github", + "additionalProperties": false } }, "required": [], diff --git a/go.mod b/go.mod index a117f91050..e14ab66d6c 100644 --- a/go.mod +++ b/go.mod @@ -28,6 +28,7 @@ require ( github.com/grpc-ecosystem/go-grpc-middleware v1.4.0 github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0 github.com/grpc-ecosystem/grpc-gateway/v2 v2.16.2 + github.com/h2non/gock v1.2.0 github.com/hashicorp/cap v0.3.4 github.com/hashicorp/go-multierror v1.1.1 github.com/lib/pq v1.10.9 @@ -62,6 +63,7 @@ require ( go.uber.org/zap v1.25.0 golang.org/x/exp v0.0.0-20230510235704-dd950f8aeaea golang.org/x/net v0.14.0 + golang.org/x/oauth2 v0.10.0 golang.org/x/sync v0.3.0 google.golang.org/genproto/googleapis/api v0.0.0-20230726155614-23370e0ffb3e google.golang.org/grpc v1.57.0 @@ -121,6 +123,7 @@ require ( github.com/google/uuid v1.3.0 // indirect github.com/gorilla/securecookie v1.1.1 // indirect github.com/grpc-ecosystem/grpc-gateway v1.16.0 // indirect + github.com/h2non/parth v0.0.0-20190131123155-b4df798d6542 // indirect github.com/hashicorp/errwrap v1.1.0 // indirect github.com/hashicorp/go-cleanhttp v0.5.2 // indirect github.com/hashicorp/go-hclog v1.4.0 // indirect @@ -179,7 +182,6 @@ require ( go.uber.org/multierr v1.11.0 // indirect golang.org/x/crypto v0.12.0 // indirect golang.org/x/mod v0.10.0 // indirect - golang.org/x/oauth2 v0.10.0 // indirect golang.org/x/sys v0.11.0 // indirect golang.org/x/text v0.12.0 // indirect golang.org/x/time v0.3.0 // indirect diff --git a/go.sum b/go.sum index 9baa3c3f46..7b5207c421 100644 --- a/go.sum +++ b/go.sum @@ -329,6 +329,10 @@ github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFb github.com/grpc-ecosystem/grpc-gateway/v2 v2.7.0/go.mod h1:hgWBS7lorOAVIJEQMi4ZsPv9hVvWI6+ch50m39Pf2Ks= github.com/grpc-ecosystem/grpc-gateway/v2 v2.16.2 h1:dygLcbEBA+t/P7ck6a8AkXv6juQ4cK0RHBoh32jxhHM= github.com/grpc-ecosystem/grpc-gateway/v2 v2.16.2/go.mod h1:Ap9RLCIJVtgQg1/BBgVEfypOAySvvlcpcVQkSzJCH4Y= +github.com/h2non/gock v1.2.0 h1:K6ol8rfrRkUOefooBC8elXoaNGYkpp7y2qcxGG6BzUE= +github.com/h2non/gock v1.2.0/go.mod h1:tNhoxHYW2W42cYkYb1WqzdbYIieALC99kpYr7rH/BQk= +github.com/h2non/parth v0.0.0-20190131123155-b4df798d6542 h1:2VTzZjLZBgl62/EtslCrtky5vbi9dd7HrQPQIx6wqiw= +github.com/h2non/parth v0.0.0-20190131123155-b4df798d6542/go.mod h1:Ow0tF8D4Kplbc8s8sSb3V2oUCygFHVp8gC3Dn6U4MNI= github.com/hashicorp/cap v0.3.4 h1:RoqWYqr6LaDLuvnBCpod1sZtvuEhehIhu0GncmoHW40= github.com/hashicorp/cap v0.3.4/go.mod h1:dHTmyMIVbzT981XxRoci5G//dfWmd/HhuNiCH6J5+IA= github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= @@ -475,6 +479,8 @@ github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7P github.com/mpvl/unique v0.0.0-20150818121801-cbe035fff7de h1:D5x39vF5KCwKQaw+OC9ZPiLVHXz3UFw2+psEX+gYcto= github.com/mpvl/unique v0.0.0-20150818121801-cbe035fff7de/go.mod h1:kJun4WP5gFuHZgRjZUWWuH1DTxCtxbHDOIJsudS8jzY= github.com/mrunalp/fileutils v0.5.0/go.mod h1:M1WthSahJixYnrXQl/DFQuteStB1weuxD2QJNHXfbSQ= +github.com/nbio/st v0.0.0-20140626010706-e9e8d9816f32 h1:W6apQkHrMkS0Muv8G/TipAy/FJl/rCYT0+EuS8+Z0z4= +github.com/nbio/st v0.0.0-20140626010706-e9e8d9816f32/go.mod h1:9wM+0iRr9ahx58uYLpLIr5fm8diHn0JbqRycJi6w0Ms= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE= diff --git a/internal/cmd/auth.go b/internal/cmd/auth.go index 898577145d..1d95d8a6d7 100644 --- a/internal/cmd/auth.go +++ b/internal/cmd/auth.go @@ -13,6 +13,8 @@ import ( "go.flipt.io/flipt/internal/containers" "go.flipt.io/flipt/internal/gateway" "go.flipt.io/flipt/internal/server/auth" + "go.flipt.io/flipt/internal/server/auth/method" + authgithub "go.flipt.io/flipt/internal/server/auth/method/github" authkubernetes "go.flipt.io/flipt/internal/server/auth/method/kubernetes" authoidc "go.flipt.io/flipt/internal/server/auth/method/oidc" authtoken "go.flipt.io/flipt/internal/server/auth/method/token" @@ -119,6 +121,15 @@ func authenticationGRPC( logger.Debug("authentication method \"oidc\" server registered") } + if authCfg.Methods.Github.Enabled { + githubServer := authgithub.NewServer(logger, store, authCfg) + register.Add(githubServer) + + authOpts = append(authOpts, auth.WithServerSkipsAuthentication(githubServer)) + + logger.Debug("authentication method \"github\" registered") + } + if authCfg.Methods.Kubernetes.Enabled { kubernetesServer, err := authkubernetes.New(logger, store, authCfg) if err != nil { @@ -211,14 +222,21 @@ func authenticationHTTPMount( muxOpts = append(muxOpts, registerFunc(ctx, conn, rpcauth.RegisterAuthenticationMethodTokenServiceHandler)) } - if cfg.Methods.OIDC.Enabled { - oidcmiddleware := authoidc.NewHTTPMiddleware(cfg.Session) - muxOpts = append(muxOpts, - runtime.WithMetadata(authoidc.ForwardCookies), - runtime.WithForwardResponseOption(oidcmiddleware.ForwardResponseOption), - registerFunc(ctx, conn, rpcauth.RegisterAuthenticationMethodOIDCServiceHandler)) + if cfg.SessionEnabled() { + muxOpts = append(muxOpts, runtime.WithMetadata(method.ForwardCookies)) + + methodMiddleware := method.NewHTTPMiddleware(cfg.Session) + muxOpts = append(muxOpts, runtime.WithForwardResponseOption(methodMiddleware.ForwardResponseOption)) + + if cfg.Methods.OIDC.Enabled { + muxOpts = append(muxOpts, registerFunc(ctx, conn, rpcauth.RegisterAuthenticationMethodOIDCServiceHandler)) + } + + if cfg.Methods.Github.Enabled { + muxOpts = append(muxOpts, registerFunc(ctx, conn, rpcauth.RegisterAuthenticationMethodGithubServiceHandler)) + } - middleware = append(middleware, oidcmiddleware.Handler) + middleware = append(middleware, methodMiddleware.Handler) } if cfg.Methods.Kubernetes.Enabled { diff --git a/internal/config/authentication.go b/internal/config/authentication.go index 5c9bb0420d..e64c4c6d09 100644 --- a/internal/config/authentication.go +++ b/internal/config/authentication.go @@ -114,6 +114,15 @@ func (c *AuthenticationConfig) setDefaults(v *viper.Viper) { }) } +func (c *AuthenticationConfig) SessionEnabled() bool { + var sessionEnabled bool + for _, info := range c.Methods.AllMethods() { + sessionEnabled = sessionEnabled || (info.Enabled && info.SessionCompatible) + } + + return sessionEnabled +} + func (c *AuthenticationConfig) validate() error { var sessionEnabled bool for _, info := range c.Methods.AllMethods() { @@ -192,6 +201,7 @@ type AuthenticationSessionCSRF struct { // method available for use within Flipt. type AuthenticationMethods struct { Token AuthenticationMethod[AuthenticationMethodTokenConfig] `json:"token,omitempty" mapstructure:"token"` + Github AuthenticationMethod[AuthenticationMethodGithubConfig] `json:"github,omitempty" mapstructure:"github"` OIDC AuthenticationMethod[AuthenticationMethodOIDCConfig] `json:"oidc,omitempty" mapstructure:"oidc"` Kubernetes AuthenticationMethod[AuthenticationMethodKubernetesConfig] `json:"kubernetes,omitempty" mapstructure:"kubernetes"` } @@ -200,6 +210,7 @@ type AuthenticationMethods struct { func (a *AuthenticationMethods) AllMethods() []StaticAuthenticationMethodInfo { return []StaticAuthenticationMethodInfo{ a.Token.info(), + a.Github.info(), a.OIDC.info(), a.Kubernetes.info(), } @@ -405,3 +416,31 @@ func (a AuthenticationMethodKubernetesConfig) info() AuthenticationMethodInfo { SessionCompatible: false, } } + +// AuthenticationMethodGithubConfig contains configuration and information for completing an OAuth +// 2.0 flow with GitHub as a provider. +type AuthenticationMethodGithubConfig struct { + ClientSecret string `json:"clientSecret,omitempty" mapstructure:"client_secret"` + ClientId string `json:"clientId,omitempty" mapstructure:"client_id"` + RedirectAddress string `json:"redirectAddress,omitempty" mapstructure:"redirect_address"` + Scopes []string `json:"scopes,omitempty" mapstructure:"scopes"` +} + +func (a AuthenticationMethodGithubConfig) setDefaults(defaults map[string]any) {} + +// info describes properties of the authentication method "github". +func (a AuthenticationMethodGithubConfig) info() AuthenticationMethodInfo { + info := AuthenticationMethodInfo{ + Method: auth.Method_METHOD_GITHUB, + SessionCompatible: true, + } + + var metadata = make(map[string]any) + + metadata["authorize_url"] = "/auth/v1/method/github/authorize" + metadata["callback_url"] = "/auth/v1/method/github/callback" + + info.Metadata, _ = structpb.NewStruct(metadata) + + return info +} diff --git a/internal/config/config_test.go b/internal/config/config_test.go index 34cc1bc460..c1023487b4 100644 --- a/internal/config/config_test.go +++ b/internal/config/config_test.go @@ -589,6 +589,18 @@ func TestLoad(t *testing.T) { GracePeriod: 48 * time.Hour, }, }, + Github: AuthenticationMethod[AuthenticationMethodGithubConfig]{ + Method: AuthenticationMethodGithubConfig{ + ClientId: "abcdefg", + ClientSecret: "bcdefgh", + RedirectAddress: "http://auth.flipt.io", + }, + Enabled: true, + Cleanup: &AuthenticationCleanupSchedule{ + Interval: 2 * time.Hour, + GracePeriod: 48 * time.Hour, + }, + }, }, } return cfg diff --git a/internal/config/testdata/advanced.yml b/internal/config/testdata/advanced.yml index d79cdf79c1..36a5a1e9c6 100644 --- a/internal/config/testdata/advanced.yml +++ b/internal/config/testdata/advanced.yml @@ -95,3 +95,11 @@ authentication: cleanup: interval: 2h grace_period: 48h + github: + enabled: true + client_id: "abcdefg" + client_secret: "bcdefgh" + redirect_address: "http://auth.flipt.io" + cleanup: + interval: 2h + grace_period: 48h diff --git a/internal/server/auth/method/github/server.go b/internal/server/auth/method/github/server.go new file mode 100644 index 0000000000..aec8d9e86e --- /dev/null +++ b/internal/server/auth/method/github/server.go @@ -0,0 +1,175 @@ +package github + +import ( + "context" + "encoding/json" + "fmt" + "net/http" + "strings" + "time" + + "go.flipt.io/flipt/errors" + "go.flipt.io/flipt/internal/config" + "go.flipt.io/flipt/internal/server/auth/method" + storageauth "go.flipt.io/flipt/internal/storage/auth" + "go.flipt.io/flipt/rpc/flipt/auth" + "go.uber.org/zap" + "golang.org/x/oauth2" + oauth2GitHub "golang.org/x/oauth2/github" + "google.golang.org/grpc" + "google.golang.org/protobuf/types/known/timestamppb" +) + +const ( + githubUserAPI = "https://api.github.com/user" +) + +// OAuth2Client is our abstraction of communication with an OAuth2 Provider. +type OAuth2Client interface { + AuthCodeURL(state string, opts ...oauth2.AuthCodeOption) string + Exchange(ctx context.Context, code string, opts ...oauth2.AuthCodeOption) (*oauth2.Token, error) + Client(ctx context.Context, t *oauth2.Token) *http.Client +} + +const ( + storageMetadataGithubEmail = "io.flipt.auth.github.email" + storageMetadataGithubName = "io.flipt.auth.github.name" + storageMetadataGithubPicture = "io.flipt.auth.github.picture" +) + +// Server is an Github server side handler. +type Server struct { + logger *zap.Logger + store storageauth.Store + config config.AuthenticationConfig + oauth2Config OAuth2Client + + auth.UnimplementedAuthenticationMethodGithubServiceServer +} + +// NewServer constructs a Server. +func NewServer( + logger *zap.Logger, + store storageauth.Store, + config config.AuthenticationConfig, +) *Server { + return &Server{ + logger: logger, + store: store, + config: config, + oauth2Config: &oauth2.Config{ + ClientID: config.Methods.Github.Method.ClientId, + ClientSecret: config.Methods.Github.Method.ClientSecret, + Endpoint: oauth2GitHub.Endpoint, + RedirectURL: callbackURL(config.Methods.Github.Method.RedirectAddress), + Scopes: config.Methods.Github.Method.Scopes, + }, + } +} + +// RegisterGRPC registers the server as an Server on the provided grpc server. +func (s *Server) RegisterGRPC(server *grpc.Server) { + auth.RegisterAuthenticationMethodGithubServiceServer(server, s) +} + +func callbackURL(host string) string { + // strip trailing slash from host + host = strings.TrimSuffix(host, "/") + return host + "/auth/v1/method/github/callback" +} + +// AuthorizeURL will return a URL for the client to redirect to for completion of the OAuth flow with GitHub. +func (s *Server) AuthorizeURL(ctx context.Context, req *auth.AuthorizeURLRequest) (*auth.AuthorizeURLResponse, error) { + u := s.oauth2Config.AuthCodeURL(req.State) + + return &auth.AuthorizeURLResponse{ + AuthorizeUrl: u, + }, nil +} + +// Callback is the OAuth callback method for Github authentication. It will take in a Code +// which is the OAuth grant passed in by the OAuth service, and exchange the grant with an Authentication +// that includes the user information. +func (s *Server) Callback(ctx context.Context, r *auth.CallbackRequest) (*auth.CallbackResponse, error) { + if r.State != "" { + if err := method.CallbackValidateState(ctx, r.State); err != nil { + return nil, err + } + } + + token, err := s.oauth2Config.Exchange(ctx, r.Code) + if err != nil { + return nil, err + } + + if !token.Valid() { + return nil, errors.New("invalid token") + } + + c := &http.Client{ + Timeout: 5 * time.Second, + } + + userReq, err := http.NewRequestWithContext(ctx, "GET", githubUserAPI, nil) + if err != nil { + return nil, err + } + + userReq.Header.Set("Authorization", fmt.Sprintf("Bearer %s", token.AccessToken)) + userReq.Header.Set("Accept", "application/vnd.github+json") + + userResp, err := c.Do(userReq) + if err != nil { + return nil, err + } + + defer func() { + userResp.Body.Close() + }() + + if userResp.StatusCode != http.StatusOK { + return nil, fmt.Errorf("github user info response status: %q", userResp.Status) + } + + var githubUserResponse struct { + Name string `json:"name,omitempty"` + Email string `json:"email,omitempty"` + AvatarURL string `json:"avatar_url,omitempty"` + } + + if err := json.NewDecoder(userResp.Body).Decode(&githubUserResponse); err != nil { + return nil, err + } + + metadata := map[string]string{ + storageMetadataGithubEmail: githubUserResponse.Email, + storageMetadataGithubName: githubUserResponse.Name, + storageMetadataGithubPicture: githubUserResponse.AvatarURL, + } + + if githubUserResponse.Name != "" { + metadata[storageMetadataGithubName] = githubUserResponse.Name + } + + if githubUserResponse.Email != "" { + metadata[storageMetadataGithubEmail] = githubUserResponse.Email + } + + if githubUserResponse.AvatarURL != "" { + metadata[storageMetadataGithubPicture] = githubUserResponse.AvatarURL + } + + clientToken, a, err := s.store.CreateAuthentication(ctx, &storageauth.CreateAuthenticationRequest{ + Method: auth.Method_METHOD_GITHUB, + ExpiresAt: timestamppb.New(time.Now().UTC().Add(s.config.Session.TokenLifetime)), + Metadata: metadata, + }) + if err != nil { + return nil, err + } + + return &auth.CallbackResponse{ + ClientToken: clientToken, + Authentication: a, + }, nil +} diff --git a/internal/server/auth/method/github/server_test.go b/internal/server/auth/method/github/server_test.go new file mode 100644 index 0000000000..6c1d810c3d --- /dev/null +++ b/internal/server/auth/method/github/server_test.go @@ -0,0 +1,159 @@ +package github + +import ( + "context" + "net" + "net/http" + "net/url" + "testing" + "time" + + grpc_middleware "github.com/grpc-ecosystem/go-grpc-middleware" + "github.com/h2non/gock" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "go.flipt.io/flipt/internal/config" + middleware "go.flipt.io/flipt/internal/server/middleware/grpc" + "go.flipt.io/flipt/internal/storage/auth/memory" + "go.flipt.io/flipt/rpc/flipt/auth" + "go.uber.org/zap/zaptest" + "golang.org/x/oauth2" + "google.golang.org/grpc" + "google.golang.org/grpc/test/bufconn" +) + +type OAuth2Mock struct{} + +func (o *OAuth2Mock) AuthCodeURL(state string, opts ...oauth2.AuthCodeOption) string { + u := url.URL{ + Scheme: "http", + Host: "github.com", + Path: "login/oauth/authorize", + } + + v := url.Values{} + v.Set("state", state) + + u.RawQuery = v.Encode() + return u.String() +} + +func (o *OAuth2Mock) Exchange(ctx context.Context, code string, opts ...oauth2.AuthCodeOption) (*oauth2.Token, error) { + return &oauth2.Token{ + AccessToken: "AccessToken", + Expiry: time.Now().Add(1 * time.Hour), + }, nil +} + +func (o *OAuth2Mock) Client(ctx context.Context, t *oauth2.Token) *http.Client { + return &http.Client{} +} + +func TestServer_Github(t *testing.T) { + var ( + logger = zaptest.NewLogger(t) + store = memory.NewStore() + listener = bufconn.Listen(1024 * 1024) + server = grpc.NewServer( + grpc_middleware.WithUnaryServerChain( + middleware.ErrorUnaryInterceptor, + ), + ) + errC = make(chan error) + shutdown = func(t *testing.T) { + t.Helper() + + server.Stop() + if err := <-errC; err != nil { + t.Fatal(err) + } + } + ) + + defer shutdown(t) + + s := &Server{ + logger: logger, + store: store, + config: config.AuthenticationConfig{ + Methods: config.AuthenticationMethods{ + Github: config.AuthenticationMethod[config.AuthenticationMethodGithubConfig]{ + Enabled: true, + Method: config.AuthenticationMethodGithubConfig{ + ClientSecret: "topsecret", + ClientId: "githubid", + RedirectAddress: "test.flipt.io", + Scopes: []string{"user", "email"}, + }, + }, + }, + }, + oauth2Config: &OAuth2Mock{}, + } + + auth.RegisterAuthenticationMethodGithubServiceServer(server, s) + + go func() { + errC <- server.Serve(listener) + }() + + var ( + ctx = context.Background() + dialer = func(context.Context, string) (net.Conn, error) { + return listener.Dial() + } + ) + + conn, err := grpc.DialContext(ctx, "", grpc.WithInsecure(), grpc.WithContextDialer(dialer)) + require.NoError(t, err) + defer conn.Close() + + client := auth.NewAuthenticationMethodGithubServiceClient(conn) + + a, err := client.AuthorizeURL(ctx, &auth.AuthorizeURLRequest{ + Provider: "github", + State: "random-state", + }) + require.NoError(t, err) + + assert.Equal(t, "http://github.com/login/oauth/authorize?state=random-state", a.AuthorizeUrl) + + gock.New("https://api.github.com"). + MatchHeader("Authorization", "Bearer AccessToken"). + MatchHeader("Accept", "application/vnd.github+json"). + Get("/user"). + Reply(200). + JSON(map[string]string{"name": "fliptuser", "email": "user@flipt.io", "avatar_url": "https://thispicture.com"}) + + c, err := client.Callback(ctx, &auth.CallbackRequest{Code: "github_code"}) + require.NoError(t, err) + + assert.NotEmpty(t, c.ClientToken) + assert.Equal(t, auth.Method_METHOD_GITHUB, c.Authentication.Method) + assert.Equal(t, map[string]string{ + storageMetadataGithubEmail: "user@flipt.io", + storageMetadataGithubName: "fliptuser", + storageMetadataGithubPicture: "https://thispicture.com", + }, c.Authentication.Metadata) + + gock.Off() + + gock.New("https://api.github.com"). + MatchHeader("Authorization", "Bearer AccessToken"). + MatchHeader("Accept", "application/vnd.github+json"). + Get("/user"). + Reply(400) + + _, err = client.Callback(ctx, &auth.CallbackRequest{Code: "github_code"}) + assert.EqualError(t, err, "rpc error: code = Internal desc = github user info response status: \"400 Bad Request\"") + + gock.Off() +} + +func TestCallbackURL(t *testing.T) { + callback := callbackURL("https://flipt.io") + assert.Equal(t, callback, "https://flipt.io/auth/v1/method/github/callback") + + callback = callbackURL("https://flipt.io/") + assert.Equal(t, callback, "https://flipt.io/auth/v1/method/github/callback") +} diff --git a/internal/server/auth/method/oidc/http.go b/internal/server/auth/method/http.go similarity index 92% rename from internal/server/auth/method/oidc/http.go rename to internal/server/auth/method/http.go index ddbf600498..f92af770c1 100644 --- a/internal/server/auth/method/oidc/http.go +++ b/internal/server/auth/method/http.go @@ -1,4 +1,4 @@ -package oidc +package method import ( "context" @@ -6,6 +6,7 @@ import ( "encoding/base64" "encoding/json" "net/http" + "path" "strings" "time" @@ -16,6 +17,8 @@ import ( ) var ( + githubPrefix = "/auth/v1/method/github" + oidcPrefix = "/auth/v1/method/oidc/" stateCookieKey = "flipt_client_state" tokenCookieKey = "flipt_client_token" ) @@ -61,7 +64,7 @@ func (m Middleware) ForwardResponseOption(ctx context.Context, w http.ResponseWr if ok { cookie := &http.Cookie{ Name: tokenCookieKey, - Value: r.ClientToken, + Value: r.GetClientToken(), Domain: m.config.Domain, Path: "/", Expires: time.Now().Add(m.config.TokenLifetime), @@ -90,8 +93,9 @@ func (m Middleware) ForwardResponseOption(ctx context.Context, w http.ResponseWr // The payload is then also encoded as a http cookie which is bound to the callback path. func (m Middleware) Handler(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - provider, method, match := parts(r.URL.Path) - if !match { + prefix, method := path.Split(r.URL.Path) + + if !((strings.HasPrefix(prefix, oidcPrefix) || strings.HasPrefix(prefix, githubPrefix)) && method == "authorize") { next.ServeHTTP(w, r) return } @@ -126,7 +130,7 @@ func (m Middleware) Handler(next http.Handler) http.Handler { Name: stateCookieKey, Value: encoded, // bind state cookie to provider callback - Path: "/auth/v1/method/oidc/" + provider + "/callback", + Path: prefix + "callback", Expires: time.Now().Add(m.config.StateLifetime), Secure: m.config.Secure, HttpOnly: true, @@ -150,15 +154,6 @@ func (m Middleware) Handler(next http.Handler) http.Handler { }) } -func parts(path string) (provider, method string, ok bool) { - const prefix = "/auth/v1/method/oidc/" - if !strings.HasPrefix(path, prefix) { - return "", "", false - } - - return strings.Cut(path[len(prefix):], "/") -} - func generateSecurityToken() string { var token [64]byte if _, err := rand.Read(token[:]); err != nil { diff --git a/internal/server/auth/method/oidc/server.go b/internal/server/auth/method/oidc/server.go index 84e4b23ba5..1db3c67b6f 100644 --- a/internal/server/auth/method/oidc/server.go +++ b/internal/server/auth/method/oidc/server.go @@ -10,11 +10,11 @@ import ( capoidc "github.com/hashicorp/cap/oidc" "go.flipt.io/flipt/errors" "go.flipt.io/flipt/internal/config" + "go.flipt.io/flipt/internal/server/auth/method" storageauth "go.flipt.io/flipt/internal/storage/auth" "go.flipt.io/flipt/rpc/flipt/auth" "go.uber.org/zap" "google.golang.org/grpc" - "google.golang.org/grpc/metadata" "google.golang.org/protobuf/types/known/timestamppb" ) @@ -107,18 +107,8 @@ func (s *Server) Callback(ctx context.Context, req *auth.CallbackRequest) (_ *au }() if req.State != "" { - md, ok := metadata.FromIncomingContext(ctx) - if !ok { - return nil, errors.ErrUnauthenticatedf("missing state parameter") - } - - state, ok := md["flipt_client_state"] - if !ok || len(state) < 1 { - return nil, errors.ErrUnauthenticatedf("missing state parameter") - } - - if req.State != state[0] { - return nil, errors.ErrUnauthenticatedf("unexpected state parameter") + if err := method.CallbackValidateState(ctx, req.State); err != nil { + return nil, err } } diff --git a/internal/server/auth/method/oidc/testing/http.go b/internal/server/auth/method/oidc/testing/http.go index ab4ae20c39..1f143f6116 100644 --- a/internal/server/auth/method/oidc/testing/http.go +++ b/internal/server/auth/method/oidc/testing/http.go @@ -9,7 +9,7 @@ import ( "github.com/stretchr/testify/require" "go.flipt.io/flipt/internal/config" "go.flipt.io/flipt/internal/gateway" - "go.flipt.io/flipt/internal/server/auth/method/oidc" + "go.flipt.io/flipt/internal/server/auth/method" "go.flipt.io/flipt/rpc/flipt/auth" "go.uber.org/zap" ) @@ -32,10 +32,10 @@ func StartHTTPServer( GRPCServer: StartGRPCServer(t, ctx, logger, conf), } - oidcmiddleware = oidc.NewHTTPMiddleware(conf.Session) + oidcmiddleware = method.NewHTTPMiddleware(conf.Session) mux = gateway.NewGatewayServeMux( logger, - runtime.WithMetadata(oidc.ForwardCookies), + runtime.WithMetadata(method.ForwardCookies), runtime.WithForwardResponseOption(oidcmiddleware.ForwardResponseOption), ) ) diff --git a/internal/server/auth/method/util.go b/internal/server/auth/method/util.go new file mode 100644 index 0000000000..c9b25c7122 --- /dev/null +++ b/internal/server/auth/method/util.go @@ -0,0 +1,28 @@ +package method + +import ( + "context" + + "go.flipt.io/flipt/errors" + "google.golang.org/grpc/metadata" +) + +// CallbackValidateState validates the state for the callback request on both OIDC and GitHub as +// an OAuth provider. +func CallbackValidateState(ctx context.Context, state string) error { + md, ok := metadata.FromIncomingContext(ctx) + if !ok { + return errors.ErrUnauthenticatedf("missing state parameter") + } + + fstate, ok := md["flipt_client_state"] + if !ok || len(state) < 1 { + return errors.ErrUnauthenticatedf("missing state parameter") + } + + if state != fstate[0] { + return errors.ErrUnauthenticatedf("unexpected state parameter") + } + + return nil +} diff --git a/rpc/flipt/auth/auth.pb.go b/rpc/flipt/auth/auth.pb.go index 691c92a36d..a4fde57812 100644 --- a/rpc/flipt/auth/auth.pb.go +++ b/rpc/flipt/auth/auth.pb.go @@ -30,6 +30,7 @@ const ( Method_METHOD_TOKEN Method = 1 Method_METHOD_OIDC Method = 2 Method_METHOD_KUBERNETES Method = 3 + Method_METHOD_GITHUB Method = 4 ) // Enum value maps for Method. @@ -39,12 +40,14 @@ var ( 1: "METHOD_TOKEN", 2: "METHOD_OIDC", 3: "METHOD_KUBERNETES", + 4: "METHOD_GITHUB", } Method_value = map[string]int32{ "METHOD_NONE": 0, "METHOD_TOKEN": 1, "METHOD_OIDC": 2, "METHOD_KUBERNETES": 3, + "METHOD_GITHUB": 4, } ) @@ -1112,83 +1115,96 @@ var file_auth_auth_proto_rawDesc = []byte{ 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x66, 0x6c, 0x69, 0x70, 0x74, 0x2e, 0x61, 0x75, 0x74, 0x68, 0x2e, 0x41, 0x75, 0x74, 0x68, 0x65, 0x6e, 0x74, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x0e, 0x61, 0x75, 0x74, 0x68, 0x65, 0x6e, 0x74, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, - 0x6e, 0x2a, 0x53, 0x0a, 0x06, 0x4d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x12, 0x0f, 0x0a, 0x0b, 0x4d, + 0x6e, 0x2a, 0x66, 0x0a, 0x06, 0x4d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x12, 0x0f, 0x0a, 0x0b, 0x4d, 0x45, 0x54, 0x48, 0x4f, 0x44, 0x5f, 0x4e, 0x4f, 0x4e, 0x45, 0x10, 0x00, 0x12, 0x10, 0x0a, 0x0c, 0x4d, 0x45, 0x54, 0x48, 0x4f, 0x44, 0x5f, 0x54, 0x4f, 0x4b, 0x45, 0x4e, 0x10, 0x01, 0x12, 0x0f, 0x0a, 0x0b, 0x4d, 0x45, 0x54, 0x48, 0x4f, 0x44, 0x5f, 0x4f, 0x49, 0x44, 0x43, 0x10, 0x02, 0x12, 0x15, 0x0a, 0x11, 0x4d, 0x45, 0x54, 0x48, 0x4f, 0x44, 0x5f, 0x4b, 0x55, 0x42, 0x45, 0x52, 0x4e, - 0x45, 0x54, 0x45, 0x53, 0x10, 0x03, 0x32, 0x83, 0x01, 0x0a, 0x1b, 0x50, 0x75, 0x62, 0x6c, 0x69, - 0x63, 0x41, 0x75, 0x74, 0x68, 0x65, 0x6e, 0x74, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x53, - 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x64, 0x0a, 0x19, 0x4c, 0x69, 0x73, 0x74, 0x41, 0x75, - 0x74, 0x68, 0x65, 0x6e, 0x74, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x4d, 0x65, 0x74, 0x68, - 0x6f, 0x64, 0x73, 0x12, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, - 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x2d, 0x2e, 0x66, 0x6c, - 0x69, 0x70, 0x74, 0x2e, 0x61, 0x75, 0x74, 0x68, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x41, 0x75, 0x74, - 0x68, 0x65, 0x6e, 0x74, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x4d, 0x65, 0x74, 0x68, 0x6f, - 0x64, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x32, 0xe7, 0x03, 0x0a, - 0x15, 0x41, 0x75, 0x74, 0x68, 0x65, 0x6e, 0x74, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x53, - 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x4d, 0x0a, 0x15, 0x47, 0x65, 0x74, 0x41, 0x75, 0x74, - 0x68, 0x65, 0x6e, 0x74, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x65, 0x6c, 0x66, 0x12, - 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, - 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x1a, 0x2e, 0x66, 0x6c, 0x69, 0x70, 0x74, 0x2e, - 0x61, 0x75, 0x74, 0x68, 0x2e, 0x41, 0x75, 0x74, 0x68, 0x65, 0x6e, 0x74, 0x69, 0x63, 0x61, 0x74, - 0x69, 0x6f, 0x6e, 0x22, 0x00, 0x12, 0x57, 0x0a, 0x11, 0x47, 0x65, 0x74, 0x41, 0x75, 0x74, 0x68, - 0x65, 0x6e, 0x74, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x24, 0x2e, 0x66, 0x6c, 0x69, - 0x70, 0x74, 0x2e, 0x61, 0x75, 0x74, 0x68, 0x2e, 0x47, 0x65, 0x74, 0x41, 0x75, 0x74, 0x68, 0x65, - 0x6e, 0x74, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, - 0x1a, 0x1a, 0x2e, 0x66, 0x6c, 0x69, 0x70, 0x74, 0x2e, 0x61, 0x75, 0x74, 0x68, 0x2e, 0x41, 0x75, - 0x74, 0x68, 0x65, 0x6e, 0x74, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x22, 0x00, 0x12, 0x68, - 0x0a, 0x13, 0x4c, 0x69, 0x73, 0x74, 0x41, 0x75, 0x74, 0x68, 0x65, 0x6e, 0x74, 0x69, 0x63, 0x61, - 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x26, 0x2e, 0x66, 0x6c, 0x69, 0x70, 0x74, 0x2e, 0x61, 0x75, - 0x74, 0x68, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x41, 0x75, 0x74, 0x68, 0x65, 0x6e, 0x74, 0x69, 0x63, - 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x27, 0x2e, - 0x66, 0x6c, 0x69, 0x70, 0x74, 0x2e, 0x61, 0x75, 0x74, 0x68, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x41, - 0x75, 0x74, 0x68, 0x65, 0x6e, 0x74, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, - 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x59, 0x0a, 0x14, 0x44, 0x65, 0x6c, 0x65, - 0x74, 0x65, 0x41, 0x75, 0x74, 0x68, 0x65, 0x6e, 0x74, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, - 0x12, 0x27, 0x2e, 0x66, 0x6c, 0x69, 0x70, 0x74, 0x2e, 0x61, 0x75, 0x74, 0x68, 0x2e, 0x44, 0x65, - 0x6c, 0x65, 0x74, 0x65, 0x41, 0x75, 0x74, 0x68, 0x65, 0x6e, 0x74, 0x69, 0x63, 0x61, 0x74, 0x69, - 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, - 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, - 0x79, 0x22, 0x00, 0x12, 0x61, 0x0a, 0x18, 0x45, 0x78, 0x70, 0x69, 0x72, 0x65, 0x41, 0x75, 0x74, - 0x68, 0x65, 0x6e, 0x74, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x65, 0x6c, 0x66, 0x12, - 0x2b, 0x2e, 0x66, 0x6c, 0x69, 0x70, 0x74, 0x2e, 0x61, 0x75, 0x74, 0x68, 0x2e, 0x45, 0x78, 0x70, - 0x69, 0x72, 0x65, 0x41, 0x75, 0x74, 0x68, 0x65, 0x6e, 0x74, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, - 0x6e, 0x53, 0x65, 0x6c, 0x66, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x16, 0x2e, 0x67, + 0x45, 0x54, 0x45, 0x53, 0x10, 0x03, 0x12, 0x11, 0x0a, 0x0d, 0x4d, 0x45, 0x54, 0x48, 0x4f, 0x44, + 0x5f, 0x47, 0x49, 0x54, 0x48, 0x55, 0x42, 0x10, 0x04, 0x32, 0x83, 0x01, 0x0a, 0x1b, 0x50, 0x75, + 0x62, 0x6c, 0x69, 0x63, 0x41, 0x75, 0x74, 0x68, 0x65, 0x6e, 0x74, 0x69, 0x63, 0x61, 0x74, 0x69, + 0x6f, 0x6e, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x64, 0x0a, 0x19, 0x4c, 0x69, 0x73, + 0x74, 0x41, 0x75, 0x74, 0x68, 0x65, 0x6e, 0x74, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x4d, + 0x65, 0x74, 0x68, 0x6f, 0x64, 0x73, 0x12, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, + 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x2d, + 0x2e, 0x66, 0x6c, 0x69, 0x70, 0x74, 0x2e, 0x61, 0x75, 0x74, 0x68, 0x2e, 0x4c, 0x69, 0x73, 0x74, + 0x41, 0x75, 0x74, 0x68, 0x65, 0x6e, 0x74, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x4d, 0x65, + 0x74, 0x68, 0x6f, 0x64, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x32, + 0xe7, 0x03, 0x0a, 0x15, 0x41, 0x75, 0x74, 0x68, 0x65, 0x6e, 0x74, 0x69, 0x63, 0x61, 0x74, 0x69, + 0x6f, 0x6e, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x4d, 0x0a, 0x15, 0x47, 0x65, 0x74, + 0x41, 0x75, 0x74, 0x68, 0x65, 0x6e, 0x74, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x65, + 0x6c, 0x66, 0x12, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, + 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x1a, 0x2e, 0x66, 0x6c, 0x69, + 0x70, 0x74, 0x2e, 0x61, 0x75, 0x74, 0x68, 0x2e, 0x41, 0x75, 0x74, 0x68, 0x65, 0x6e, 0x74, 0x69, + 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x22, 0x00, 0x12, 0x57, 0x0a, 0x11, 0x47, 0x65, 0x74, 0x41, + 0x75, 0x74, 0x68, 0x65, 0x6e, 0x74, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x24, 0x2e, + 0x66, 0x6c, 0x69, 0x70, 0x74, 0x2e, 0x61, 0x75, 0x74, 0x68, 0x2e, 0x47, 0x65, 0x74, 0x41, 0x75, + 0x74, 0x68, 0x65, 0x6e, 0x74, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, + 0x65, 0x73, 0x74, 0x1a, 0x1a, 0x2e, 0x66, 0x6c, 0x69, 0x70, 0x74, 0x2e, 0x61, 0x75, 0x74, 0x68, + 0x2e, 0x41, 0x75, 0x74, 0x68, 0x65, 0x6e, 0x74, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x22, + 0x00, 0x12, 0x68, 0x0a, 0x13, 0x4c, 0x69, 0x73, 0x74, 0x41, 0x75, 0x74, 0x68, 0x65, 0x6e, 0x74, + 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x26, 0x2e, 0x66, 0x6c, 0x69, 0x70, 0x74, + 0x2e, 0x61, 0x75, 0x74, 0x68, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x41, 0x75, 0x74, 0x68, 0x65, 0x6e, + 0x74, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x1a, 0x27, 0x2e, 0x66, 0x6c, 0x69, 0x70, 0x74, 0x2e, 0x61, 0x75, 0x74, 0x68, 0x2e, 0x4c, 0x69, + 0x73, 0x74, 0x41, 0x75, 0x74, 0x68, 0x65, 0x6e, 0x74, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, + 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x59, 0x0a, 0x14, 0x44, + 0x65, 0x6c, 0x65, 0x74, 0x65, 0x41, 0x75, 0x74, 0x68, 0x65, 0x6e, 0x74, 0x69, 0x63, 0x61, 0x74, + 0x69, 0x6f, 0x6e, 0x12, 0x27, 0x2e, 0x66, 0x6c, 0x69, 0x70, 0x74, 0x2e, 0x61, 0x75, 0x74, 0x68, + 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x41, 0x75, 0x74, 0x68, 0x65, 0x6e, 0x74, 0x69, 0x63, + 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, - 0x6d, 0x70, 0x74, 0x79, 0x22, 0x00, 0x32, 0x74, 0x0a, 0x20, 0x41, 0x75, 0x74, 0x68, 0x65, 0x6e, - 0x74, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x4d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x54, 0x6f, - 0x6b, 0x65, 0x6e, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x50, 0x0a, 0x0b, 0x43, 0x72, - 0x65, 0x61, 0x74, 0x65, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x12, 0x1e, 0x2e, 0x66, 0x6c, 0x69, 0x70, - 0x74, 0x2e, 0x61, 0x75, 0x74, 0x68, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x54, 0x6f, 0x6b, - 0x65, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1f, 0x2e, 0x66, 0x6c, 0x69, 0x70, - 0x74, 0x2e, 0x61, 0x75, 0x74, 0x68, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x54, 0x6f, 0x6b, - 0x65, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x32, 0xbf, 0x01, 0x0a, - 0x1f, 0x41, 0x75, 0x74, 0x68, 0x65, 0x6e, 0x74, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x4d, - 0x65, 0x74, 0x68, 0x6f, 0x64, 0x4f, 0x49, 0x44, 0x43, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, - 0x12, 0x53, 0x0a, 0x0c, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x65, 0x55, 0x52, 0x4c, - 0x12, 0x1f, 0x2e, 0x66, 0x6c, 0x69, 0x70, 0x74, 0x2e, 0x61, 0x75, 0x74, 0x68, 0x2e, 0x41, 0x75, - 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x65, 0x55, 0x52, 0x4c, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, - 0x74, 0x1a, 0x20, 0x2e, 0x66, 0x6c, 0x69, 0x70, 0x74, 0x2e, 0x61, 0x75, 0x74, 0x68, 0x2e, 0x41, - 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x65, 0x55, 0x52, 0x4c, 0x52, 0x65, 0x73, 0x70, 0x6f, - 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x47, 0x0a, 0x08, 0x43, 0x61, 0x6c, 0x6c, 0x62, 0x61, 0x63, - 0x6b, 0x12, 0x1b, 0x2e, 0x66, 0x6c, 0x69, 0x70, 0x74, 0x2e, 0x61, 0x75, 0x74, 0x68, 0x2e, 0x43, - 0x61, 0x6c, 0x6c, 0x62, 0x61, 0x63, 0x6b, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1c, - 0x2e, 0x66, 0x6c, 0x69, 0x70, 0x74, 0x2e, 0x61, 0x75, 0x74, 0x68, 0x2e, 0x43, 0x61, 0x6c, 0x6c, - 0x62, 0x61, 0x63, 0x6b, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x32, 0x94, - 0x01, 0x0a, 0x25, 0x41, 0x75, 0x74, 0x68, 0x65, 0x6e, 0x74, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, - 0x6e, 0x4d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x4b, 0x75, 0x62, 0x65, 0x72, 0x6e, 0x65, 0x74, 0x65, - 0x73, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x6b, 0x0a, 0x14, 0x56, 0x65, 0x72, 0x69, - 0x66, 0x79, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, - 0x12, 0x27, 0x2e, 0x66, 0x6c, 0x69, 0x70, 0x74, 0x2e, 0x61, 0x75, 0x74, 0x68, 0x2e, 0x56, 0x65, - 0x72, 0x69, 0x66, 0x79, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x41, 0x63, 0x63, 0x6f, 0x75, - 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x28, 0x2e, 0x66, 0x6c, 0x69, 0x70, - 0x74, 0x2e, 0x61, 0x75, 0x74, 0x68, 0x2e, 0x56, 0x65, 0x72, 0x69, 0x66, 0x79, 0x53, 0x65, 0x72, - 0x76, 0x69, 0x63, 0x65, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, - 0x6e, 0x73, 0x65, 0x22, 0x00, 0x42, 0x22, 0x5a, 0x20, 0x67, 0x6f, 0x2e, 0x66, 0x6c, 0x69, 0x70, - 0x74, 0x2e, 0x69, 0x6f, 0x2f, 0x66, 0x6c, 0x69, 0x70, 0x74, 0x2f, 0x72, 0x70, 0x63, 0x2f, 0x66, - 0x6c, 0x69, 0x70, 0x74, 0x2f, 0x61, 0x75, 0x74, 0x68, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, - 0x33, + 0x6d, 0x70, 0x74, 0x79, 0x22, 0x00, 0x12, 0x61, 0x0a, 0x18, 0x45, 0x78, 0x70, 0x69, 0x72, 0x65, + 0x41, 0x75, 0x74, 0x68, 0x65, 0x6e, 0x74, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x65, + 0x6c, 0x66, 0x12, 0x2b, 0x2e, 0x66, 0x6c, 0x69, 0x70, 0x74, 0x2e, 0x61, 0x75, 0x74, 0x68, 0x2e, + 0x45, 0x78, 0x70, 0x69, 0x72, 0x65, 0x41, 0x75, 0x74, 0x68, 0x65, 0x6e, 0x74, 0x69, 0x63, 0x61, + 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x65, 0x6c, 0x66, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, + 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, + 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x22, 0x00, 0x32, 0x74, 0x0a, 0x20, 0x41, 0x75, 0x74, + 0x68, 0x65, 0x6e, 0x74, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x4d, 0x65, 0x74, 0x68, 0x6f, + 0x64, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x50, 0x0a, + 0x0b, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x12, 0x1e, 0x2e, 0x66, + 0x6c, 0x69, 0x70, 0x74, 0x2e, 0x61, 0x75, 0x74, 0x68, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, + 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1f, 0x2e, 0x66, + 0x6c, 0x69, 0x70, 0x74, 0x2e, 0x61, 0x75, 0x74, 0x68, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, + 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x32, + 0xbf, 0x01, 0x0a, 0x1f, 0x41, 0x75, 0x74, 0x68, 0x65, 0x6e, 0x74, 0x69, 0x63, 0x61, 0x74, 0x69, + 0x6f, 0x6e, 0x4d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x4f, 0x49, 0x44, 0x43, 0x53, 0x65, 0x72, 0x76, + 0x69, 0x63, 0x65, 0x12, 0x53, 0x0a, 0x0c, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x65, + 0x55, 0x52, 0x4c, 0x12, 0x1f, 0x2e, 0x66, 0x6c, 0x69, 0x70, 0x74, 0x2e, 0x61, 0x75, 0x74, 0x68, + 0x2e, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x65, 0x55, 0x52, 0x4c, 0x52, 0x65, 0x71, + 0x75, 0x65, 0x73, 0x74, 0x1a, 0x20, 0x2e, 0x66, 0x6c, 0x69, 0x70, 0x74, 0x2e, 0x61, 0x75, 0x74, + 0x68, 0x2e, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x65, 0x55, 0x52, 0x4c, 0x52, 0x65, + 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x47, 0x0a, 0x08, 0x43, 0x61, 0x6c, 0x6c, + 0x62, 0x61, 0x63, 0x6b, 0x12, 0x1b, 0x2e, 0x66, 0x6c, 0x69, 0x70, 0x74, 0x2e, 0x61, 0x75, 0x74, + 0x68, 0x2e, 0x43, 0x61, 0x6c, 0x6c, 0x62, 0x61, 0x63, 0x6b, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, + 0x74, 0x1a, 0x1c, 0x2e, 0x66, 0x6c, 0x69, 0x70, 0x74, 0x2e, 0x61, 0x75, 0x74, 0x68, 0x2e, 0x43, + 0x61, 0x6c, 0x6c, 0x62, 0x61, 0x63, 0x6b, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, + 0x00, 0x32, 0x94, 0x01, 0x0a, 0x25, 0x41, 0x75, 0x74, 0x68, 0x65, 0x6e, 0x74, 0x69, 0x63, 0x61, + 0x74, 0x69, 0x6f, 0x6e, 0x4d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x4b, 0x75, 0x62, 0x65, 0x72, 0x6e, + 0x65, 0x74, 0x65, 0x73, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x6b, 0x0a, 0x14, 0x56, + 0x65, 0x72, 0x69, 0x66, 0x79, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x41, 0x63, 0x63, 0x6f, + 0x75, 0x6e, 0x74, 0x12, 0x27, 0x2e, 0x66, 0x6c, 0x69, 0x70, 0x74, 0x2e, 0x61, 0x75, 0x74, 0x68, + 0x2e, 0x56, 0x65, 0x72, 0x69, 0x66, 0x79, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x41, 0x63, + 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x28, 0x2e, 0x66, + 0x6c, 0x69, 0x70, 0x74, 0x2e, 0x61, 0x75, 0x74, 0x68, 0x2e, 0x56, 0x65, 0x72, 0x69, 0x66, 0x79, + 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x52, 0x65, + 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x32, 0xc1, 0x01, 0x0a, 0x21, 0x41, 0x75, 0x74, + 0x68, 0x65, 0x6e, 0x74, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x4d, 0x65, 0x74, 0x68, 0x6f, + 0x64, 0x47, 0x69, 0x74, 0x68, 0x75, 0x62, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x53, + 0x0a, 0x0c, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x65, 0x55, 0x52, 0x4c, 0x12, 0x1f, + 0x2e, 0x66, 0x6c, 0x69, 0x70, 0x74, 0x2e, 0x61, 0x75, 0x74, 0x68, 0x2e, 0x41, 0x75, 0x74, 0x68, + 0x6f, 0x72, 0x69, 0x7a, 0x65, 0x55, 0x52, 0x4c, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, + 0x20, 0x2e, 0x66, 0x6c, 0x69, 0x70, 0x74, 0x2e, 0x61, 0x75, 0x74, 0x68, 0x2e, 0x41, 0x75, 0x74, + 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x65, 0x55, 0x52, 0x4c, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, + 0x65, 0x22, 0x00, 0x12, 0x47, 0x0a, 0x08, 0x43, 0x61, 0x6c, 0x6c, 0x62, 0x61, 0x63, 0x6b, 0x12, + 0x1b, 0x2e, 0x66, 0x6c, 0x69, 0x70, 0x74, 0x2e, 0x61, 0x75, 0x74, 0x68, 0x2e, 0x43, 0x61, 0x6c, + 0x6c, 0x62, 0x61, 0x63, 0x6b, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1c, 0x2e, 0x66, + 0x6c, 0x69, 0x70, 0x74, 0x2e, 0x61, 0x75, 0x74, 0x68, 0x2e, 0x43, 0x61, 0x6c, 0x6c, 0x62, 0x61, + 0x63, 0x6b, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x42, 0x22, 0x5a, 0x20, + 0x67, 0x6f, 0x2e, 0x66, 0x6c, 0x69, 0x70, 0x74, 0x2e, 0x69, 0x6f, 0x2f, 0x66, 0x6c, 0x69, 0x70, + 0x74, 0x2f, 0x72, 0x70, 0x63, 0x2f, 0x66, 0x6c, 0x69, 0x70, 0x74, 0x2f, 0x61, 0x75, 0x74, 0x68, + 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( @@ -1254,18 +1270,22 @@ var file_auth_auth_proto_depIdxs = []int32{ 11, // 22: flipt.auth.AuthenticationMethodOIDCService.AuthorizeURL:input_type -> flipt.auth.AuthorizeURLRequest 13, // 23: flipt.auth.AuthenticationMethodOIDCService.Callback:input_type -> flipt.auth.CallbackRequest 15, // 24: flipt.auth.AuthenticationMethodKubernetesService.VerifyServiceAccount:input_type -> flipt.auth.VerifyServiceAccountRequest - 2, // 25: flipt.auth.PublicAuthenticationService.ListAuthenticationMethods:output_type -> flipt.auth.ListAuthenticationMethodsResponse - 3, // 26: flipt.auth.AuthenticationService.GetAuthenticationSelf:output_type -> flipt.auth.Authentication - 3, // 27: flipt.auth.AuthenticationService.GetAuthentication:output_type -> flipt.auth.Authentication - 6, // 28: flipt.auth.AuthenticationService.ListAuthentications:output_type -> flipt.auth.ListAuthenticationsResponse - 20, // 29: flipt.auth.AuthenticationService.DeleteAuthentication:output_type -> google.protobuf.Empty - 20, // 30: flipt.auth.AuthenticationService.ExpireAuthenticationSelf:output_type -> google.protobuf.Empty - 10, // 31: flipt.auth.AuthenticationMethodTokenService.CreateToken:output_type -> flipt.auth.CreateTokenResponse - 12, // 32: flipt.auth.AuthenticationMethodOIDCService.AuthorizeURL:output_type -> flipt.auth.AuthorizeURLResponse - 14, // 33: flipt.auth.AuthenticationMethodOIDCService.Callback:output_type -> flipt.auth.CallbackResponse - 16, // 34: flipt.auth.AuthenticationMethodKubernetesService.VerifyServiceAccount:output_type -> flipt.auth.VerifyServiceAccountResponse - 25, // [25:35] is the sub-list for method output_type - 15, // [15:25] is the sub-list for method input_type + 11, // 25: flipt.auth.AuthenticationMethodGithubService.AuthorizeURL:input_type -> flipt.auth.AuthorizeURLRequest + 13, // 26: flipt.auth.AuthenticationMethodGithubService.Callback:input_type -> flipt.auth.CallbackRequest + 2, // 27: flipt.auth.PublicAuthenticationService.ListAuthenticationMethods:output_type -> flipt.auth.ListAuthenticationMethodsResponse + 3, // 28: flipt.auth.AuthenticationService.GetAuthenticationSelf:output_type -> flipt.auth.Authentication + 3, // 29: flipt.auth.AuthenticationService.GetAuthentication:output_type -> flipt.auth.Authentication + 6, // 30: flipt.auth.AuthenticationService.ListAuthentications:output_type -> flipt.auth.ListAuthenticationsResponse + 20, // 31: flipt.auth.AuthenticationService.DeleteAuthentication:output_type -> google.protobuf.Empty + 20, // 32: flipt.auth.AuthenticationService.ExpireAuthenticationSelf:output_type -> google.protobuf.Empty + 10, // 33: flipt.auth.AuthenticationMethodTokenService.CreateToken:output_type -> flipt.auth.CreateTokenResponse + 12, // 34: flipt.auth.AuthenticationMethodOIDCService.AuthorizeURL:output_type -> flipt.auth.AuthorizeURLResponse + 14, // 35: flipt.auth.AuthenticationMethodOIDCService.Callback:output_type -> flipt.auth.CallbackResponse + 16, // 36: flipt.auth.AuthenticationMethodKubernetesService.VerifyServiceAccount:output_type -> flipt.auth.VerifyServiceAccountResponse + 12, // 37: flipt.auth.AuthenticationMethodGithubService.AuthorizeURL:output_type -> flipt.auth.AuthorizeURLResponse + 14, // 38: flipt.auth.AuthenticationMethodGithubService.Callback:output_type -> flipt.auth.CallbackResponse + 27, // [27:39] is the sub-list for method output_type + 15, // [15:27] is the sub-list for method input_type 15, // [15:15] is the sub-list for extension type_name 15, // [15:15] is the sub-list for extension extendee 0, // [0:15] is the sub-list for field type_name @@ -1479,7 +1499,7 @@ func file_auth_auth_proto_init() { NumEnums: 1, NumMessages: 17, NumExtensions: 0, - NumServices: 5, + NumServices: 6, }, GoTypes: file_auth_auth_proto_goTypes, DependencyIndexes: file_auth_auth_proto_depIdxs, diff --git a/rpc/flipt/auth/auth.pb.gw.go b/rpc/flipt/auth/auth.pb.gw.go index c47f93fa73..8f4b2a4f42 100644 --- a/rpc/flipt/auth/auth.pb.gw.go +++ b/rpc/flipt/auth/auth.pb.gw.go @@ -452,6 +452,78 @@ func local_request_AuthenticationMethodKubernetesService_VerifyServiceAccount_0( } +var ( + filter_AuthenticationMethodGithubService_AuthorizeURL_0 = &utilities.DoubleArray{Encoding: map[string]int{}, Base: []int(nil), Check: []int(nil)} +) + +func request_AuthenticationMethodGithubService_AuthorizeURL_0(ctx context.Context, marshaler runtime.Marshaler, client AuthenticationMethodGithubServiceClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq AuthorizeURLRequest + var metadata runtime.ServerMetadata + + if err := req.ParseForm(); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_AuthenticationMethodGithubService_AuthorizeURL_0); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + + msg, err := client.AuthorizeURL(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + return msg, metadata, err + +} + +func local_request_AuthenticationMethodGithubService_AuthorizeURL_0(ctx context.Context, marshaler runtime.Marshaler, server AuthenticationMethodGithubServiceServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq AuthorizeURLRequest + var metadata runtime.ServerMetadata + + if err := req.ParseForm(); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_AuthenticationMethodGithubService_AuthorizeURL_0); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + + msg, err := server.AuthorizeURL(ctx, &protoReq) + return msg, metadata, err + +} + +var ( + filter_AuthenticationMethodGithubService_Callback_0 = &utilities.DoubleArray{Encoding: map[string]int{}, Base: []int(nil), Check: []int(nil)} +) + +func request_AuthenticationMethodGithubService_Callback_0(ctx context.Context, marshaler runtime.Marshaler, client AuthenticationMethodGithubServiceClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq CallbackRequest + var metadata runtime.ServerMetadata + + if err := req.ParseForm(); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_AuthenticationMethodGithubService_Callback_0); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + + msg, err := client.Callback(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + return msg, metadata, err + +} + +func local_request_AuthenticationMethodGithubService_Callback_0(ctx context.Context, marshaler runtime.Marshaler, server AuthenticationMethodGithubServiceServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq CallbackRequest + var metadata runtime.ServerMetadata + + if err := req.ParseForm(); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_AuthenticationMethodGithubService_Callback_0); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + + msg, err := server.Callback(ctx, &protoReq) + return msg, metadata, err + +} + // RegisterPublicAuthenticationServiceHandlerServer registers the http handlers for service PublicAuthenticationService to "mux". // UnaryRPC :call PublicAuthenticationServiceServer directly. // StreamingRPC :currently unsupported pending https://github.com/grpc/grpc-go/issues/906. @@ -747,6 +819,65 @@ func RegisterAuthenticationMethodKubernetesServiceHandlerServer(ctx context.Cont return nil } +// RegisterAuthenticationMethodGithubServiceHandlerServer registers the http handlers for service AuthenticationMethodGithubService to "mux". +// UnaryRPC :call AuthenticationMethodGithubServiceServer directly. +// StreamingRPC :currently unsupported pending https://github.com/grpc/grpc-go/issues/906. +// Note that using this registration option will cause many gRPC library features to stop working. Consider using RegisterAuthenticationMethodGithubServiceHandlerFromEndpoint instead. +func RegisterAuthenticationMethodGithubServiceHandlerServer(ctx context.Context, mux *runtime.ServeMux, server AuthenticationMethodGithubServiceServer) error { + + mux.Handle("GET", pattern_AuthenticationMethodGithubService_AuthorizeURL_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + var stream runtime.ServerTransportStream + ctx = grpc.NewContextWithServerTransportStream(ctx, &stream) + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + var err error + var annotatedContext context.Context + annotatedContext, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/flipt.auth.AuthenticationMethodGithubService/AuthorizeURL", runtime.WithHTTPPathPattern("/auth/v1/method/github/authorize")) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := local_request_AuthenticationMethodGithubService_AuthorizeURL_0(annotatedContext, inboundMarshaler, server, req, pathParams) + md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer()) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) + if err != nil { + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) + return + } + + forward_AuthenticationMethodGithubService_AuthorizeURL_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("GET", pattern_AuthenticationMethodGithubService_Callback_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + var stream runtime.ServerTransportStream + ctx = grpc.NewContextWithServerTransportStream(ctx, &stream) + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + var err error + var annotatedContext context.Context + annotatedContext, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/flipt.auth.AuthenticationMethodGithubService/Callback", runtime.WithHTTPPathPattern("/auth/v1/method/github/callback")) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := local_request_AuthenticationMethodGithubService_Callback_0(annotatedContext, inboundMarshaler, server, req, pathParams) + md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer()) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) + if err != nil { + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) + return + } + + forward_AuthenticationMethodGithubService_Callback_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + return nil +} + // RegisterPublicAuthenticationServiceHandlerFromEndpoint is same as RegisterPublicAuthenticationServiceHandler but // automatically dials to "endpoint" and closes the connection when "ctx" gets done. func RegisterPublicAuthenticationServiceHandlerFromEndpoint(ctx context.Context, mux *runtime.ServeMux, endpoint string, opts []grpc.DialOption) (err error) { @@ -1231,3 +1362,100 @@ var ( var ( forward_AuthenticationMethodKubernetesService_VerifyServiceAccount_0 = runtime.ForwardResponseMessage ) + +// RegisterAuthenticationMethodGithubServiceHandlerFromEndpoint is same as RegisterAuthenticationMethodGithubServiceHandler but +// automatically dials to "endpoint" and closes the connection when "ctx" gets done. +func RegisterAuthenticationMethodGithubServiceHandlerFromEndpoint(ctx context.Context, mux *runtime.ServeMux, endpoint string, opts []grpc.DialOption) (err error) { + conn, err := grpc.DialContext(ctx, endpoint, opts...) + if err != nil { + return err + } + defer func() { + if err != nil { + if cerr := conn.Close(); cerr != nil { + grpclog.Infof("Failed to close conn to %s: %v", endpoint, cerr) + } + return + } + go func() { + <-ctx.Done() + if cerr := conn.Close(); cerr != nil { + grpclog.Infof("Failed to close conn to %s: %v", endpoint, cerr) + } + }() + }() + + return RegisterAuthenticationMethodGithubServiceHandler(ctx, mux, conn) +} + +// RegisterAuthenticationMethodGithubServiceHandler registers the http handlers for service AuthenticationMethodGithubService to "mux". +// The handlers forward requests to the grpc endpoint over "conn". +func RegisterAuthenticationMethodGithubServiceHandler(ctx context.Context, mux *runtime.ServeMux, conn *grpc.ClientConn) error { + return RegisterAuthenticationMethodGithubServiceHandlerClient(ctx, mux, NewAuthenticationMethodGithubServiceClient(conn)) +} + +// RegisterAuthenticationMethodGithubServiceHandlerClient registers the http handlers for service AuthenticationMethodGithubService +// to "mux". The handlers forward requests to the grpc endpoint over the given implementation of "AuthenticationMethodGithubServiceClient". +// Note: the gRPC framework executes interceptors within the gRPC handler. If the passed in "AuthenticationMethodGithubServiceClient" +// doesn't go through the normal gRPC flow (creating a gRPC client etc.) then it will be up to the passed in +// "AuthenticationMethodGithubServiceClient" to call the correct interceptors. +func RegisterAuthenticationMethodGithubServiceHandlerClient(ctx context.Context, mux *runtime.ServeMux, client AuthenticationMethodGithubServiceClient) error { + + mux.Handle("GET", pattern_AuthenticationMethodGithubService_AuthorizeURL_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + var err error + var annotatedContext context.Context + annotatedContext, err = runtime.AnnotateContext(ctx, mux, req, "/flipt.auth.AuthenticationMethodGithubService/AuthorizeURL", runtime.WithHTTPPathPattern("/auth/v1/method/github/authorize")) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := request_AuthenticationMethodGithubService_AuthorizeURL_0(annotatedContext, inboundMarshaler, client, req, pathParams) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) + if err != nil { + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) + return + } + + forward_AuthenticationMethodGithubService_AuthorizeURL_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("GET", pattern_AuthenticationMethodGithubService_Callback_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + var err error + var annotatedContext context.Context + annotatedContext, err = runtime.AnnotateContext(ctx, mux, req, "/flipt.auth.AuthenticationMethodGithubService/Callback", runtime.WithHTTPPathPattern("/auth/v1/method/github/callback")) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := request_AuthenticationMethodGithubService_Callback_0(annotatedContext, inboundMarshaler, client, req, pathParams) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) + if err != nil { + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) + return + } + + forward_AuthenticationMethodGithubService_Callback_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + return nil +} + +var ( + pattern_AuthenticationMethodGithubService_AuthorizeURL_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3, 2, 4}, []string{"auth", "v1", "method", "github", "authorize"}, "")) + + pattern_AuthenticationMethodGithubService_Callback_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3, 2, 4}, []string{"auth", "v1", "method", "github", "callback"}, "")) +) + +var ( + forward_AuthenticationMethodGithubService_AuthorizeURL_0 = runtime.ForwardResponseMessage + + forward_AuthenticationMethodGithubService_Callback_0 = runtime.ForwardResponseMessage +) diff --git a/rpc/flipt/auth/auth.proto b/rpc/flipt/auth/auth.proto index 4d17752946..293cbb7989 100644 --- a/rpc/flipt/auth/auth.proto +++ b/rpc/flipt/auth/auth.proto @@ -13,6 +13,7 @@ enum Method { METHOD_TOKEN = 1; METHOD_OIDC = 2; METHOD_KUBERNETES = 3; + METHOD_GITHUB = 4; } message MethodInfo { @@ -122,3 +123,8 @@ message VerifyServiceAccountResponse { service AuthenticationMethodKubernetesService { rpc VerifyServiceAccount(VerifyServiceAccountRequest) returns (VerifyServiceAccountResponse) {} } + +service AuthenticationMethodGithubService { + rpc AuthorizeURL(AuthorizeURLRequest) returns (AuthorizeURLResponse) {} + rpc Callback(CallbackRequest) returns (CallbackResponse) {} +} diff --git a/rpc/flipt/auth/auth_grpc.pb.go b/rpc/flipt/auth/auth_grpc.pb.go index 216097e77b..c7086660fb 100644 --- a/rpc/flipt/auth/auth_grpc.pb.go +++ b/rpc/flipt/auth/auth_grpc.pb.go @@ -657,3 +657,131 @@ var AuthenticationMethodKubernetesService_ServiceDesc = grpc.ServiceDesc{ Streams: []grpc.StreamDesc{}, Metadata: "auth/auth.proto", } + +const ( + AuthenticationMethodGithubService_AuthorizeURL_FullMethodName = "/flipt.auth.AuthenticationMethodGithubService/AuthorizeURL" + AuthenticationMethodGithubService_Callback_FullMethodName = "/flipt.auth.AuthenticationMethodGithubService/Callback" +) + +// AuthenticationMethodGithubServiceClient is the client API for AuthenticationMethodGithubService service. +// +// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream. +type AuthenticationMethodGithubServiceClient interface { + AuthorizeURL(ctx context.Context, in *AuthorizeURLRequest, opts ...grpc.CallOption) (*AuthorizeURLResponse, error) + Callback(ctx context.Context, in *CallbackRequest, opts ...grpc.CallOption) (*CallbackResponse, error) +} + +type authenticationMethodGithubServiceClient struct { + cc grpc.ClientConnInterface +} + +func NewAuthenticationMethodGithubServiceClient(cc grpc.ClientConnInterface) AuthenticationMethodGithubServiceClient { + return &authenticationMethodGithubServiceClient{cc} +} + +func (c *authenticationMethodGithubServiceClient) AuthorizeURL(ctx context.Context, in *AuthorizeURLRequest, opts ...grpc.CallOption) (*AuthorizeURLResponse, error) { + out := new(AuthorizeURLResponse) + err := c.cc.Invoke(ctx, AuthenticationMethodGithubService_AuthorizeURL_FullMethodName, in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *authenticationMethodGithubServiceClient) Callback(ctx context.Context, in *CallbackRequest, opts ...grpc.CallOption) (*CallbackResponse, error) { + out := new(CallbackResponse) + err := c.cc.Invoke(ctx, AuthenticationMethodGithubService_Callback_FullMethodName, in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +// AuthenticationMethodGithubServiceServer is the server API for AuthenticationMethodGithubService service. +// All implementations must embed UnimplementedAuthenticationMethodGithubServiceServer +// for forward compatibility +type AuthenticationMethodGithubServiceServer interface { + AuthorizeURL(context.Context, *AuthorizeURLRequest) (*AuthorizeURLResponse, error) + Callback(context.Context, *CallbackRequest) (*CallbackResponse, error) + mustEmbedUnimplementedAuthenticationMethodGithubServiceServer() +} + +// UnimplementedAuthenticationMethodGithubServiceServer must be embedded to have forward compatible implementations. +type UnimplementedAuthenticationMethodGithubServiceServer struct { +} + +func (UnimplementedAuthenticationMethodGithubServiceServer) AuthorizeURL(context.Context, *AuthorizeURLRequest) (*AuthorizeURLResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method AuthorizeURL not implemented") +} +func (UnimplementedAuthenticationMethodGithubServiceServer) Callback(context.Context, *CallbackRequest) (*CallbackResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method Callback not implemented") +} +func (UnimplementedAuthenticationMethodGithubServiceServer) mustEmbedUnimplementedAuthenticationMethodGithubServiceServer() { +} + +// UnsafeAuthenticationMethodGithubServiceServer may be embedded to opt out of forward compatibility for this service. +// Use of this interface is not recommended, as added methods to AuthenticationMethodGithubServiceServer will +// result in compilation errors. +type UnsafeAuthenticationMethodGithubServiceServer interface { + mustEmbedUnimplementedAuthenticationMethodGithubServiceServer() +} + +func RegisterAuthenticationMethodGithubServiceServer(s grpc.ServiceRegistrar, srv AuthenticationMethodGithubServiceServer) { + s.RegisterService(&AuthenticationMethodGithubService_ServiceDesc, srv) +} + +func _AuthenticationMethodGithubService_AuthorizeURL_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(AuthorizeURLRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(AuthenticationMethodGithubServiceServer).AuthorizeURL(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: AuthenticationMethodGithubService_AuthorizeURL_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(AuthenticationMethodGithubServiceServer).AuthorizeURL(ctx, req.(*AuthorizeURLRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _AuthenticationMethodGithubService_Callback_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(CallbackRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(AuthenticationMethodGithubServiceServer).Callback(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: AuthenticationMethodGithubService_Callback_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(AuthenticationMethodGithubServiceServer).Callback(ctx, req.(*CallbackRequest)) + } + return interceptor(ctx, in, info, handler) +} + +// AuthenticationMethodGithubService_ServiceDesc is the grpc.ServiceDesc for AuthenticationMethodGithubService service. +// It's only intended for direct use with grpc.RegisterService, +// and not to be introspected or modified (even as a copy) +var AuthenticationMethodGithubService_ServiceDesc = grpc.ServiceDesc{ + ServiceName: "flipt.auth.AuthenticationMethodGithubService", + HandlerType: (*AuthenticationMethodGithubServiceServer)(nil), + Methods: []grpc.MethodDesc{ + { + MethodName: "AuthorizeURL", + Handler: _AuthenticationMethodGithubService_AuthorizeURL_Handler, + }, + { + MethodName: "Callback", + Handler: _AuthenticationMethodGithubService_Callback_Handler, + }, + }, + Streams: []grpc.StreamDesc{}, + Metadata: "auth/auth.proto", +} diff --git a/rpc/flipt/flipt.yaml b/rpc/flipt/flipt.yaml index 3a40c84b5b..286692e1b8 100644 --- a/rpc/flipt/flipt.yaml +++ b/rpc/flipt/flipt.yaml @@ -306,3 +306,8 @@ http: - selector: flipt.auth.AuthenticationMethodKubernetesService.VerifyServiceAccount post: /auth/v1/method/kubernetes/serviceaccount body: "*" + # method: oauth + - selector: flipt.auth.AuthenticationMethodGithubService.AuthorizeURL + get: /auth/v1/method/github/authorize + - selector: flipt.auth.AuthenticationMethodGithubService.Callback + get: /auth/v1/method/github/callback diff --git a/sdk/go/auth.sdk.gen.go b/sdk/go/auth.sdk.gen.go index 9c51c51f66..39ee6bfcf9 100644 --- a/sdk/go/auth.sdk.gen.go +++ b/sdk/go/auth.sdk.gen.go @@ -14,6 +14,7 @@ type AuthClient interface { AuthenticationMethodTokenServiceClient() auth.AuthenticationMethodTokenServiceClient AuthenticationMethodOIDCServiceClient() auth.AuthenticationMethodOIDCServiceClient AuthenticationMethodKubernetesServiceClient() auth.AuthenticationMethodKubernetesServiceClient + AuthenticationMethodGithubServiceClient() auth.AuthenticationMethodGithubServiceClient } type Auth struct { @@ -157,3 +158,30 @@ func (x *AuthenticationMethodKubernetesService) VerifyServiceAccount(ctx context } return x.transport.VerifyServiceAccount(ctx, v) } + +type AuthenticationMethodGithubService struct { + transport auth.AuthenticationMethodGithubServiceClient + tokenProvider ClientTokenProvider +} + +func (s Auth) AuthenticationMethodGithubService() *AuthenticationMethodGithubService { + return &AuthenticationMethodGithubService{ + transport: s.transport.AuthenticationMethodGithubServiceClient(), + tokenProvider: s.tokenProvider, + } +} +func (x *AuthenticationMethodGithubService) AuthorizeURL(ctx context.Context, v *auth.AuthorizeURLRequest) (*auth.AuthorizeURLResponse, error) { + ctx, err := authenticate(ctx, x.tokenProvider) + if err != nil { + return nil, err + } + return x.transport.AuthorizeURL(ctx, v) +} + +func (x *AuthenticationMethodGithubService) Callback(ctx context.Context, v *auth.CallbackRequest) (*auth.CallbackResponse, error) { + ctx, err := authenticate(ctx, x.tokenProvider) + if err != nil { + return nil, err + } + return x.transport.Callback(ctx, v) +} diff --git a/sdk/go/grpc/grpc.sdk.gen.go b/sdk/go/grpc/grpc.sdk.gen.go index 982d3b0b1d..96f7cd0644 100644 --- a/sdk/go/grpc/grpc.sdk.gen.go +++ b/sdk/go/grpc/grpc.sdk.gen.go @@ -45,6 +45,10 @@ func (t authClient) AuthenticationMethodKubernetesServiceClient() auth.Authentic return auth.NewAuthenticationMethodKubernetesServiceClient(t.cc) } +func (t authClient) AuthenticationMethodGithubServiceClient() auth.AuthenticationMethodGithubServiceClient { + return auth.NewAuthenticationMethodGithubServiceClient(t.cc) +} + func (t Transport) AuthClient() _go.AuthClient { return authClient{cc: t.cc} } diff --git a/sdk/go/http/auth.sdk.gen.go b/sdk/go/http/auth.sdk.gen.go index 4ed53c2014..33753ec07b 100644 --- a/sdk/go/http/auth.sdk.gen.go +++ b/sdk/go/http/auth.sdk.gen.go @@ -368,6 +368,74 @@ func (x *authenticationMethodKubernetesServiceClient) VerifyServiceAccount(ctx c return &output, nil } +func (t authClient) AuthenticationMethodGithubServiceClient() auth.AuthenticationMethodGithubServiceClient { + return &authenticationMethodGithubServiceClient{client: t.client, addr: t.addr} +} + +type authenticationMethodGithubServiceClient struct { + client *http.Client + addr string +} + +func (x *authenticationMethodGithubServiceClient) AuthorizeURL(ctx context.Context, v *auth.AuthorizeURLRequest, _ ...grpc.CallOption) (*auth.AuthorizeURLResponse, error) { + var body io.Reader + values := url.Values{} + values.Set("provider", v.Provider) + values.Set("state", v.State) + req, err := http.NewRequestWithContext(ctx, http.MethodGet, x.addr+"/auth/v1/method/github/authorize", body) + if err != nil { + return nil, err + } + req.URL.RawQuery = values.Encode() + resp, err := x.client.Do(req) + if err != nil { + return nil, err + } + defer resp.Body.Close() + var output auth.AuthorizeURLResponse + respData, err := io.ReadAll(resp.Body) + if err != nil { + return nil, err + } + if err := checkResponse(resp, respData); err != nil { + return nil, err + } + if err := (protojson.UnmarshalOptions{DiscardUnknown: true}).Unmarshal(respData, &output); err != nil { + return nil, err + } + return &output, nil +} + +func (x *authenticationMethodGithubServiceClient) Callback(ctx context.Context, v *auth.CallbackRequest, _ ...grpc.CallOption) (*auth.CallbackResponse, error) { + var body io.Reader + values := url.Values{} + values.Set("provider", v.Provider) + values.Set("code", v.Code) + values.Set("state", v.State) + req, err := http.NewRequestWithContext(ctx, http.MethodGet, x.addr+"/auth/v1/method/github/callback", body) + if err != nil { + return nil, err + } + req.URL.RawQuery = values.Encode() + resp, err := x.client.Do(req) + if err != nil { + return nil, err + } + defer resp.Body.Close() + var output auth.CallbackResponse + respData, err := io.ReadAll(resp.Body) + if err != nil { + return nil, err + } + if err := checkResponse(resp, respData); err != nil { + return nil, err + } + if err := (protojson.UnmarshalOptions{DiscardUnknown: true}).Unmarshal(respData, &output); err != nil { + return nil, err + } + return &output, nil +} + func (t Transport) AuthClient() _go.AuthClient { return authClient{client: t.client, addr: t.addr} } diff --git a/ui/src/app/auth/Login.tsx b/ui/src/app/auth/Login.tsx index 21bf76b28d..a5b2548dd8 100644 --- a/ui/src/app/auth/Login.tsx +++ b/ui/src/app/auth/Login.tsx @@ -1,4 +1,5 @@ import { + faGithub, faGitlab, faGoogle, faOpenid @@ -14,6 +15,7 @@ import { listAuthMethods } from '~/data/api'; import { useError } from '~/data/hooks/error'; import { useSession } from '~/data/hooks/session'; import { IAuthMethod } from '~/types/Auth'; +import { IAuthMethodGithub } from '~/types/auth/Github'; import { IAuthMethodOIDC } from '~/types/auth/OIDC'; interface ILoginProvider { @@ -32,6 +34,10 @@ const knownProviders: Record = { }, auth0: { displayName: 'Auth0' + }, + github: { + displayName: 'Github', + icon: faGithub } }; @@ -77,12 +83,20 @@ function InnerLogin() { (m: IAuthMethod) => m.method === 'METHOD_OIDC' && m.enabled ) as IAuthMethodOIDC; - if (!authOIDC) { + const authGithub = resp.methods.find( + (m: IAuthMethod) => m.method === 'METHOD_GITHUB' && m.enabled + ) as IAuthMethodGithub; + + if (!authOIDC && !authGithub) { return; } - const loginProviders = Object.entries(authOIDC.metadata.providers).map( - ([k, v]) => { + let loginProviders: any[] = []; + + if (authOIDC) { + const oidcLoginProviders = Object.entries( + authOIDC.metadata.providers + ).map(([k, v]) => { k = toLower(k); return { name: knownProviders[k]?.displayName || upperFirst(k), // if we dont know the provider, just capitalize the first letter @@ -90,9 +104,25 @@ function InnerLogin() { callback_url: v.callback_url, icon: knownProviders[k]?.icon || faOpenid // if we dont know the provider icon, use the openid icon }; - } - ); - setProviders(loginProviders); + }); + + loginProviders = loginProviders.concat(oidcLoginProviders); + } + + if (authGithub) { + const githubLogin = [ + { + name: 'GitHub', + authorize_url: authGithub.metadata.authorize_url, + callback_url: authGithub.metadata.callback_url, + icon: faGithub + } + ]; + + loginProviders = loginProviders.concat(githubLogin); + } + + setProviders([...loginProviders]); } catch (err) { setError(err); } diff --git a/ui/src/components/Header.tsx b/ui/src/components/Header.tsx index 7a27305bae..a1ea2c7ece 100644 --- a/ui/src/components/Header.tsx +++ b/ui/src/components/Header.tsx @@ -70,10 +70,7 @@ export default function Header(props: HeaderProps) { {/* user profile */} {session && session.self && ( - + )} diff --git a/ui/src/components/SessionProvider.tsx b/ui/src/components/SessionProvider.tsx index 93e07e8f38..2e7d1f2cac 100644 --- a/ui/src/components/SessionProvider.tsx +++ b/ui/src/components/SessionProvider.tsx @@ -1,12 +1,13 @@ import { createContext, useEffect, useMemo } from 'react'; import { getAuthSelf, getInfo } from '~/data/api'; import { useLocalStorage } from '~/data/hooks/storage'; +import { IAuthGithubInternal } from '~/types/auth/Github'; import { IAuthOIDCInternal } from '~/types/auth/OIDC'; type Session = { required: boolean; authenticated: boolean; - self?: IAuthOIDCInternal; + self?: IAuthOIDCInternal | IAuthGithubInternal; }; interface SessionContextType { @@ -42,7 +43,7 @@ export default function SessionProvider({ } try { - const self = await getAuthSelf(); + const self: IAuthOIDCInternal | IAuthOIDCInternal = await getAuthSelf(); session = { authenticated: true, required: true, diff --git a/ui/src/components/header/UserProfile.tsx b/ui/src/components/header/UserProfile.tsx index cb81588ad6..1cc28266ec 100644 --- a/ui/src/components/header/UserProfile.tsx +++ b/ui/src/components/header/UserProfile.tsx @@ -4,19 +4,37 @@ import { Fragment } from 'react'; import { expireAuthSelf } from '~/data/api'; import { useError } from '~/data/hooks/error'; import { useSession } from '~/data/hooks/session'; +import { IAuthMethodGithubMetadata } from '~/types/auth/Github'; +import { IAuthMethodOIDCMetadata } from '~/types/auth/OIDC'; import { classNames } from '~/utils/helpers'; type UserProfileProps = { - name?: string; - imgURL?: string; + metadata?: IAuthMethodOIDCMetadata | IAuthMethodGithubMetadata; }; export default function UserProfile(props: UserProfileProps) { - const { name, imgURL } = props; + const { metadata } = props; const { setError } = useError(); const { clearSession } = useSession(); + let name: string | undefined; + let imgURL: string | undefined; + + if (metadata) { + if ('io.flipt.auth.github.name' in metadata) { + name = metadata['io.flipt.auth.github.name'] ?? 'User'; + if (metadata['io.flipt.auth.github.picture']) { + imgURL = metadata['io.flipt.auth.github.picture']; + } + } else if ('io.flipt.auth.oidc.name' in metadata) { + name = metadata['io.flipt.auth.oidc.name'] ?? 'User'; + if (metadata['io.flipt.auth.oidc.picture']) { + imgURL = metadata['io.flipt.auth.oidc.picture']; + } + } + } + const logout = async () => { expireAuthSelf() .then(() => { @@ -37,8 +55,8 @@ export default function UserProfile(props: UserProfileProps) { {name )} diff --git a/ui/src/types/Auth.ts b/ui/src/types/Auth.ts index 0ede14467b..0e1ef0f13f 100644 --- a/ui/src/types/Auth.ts +++ b/ui/src/types/Auth.ts @@ -1,5 +1,5 @@ export interface IAuthMethod { - method: 'METHOD_TOKEN' | 'METHOD_OIDC'; + method: 'METHOD_TOKEN' | 'METHOD_OIDC' | 'METHOD_GITHUB'; enabled: boolean; sessionCompatible: boolean; metadata: { [key: string]: any }; diff --git a/ui/src/types/auth/Github.ts b/ui/src/types/auth/Github.ts new file mode 100644 index 0000000000..ceeee070c6 --- /dev/null +++ b/ui/src/types/auth/Github.ts @@ -0,0 +1,19 @@ +import { IAuth, IAuthMethod } from '~/types/Auth'; + +export interface IAuthMethodGithub extends IAuthMethod { + method: 'METHOD_GITHUB'; + metadata: { + authorize_url: string; + callback_url: string; + }; +} + +export interface IAuthMethodGithubMetadata { + 'io.flipt.auth.github.email'?: string; + 'io.flipt.auth.github.name'?: string; + 'io.flipt.auth.github.picture'?: string; +} + +export interface IAuthGithubInternal extends IAuth { + metadata: IAuthMethodGithubMetadata; +}