Accounts Phase 2: Google sign-in (OAuth 2.0 / OIDC)
- oauth_google.py (stdlib): PKCE, auth URL, code exchange, ID-token claim validation (iss/aud/exp/email_verified — token comes straight from Google's token endpoint over TLS, so no signature re-verify / JWKS needed). - API: GET /api/auth/google/start (302 to Google, PKCE + signed state cookie binding the flow to the browser) and /callback (CSRF-checked state, exchange, find-or-create by verified email → links to an existing magic-link account, session cookie, redirect home). Errors land on /auth/verify?error=google. - SignIn modal: "Continue with Google" + an "or email link" divider. - 112 tests pass. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -35,10 +35,21 @@
|
||||
{:else}
|
||||
<h2>Sign in to Upbeat Bytes</h2>
|
||||
<p class="sub">
|
||||
Save articles and keep your history across devices. We'll email you a one-time
|
||||
link — no password to remember.
|
||||
Save articles and keep your history across devices.
|
||||
</p>
|
||||
|
||||
<a class="google" href="/api/auth/google/start">
|
||||
<svg width="18" height="18" viewBox="0 0 18 18" aria-hidden="true">
|
||||
<path fill="#4285F4" d="M17.64 9.2c0-.64-.06-1.25-.16-1.84H9v3.48h4.84a4.14 4.14 0 0 1-1.8 2.72v2.26h2.92c1.71-1.57 2.68-3.89 2.68-6.62z"/>
|
||||
<path fill="#34A853" d="M9 18c2.43 0 4.47-.8 5.96-2.18l-2.92-2.26c-.81.54-1.84.86-3.04.86-2.34 0-4.32-1.58-5.03-3.7H.96v2.33A9 9 0 0 0 9 18z"/>
|
||||
<path fill="#FBBC05" d="M3.97 10.72a5.4 5.4 0 0 1 0-3.44V4.95H.96a9 9 0 0 0 0 8.1l3.01-2.33z"/>
|
||||
<path fill="#EA4335" d="M9 3.58c1.32 0 2.5.45 3.44 1.35l2.58-2.58C13.46.9 11.43 0 9 0A9 9 0 0 0 .96 4.95l3.01 2.33C4.68 5.16 6.66 3.58 9 3.58z"/>
|
||||
</svg>
|
||||
Continue with Google
|
||||
</a>
|
||||
|
||||
<div class="or"><span>or email me a one-time link — no password</span></div>
|
||||
|
||||
<form onsubmit={submit}>
|
||||
<input
|
||||
type="email"
|
||||
@@ -53,7 +64,6 @@
|
||||
</form>
|
||||
|
||||
{#if status === 'error'}<p class="err">{error}</p>{/if}
|
||||
<!-- Phase 2: "Continue with Google" button slots in here -->
|
||||
<p class="fine">No account needed to browse — signing in just saves your stuff.</p>
|
||||
{/if}
|
||||
</div>
|
||||
@@ -135,6 +145,41 @@
|
||||
opacity: 0.6;
|
||||
cursor: default;
|
||||
}
|
||||
.google {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 10px;
|
||||
width: 100%;
|
||||
padding: 11px 16px;
|
||||
border: 1px solid var(--line);
|
||||
border-radius: 999px;
|
||||
background: #fff;
|
||||
color: var(--ink);
|
||||
font-weight: 600;
|
||||
font-size: 0.95rem;
|
||||
cursor: pointer;
|
||||
transition: background 0.14s ease, border-color 0.14s ease;
|
||||
}
|
||||
.google:hover {
|
||||
background: #f7f7f5;
|
||||
border-color: #d9d3c6;
|
||||
}
|
||||
.or {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
margin: 16px 0;
|
||||
color: var(--muted);
|
||||
font-size: 0.78rem;
|
||||
}
|
||||
.or::before,
|
||||
.or::after {
|
||||
content: '';
|
||||
flex: 1;
|
||||
height: 1px;
|
||||
background: var(--line);
|
||||
}
|
||||
.err {
|
||||
margin: 10px 0 0;
|
||||
color: #9a3b3b;
|
||||
|
||||
Reference in New Issue
Block a user