diff --git a/CHANGELOG.md b/CHANGELOG.md index 13d94588..1c4de5db 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ ## v0.29.0 (WIP) +- Enabled calling the `/auth-refresh` endpoint with nonrenewable tokens. + _When used with nonrenewable tokens (e.g. impersonate) the endpoint will simply return the same token with the up-to-date user data associated with it._ + + - Added the triggered rate rimit rule in the error log `details`. - Other minor improvements (wrapped the backup restore in a transaction as an extra precaution, updated npm deps, regenerated JSVM docs with the recent tygoja changes, etc.). @@ -502,7 +506,7 @@ There are a lot of changes but to highlight some of the most notable ones: - Admins are now system `_superusers` auth records. - Builtin rate limiter (_supports tags, wildcards and exact routes matching_). - Batch/transactional Web API endpoint. -- Impersonate Web API endpoint (_it could be also used for generating fixed/non-refreshable superuser tokens, aka. "API keys"_). +- Enabled Web API endpoint (_it could be also used for generating fixed/nonrenewable superuser tokens, aka. "API keys"_). - Support for custom user request activity log attributes. - One-Time Password (OTP) auth method (_via email code_). - Multi-Factor Authentication (MFA) support (_currently requires any 2 different auth methods to be used_). diff --git a/apis/record_auth_refresh.go b/apis/record_auth_refresh.go index 63230a87..8d42962f 100644 --- a/apis/record_auth_refresh.go +++ b/apis/record_auth_refresh.go @@ -12,18 +12,24 @@ func recordAuthRefresh(e *core.RequestEvent) error { return e.NotFoundError("Missing auth record context.", nil) } - currentToken := getAuthTokenFromRequest(e) - claims, _ := security.ParseUnverifiedJWT(currentToken) - if v, ok := claims[core.TokenClaimRefreshable]; !ok || !cast.ToBool(v) { - return e.ForbiddenError("The current auth token is not refreshable.", nil) - } - event := new(core.RecordAuthRefreshRequestEvent) event.RequestEvent = e event.Collection = record.Collection() event.Record = record return e.App.OnRecordAuthRefreshRequest().Trigger(event, func(e *core.RecordAuthRefreshRequestEvent) error { - return RecordAuthResponse(e.RequestEvent, e.Record, "", nil) + token := getAuthTokenFromRequest(e.RequestEvent) + + // skip token renewal if the token's payload doesn't explicitly allow it (e.g. impersonate tokens) + claims, _ := security.ParseUnverifiedJWT(token) // + if v, ok := claims[core.TokenClaimRefreshable]; ok && cast.ToBool(v) { + var tokenErr error + token, tokenErr = e.Record.NewAuthToken() + if tokenErr != nil { + return e.InternalServerError("Failed to refresh auth token.", tokenErr) + } + } + + return recordAuthResponse(e.RequestEvent, e.Record, token, "", nil) }) } diff --git a/apis/record_auth_refresh_test.go b/apis/record_auth_refresh_test.go index d8e4b835..ffca2efd 100644 --- a/apis/record_auth_refresh_test.go +++ b/apis/record_auth_refresh_test.go @@ -73,6 +73,8 @@ func TestRecordAuthRefresh(t *testing.T) { }, NotExpectedContent: []string{ `"missing":`, + // should return a different token + "eyJhbGciOiJIUzI1NiJ9.eyJpZCI6IjRxMXhsY2xtZmxva3UzMyIsInR5cGUiOiJhdXRoIiwiY29sbGVjdGlvbklkIjoiX3BiX3VzZXJzX2F1dGhfIiwiZXhwIjoyNTI0NjA0NDYxLCJyZWZyZXNoYWJsZSI6dHJ1ZX0.ZT3F0Z3iM-xbGgSG3LEKiEzHrPHr8t8IuHLZGGNuxLo", }, ExpectedEvents: map[string]int{ "*": 0, @@ -88,9 +90,21 @@ func TestRecordAuthRefresh(t *testing.T) { Headers: map[string]string{ "Authorization": "eyJhbGciOiJIUzI1NiJ9.eyJpZCI6IjRxMXhsY2xtZmxva3UzMyIsInR5cGUiOiJhdXRoIiwiY29sbGVjdGlvbklkIjoiX3BiX3VzZXJzX2F1dGhfIiwiZXhwIjoyNTI0NjA0NDYxLCJyZWZyZXNoYWJsZSI6ZmFsc2V9.4IsO6YMsR19crhwl_YWzvRH8pfq2Ri4Gv2dzGyneLak", }, - ExpectedStatus: 403, - ExpectedContent: []string{`"data":{}`}, - ExpectedEvents: map[string]int{"*": 0}, + ExpectedStatus: 200, + ExpectedContent: []string{ + // should return the same token + `"token":"eyJhbGciOiJIUzI1NiJ9.eyJpZCI6IjRxMXhsY2xtZmxva3UzMyIsInR5cGUiOiJhdXRoIiwiY29sbGVjdGlvbklkIjoiX3BiX3VzZXJzX2F1dGhfIiwiZXhwIjoyNTI0NjA0NDYxLCJyZWZyZXNoYWJsZSI6ZmFsc2V9.4IsO6YMsR19crhwl_YWzvRH8pfq2Ri4Gv2dzGyneLak"`, + `"record":`, + `"id":"4q1xlclmfloku33"`, + `"emailVisibility":false`, + `"email":"test@example.com"`, // the owner can always view their email address + }, + ExpectedEvents: map[string]int{ + "*": 0, + "OnRecordAuthRefreshRequest": 1, + "OnRecordAuthRequest": 1, + "OnRecordEnrich": 1, + }, }, { Name: "unverified auth record in onlyVerified collection",