Add Google Calendar OAuth connect and disconnect
GET /api/v1/integrations/google/connect sends the browser to Google's consent screen (calendar.events and calendar.calendarlist.readonly, access_type=offline, prompt=consent, PKCE S256) with a state carried in a signed __Host-mealquest_google flow cookie bound to the session. GET /api/v1/integrations/google/callback checks the state and session, exchanges the code, refuses a response without a refresh token (reconnect_required) or without both scopes, seals the refresh token with secretbox into integrations_google and sets connected_at. DELETE /api/v1/integrations/google revokes the token at Google and clears it; a revoke failure keeps the connection so it can be retried. - internal/gcal: the connect service; Google endpoints are overridable and internal/gcal/gcaltest fakes the token and revoke endpoints. - internal/auth: the login cookie becomes the exported FlowCookie both flows use (same wire format); CheckRedirect and ExchangeError are exported for reuse. - internal/config: MEALQUEST_GOOGLE_CLIENT_ID and MEALQUEST_GOOGLE_CLIENT_SECRET_FILE, required at boot. - DeleteIntegrationsGoogle query (sqlc regenerated); no migration, the integrations_google table already exists. - deploy: compose mounts google_client_secret; .env.example and RUNBOOK document the Google OAuth client, its redirect URI and rotation. Maestro task 1e9d7850-3a09-4572-b8a1-cb18cd4774cb. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -29,6 +29,9 @@ const (
|
||||
EnvOIDCClientSecretFile = "MEALQUEST_OIDC_CLIENT_SECRET_FILE"
|
||||
EnvOIDCAllowedGroups = "MEALQUEST_OIDC_ALLOWED_GROUPS"
|
||||
|
||||
EnvGoogleClientID = "MEALQUEST_GOOGLE_CLIENT_ID"
|
||||
EnvGoogleClientSecretFile = "MEALQUEST_GOOGLE_CLIENT_SECRET_FILE"
|
||||
|
||||
EnvSessionIdle = "MEALQUEST_SESSION_IDLE"
|
||||
EnvSessionMax = "MEALQUEST_SESSION_MAX"
|
||||
|
||||
@@ -61,6 +64,12 @@ type Config struct {
|
||||
// OIDCAllowedGroups lists the groups whose members may sign in.
|
||||
OIDCAllowedGroups []string
|
||||
|
||||
// GoogleClientID and GoogleClientSecret are the Google OAuth web client
|
||||
// Push to Calendar connects with. Its redirect URI is PublicURL's origin
|
||||
// followed by /api/v1/integrations/google/callback.
|
||||
GoogleClientID string
|
||||
GoogleClientSecret Secret
|
||||
|
||||
// SessionIdle ends a session left unused this long.
|
||||
SessionIdle time.Duration
|
||||
// SessionMax ends every session this long after sign-in, used or not.
|
||||
@@ -110,6 +119,9 @@ func load(lookup func(string) (string, bool)) (Config, error) {
|
||||
OIDCClientSecret: r.secretFile(EnvOIDCClientSecretFile),
|
||||
OIDCAllowedGroups: r.list(EnvOIDCAllowedGroups),
|
||||
|
||||
GoogleClientID: r.str(EnvGoogleClientID),
|
||||
GoogleClientSecret: r.secretFile(EnvGoogleClientSecretFile),
|
||||
|
||||
SessionIdle: r.sessionDuration(EnvSessionIdle, DefaultSessionIdle),
|
||||
SessionMax: r.sessionDuration(EnvSessionMax, DefaultSessionMax),
|
||||
|
||||
|
||||
Reference in New Issue
Block a user