refactor(10.2-01): nest framework packages under modules
- Move remaining beach packages and embedded admin assets\n- Rewrite framework, example, build, and gate paths
This commit is contained in:
144
modules/cabana/refresh_revocation_test.go
Normal file
144
modules/cabana/refresh_revocation_test.go
Normal file
@@ -0,0 +1,144 @@
|
||||
package cabana_test
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
|
||||
"git.golem15.com/golem15/summercms/modules/bonfire"
|
||||
"git.golem15.com/golem15/summercms/modules/cabana"
|
||||
)
|
||||
|
||||
// TestAdminRefreshRevocation pins CR-01: POST {prefix}/api/v1/auth/refresh
|
||||
// applies the backend guard's subject checks before minting. A token issued
|
||||
// before `summer admin:reset-password` (tokens_valid_after), or held by a
|
||||
// deactivated or soft-deleted admin, cannot be refreshed over either
|
||||
// transport, and a refused cookie refresh expires summer_admin.
|
||||
func TestAdminRefreshRevocation(t *testing.T) {
|
||||
gdb := adminGorm(t)
|
||||
|
||||
cookieLogin := func(t *testing.T, h http.Handler, login string) *http.Cookie {
|
||||
t.Helper()
|
||||
rec := phase10Send(t, h, http.MethodPost, adminAPI("/auth/login"),
|
||||
map[string]string{"login": login, "password": adminTestPassword}, nil, true)
|
||||
if rec.Code != http.StatusOK {
|
||||
t.Fatalf("cookie login status=%d body=%s", rec.Code, rec.Body.String())
|
||||
}
|
||||
return phase10Cookie(t, rec, cabana.DefaultAdminPrefix)
|
||||
}
|
||||
bearerLogin := func(t *testing.T, h http.Handler, login string) string {
|
||||
t.Helper()
|
||||
rec := phase10Send(t, h, http.MethodPost, adminAPI("/auth/login"),
|
||||
map[string]string{"login": login, "password": adminTestPassword}, nil, false)
|
||||
if rec.Code != http.StatusOK {
|
||||
t.Fatalf("bearer login status=%d body=%s", rec.Code, rec.Body.String())
|
||||
}
|
||||
return accessToken(t, rec.Body.Bytes())
|
||||
}
|
||||
resetPassword := func(t *testing.T, login string) {
|
||||
t.Helper()
|
||||
reset := commandByName(t, cabana.RuntimeCommands(commandApp(t, gdb)), "admin:reset-password")
|
||||
var buf bytes.Buffer
|
||||
if err := reset.Run(context.Background(), flagInput{
|
||||
args: []string{login},
|
||||
flags: map[string]string{"password": "rrev-replacement-password"},
|
||||
}, bonfire.NewOutput(nil, &buf, &buf)); err != nil {
|
||||
t.Fatalf("admin:reset-password: %v output=%s", err, buf.String())
|
||||
}
|
||||
}
|
||||
assertRefusedWithExpiredCookie := func(t *testing.T, rec *httptest.ResponseRecorder) {
|
||||
t.Helper()
|
||||
if rec.Code != http.StatusUnauthorized || phase10ErrorCode(t, rec) != "unauthenticated" {
|
||||
t.Fatalf("refresh status=%d body=%s, want 401 unauthenticated", rec.Code, rec.Body.String())
|
||||
}
|
||||
var expired *http.Cookie
|
||||
for _, c := range rec.Result().Cookies() {
|
||||
if c.Name == cabana.AdminCookieName {
|
||||
expired = c
|
||||
}
|
||||
}
|
||||
if expired == nil || expired.Value != "" || expired.MaxAge >= 0 || expired.Path != cabana.DefaultAdminPrefix {
|
||||
t.Fatalf("refused refresh cookie = %+v, want an expiring %s with Path %s", expired, cabana.AdminCookieName, cabana.DefaultAdminPrefix)
|
||||
}
|
||||
}
|
||||
|
||||
t.Run("pre-reset cookie is refused and expired", func(t *testing.T) {
|
||||
h := adminHandler(t, gdb, nil)
|
||||
insertAdmin(t, gdb, "rrev-cookie", "rrev-cookie@example.test", adminTestPassword, true, false)
|
||||
old := cookieLogin(t, h, "rrev-cookie")
|
||||
resetPassword(t, "rrev-cookie")
|
||||
|
||||
if me := phase10Send(t, h, http.MethodGet, adminAPI("/auth/me"), nil, old, true); me.Code != http.StatusUnauthorized {
|
||||
t.Fatalf("pre-reset cookie /auth/me status=%d body=%s", me.Code, me.Body.String())
|
||||
}
|
||||
rec := phase10Send(t, h, http.MethodPost, adminAPI("/auth/refresh"), nil, old, true)
|
||||
assertRefusedWithExpiredCookie(t, rec)
|
||||
})
|
||||
|
||||
t.Run("pre-reset bearer is refused without cookies", func(t *testing.T) {
|
||||
h := adminHandler(t, gdb, nil)
|
||||
insertAdmin(t, gdb, "rrev-bearer", "rrev-bearer@example.test", adminTestPassword, true, false)
|
||||
token := bearerLogin(t, h, "rrev-bearer")
|
||||
resetPassword(t, "rrev-bearer")
|
||||
|
||||
rec := postAuth(t, h, http.MethodPost, adminAPI("/auth/refresh"), token, nil)
|
||||
if rec.Code != http.StatusUnauthorized || phase10ErrorCode(t, rec) != "unauthenticated" {
|
||||
t.Fatalf("pre-reset bearer refresh status=%d body=%s, want 401 unauthenticated", rec.Code, rec.Body.String())
|
||||
}
|
||||
if got := rec.Header().Values("Set-Cookie"); len(got) != 0 {
|
||||
t.Fatalf("bearer refresh set cookies: %q", got)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("deactivated admin is refused", func(t *testing.T) {
|
||||
h := adminHandler(t, gdb, nil)
|
||||
user := insertAdmin(t, gdb, "rrev-deactivated", "rrev-deactivated@example.test", adminTestPassword, true, false)
|
||||
old := cookieLogin(t, h, "rrev-deactivated")
|
||||
if err := gdb.Exec(`UPDATE backend_users SET is_activated = false WHERE id = ?`, user.ID).Error; err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
rec := phase10Send(t, h, http.MethodPost, adminAPI("/auth/refresh"), nil, old, true)
|
||||
assertRefusedWithExpiredCookie(t, rec)
|
||||
})
|
||||
|
||||
t.Run("soft-deleted admin is refused", func(t *testing.T) {
|
||||
h := adminHandler(t, gdb, nil)
|
||||
user := insertAdmin(t, gdb, "rrev-deleted", "rrev-deleted@example.test", adminTestPassword, true, false)
|
||||
old := cookieLogin(t, h, "rrev-deleted")
|
||||
if err := gdb.Delete(&user).Error; err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
rec := phase10Send(t, h, http.MethodPost, adminAPI("/auth/refresh"), nil, old, true)
|
||||
assertRefusedWithExpiredCookie(t, rec)
|
||||
})
|
||||
|
||||
t.Run("active admin still refreshes", func(t *testing.T) {
|
||||
h := adminHandler(t, gdb, nil)
|
||||
insertAdmin(t, gdb, "rrev-active", "rrev-active@example.test", adminTestPassword, true, false)
|
||||
first := cookieLogin(t, h, "rrev-active")
|
||||
rec := phase10Send(t, h, http.MethodPost, adminAPI("/auth/refresh"), nil, first, true)
|
||||
if rec.Code != http.StatusOK {
|
||||
t.Fatalf("cookie refresh status=%d body=%s", rec.Code, rec.Body.String())
|
||||
}
|
||||
second := phase10Cookie(t, rec, cabana.DefaultAdminPrefix)
|
||||
if second.Value == first.Value {
|
||||
t.Fatal("cookie refresh did not rotate the token")
|
||||
}
|
||||
phase10AssertCookieBody(t, rec, second.Value)
|
||||
if me := phase10Send(t, h, http.MethodGet, adminAPI("/auth/me"), nil, second, true); me.Code != http.StatusOK {
|
||||
t.Fatalf("rotated cookie /auth/me status=%d body=%s", me.Code, me.Body.String())
|
||||
}
|
||||
|
||||
token := bearerLogin(t, h, "rrev-active")
|
||||
bearer := postAuth(t, h, http.MethodPost, adminAPI("/auth/refresh"), token, nil)
|
||||
if bearer.Code != http.StatusOK {
|
||||
t.Fatalf("bearer refresh status=%d body=%s", bearer.Code, bearer.Body.String())
|
||||
}
|
||||
phase10AssertBearerBody(t, bearer)
|
||||
if got := bearer.Header().Values("Set-Cookie"); len(got) != 0 {
|
||||
t.Fatalf("bearer refresh set cookies: %q", got)
|
||||
}
|
||||
})
|
||||
}
|
||||
Reference in New Issue
Block a user