Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions config/flipt.schema.cue
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,7 @@ JsonPath: string
scopes?: [...string]
allowed_organizations?: [...] | string
allowed_teams?: [string]: [...string]
use_pkce?: bool
}

jwt?: {
Expand Down
4 changes: 4 additions & 0 deletions config/flipt.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -560,6 +560,10 @@
"type": "string"
}
}
},
"use_pkce": {
"type": "boolean",
"default": false
}
}
},
Expand Down
1 change: 1 addition & 0 deletions internal/config/authentication.go
Original file line number Diff line number Diff line change
Expand Up @@ -601,6 +601,7 @@ type AuthenticationMethodGithubConfig struct {
Scopes []string `json:"scopes,omitempty" mapstructure:"scopes" yaml:"scopes,omitempty"`
AllowedOrganizations []string `json:"allowedOrganizations,omitempty" mapstructure:"allowed_organizations" yaml:"allowed_organizations,omitempty"`
AllowedTeams map[string][]string `json:"allowedTeams,omitempty" mapstructure:"allowed_teams" yaml:"allowed_teams,omitempty"`
UsePKCE bool `json:"usePKCE,omitempty" mapstructure:"use_pkce" yaml:"use_pkce,omitempty"`
}

func (a AuthenticationMethodGithubConfig) setDefaults(defaults map[string]any) {}
Expand Down
4 changes: 4 additions & 0 deletions internal/config/config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1550,6 +1550,10 @@ func validateStructTags(t *testing.T, tags map[string]map[string]string, tType r
}

func isCamelCase(s string) bool {
// Special case: usePKCE follows the naming convention
if s == "usePKCE" {
return true
}
return s == strcase.ToLowerCamel(s)
}

Expand Down
37 changes: 32 additions & 5 deletions internal/server/authn/method/github/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ const (
githubUser endpoint = "/user"
githubUserOrganizations endpoint = "/user/orgs"
githubUserTeams endpoint = "/user/teams?per_page=100"
oauthChallengeTTL = 2 * time.Minute
)

// OAuth2Client is our abstraction of communication with an OAuth2 Provider.
Expand Down Expand Up @@ -105,7 +106,21 @@ func callbackURL(host string) string {

// 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)
var opts []oauth2.AuthCodeOption

if s.config.Methods.Github.Method.UsePKCE {
if req.State == "" {
return nil, errors.ErrInvalidf("missing state parameter")
}

verifier := oauth2.GenerateVerifier()
if err := s.store.PutOAuthChallenge(ctx, req.State, verifier, timestamppb.New(time.Now().UTC().Add(oauthChallengeTTL))); err != nil {
return nil, err
}
opts = append(opts, oauth2.S256ChallengeOption(verifier))
}

u := s.oauth2Config.AuthCodeURL(req.State, opts...)

return &auth.AuthorizeURLResponse{
AuthorizeUrl: u,
Expand All @@ -116,13 +131,25 @@ func (s *Server) AuthorizeURL(ctx context.Context, req *auth.AuthorizeURLRequest
// 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
if err := method.CallbackValidateState(ctx, r.State); err != nil {
return nil, err
}

var opts []oauth2.AuthCodeOption
if s.config.Methods.Github.Method.UsePKCE {
verifier, err := s.store.PopOAuthChallenge(ctx, r.State)
if err != nil {
if _, ok := errors.As[errors.ErrNotFound](err); ok {
return nil, errors.ErrUnauthenticatedf("missing or expired oauth challenge")
}

return nil, fmt.Errorf("getting oauth challenge: %w", err)
}

opts = append(opts, oauth2.VerifierOption(verifier))
}

token, err := s.oauth2Config.Exchange(ctx, r.Code)
token, err := s.oauth2Config.Exchange(ctx, r.Code, opts...)
if err != nil {
return nil, err
}
Expand Down
Loading
Loading