oficina / memoria Public

Signed out of Home is the door; the address check is shape only

Review findings: /signed-out rendered Home's bare page while /signin
was the door, and plausibleAddress refused dotless domains and a quoted
local part's @.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Paul Campbell pushed by paul@keymail.dev 6a5cfdd6307dc49793fd976ce21266eb441e1654 parent 8b36ece
6 files changed, +44 −16
  • internal/memoria/auth.go +3 −6
  • internal/memoria/auth_test.go +2 −2
  • internal/memoria/handlers.go +7 −4
  • internal/memoria/home_render.go +17 −3
  • internal/memoria/home_router_test.go +14 −0
  • internal/memoria/templates/signin.html +1 −1
diff --git a/internal/memoria/auth.go b/internal/memoria/auth.go
index 57c1154..3ea2aa1 100644
--- a/internal/memoria/auth.go
+++ b/internal/memoria/auth.go
@@ -278,13 +278,10 @@ type oauthPage struct {
}
// plausibleAddress is the shape of an email address and nothing more:
-// something, one @, a domain with a dot, no spaces.
+// something, an @, something, no spaces. The last @ splits it (a quoted
+// local part may hold one), and a dotless domain is still an address.
func plausibleAddress(v string) bool {
v = strings.TrimSpace(v)
at := strings.LastIndexByte(v, '@')
- if at <= 0 || at == len(v)-1 || strings.ContainsAny(v, " \t\r\n") || strings.Count(v, "@") != 1 {
- return false
- }
- domain := v[at+1:]
- return strings.Contains(domain, ".") && !strings.HasPrefix(domain, ".") && !strings.HasSuffix(domain, ".")
+ return at > 0 && at < len(v)-1 && !strings.ContainsAny(v, " \t\r\n")
}
diff --git a/internal/memoria/auth_test.go b/internal/memoria/auth_test.go
index 101568f..5993d93 100644
--- a/internal/memoria/auth_test.go
+++ b/internal/memoria/auth_test.go
@@ -6,8 +6,8 @@ func TestPlausibleAddressIsShapeOnly(t *testing.T) {
for v, want := range map[string]bool{
"paul@example.com": true, " paul@example.com ": true, "a@b.co": true,
"": false, "paul@": false, "@example.com": false, "paul": false,
- "paul@example": false, "paul@.com": false, "paul@example.": false,
- "pa ul@example.com": false, "a@b@c.com": false,
+ "paul@localhost": true, `"a@b"@example.com`: true,
+ "pa ul@example.com": false, "paul@example.com@": false,
} {
if got := plausibleAddress(v); got != want {
t.Errorf("plausibleAddress(%q) = %v, want %v", v, got, want)
diff --git a/internal/memoria/handlers.go b/internal/memoria/handlers.go
index bd34e24..164b9d8 100644
--- a/internal/memoria/handlers.go
+++ b/internal/memoria/handlers.go
@@ -1124,12 +1124,15 @@ func (a *app) deleteDocument(w http.ResponseWriter, r *http.Request) {
}
// signinPage is the sign-in page: the layout's page (footer, egress claim
-// and all) around the suite's door. Exactly one of Door and Sent is set —
-// the form, or the check-your-email notice that replaces it.
+// and all) around the suite's door. Exactly one of Door, Sent and
+// SignedOut is set —
+// the form, the check-your-email notice that replaces it, or the page
+// Home sign-out lands on.
type signinPage struct {
page
- Door *chrome.SignIn
- Sent *chrome.SignInNotice
+ Door *chrome.SignIn
+ Sent *chrome.SignInNotice
+ SignedOut *chrome.SignInNotice
}
// signinLede is what the door says under its heading on a plain visit.
diff --git a/internal/memoria/home_render.go b/internal/memoria/home_render.go
index f57f506..c71ace2 100644
--- a/internal/memoria/home_render.go
+++ b/internal/memoria/home_render.go
@@ -1,9 +1,12 @@
package memoria
import (
- "amadan.net/oficina/home/connect"
"bytes"
"net/http"
+
+ "amadan.net/oficina/estilo.git/chrome"
+ "amadan.net/oficina/estilo.git/sky"
+ "amadan.net/oficina/home/connect"
)
type homePage struct {
@@ -12,9 +15,20 @@ type homePage struct {
}
func (a *app) renderHome(w http.ResponseWriter, r *http.Request, state, label string, status int) {
- p := homePage{page: page{Chunks: a.chunkCount(), ModelOn: a.modelOn()}, Home: connect.Page(state, label)}
+ pg := page{Chunks: a.chunkCount(), ModelOn: a.modelOn()}
+ data := connect.Page(state, label)
+ tree, view := pages["home"], any(homePage{page: pg, Home: data})
+ // Signed out of Home is the door: Home's heading, sentence and link,
+ // on Memoria's card and sky, like the sign-in page it sits beside.
+ if state == "signedout" {
+ tree, view = pages["signin"], signinPage{page: pg, SignedOut: &chrome.SignInNotice{
+ Icon: assets.Path("static/brand/icon.svg"), App: "Memoria", Sky: sky.For(r.Host),
+ Heading: data.Title, Body: data.Body,
+ Link: &chrome.Link{Href: data.Action, Label: data.ActionLabel},
+ }}
+ }
var body bytes.Buffer
- if err := pages["home"].ExecuteTemplate(&body, "layout", p); err != nil {
+ if err := tree.ExecuteTemplate(&body, "layout", view); err != nil {
http.Error(w, "something went wrong", http.StatusInternalServerError)
return
}
diff --git a/internal/memoria/home_router_test.go b/internal/memoria/home_router_test.go
index 1ab5119..fa8b6f4 100644
--- a/internal/memoria/home_router_test.go
+++ b/internal/memoria/home_router_test.go
@@ -375,3 +375,17 @@ func TestHomePagesStateTheModelSwitch(t *testing.T) {
}
}
}
+
+// Where Home sign-out lands is the door, like the sign-in page beside it:
+// Memoria's card on the sky, with Home's words and link, and the door's
+// stylesheets linked so the card is drawn.
+func TestSignedOutIsTheDoor(t *testing.T) {
+ f := newHomeRouterFixture(t)
+ w := f.do("GET", "/signed-out", "")
+ b := w.Body.String()
+ for _, want := range []string{"estilo-door", "estilo-sky-layer", "estilo-signin", "<span>Memoria</span>", "/static/chrome.", "/static/sky."} {
+ if !strings.Contains(b, want) {
+ t.Errorf("/signed-out (%d) lacks %q", w.Code, want)
+ }
+ }
+}
diff --git a/internal/memoria/templates/signin.html b/internal/memoria/templates/signin.html
index c4d1151..bbf136c 100644
--- a/internal/memoria/templates/signin.html
+++ b/internal/memoria/templates/signin.html
@@ -5,4 +5,4 @@
The layout's footer, and with it the egress claim, stays under it. */}}
{{define "head"}}<link rel="stylesheet" href="{{asset "static/chrome.css"}}">
<link rel="stylesheet" href="{{asset "static/sky.css"}}">{{end}}
-{{define "content"}}{{with .Sent}}{{template "estilo-signin-sent" .}}{{else}}{{template "estilo-signin" .Door}}{{end}}{{end}}
+{{define "content"}}{{with .Sent}}{{template "estilo-signin-sent" .}}{{else with .SignedOut}}{{template "estilo-signed-out" .}}{{else}}{{template "estilo-signin" .Door}}{{end}}{{end}}