Refuse disabled Home sessions and preserve sessions during storage faults
4 files changed,
+72
−3
internal/sheets/app.go+1 −1internal/sheets/home.go+18 −0internal/sheets/home_navigation_test.go+50 −0internal/sheets/page.go+3 −2
CI passed — run details
diff --git a/internal/sheets/app.go b/internal/sheets/app.go| index 71f84cb..9a8052a 100644 |
| --- a/internal/sheets/app.go |
| +++ b/internal/sheets/app.go |
| @@ -274,5 +274,5 @@ func (a *App) requireSession(next http.Handler) http.Handler { |
| if a.auth == nil { |
| return next |
| } |
| - return a.auth.RequireSession(next) |
| + return a.auth.RequireSession(a.rejectDisabledHome(next)) |
| } |
diff --git a/internal/sheets/home.go b/internal/sheets/home.go| index 020a846..b9928a2 100644 |
| --- a/internal/sheets/home.go |
| +++ b/internal/sheets/home.go |
| @@ -103,6 +103,13 @@ func (a *App) homePerson(ctx context.Context, q linkstore.Queryer, id int64) (st |
| func (a *App) homeRequire(next http.Handler) http.Handler { |
| return a.homeSessions.Middleware(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { |
| session, ok := a.homeSessions.From(r) |
| + if ok && session.Method == connect.HomeMethod { |
| + if _, err := a.home.ResolveSession(r); errors.Is(err, linkstore.ErrStorage) { |
| + w.Header().Set("Cache-Control", "no-store") |
| + http.Error(w, "Sign-in unavailable.", http.StatusServiceUnavailable) |
| + return |
| + } |
| + } |
| address, admitted := a.identity(r) |
| if !ok || !admitted || !a.Admits(address) { |
| if ok { |
| @@ -148,3 +155,14 @@ func (a *App) homeLocalBegin(w http.ResponseWriter, r *http.Request) { |
| a.render(w, r, "home_oauth", p.Title, http.StatusOK, p) |
| })(w, r) |
| } |
| + |
| +func (a *App) rejectDisabledHome(next http.Handler) http.Handler { |
| + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { |
| + if s, ok := sessions.Current(r); ok && s.Method == connect.HomeMethod { |
| + w.Header().Set("Cache-Control", "no-store") |
| + http.Error(w, "Sign in first.", http.StatusForbidden) |
| + return |
| + } |
| + next.ServeHTTP(w, r) |
| + }) |
| +} |
diff --git a/internal/sheets/home_navigation_test.go b/internal/sheets/home_navigation_test.go| index 782c1da..6998448 100644 |
| --- a/internal/sheets/home_navigation_test.go |
| +++ b/internal/sheets/home_navigation_test.go |
| @@ -98,3 +98,53 @@ func TestHomeFrameworkPolicyAndDuplicateCookies(t *testing.T) { |
| t.Fatal("duplicate session accepted", w.Code) |
| } |
| } |
| + |
| +func TestHomeDisabledRefusesRetainedOpaqueSession(t *testing.T) { |
| + f := newHomeFixture(t) |
| + f.local(t, signedInAddress) |
| + f.link(t, "retained-home") |
| + f.jar = map[string]*http.Cookie{} |
| + start := f.request(t, "GET", "/auth/start", nil, false) |
| + if w := f.exchange(t, start, "retained-home"); w.Code != 303 { |
| + t.Fatal(w.Code) |
| + } |
| + f.app.home = nil |
| + f.app.homeRoutes = nil |
| + f.app.homeSessions = nil |
| + var before, after int64 |
| + f.app.db.Model(&Member{}).Count(&before) |
| + w := f.request(t, "GET", "/sheets", nil, false) |
| + if w.Code != 403 { |
| + t.Fatal("retained Home session accepted", w.Code) |
| + } |
| + f.app.db.Model(&Member{}).Count(&after) |
| + if after != before { |
| + t.Fatal("opaque member created") |
| + } |
| +} |
| +func TestHomeStorageFailureDoesNotRevokeSession(t *testing.T) { |
| + f := newHomeFixture(t) |
| + f.local(t, signedInAddress) |
| + f.link(t, "storage-home") |
| + f.jar = map[string]*http.Cookie{} |
| + if w := f.exchange(t, f.request(t, "GET", "/auth/start", nil, false), "storage-home"); w.Code != 303 { |
| + t.Fatal(w.Code) |
| + } |
| + cookie := f.jar[f.app.homeSessions.CookieName()] |
| + if err := f.app.db.Exec("ALTER TABLE home_identity_links RENAME TO unavailable_links").Error; err != nil { |
| + t.Fatal(err) |
| + } |
| + w := f.request(t, "GET", "/sheets", nil, false) |
| + if w.Code != 503 { |
| + t.Fatal("storage response", w.Code) |
| + } |
| + if f.jar[cookie.Name] == nil || f.jar[cookie.Name].Value != cookie.Value { |
| + t.Fatal("storage failure revoked session") |
| + } |
| + if err := f.app.db.Exec("ALTER TABLE unavailable_links RENAME TO home_identity_links").Error; err != nil { |
| + t.Fatal(err) |
| + } |
| + if w := f.request(t, "GET", "/sheets", nil, false); w.Code != 200 { |
| + t.Fatal("session did not recover", w.Code) |
| + } |
| +} |
diff --git a/internal/sheets/page.go b/internal/sheets/page.go| index 945549d..f546fc9 100644 |
| --- a/internal/sheets/page.go |
| +++ b/internal/sheets/page.go |
| @@ -1,8 +1,9 @@ |
| package sheets |
| import ( |
| - "amadan.net/oficina/home/connect" |
| "net/http" |
| + |
| + "amadan.net/oficina/home/connect" |
| ) |
| // The root page is the anonymous entry page, and nothing else. |
| @@ -52,7 +53,7 @@ func (a *App) identity(r *http.Request) (string, bool) { |
| return "", false |
| } |
| id, ok := a.auth.SessionFrom(r) |
| - if !ok { |
| + if !ok || string(id.Method) == connect.HomeMethod { |
| return "", false |
| } |
| return id.Address, true |