feat(web): web-initiated YouTube OAuth connect flow (ADR-006)
Add GET /oauth/youtube/connect and /oauth/youtube/callback, mounted inside the login + registration guard so CurrentUserID is always set and every connection binds to the authenticated tapir user. - connect: generate a per-user CSRF state (single-use, short TTL, bound to the user), redirect to Google consent with access_type=offline and prompt=consent so a refresh token comes back. - callback: verify the state belongs to this user, exchange the code via the existing auth.Exchange, persist the refresh token under a PER-USER ref (web.YouTubeTokenRef = "youtube/<userID>/refresh_token") so tenants never collide, then UpsertConnection (provider=youtube, status=active). Any failure renders a clean error page and leaves no half-written state. Reuses auth.Exchange and adds auth.AuthCodeURL (offline + consent) rather than the CLI's listener/terminal flow (ADR-006: web flow, not CLI). The ConnectHandler depends on a narrow web.Connections port, not the concrete store. Wired in cmdServe only when YT client credentials are present; TAPIR_YT_CONNECT_REDIRECT_URL configures the callback URL. Per-user token-ref scheme documented in docs/homelab-integration.md. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -72,6 +72,17 @@ func oauthConfig(c Config) *oauth2.Config {
|
||||
}
|
||||
}
|
||||
|
||||
// AuthCodeURL builds the provider consent URL the web connect flow redirects to
|
||||
// (internal/web). It reuses oauthConfig and pins access_type=offline + prompt=
|
||||
// consent so Google returns a refresh token even on a repeat authorization —
|
||||
// without one, Exchange would reject the result. state is the per-request CSRF
|
||||
// token the caller binds to the user and verifies on the callback.
|
||||
func AuthCodeURL(c Config, state string) string {
|
||||
return oauthConfig(c).AuthCodeURL(state,
|
||||
oauth2.AccessTypeOffline,
|
||||
oauth2.SetAuthURLParam("prompt", "consent"))
|
||||
}
|
||||
|
||||
// Exchange swaps an authorization code for a token and persists the refresh
|
||||
// token through the writer. It errors if the provider returned no refresh token
|
||||
// (e.g. consent was not forced with offline access), since without one the
|
||||
|
||||
Reference in New Issue
Block a user