Auth: Add feature flag to move token rotation to client (#65060)

* FeatureToggle: Add toggle to use a new way of rotating tokens

* API: Add endpoints to perform token rotation, one endpoint for api request and one endpoint for redirectsd

* Auth: Aling not authorized handling between auth middleware and access
control middleware

* API: add utility function to get redirect for login

* API: Handle token rotation redirect for login page

* Frontend: Add job scheduling for token rotation and make call to token rotation as fallback in retry request

* ContextHandler: Prevent in-request rotation if feature flag is enabled and check if token needs to be rotated

* AuthN: Prevent in-request rotation if feature flag is enabled and check if token needs to be rotated

* Cookies: Add option NotHttpOnly

* AuthToken: Add helper function to get next rotation time and another function to check if token need to be rotated

* AuthN: Add function to delete session cookie and set expiry cookie

Co-authored-by: Ieva <ieva.vasiljeva@grafana.com>
This commit is contained in:
Karl Persson
2023-03-23 14:39:04 +01:00
committed by GitHub
co-authored by Ieva
parent d13488a435
commit 382b24742a
30 changed files with 813 additions and 261 deletions
+57 -3
View File
@@ -8,6 +8,7 @@ import (
"testing"
"time"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"golang.org/x/sync/singleflight"
@@ -311,7 +312,7 @@ func TestUserAuthToken(t *testing.T) {
require.Nil(t, err)
require.NotNil(t, lookedUpUserToken)
require.Equal(t, model.Id, lookedUpUserToken.Id)
require.True(t, lookedUpUserToken.AuthTokenSeen)
require.False(t, lookedUpUserToken.AuthTokenSeen)
getTime = func() time.Time {
return now.Add(time.Hour + (2 * time.Minute))
@@ -320,7 +321,7 @@ func TestUserAuthToken(t *testing.T) {
lookedUpUserToken, err = ctx.tokenService.LookupToken(context.Background(), unhashedPrev)
require.Nil(t, err)
require.NotNil(t, lookedUpUserToken)
require.True(t, lookedUpUserToken.AuthTokenSeen)
require.False(t, lookedUpUserToken.AuthTokenSeen)
lookedUpModel, err := ctx.getAuthTokenByID(lookedUpUserToken.Id)
require.Nil(t, err)
@@ -390,7 +391,7 @@ func TestUserAuthToken(t *testing.T) {
require.True(t, lookedUpModel.AuthTokenSeen)
})
t.Run("Rotate token", func(t *testing.T) {
t.Run("TryRotateToken", func(t *testing.T) {
t.Run("Should rotate current token and previous token when auth token seen", func(t *testing.T) {
getTime = func() time.Time { return now }
userToken, err := ctx.tokenService.CreateToken(context.Background(), user,
@@ -471,6 +472,59 @@ func TestUserAuthToken(t *testing.T) {
})
})
t.Run("RotateToken", func(t *testing.T) {
var prev string
token, err := ctx.tokenService.CreateToken(context.Background(), user, nil, "")
require.NoError(t, err)
t.Run("should rotate token when called with current auth token", func(t *testing.T) {
prev = token.UnhashedToken
token, err = ctx.tokenService.RotateToken(context.Background(), auth.RotateCommand{UnHashedToken: token.UnhashedToken})
require.NoError(t, err)
assert.True(t, token.UnhashedToken != prev)
assert.True(t, token.PrevAuthToken == hashToken(prev))
})
t.Run("should rotate token when called with previous", func(t *testing.T) {
newPrev := token.UnhashedToken
token, err = ctx.tokenService.RotateToken(context.Background(), auth.RotateCommand{UnHashedToken: prev})
require.NoError(t, err)
assert.True(t, token.PrevAuthToken == hashToken(newPrev))
})
t.Run("should not rotate token when called with old previous", func(t *testing.T) {
_, err = ctx.tokenService.RotateToken(context.Background(), auth.RotateCommand{UnHashedToken: prev})
require.ErrorIs(t, err, auth.ErrUserTokenNotFound)
})
t.Run("should return error when token is revoked", func(t *testing.T) {
revokedToken, err := ctx.tokenService.CreateToken(context.Background(), user, nil, "")
require.NoError(t, err)
// mark token as revoked
err = ctx.sqlstore.WithDbSession(context.Background(), func(sess *db.Session) error {
_, err := sess.Exec("UPDATE user_auth_token SET revoked_at = 1 WHERE id = ?", revokedToken.Id)
return err
})
require.NoError(t, err)
_, err = ctx.tokenService.RotateToken(context.Background(), auth.RotateCommand{UnHashedToken: revokedToken.UnhashedToken})
assert.ErrorIs(t, err, auth.ErrInvalidSessionToken)
})
t.Run("should return error when token has expired", func(t *testing.T) {
expiredToken, err := ctx.tokenService.CreateToken(context.Background(), user, nil, "")
require.NoError(t, err)
// mark token as expired
err = ctx.sqlstore.WithDbSession(context.Background(), func(sess *db.Session) error {
_, err := sess.Exec("UPDATE user_auth_token SET created_at = 1 WHERE id = ?", expiredToken.Id)
return err
})
require.NoError(t, err)
_, err = ctx.tokenService.RotateToken(context.Background(), auth.RotateCommand{UnHashedToken: expiredToken.UnhashedToken})
assert.ErrorIs(t, err, auth.ErrInvalidSessionToken)
})
})
t.Run("When populating userAuthToken from UserToken should copy all properties", func(t *testing.T) {
ut := auth.UserToken{
Id: 1,