diff --git a/internal/handler/oauth.go b/internal/handler/oauth.go index 1fc8fbf..bc94a73 100644 --- a/internal/handler/oauth.go +++ b/internal/handler/oauth.go @@ -87,7 +87,7 @@ func (h *OAuthHandler) Start(w http.ResponseWriter, r *http.Request) { }) cfg := prov.Config(h.callbackURL(providerName)) - authURL := cfg.AuthCodeURL(state) + authURL := cfg.AuthCodeURL(state, prov.AuthCodeOptions()...) http.Redirect(w, r, authURL, http.StatusFound) } diff --git a/pkg/oauth/oauth.go b/pkg/oauth/oauth.go index a6bd483..bb4554e 100644 --- a/pkg/oauth/oauth.go +++ b/pkg/oauth/oauth.go @@ -42,6 +42,12 @@ type Profile struct { type Provider interface { Name() string Config(redirectURL string) *oauth2.Config + // AuthCodeOptions returns extra parameters appended to AuthCodeURL. + // Used by Facebook to set `auth_type=rerequest` so a user who + // previously declined the email permission gets re-prompted on the + // next sign-in attempt (Facebook silently re-uses prior declines + // otherwise). Most providers return nil. + AuthCodeOptions() []oauth2.AuthCodeOption FetchProfile(ctx context.Context, tok *oauth2.Token) (*Profile, error) } @@ -102,6 +108,8 @@ func (g *google) Config(redirectURL string) *oauth2.Config { } } +func (g *google) AuthCodeOptions() []oauth2.AuthCodeOption { return nil } + // googleUserinfo is the subset of the v3 userinfo response we read. The // full payload has more fields (locale, given_name, family_name) we don't // currently need. @@ -155,6 +163,16 @@ func (f *facebook) Config(redirectURL string) *oauth2.Config { } } +// AuthCodeOptions: `auth_type=rerequest` forces Facebook to re-prompt for +// any permissions the user previously declined (specifically `email`). +// Without this, a user who unchecked Email on their first attempt would +// be stuck — Facebook silently re-grants the old decline forever. +func (f *facebook) AuthCodeOptions() []oauth2.AuthCodeOption { + return []oauth2.AuthCodeOption{ + oauth2.SetAuthURLParam("auth_type", "rerequest"), + } +} + // facebookMe captures the fields requested via ?fields=id,email,name,picture. // picture is an envelope: { data: { url, ... } }. type facebookMe struct {