Release 0.7.0: email/OTC sign-in (Gitea OAuth retained as fallback)
Replaces the Gitea OAuth gesture as the primary human-auth path (roadmap item #5, SPEC §6.2). Users sign in by entering their email, receiving a six-digit code via the existing SMTP layer, and entering the code on a two-step /login surface. The Gitea OAuth callback remains functional during migration — the new UI links to it as a fallback for users with active OAuth sessions or older invite paths — and is scheduled for removal in a future release once OTC adoption is universal. Existing users are linked by email on first OTC sign- in (gitea_id preserved); new users are provisioned with NULL gitea_id and rely on email as the identity key. The migration introduces backend/migrations/012_otc.sql (otc_codes table + users schema rebuild for nullable gitea_id and a partial unique index on email), two new endpoints (POST /auth/otc/request, POST /auth/otc/verify), bcrypt as a new backend dependency for code hashing, and 11 new tests in test_otc_vertical.py covering the happy path, expired and consumed and wrong codes, the per-email rate limit, the allowlist gate, the OAuth-era link path, fresh provisioning, and prior-code invalidation on re-request. No new secrets are required — the existing SECRET_KEY signs sessions and bcrypt's per-row salt covers the code hashes. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -352,6 +352,89 @@
|
||||
}
|
||||
.landing .secondary-link:hover { color: #1a1a1a; text-decoration: underline; }
|
||||
|
||||
/* --- v0.7.0: email + one-time-code sign-in (§6.2) --- */
|
||||
|
||||
.otc-login {
|
||||
flex: 1;
|
||||
display: flex; align-items: center; justify-content: center;
|
||||
padding: 40px 24px;
|
||||
}
|
||||
.otc-login-inner {
|
||||
max-width: 360px;
|
||||
width: 100%;
|
||||
display: flex; flex-direction: column;
|
||||
gap: 14px;
|
||||
}
|
||||
.otc-login h1 {
|
||||
font-size: 22px;
|
||||
font-weight: 600;
|
||||
margin: 0 0 4px;
|
||||
}
|
||||
.otc-login .otc-hint {
|
||||
color: #555;
|
||||
font-size: 14px;
|
||||
line-height: 1.5;
|
||||
margin: 0;
|
||||
}
|
||||
.otc-login input {
|
||||
width: 100%;
|
||||
padding: 10px 12px;
|
||||
font-size: 15px;
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 6px;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
.otc-login input:focus {
|
||||
outline: none;
|
||||
border-color: #1a1a1a;
|
||||
}
|
||||
.otc-login button[type="submit"] {
|
||||
background: #1a1a1a; color: #fff;
|
||||
border: none; border-radius: 6px;
|
||||
padding: 10px 18px;
|
||||
font-size: 14px; font-weight: 600;
|
||||
cursor: pointer;
|
||||
}
|
||||
.otc-login button[type="submit"]:hover:not(:disabled) { background: #333; }
|
||||
.otc-login button[type="submit"]:disabled { opacity: 0.5; cursor: not-allowed; }
|
||||
.otc-login form {
|
||||
display: flex; flex-direction: column;
|
||||
gap: 10px;
|
||||
}
|
||||
.otc-actions {
|
||||
display: flex; align-items: center; gap: 12px;
|
||||
}
|
||||
.otc-login .btn-link-quiet {
|
||||
background: none; border: none;
|
||||
color: #666; font-size: 13px;
|
||||
cursor: pointer; padding: 0;
|
||||
}
|
||||
.otc-login .btn-link-quiet:hover { color: #1a1a1a; text-decoration: underline; }
|
||||
.otc-shortcut-hint {
|
||||
color: #888; font-size: 12px; margin: 4px 0 0;
|
||||
}
|
||||
.otc-shortcut-hint kbd {
|
||||
background: #f0f0ee; border: 1px solid #ddd; border-radius: 3px;
|
||||
padding: 1px 5px; font-size: 11px; font-family: inherit;
|
||||
}
|
||||
.otc-status {
|
||||
color: #555; font-size: 13px;
|
||||
background: #f7f6f0;
|
||||
border-left: 3px solid #cfc8a8;
|
||||
padding: 8px 12px;
|
||||
margin: 4px 0 0;
|
||||
}
|
||||
.otc-fallback {
|
||||
font-size: 12px; color: #777;
|
||||
margin: 16px 0 0;
|
||||
display: flex; gap: 8px; align-items: center; flex-wrap: wrap;
|
||||
}
|
||||
.otc-fallback a, .otc-fallback .otc-fallback-link {
|
||||
color: #666; text-decoration: none;
|
||||
}
|
||||
.otc-fallback a:hover { color: #1a1a1a; text-decoration: underline; }
|
||||
.otc-fallback-sep { color: #ccc; }
|
||||
|
||||
/* --- Beta-pending page (post-OAuth-rejection) --- */
|
||||
|
||||
.beta-pending {
|
||||
|
||||
Reference in New Issue
Block a user