package api_test import ( "net/http" "net/url" "strings" "testing" "time" "atlas9.dev/c/core/assert" "atlas9.dev/c/core/iam" "atlas9.dev/c/demo/api" "atlas9.dev/c/demo/todos" ) // A malformed or unknown session cookie is treated as anonymous — private // routes answer 401, not 500. func TestMiddleware_UnknownSessionCookie(t *testing.T) { s := startServer(t) s.logout(t) u, err := url.Parse(s.URL) assert.Ok(t, err) s.client.Jar.SetCookies(u, []*http.Cookie{{ Name: "atlas9_test_session", // matches the session config in startServer Value: "not-a-real-token", }}) httpRes := s.call(t, "/Todos_CreateList", api.Todos_CreateListReq{ List: todos.List{Tenant: defaultTenant, Name: "Nope"}, }, nil) assert.Eq(t, httpRes.StatusCode, http.StatusUnauthorized) } // An expired session is treated the same as a missing one: the request is // anonymous and private routes answer 401. func TestMiddleware_ExpiredSession(t *testing.T) { s := startServer(t) // Age every session; the default principal is logged in, so its // session is the one expiring. Expiration is stored as unix seconds. _, err := s.DB.ExecContext(t.Context(), `UPDATE sessions SET expiration = $1`, time.Now().Add(-time.Minute).Unix()) assert.Ok(t, err) httpRes := s.call(t, "/Todos_CreateList", api.Todos_CreateListReq{ List: todos.List{Tenant: defaultTenant, Name: "Nope"}, }, nil) assert.Eq(t, httpRes.StatusCode, http.StatusUnauthorized) } // A logged-in principal seeded with no extra caps holds only the member role // for their home tenant plus the system Tenants.Create baseline. This pins the // middleware-to-store linkage — endpoints answer from loaded grants, not from // session presence. func TestMiddleware_ZeroCapUser(t *testing.T) { s := startServer(t) s.seedUser(t, "nocaps@test", "pass") s.login(t, "nocaps@test", "pass") var created api.Tenants_CreateRes httpRes := s.call(t, "/Tenants_Create", api.Tenants_CreateReq{ Tenant: iam.Tenant{Name: "Base"}, }, &created) assert.Eq(t, httpRes.StatusCode, http.StatusOK) // Owner-only operations on defaultTenant are forbidden; the user is only a member there. httpRes = s.call(t, "/Todos_CreateList", api.Todos_CreateListReq{ List: todos.List{Tenant: defaultTenant, Name: "Nope"}, }, nil) assert.Eq(t, httpRes.StatusCode, http.StatusForbidden) httpRes = s.call(t, "/Tenants_Update", api.Tenants_UpdateReq{ Tenant: iam.Tenant{ID: defaultTenant, Name: "Nope"}, }, nil) assert.Eq(t, httpRes.StatusCode, http.StatusForbidden) } // Bot auth is bearer-token only. A Signature header is an unrecognized // scheme: the request is anonymous and private routes answer 401. This pins // the security property that a Signature header alone never authenticates. // (Before bearer auth landed this was a 403 from the key-store guard // denying the anonymous key lookup.) func TestMiddleware_SignatureHeaderDoesNotAuthenticate(t *testing.T) { s := startServer(t) // No cookie jar: the only credential offered is the Signature header. client := &http.Client{} req, err := http.NewRequestWithContext(t.Context(), "POST", s.URL+"/Todos_ListLists", strings.NewReader("{}")) assert.Ok(t, err) req.Header.Set("Content-Type", "application/json") req.Header.Set("Authorization", "Signature keyId=anything") res, err := client.Do(req) assert.Ok(t, err) defer res.Body.Close() assert.Eq(t, res.StatusCode, http.StatusUnauthorized) }