From e25c252fc266c73b72e65719b712194159bb890c Mon Sep 17 00:00:00 2001 From: Gani Georgiev Date: Tue, 17 Jan 2023 23:04:13 +0200 Subject: [PATCH] [#1623] added apis.RecordAuthResponse helper --- CHANGELOG.md | 2 + apis/record_auth.go | 55 +----------------- apis/record_helpers.go | 51 ++++++++++++++++ apis/record_helpers_test.go | 112 ++++++++++++++++++++++++++++++++++++ 4 files changed, 168 insertions(+), 52 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ed8ff393..c837e1de 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -33,6 +33,8 @@ - Refactored all `forms` Submit interceptors to use a Generic data type as their payload. +- Added new helper `apis.RecordAuthResponse(app, httpContext, record, meta)` to return a standard Record auth API response ([#1623](https://github.com/pocketbase/pocketbase/issues/1623)). + ## v0.11.2 diff --git a/apis/record_auth.go b/apis/record_auth.go index c7af8b59..42669dd7 100644 --- a/apis/record_auth.go +++ b/apis/record_auth.go @@ -5,7 +5,6 @@ import ( "fmt" "log" "net/http" - "strings" "github.com/labstack/echo/v5" "github.com/pocketbase/dbx" @@ -14,7 +13,6 @@ import ( "github.com/pocketbase/pocketbase/forms" "github.com/pocketbase/pocketbase/models" "github.com/pocketbase/pocketbase/resolvers" - "github.com/pocketbase/pocketbase/tokens" "github.com/pocketbase/pocketbase/tools/auth" "github.com/pocketbase/pocketbase/tools/routine" "github.com/pocketbase/pocketbase/tools/search" @@ -51,53 +49,6 @@ type recordAuthApi struct { app core.App } -func (api *recordAuthApi) authResponse(c echo.Context, authRecord *models.Record, meta any) error { - token, tokenErr := tokens.NewRecordAuthToken(api.app, authRecord) - if tokenErr != nil { - return NewBadRequestError("Failed to create auth token.", tokenErr) - } - - event := &core.RecordAuthEvent{ - HttpContext: c, - Record: authRecord, - Token: token, - Meta: meta, - } - - return api.app.OnRecordAuthRequest().Trigger(event, func(e *core.RecordAuthEvent) error { - // allow always returning the email address of the authenticated account - e.Record.IgnoreEmailVisibility(true) - - // expand record relations - expands := strings.Split(c.QueryParam(expandQueryParam), ",") - if len(expands) > 0 { - // create a copy of the cached request data and adjust it to the current auth record - requestData := *RequestData(e.HttpContext) - requestData.Admin = nil - requestData.AuthRecord = e.Record - failed := api.app.Dao().ExpandRecord( - e.Record, - expands, - expandFetch(api.app.Dao(), &requestData), - ) - if len(failed) > 0 && api.app.IsDebug() { - log.Println("Failed to expand relations: ", failed) - } - } - - result := map[string]any{ - "token": e.Token, - "record": e.Record, - } - - if e.Meta != nil { - result["meta"] = e.Meta - } - - return e.HttpContext.JSON(http.StatusOK, result) - }) -} - func (api *recordAuthApi) authRefresh(c echo.Context) error { record, _ := c.Get(ContextAuthRecordKey).(*models.Record) if record == nil { @@ -110,7 +61,7 @@ func (api *recordAuthApi) authRefresh(c echo.Context) error { } handlerErr := api.app.OnRecordBeforeAuthRefreshRequest().Trigger(event, func(e *core.RecordAuthRefreshEvent) error { - return api.authResponse(e.HttpContext, e.Record, nil) + return RecordAuthResponse(api.app, e.HttpContext, e.Record, nil) }) if handlerErr == nil { @@ -273,7 +224,7 @@ func (api *recordAuthApi) authWithOAuth2(c echo.Context) error { e.Record = data.Record e.OAuth2User = data.OAuth2User - return api.authResponse(e.HttpContext, e.Record, e.OAuth2User) + return RecordAuthResponse(api.app, e.HttpContext, e.Record, e.OAuth2User) }) } }) @@ -313,7 +264,7 @@ func (api *recordAuthApi) authWithPassword(c echo.Context) error { return NewBadRequestError("Failed to authenticate.", err) } - return api.authResponse(e.HttpContext, e.Record, nil) + return RecordAuthResponse(api.app, e.HttpContext, e.Record, nil) }) } }) diff --git a/apis/record_helpers.go b/apis/record_helpers.go index d9c1576e..a8f77954 100644 --- a/apis/record_helpers.go +++ b/apis/record_helpers.go @@ -2,13 +2,17 @@ package apis import ( "fmt" + "log" + "net/http" "strings" "github.com/labstack/echo/v5" "github.com/pocketbase/dbx" + "github.com/pocketbase/pocketbase/core" "github.com/pocketbase/pocketbase/daos" "github.com/pocketbase/pocketbase/models" "github.com/pocketbase/pocketbase/resolvers" + "github.com/pocketbase/pocketbase/tokens" "github.com/pocketbase/pocketbase/tools/rest" "github.com/pocketbase/pocketbase/tools/search" ) @@ -41,6 +45,53 @@ func RequestData(c echo.Context) *models.RequestData { return result } +func RecordAuthResponse(app core.App, c echo.Context, authRecord *models.Record, meta any) error { + token, tokenErr := tokens.NewRecordAuthToken(app, authRecord) + if tokenErr != nil { + return NewBadRequestError("Failed to create auth token.", tokenErr) + } + + event := &core.RecordAuthEvent{ + HttpContext: c, + Record: authRecord, + Token: token, + Meta: meta, + } + + return app.OnRecordAuthRequest().Trigger(event, func(e *core.RecordAuthEvent) error { + // allow always returning the email address of the authenticated account + e.Record.IgnoreEmailVisibility(true) + + // expand record relations + expands := strings.Split(c.QueryParam(expandQueryParam), ",") + if len(expands) > 0 { + // create a copy of the cached request data and adjust it to the current auth record + requestData := *RequestData(e.HttpContext) + requestData.Admin = nil + requestData.AuthRecord = e.Record + failed := app.Dao().ExpandRecord( + e.Record, + expands, + expandFetch(app.Dao(), &requestData), + ) + if len(failed) > 0 && app.IsDebug() { + log.Println("Failed to expand relations: ", failed) + } + } + + result := map[string]any{ + "token": e.Token, + "record": e.Record, + } + + if e.Meta != nil { + result["meta"] = e.Meta + } + + return e.HttpContext.JSON(http.StatusOK, result) + }) +} + // EnrichRecord parses the request context and enrich the provided record: // - expands relations (if defaultExpands and/or ?expand query param is set) // - ensures that the emails of the auth record and its expanded auth relations diff --git a/apis/record_helpers_test.go b/apis/record_helpers_test.go index a147c491..7f96c2ff 100644 --- a/apis/record_helpers_test.go +++ b/apis/record_helpers_test.go @@ -59,6 +59,118 @@ func TestRequestData(t *testing.T) { } } +func TestRecordAuthResponse(t *testing.T) { + app, _ := tests.NewTestApp() + defer app.Cleanup() + + dummyAdmin := &models.Admin{} + dummyAdmin.Id = "id1" + + nonAuthRecord, err := app.Dao().FindRecordById("demo1", "al1h9ijdeojtsjy") + if err != nil { + t.Fatal(err) + } + + authRecord, err := app.Dao().FindRecordById("users", "4q1xlclmfloku33") + if err != nil { + t.Fatal(err) + } + + scenarios := []struct { + name string + record *models.Record + meta any + expectError bool + expectedContent []string + notExpectedContent []string + expectedEvents map[string]int + }{ + { + name: "non auth record", + record: nonAuthRecord, + expectError: true, + }, + { + name: "valid auth record - without meta", + record: authRecord, + expectError: false, + expectedContent: []string{ + `"token":"`, + `"record":{`, + `"id":"`, + `"expand":{"rel":{`, + }, + notExpectedContent: []string{ + `"meta":`, + }, + expectedEvents: map[string]int{ + "OnRecordAuthRequest": 1, + }, + }, + { + name: "valid auth record - with meta", + record: authRecord, + meta: map[string]any{"meta_test": 123}, + expectError: false, + expectedContent: []string{ + `"token":"`, + `"record":{`, + `"id":"`, + `"expand":{"rel":{`, + `"meta":{"meta_test":123`, + }, + expectedEvents: map[string]int{ + "OnRecordAuthRequest": 1, + }, + }, + } + + for _, s := range scenarios { + app.ResetEventCalls() + + e := echo.New() + req := httptest.NewRequest(http.MethodGet, "/?expand=rel", nil) + req.Header.Set(echo.HeaderContentType, echo.MIMEApplicationJSON) + rec := httptest.NewRecorder() + c := e.NewContext(req, rec) + c.Set(apis.ContextAdminKey, dummyAdmin) + + responseErr := apis.RecordAuthResponse(app, c, s.record, s.meta) + + hasErr := responseErr != nil + if hasErr != s.expectError { + t.Fatalf("[%s] Expected hasErr to be %v, got %v (%v)", s.name, s.expectError, hasErr, responseErr) + } + + if len(app.EventCalls) != len(s.expectedEvents) { + t.Fatalf("[%s] Expected events \n%v, \ngot \n%v", s.name, s.expectedEvents, app.EventCalls) + } + for k, v := range s.expectedEvents { + if app.EventCalls[k] != v { + t.Fatalf("[%s] Expected event %s to be called %d times, got %d", s.name, k, v, app.EventCalls[k]) + } + } + + if hasErr { + continue + } + + response := rec.Body.String() + + for _, v := range s.expectedContent { + if !strings.Contains(response, v) { + t.Fatalf("[%s] Missing %v in response \n%v", s.name, v, response) + } + } + + for _, v := range s.notExpectedContent { + if strings.Contains(response, v) { + t.Fatalf("[%s] Unexpected %v in response \n%v", s.name, v, response) + } + } + } +} + func TestEnrichRecords(t *testing.T) { e := echo.New() req := httptest.NewRequest(http.MethodGet, "/?expand=rel_many", nil)