fix: set authenticated user on request context in bearer token auth
tryBearerAuth validated the bearer token but never looked up the associated user or set it on the request context. This meant downstream handlers calling GetCurrentUser would get nil even with a valid token. Changes: - Add ContextWithUser/UserFromContext helpers in auth package - tryBearerAuth now looks up the user by token's UserID and sets it on the request context via auth.ContextWithUser - GetCurrentUser checks context first before falling back to session cookie - Add integration tests for bearer auth user context
This commit is contained in:
160
internal/middleware/bearer_auth_test.go
Normal file
160
internal/middleware/bearer_auth_test.go
Normal file
@@ -0,0 +1,160 @@
|
||||
package middleware_test
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"go.uber.org/fx"
|
||||
|
||||
"git.eeqj.de/sneak/upaas/internal/config"
|
||||
"git.eeqj.de/sneak/upaas/internal/database"
|
||||
"git.eeqj.de/sneak/upaas/internal/globals"
|
||||
"git.eeqj.de/sneak/upaas/internal/logger"
|
||||
"git.eeqj.de/sneak/upaas/internal/middleware"
|
||||
"git.eeqj.de/sneak/upaas/internal/models"
|
||||
"git.eeqj.de/sneak/upaas/internal/service/auth"
|
||||
)
|
||||
|
||||
// setupMiddleware creates a Middleware with a real SQLite database for
|
||||
// integration testing.
|
||||
func setupMiddleware(t *testing.T) (*middleware.Middleware, *auth.Service, *database.Database) {
|
||||
t.Helper()
|
||||
|
||||
tmpDir := t.TempDir()
|
||||
|
||||
globals.SetAppname("upaas-test")
|
||||
globals.SetVersion("test")
|
||||
|
||||
globalsInst, err := globals.New(fx.Lifecycle(nil))
|
||||
require.NoError(t, err)
|
||||
|
||||
loggerInst, err := logger.New(
|
||||
fx.Lifecycle(nil),
|
||||
logger.Params{Globals: globalsInst},
|
||||
)
|
||||
require.NoError(t, err)
|
||||
|
||||
cfg := &config.Config{
|
||||
Port: 8080,
|
||||
DataDir: tmpDir,
|
||||
SessionSecret: "test-secret-key-at-least-32-chars!!",
|
||||
}
|
||||
_ = filepath.Join(tmpDir, "upaas.db")
|
||||
|
||||
dbInst, err := database.New(fx.Lifecycle(nil), database.Params{
|
||||
Logger: loggerInst,
|
||||
Config: cfg,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
authSvc, err := auth.New(fx.Lifecycle(nil), auth.ServiceParams{
|
||||
Logger: loggerInst,
|
||||
Config: cfg,
|
||||
Database: dbInst,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
mw, err := middleware.New(fx.Lifecycle(nil), middleware.Params{
|
||||
Logger: loggerInst,
|
||||
Globals: globalsInst,
|
||||
Config: cfg,
|
||||
Auth: authSvc,
|
||||
Database: dbInst,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
return mw, authSvc, dbInst
|
||||
}
|
||||
|
||||
func TestAPISessionAuth_BearerTokenSetsUserContext(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
mw, authSvc, dbInst := setupMiddleware(t)
|
||||
ctx := t.Context()
|
||||
|
||||
// Create a user.
|
||||
user, err := authSvc.CreateUser(ctx, "testuser", "password123")
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, user)
|
||||
|
||||
// Create an API token for the user.
|
||||
rawToken, err := models.GenerateToken()
|
||||
require.NoError(t, err)
|
||||
|
||||
tokenHash := database.HashAPIToken(rawToken)
|
||||
apiToken := models.NewAPIToken(dbInst)
|
||||
apiToken.UserID = user.ID
|
||||
apiToken.Name = "test-token"
|
||||
apiToken.TokenHash = tokenHash
|
||||
|
||||
err = apiToken.Save(ctx)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Build a handler behind APISessionAuth that checks user context.
|
||||
var gotUser *models.User
|
||||
|
||||
var getUserErr error
|
||||
|
||||
handler := mw.APISessionAuth()(http.HandlerFunc(
|
||||
func(w http.ResponseWriter, r *http.Request) {
|
||||
gotUser, getUserErr = authSvc.GetCurrentUser(r.Context(), r)
|
||||
|
||||
w.WriteHeader(http.StatusOK)
|
||||
},
|
||||
))
|
||||
|
||||
// Make request with bearer token.
|
||||
req := httptest.NewRequest(http.MethodGet, "/api/test", nil)
|
||||
req.Header.Set("Authorization", "Bearer "+rawToken)
|
||||
|
||||
rec := httptest.NewRecorder()
|
||||
handler.ServeHTTP(rec, req)
|
||||
|
||||
assert.Equal(t, http.StatusOK, rec.Code)
|
||||
require.NoError(t, getUserErr)
|
||||
require.NotNil(t, gotUser, "GetCurrentUser should return the user for bearer auth")
|
||||
assert.Equal(t, user.ID, gotUser.ID)
|
||||
assert.Equal(t, "testuser", gotUser.Username)
|
||||
}
|
||||
|
||||
func TestAPISessionAuth_NoBearerTokenReturns401(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
mw, _, _ := setupMiddleware(t)
|
||||
|
||||
handler := mw.APISessionAuth()(http.HandlerFunc(
|
||||
func(w http.ResponseWriter, _ *http.Request) {
|
||||
w.WriteHeader(http.StatusOK)
|
||||
},
|
||||
))
|
||||
|
||||
req := httptest.NewRequest(http.MethodGet, "/api/test", nil)
|
||||
rec := httptest.NewRecorder()
|
||||
handler.ServeHTTP(rec, req)
|
||||
|
||||
assert.Equal(t, http.StatusUnauthorized, rec.Code)
|
||||
}
|
||||
|
||||
func TestAPISessionAuth_InvalidBearerTokenReturns401(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
mw, _, _ := setupMiddleware(t)
|
||||
|
||||
handler := mw.APISessionAuth()(http.HandlerFunc(
|
||||
func(w http.ResponseWriter, _ *http.Request) {
|
||||
w.WriteHeader(http.StatusOK)
|
||||
},
|
||||
))
|
||||
|
||||
req := httptest.NewRequest(http.MethodGet, "/api/test", nil)
|
||||
req.Header.Set("Authorization", "Bearer invalid-token")
|
||||
|
||||
rec := httptest.NewRecorder()
|
||||
handler.ServeHTTP(rec, req)
|
||||
|
||||
assert.Equal(t, http.StatusUnauthorized, rec.Code)
|
||||
}
|
||||
Reference in New Issue
Block a user