feat(api): RequireAuth+RequireProjectAccess middleware, IDOR-scope check/apply по projectID
This commit is contained in:
@@ -18,10 +18,11 @@ import (
|
||||
// --- mocks ---
|
||||
|
||||
type mockAuthStore struct {
|
||||
registerUserFn func(ctx context.Context, email, passwordHash string) (store.User, store.Project, error)
|
||||
getUserByEmailFn func(ctx context.Context, email string) (store.User, error)
|
||||
getUserByIDFn func(ctx context.Context, userID uuid.UUID) (store.User, error)
|
||||
getUserProjectFn func(ctx context.Context, userID uuid.UUID) (store.Project, error)
|
||||
registerUserFn func(ctx context.Context, email, passwordHash string) (store.User, store.Project, error)
|
||||
getUserByEmailFn func(ctx context.Context, email string) (store.User, error)
|
||||
getUserByIDFn func(ctx context.Context, userID uuid.UUID) (store.User, error)
|
||||
getUserProjectFn func(ctx context.Context, userID uuid.UUID) (store.Project, error)
|
||||
getProjectOwnedFn func(ctx context.Context, projectID, userID uuid.UUID) (store.Project, error)
|
||||
}
|
||||
|
||||
func (m *mockAuthStore) RegisterUser(ctx context.Context, email, passwordHash string) (store.User, store.Project, error) {
|
||||
@@ -40,8 +41,13 @@ func (m *mockAuthStore) GetUserProject(ctx context.Context, userID uuid.UUID) (s
|
||||
return m.getUserProjectFn(ctx, userID)
|
||||
}
|
||||
|
||||
func (m *mockAuthStore) GetProjectOwned(ctx context.Context, projectID, userID uuid.UUID) (store.Project, error) {
|
||||
return m.getProjectOwnedFn(ctx, projectID, userID)
|
||||
}
|
||||
|
||||
type mockSessionManager struct {
|
||||
createFn func(ctx context.Context, userID uuid.UUID) (string, time.Time, error)
|
||||
createFn func(ctx context.Context, userID uuid.UUID) (string, time.Time, error)
|
||||
validateFn func(ctx context.Context, token string) (uuid.UUID, error)
|
||||
|
||||
destroyCalled bool
|
||||
destroyToken string
|
||||
@@ -52,7 +58,10 @@ func (m *mockSessionManager) Create(ctx context.Context, userID uuid.UUID) (stri
|
||||
return m.createFn(ctx, userID)
|
||||
}
|
||||
|
||||
func (m *mockSessionManager) Validate(context.Context, string) (uuid.UUID, error) {
|
||||
func (m *mockSessionManager) Validate(ctx context.Context, token string) (uuid.UUID, error) {
|
||||
if m.validateFn != nil {
|
||||
return m.validateFn(ctx, token)
|
||||
}
|
||||
return uuid.Nil, nil
|
||||
}
|
||||
|
||||
@@ -338,7 +347,7 @@ func TestAuthLogout_ClearsSessionAndDestroys(t *testing.T) {
|
||||
// now resolves the authenticated user via GetUserByID and returns their real
|
||||
// email, instead of leaving it blank.
|
||||
func TestAuthMe_ReturnsRealEmail(t *testing.T) {
|
||||
a, authStore, _ := newTestAuthAPI()
|
||||
a, authStore, sessions := newTestAuthAPI()
|
||||
userID := uuid.New()
|
||||
projectID := uuid.New()
|
||||
authStore.getUserByIDFn = func(_ context.Context, id uuid.UUID) (store.User, error) {
|
||||
@@ -350,10 +359,15 @@ func TestAuthMe_ReturnsRealEmail(t *testing.T) {
|
||||
authStore.getUserProjectFn = func(_ context.Context, uid uuid.UUID) (store.Project, error) {
|
||||
return store.Project{ID: projectID, UserID: uid, Name: "default"}, nil
|
||||
}
|
||||
// /me is now behind RequireAuth: the session cookie must resolve to
|
||||
// userID via Sessions.Validate rather than being injected directly.
|
||||
sessions.validateFn = func(context.Context, string) (uuid.UUID, error) {
|
||||
return userID, nil
|
||||
}
|
||||
|
||||
router := NewRouter(a)
|
||||
req := httptest.NewRequest(http.MethodGet, "/api/v1/auth/me", nil)
|
||||
req = req.WithContext(context.WithValue(req.Context(), ctxKeyUserID{}, userID))
|
||||
req.AddCookie(&http.Cookie{Name: sessionCookieName, Value: "some-valid-token"})
|
||||
w := httptest.NewRecorder()
|
||||
router.ServeHTTP(w, req)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user