148 lines
6.1 KiB
Go
148 lines
6.1 KiB
Go
package main
|
|
|
|
import (
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"net/url"
|
|
"strings"
|
|
"testing"
|
|
)
|
|
|
|
func TestAdmin首次初始化登录退出完整流程(t *testing.T) {
|
|
db := newMySQLTestDB(t)
|
|
router, err := newRouter(db)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
setupPage := httptest.NewRecorder()
|
|
router.ServeHTTP(setupPage, httptest.NewRequest(http.MethodGet, "/setup", nil))
|
|
if setupPage.Code != http.StatusOK || !strings.Contains(setupPage.Body.String(), "初始化管理员") {
|
|
t.Fatalf("GET /setup = %d,响应:%s", setupPage.Code, setupPage.Body.String())
|
|
}
|
|
if strings.Count(setupPage.Body.String(), `minlength="6"`) != 2 ||
|
|
!strings.Contains(setupPage.Body.String(), "至少 6 个字符") {
|
|
t.Fatalf("初始化表单密码规则未同步为 6 个字符:%s", setupPage.Body.String())
|
|
}
|
|
csrfCookie := findResponseCookie(t, setupPage, "cmautobuy_csrf")
|
|
if csrfCookie.HttpOnly || csrfCookie.Value == "" {
|
|
t.Fatalf("CSRF Cookie 应可供双提交表单使用: %#v", csrfCookie)
|
|
}
|
|
|
|
// 初始化和登录都是公开入口,但 POST 同样必须通过 CSRF。
|
|
missingCSRF := postFormRequest("/setup", url.Values{
|
|
"username": {"admin"}, "password": {"safe-password"},
|
|
"password_confirm": {"safe-password"},
|
|
}, nil)
|
|
missingCSRFResponse := httptest.NewRecorder()
|
|
router.ServeHTTP(missingCSRFResponse, missingCSRF)
|
|
if missingCSRFResponse.Code != http.StatusForbidden {
|
|
t.Fatalf("无 CSRF 初始化 = %d,期望 403", missingCSRFResponse.Code)
|
|
}
|
|
|
|
setup := postFormRequest("/setup", url.Values{
|
|
"csrf_token": {csrfCookie.Value}, "username": {"admin"},
|
|
"password": {"safe-password"}, "password_confirm": {"safe-password"},
|
|
}, csrfCookie)
|
|
setupResponse := httptest.NewRecorder()
|
|
router.ServeHTTP(setupResponse, setup)
|
|
if setupResponse.Code != http.StatusSeeOther || !strings.HasPrefix(setupResponse.Header().Get("Location"), "/login?") {
|
|
t.Fatalf("POST /setup = %d %s", setupResponse.Code, setupResponse.Header().Get("Location"))
|
|
}
|
|
|
|
// 初始化完成后入口永久关闭。
|
|
setupAgain := httptest.NewRecorder()
|
|
router.ServeHTTP(setupAgain, httptest.NewRequest(http.MethodGet, "/setup", nil))
|
|
if setupAgain.Code != http.StatusSeeOther || !strings.HasPrefix(setupAgain.Header().Get("Location"), "/login?") {
|
|
t.Fatalf("再次 GET /setup = %d %s", setupAgain.Code, setupAgain.Header().Get("Location"))
|
|
}
|
|
|
|
wrongLogin := postFormRequest("/login", url.Values{
|
|
"csrf_token": {csrfCookie.Value}, "username": {"admin"},
|
|
"password": {"wrong-password"}, "next": {"/pdd"},
|
|
}, csrfCookie)
|
|
wrongResponse := httptest.NewRecorder()
|
|
router.ServeHTTP(wrongResponse, wrongLogin)
|
|
if wrongResponse.Code != http.StatusUnauthorized || !strings.Contains(wrongResponse.Body.String(), "用户名或密码错误") {
|
|
t.Fatalf("错误密码响应 = %d %s", wrongResponse.Code, wrongResponse.Body.String())
|
|
}
|
|
|
|
login := postFormRequest("/login", url.Values{
|
|
"csrf_token": {csrfCookie.Value}, "username": {"admin"},
|
|
"password": {"safe-password"}, "next": {"/pdd"},
|
|
}, csrfCookie)
|
|
loginResponse := httptest.NewRecorder()
|
|
router.ServeHTTP(loginResponse, login)
|
|
if loginResponse.Code != http.StatusSeeOther || loginResponse.Header().Get("Location") != "/pdd" {
|
|
t.Fatalf("POST /login = %d %s", loginResponse.Code, loginResponse.Header().Get("Location"))
|
|
}
|
|
authCookie := findResponseCookie(t, loginResponse, "cmautobuy_session")
|
|
if !authCookie.HttpOnly || authCookie.Secure || authCookie.SameSite != http.SameSiteLaxMode ||
|
|
authCookie.Path != "/" || authCookie.MaxAge != 12*60*60 {
|
|
t.Fatalf("认证 Cookie 属性不正确: %#v", authCookie)
|
|
}
|
|
|
|
protected := httptest.NewRequest(http.MethodGet, "/shopee", nil)
|
|
protected.AddCookie(authCookie)
|
|
protectedResponse := httptest.NewRecorder()
|
|
router.ServeHTTP(protectedResponse, protected)
|
|
if protectedResponse.Code != http.StatusOK || !strings.Contains(protectedResponse.Body.String(), "admin") ||
|
|
!strings.Contains(protectedResponse.Body.String(), "退出登录") {
|
|
t.Fatalf("登录后业务页 = %d,响应:%s", protectedResponse.Code, protectedResponse.Body.String())
|
|
}
|
|
|
|
logout := postFormRequest("/logout", url.Values{"csrf_token": {csrfCookie.Value}}, csrfCookie)
|
|
logout.AddCookie(authCookie)
|
|
logoutResponse := httptest.NewRecorder()
|
|
router.ServeHTTP(logoutResponse, logout)
|
|
if logoutResponse.Code != http.StatusSeeOther || !strings.HasPrefix(logoutResponse.Header().Get("Location"), "/login?") {
|
|
t.Fatalf("POST /logout = %d %s", logoutResponse.Code, logoutResponse.Header().Get("Location"))
|
|
}
|
|
|
|
oldSession := httptest.NewRequest(http.MethodGet, "/shopee", nil)
|
|
oldSession.AddCookie(authCookie)
|
|
oldSessionResponse := httptest.NewRecorder()
|
|
router.ServeHTTP(oldSessionResponse, oldSession)
|
|
if oldSessionResponse.Code != http.StatusSeeOther || !strings.HasPrefix(oldSessionResponse.Header().Get("Location"), "/login?next=") {
|
|
t.Fatalf("退出后旧 Session 仍可访问: %d %s", oldSessionResponse.Code, oldSessionResponse.Header().Get("Location"))
|
|
}
|
|
}
|
|
|
|
func TestClientAPI不经过网页登录和CSRF(t *testing.T) {
|
|
db := newMySQLTestDB(t)
|
|
router, err := newRouter(db)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
request := httptest.NewRequest(http.MethodPut, "/api/v1/client/registration", strings.NewReader(`{}`))
|
|
request.Header.Set("Content-Type", "application/json")
|
|
response := httptest.NewRecorder()
|
|
router.ServeHTTP(response, request)
|
|
if response.Code != http.StatusBadRequest || !strings.Contains(response.Body.String(), "MISSING_CLIENT_ID") {
|
|
t.Fatalf("Client API 不应被网页登录或 CSRF 拦截: %d %s", response.Code, response.Body.String())
|
|
}
|
|
}
|
|
|
|
func postFormRequest(path string, form url.Values, cookies ...*http.Cookie) *http.Request {
|
|
request := httptest.NewRequest(http.MethodPost, path, strings.NewReader(form.Encode()))
|
|
request.Header.Set("Content-Type", "application/x-www-form-urlencoded")
|
|
for _, cookie := range cookies {
|
|
if cookie != nil {
|
|
request.AddCookie(cookie)
|
|
}
|
|
}
|
|
return request
|
|
}
|
|
|
|
func findResponseCookie(t *testing.T, response *httptest.ResponseRecorder, name string) *http.Cookie {
|
|
t.Helper()
|
|
for _, cookie := range response.Result().Cookies() {
|
|
if cookie.Name == name {
|
|
return cookie
|
|
}
|
|
}
|
|
t.Fatalf("响应缺少 Cookie %s", name)
|
|
return nil
|
|
}
|