refactor(auth): one Google connect route for a sign-in and for YouTube access

/auth/link and /auth/upgrade become /auth/google/connect?access=identity|read|write: one handler, one
table of what each access asks Google for, the same callback markers. An unknown access is refused
(422) instead of quietly meaning read, and an instance without Google answers ?link=error for a
grant too, not a silent return to the app. The frontend builds the URL in lib/googleConnect, and a
contract test holds it to the backend route and its access values.
This commit is contained in:
2026-10-20 12:49:11 +02:00
parent 59ec4a28ba
commit 6645caa157
6 changed files with 156 additions and 64 deletions
+5 -15
View File
@@ -1,5 +1,5 @@
import { useState } from "react";
import { withActiveAccount } from "../lib/activeAccount";
import { connectGoogle } from "../lib/googleConnect";
import { qk } from "../lib/queryKeys";
import { formatDate, formatDateTime, formatUnit } from "../lib/format";
import { useTranslation } from "react-i18next";
@@ -359,7 +359,7 @@ function AccessRow({
// Sign-in methods: link a Google account to a password account (or vice-versa set a password),
// so either method can reach the same account. Google connect is a full-page OAuth round-trip
// (auth.py attaches the identity to the current session via /auth/link); the password form posts
// (auth.py attaches the identity to the signed-in account via /auth/google/connect); the password form posts
// directly with inline errors.
export function SignInMethods({ me }: { me: Me }) {
const { t } = useTranslation();
@@ -451,13 +451,7 @@ export function SignInMethods({ me }: { me: Me }) {
{me.has_google ? (
<Badge tone="accent">{t("settings.account.googleLink.connected")}</Badge>
) : (
<Button
variant="glass"
size="sm"
onClick={() => {
window.location.href = withActiveAccount("/auth/link");
}}
>
<Button variant="glass" size="sm" onClick={() => connectGoogle("identity")}>
{t("settings.account.googleLink.connect")}
</Button>
)}
@@ -877,18 +871,14 @@ function Account({ me, onOpenWizard }: { me: Me; onOpenWizard: () => void }) {
granted={me.can_read}
grantedHint={t("settings.account.readGrantedHint")}
enableHint={t("settings.account.readEnableHint")}
onEnable={() => {
window.location.href = withActiveAccount("/auth/upgrade?access=read");
}}
onEnable={() => connectGoogle("read")}
/>
<AccessRow
title={t("settings.account.writeTitle")}
granted={me.can_write}
grantedHint={t("settings.account.writeGrantedHint")}
enableHint={t("settings.account.writeEnableHint")}
onEnable={() => {
window.location.href = withActiveAccount("/auth/upgrade?access=write");
}}
onEnable={() => connectGoogle("write")}
/>
</div>
<Button variant="link" className="mt-2 text-sm" onClick={onOpenWizard}>