All checks were successful
Server Check Build (NixCN CMS) TeamCity build finished
Signed-off-by: Asai Neko <sugar@sne.moe>
252 lines
7.3 KiB
Go
252 lines
7.3 KiB
Go
package auth
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/json"
|
|
"io"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"net/url"
|
|
"testing"
|
|
|
|
"github.com/gin-gonic/gin"
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
"nixcn-cms/service/service_auth"
|
|
"nixcn-cms/testutil"
|
|
)
|
|
|
|
func init() { gin.SetMode(gin.TestMode) }
|
|
|
|
func setupAuthRouter(t *testing.T) *gin.Engine {
|
|
t.Helper()
|
|
testutil.Setup(t)
|
|
testutil.SeedClient(t)
|
|
|
|
r := gin.New()
|
|
ApiHandler(r.Group("/auth"))
|
|
return r
|
|
}
|
|
|
|
func postJSON(t *testing.T, r *gin.Engine, path string, body any) *httptest.ResponseRecorder {
|
|
t.Helper()
|
|
b, _ := json.Marshal(body)
|
|
req := httptest.NewRequest(http.MethodPost, path, bytes.NewBuffer(b))
|
|
req.Header.Set("Content-Type", "application/json")
|
|
w := httptest.NewRecorder()
|
|
r.ServeHTTP(w, req)
|
|
return w
|
|
}
|
|
|
|
func getRequest(t *testing.T, r *gin.Engine, path string) *httptest.ResponseRecorder {
|
|
t.Helper()
|
|
req := httptest.NewRequest(http.MethodGet, path, nil)
|
|
w := httptest.NewRecorder()
|
|
r.ServeHTTP(w, req)
|
|
return w
|
|
}
|
|
|
|
func postWithBearer(t *testing.T, r *gin.Engine, path, token string, body any) *httptest.ResponseRecorder {
|
|
t.Helper()
|
|
var bodyReader io.Reader
|
|
if body != nil {
|
|
b, _ := json.Marshal(body)
|
|
bodyReader = bytes.NewBuffer(b)
|
|
}
|
|
req := httptest.NewRequest(http.MethodPost, path, bodyReader)
|
|
req.Header.Set("Content-Type", "application/json")
|
|
if token != "" {
|
|
req.Header.Set("Authorization", "Bearer "+token)
|
|
}
|
|
w := httptest.NewRecorder()
|
|
r.ServeHTTP(w, req)
|
|
return w
|
|
}
|
|
|
|
func extractQueryParam(t *testing.T, rawURL, param string) string {
|
|
t.Helper()
|
|
u, err := url.Parse(rawURL)
|
|
require.NoError(t, err)
|
|
return u.Query().Get(param)
|
|
}
|
|
|
|
// ---- Magic ----
|
|
|
|
func TestMagicHandlerSuccess(t *testing.T) {
|
|
r := setupAuthRouter(t)
|
|
|
|
w := postJSON(t, r, "/auth/magic", service_auth.MagicData{
|
|
ClientId: testutil.TestClientID,
|
|
RedirectUri: "http://localhost/callback",
|
|
State: "s",
|
|
Email: "handler@example.com",
|
|
})
|
|
|
|
assert.Equal(t, http.StatusOK, w.Code)
|
|
var resp map[string]any
|
|
require.NoError(t, json.Unmarshal(w.Body.Bytes(), &resp))
|
|
data, ok := resp["data"].(map[string]any)
|
|
require.True(t, ok)
|
|
assert.NotEmpty(t, data["uri"])
|
|
}
|
|
|
|
func TestMagicHandlerInvalidJSON(t *testing.T) {
|
|
r := setupAuthRouter(t)
|
|
|
|
req := httptest.NewRequest(http.MethodPost, "/auth/magic", bytes.NewBufferString("{invalid"))
|
|
req.Header.Set("Content-Type", "application/json")
|
|
w := httptest.NewRecorder()
|
|
r.ServeHTTP(w, req)
|
|
|
|
assert.Equal(t, http.StatusBadRequest, w.Code)
|
|
}
|
|
|
|
// ---- Redirect ----
|
|
|
|
func TestRedirectHandlerMissingParams(t *testing.T) {
|
|
r := setupAuthRouter(t)
|
|
|
|
w := getRequest(t, r, "/auth/redirect")
|
|
assert.Equal(t, http.StatusBadRequest, w.Code)
|
|
}
|
|
|
|
func TestRedirectHandlerInvalidCode(t *testing.T) {
|
|
r := setupAuthRouter(t)
|
|
|
|
w := getRequest(t, r, "/auth/redirect?client_id="+testutil.TestClientID+
|
|
"&redirect_uri=http://localhost/callback&code=bad-code&state=s")
|
|
|
|
assert.Equal(t, http.StatusForbidden, w.Code)
|
|
}
|
|
|
|
// ---- Token ----
|
|
|
|
func TestTokenHandlerInvalidCode(t *testing.T) {
|
|
r := setupAuthRouter(t)
|
|
|
|
w := postJSON(t, r, "/auth/token", service_auth.TokenData{Code: "invalid"})
|
|
assert.Equal(t, http.StatusForbidden, w.Code)
|
|
}
|
|
|
|
// ---- Refresh ----
|
|
|
|
func TestRefreshHandlerInvalidToken(t *testing.T) {
|
|
r := setupAuthRouter(t)
|
|
|
|
w := postJSON(t, r, "/auth/refresh", service_auth.RefreshData{RefreshToken: "bad"})
|
|
assert.Equal(t, http.StatusUnauthorized, w.Code)
|
|
}
|
|
|
|
// ---- Full flow: Magic → Redirect → Token → Refresh ----
|
|
|
|
func TestAuthFullFlow(t *testing.T) {
|
|
r := setupAuthRouter(t)
|
|
|
|
// 1. Magic
|
|
magicW := postJSON(t, r, "/auth/magic", service_auth.MagicData{
|
|
ClientId: testutil.TestClientID,
|
|
RedirectUri: "http://localhost/callback",
|
|
State: "s",
|
|
Email: "flow@example.com",
|
|
})
|
|
require.Equal(t, http.StatusOK, magicW.Code)
|
|
|
|
var magicResp map[string]any
|
|
require.NoError(t, json.Unmarshal(magicW.Body.Bytes(), &magicResp))
|
|
dataMap := magicResp["data"].(map[string]any)
|
|
rawURI := dataMap["uri"].(string)
|
|
code := extractQueryParam(t, rawURI, "code")
|
|
require.NotEmpty(t, code)
|
|
|
|
// 2. Redirect → 302
|
|
redirectW := getRequest(t, r, "/auth/redirect?client_id="+testutil.TestClientID+
|
|
"&redirect_uri=http://localhost/callback&code="+code+"&state=s")
|
|
require.Equal(t, http.StatusFound, redirectW.Code)
|
|
|
|
location := redirectW.Header().Get("Location")
|
|
require.NotEmpty(t, location)
|
|
tokenCode := extractQueryParam(t, location, "code")
|
|
require.NotEmpty(t, tokenCode)
|
|
|
|
// 3. Token
|
|
tokenW := postJSON(t, r, "/auth/token", service_auth.TokenData{Code: tokenCode})
|
|
require.Equal(t, http.StatusOK, tokenW.Code)
|
|
|
|
var tokenResp map[string]any
|
|
require.NoError(t, json.Unmarshal(tokenW.Body.Bytes(), &tokenResp))
|
|
tokenData := tokenResp["data"].(map[string]any)
|
|
accessToken := tokenData["access_token"].(string)
|
|
refreshToken := tokenData["refresh_token"].(string)
|
|
|
|
// 4. Refresh
|
|
refreshW := postJSON(t, r, "/auth/refresh", service_auth.RefreshData{
|
|
RefreshToken: refreshToken,
|
|
})
|
|
require.Equal(t, http.StatusOK, refreshW.Code)
|
|
|
|
var refreshResp map[string]any
|
|
require.NoError(t, json.Unmarshal(refreshW.Body.Bytes(), &refreshResp))
|
|
refreshData := refreshResp["data"].(map[string]any)
|
|
assert.NotEmpty(t, refreshData["access_token"])
|
|
assert.NotEqual(t, refreshToken, refreshData["refresh_token"])
|
|
_ = accessToken
|
|
}
|
|
|
|
// ---- Exchange ----
|
|
|
|
func TestExchangeHandlerNoAuth(t *testing.T) {
|
|
r := setupAuthRouter(t)
|
|
|
|
w := postJSON(t, r, "/auth/exchange", service_auth.ExchangeData{
|
|
ClientId: testutil.TestClientID,
|
|
RedirectUri: "http://localhost/callback",
|
|
State: "s",
|
|
})
|
|
// No auth header → 401
|
|
assert.Equal(t, http.StatusUnauthorized, w.Code)
|
|
}
|
|
|
|
func TestExchangeHandlerSuccess(t *testing.T) {
|
|
r := setupAuthRouter(t)
|
|
|
|
// Step 1: Magic (debug mode, returns redirect URI with code)
|
|
magicW := postJSON(t, r, "/auth/magic", service_auth.MagicData{
|
|
ClientId: testutil.TestClientID,
|
|
RedirectUri: "http://localhost/callback",
|
|
State: "s",
|
|
Email: "exchange@example.com",
|
|
})
|
|
require.Equal(t, http.StatusOK, magicW.Code)
|
|
var magicResp map[string]any
|
|
require.NoError(t, json.Unmarshal(magicW.Body.Bytes(), &magicResp))
|
|
rawURI := magicResp["data"].(map[string]any)["uri"].(string)
|
|
code := extractQueryParam(t, rawURI, "code")
|
|
|
|
// Step 2: Redirect → produces token code
|
|
redirectW := getRequest(t, r, "/auth/redirect?client_id="+testutil.TestClientID+
|
|
"&redirect_uri=http://localhost/callback&code="+code+"&state=s")
|
|
require.Equal(t, http.StatusFound, redirectW.Code)
|
|
location := redirectW.Header().Get("Location")
|
|
tokenCode := extractQueryParam(t, location, "code")
|
|
|
|
// Step 3: Token → get access token
|
|
tokenW := postJSON(t, r, "/auth/token", service_auth.TokenData{Code: tokenCode})
|
|
require.Equal(t, http.StatusOK, tokenW.Code)
|
|
var tokenResp map[string]any
|
|
require.NoError(t, json.Unmarshal(tokenW.Body.Bytes(), &tokenResp))
|
|
accessToken := tokenResp["data"].(map[string]any)["access_token"].(string)
|
|
|
|
// Step 4: Exchange (requires JWT Bearer token)
|
|
w := postWithBearer(t, r, "/auth/exchange", accessToken, service_auth.ExchangeData{
|
|
ClientId: testutil.TestClientID,
|
|
RedirectUri: "http://localhost/callback",
|
|
State: "s",
|
|
})
|
|
assert.Equal(t, http.StatusOK, w.Code)
|
|
var exchangeResp map[string]any
|
|
require.NoError(t, json.Unmarshal(w.Body.Bytes(), &exchangeResp))
|
|
exchangeData, ok := exchangeResp["data"].(map[string]any)
|
|
require.True(t, ok)
|
|
assert.NotEmpty(t, exchangeData["redirect_uri"])
|
|
}
|