package auth import ( "net/http/httptest" "strings" "testing" ) // 401 MEANS "I DO NOT KNOW WHO YOU ARE", 403 MEANS "I KNOW AND YOU MAY NOT". // // This surface answered 403 to both for its whole life, so an expired session was indistinguishable from a // permission refusal and a client could only guess which had happened. The two are now separated, and this // test pins which side each case falls on, because the temptation when adding a handler is to copy whichever // line is nearest. func TestNoSessionIsUnauthenticatedNotForbidden(t *testing.T) { w := httptest.NewRecorder() writeUnauthenticated(w, "no session") if w.Code != 401 { t.Errorf("a request with no credential must be 401, got %d", w.Code) } // RFC 9110 requires the header on a 401. auth := w.Header().Get("WWW-Authenticate") if auth == "" { t.Error("a 401 must carry WWW-Authenticate") } // And it must NOT be a scheme that makes a browser throw its own credential dialog over the application, // which is exactly what a cookie session exists to avoid. low := strings.ToLower(auth) if strings.HasPrefix(low, "basic") || strings.HasPrefix(low, "digest") { t.Errorf("WWW-Authenticate %q will make a browser show a native login dialog", auth) } } // The structural guard. Every "no session" answer must be 401 and every tenant scoping refusal must stay 403, // because the difference is what a client keys its retry behaviour on. func TestAuthStatusCodesStayCorrect(t *testing.T) { src := readHandlerSource(t) if strings.Contains(src, `writeError(w, http.StatusForbidden, "no session")`) { t.Error("a 'no session' case is back to 403; that is unauthenticated, not forbidden") } if strings.Contains(src, `http.Error(w, "forbidden", http.StatusForbidden)`) { t.Error("the session middleware is back to a bare 403 for a missing or invalid cookie") } // The genuinely forbidden cases must NOT have been swept into 401 along with them: a caller who is // authenticated but has no tenant is known and refused, which is 403. for _, keep := range []string{`"no validated tenant"`, `"no organization context"`} { if !strings.Contains(src, "http.StatusForbidden, "+keep) { t.Errorf("%s should remain a 403, the caller is authenticated and merely unscoped", keep) } } }