Merge "services/identity: Force the OAuth approval prompt when seeking blessings."
diff --git a/services/identity/internal/oauth/googleoauth.go b/services/identity/internal/oauth/googleoauth.go
index ca8303f..c8898cf 100644
--- a/services/identity/internal/oauth/googleoauth.go
+++ b/services/identity/internal/oauth/googleoauth.go
@@ -41,8 +41,12 @@
 	}, nil
 }
 
-func (g *googleOAuth) AuthURL(redirectUrl, state string) string {
-	return g.oauthConfig(redirectUrl).AuthCodeURL(state)
+func (g *googleOAuth) AuthURL(redirectUrl, state string, approval AuthURLApproval) string {
+	var opts []oauth2.AuthCodeOption
+	if approval == ExplicitApproval {
+		opts = append(opts, oauth2.ApprovalForce)
+	}
+	return g.oauthConfig(redirectUrl).AuthCodeURL(state, opts...)
 }
 
 // ExchangeAuthCodeForEmail exchanges the authorization code (which must
diff --git a/services/identity/internal/oauth/handler.go b/services/identity/internal/oauth/handler.go
index 6dddbf5..bea1fae 100644
--- a/services/identity/internal/oauth/handler.go
+++ b/services/identity/internal/oauth/handler.go
@@ -146,7 +146,7 @@
 		util.HTTPServerError(w, fmt.Errorf("failed to create new token: %v", err))
 		return
 	}
-	http.Redirect(w, r, h.args.OAuthProvider.AuthURL(redirectURL(h.args.Addr, listBlessingsCallbackRoute), csrf), http.StatusFound)
+	http.Redirect(w, r, h.args.OAuthProvider.AuthURL(redirectURL(h.args.Addr, listBlessingsCallbackRoute), csrf, ReuseApproval), http.StatusFound)
 }
 
 func (h *handler) listBlessingsCallback(w http.ResponseWriter, r *http.Request) {
@@ -344,7 +344,7 @@
 		util.HTTPServerError(w, fmt.Errorf("failed to create new token: %v", err))
 		return
 	}
-	http.Redirect(w, r, h.args.OAuthProvider.AuthURL(redirectURL(h.args.Addr, addCaveatsRoute), outputMacaroon), http.StatusFound)
+	http.Redirect(w, r, h.args.OAuthProvider.AuthURL(redirectURL(h.args.Addr, addCaveatsRoute), outputMacaroon, ExplicitApproval), http.StatusFound)
 }
 
 type addCaveatsMacaroon struct {
diff --git a/services/identity/internal/oauth/mockoauth.go b/services/identity/internal/oauth/mockoauth.go
index dd705be..c122af7 100644
--- a/services/identity/internal/oauth/mockoauth.go
+++ b/services/identity/internal/oauth/mockoauth.go
@@ -16,7 +16,7 @@
 	return &mockOAuth{}
 }
 
-func (m *mockOAuth) AuthURL(redirectUrl string, state string) string {
+func (m *mockOAuth) AuthURL(redirectUrl string, state string, _ AuthURLApproval) string {
 	return redirectUrl + "?state=" + state
 }
 
diff --git a/services/identity/internal/oauth/oauth_provider.go b/services/identity/internal/oauth/oauth_provider.go
index 3597810..8ba6239 100644
--- a/services/identity/internal/oauth/oauth_provider.go
+++ b/services/identity/internal/oauth/oauth_provider.go
@@ -12,11 +12,19 @@
 	ClientID string
 }
 
+// Option to OAuthProvider.AuthURL controlling whether previously provided user consent can be re-used.
+type AuthURLApproval bool
+
+const (
+	ExplicitApproval AuthURLApproval = false // Require explicit user consent.
+	ReuseApproval    AuthURLApproval = true  // Reuse a previous user consent if possible.
+)
+
 // OAuthProvider authenticates users to the identity server via the OAuth2 Web Server flow.
 type OAuthProvider interface {
 	// AuthURL is the URL the user must visit in order to authenticate with the OAuthProvider.
 	// After authentication, the user will be re-directed to redirectURL with the provided state.
-	AuthURL(redirectUrl string, state string) (url string)
+	AuthURL(redirectUrl string, state string, approval AuthURLApproval) (url string)
 	// ExchangeAuthCodeForEmail exchanges the provided authCode for the email of the
 	// authenticated user on behalf of the token has been issued.
 	ExchangeAuthCodeForEmail(authCode string, url string) (email string, err error)