feat(auth): add ability to regenerate access tokens (#6963)
- Add the ability to regenerate existing access tokens in the UI. This preserves the ID of the access token, but generates a new salt and token contents. - Integration test added. - Unit test added. - Resolves #6880 Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/6963 Reviewed-by: 0ko <0ko@noreply.codeberg.org> Reviewed-by: Gusted <gusted@noreply.codeberg.org> Co-authored-by: Dmitrii Sharshakov <d3dx12.xx@gmail.com> Co-committed-by: Dmitrii Sharshakov <d3dx12.xx@gmail.com>
This commit is contained in:
parent
9dea54a9d6
commit
30982b9e7b
8 changed files with 176 additions and 7 deletions
|
@ -98,6 +98,15 @@ func init() {
|
|||
|
||||
// NewAccessToken creates new access token.
|
||||
func NewAccessToken(ctx context.Context, t *AccessToken) error {
|
||||
err := generateAccessToken(t)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = db.GetEngine(ctx).Insert(t)
|
||||
return err
|
||||
}
|
||||
|
||||
func generateAccessToken(t *AccessToken) error {
|
||||
salt, err := util.CryptoRandomString(10)
|
||||
if err != nil {
|
||||
return err
|
||||
|
@ -110,8 +119,7 @@ func NewAccessToken(ctx context.Context, t *AccessToken) error {
|
|||
t.Token = hex.EncodeToString(token)
|
||||
t.TokenHash = HashToken(t.Token, t.TokenSalt)
|
||||
t.TokenLastEight = t.Token[len(t.Token)-8:]
|
||||
_, err = db.GetEngine(ctx).Insert(t)
|
||||
return err
|
||||
return nil
|
||||
}
|
||||
|
||||
// DisplayPublicOnly whether to display this as a public-only token.
|
||||
|
@ -234,3 +242,25 @@ func DeleteAccessTokenByID(ctx context.Context, id, userID int64) error {
|
|||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// RegenerateAccessTokenByID regenerates access token by given ID.
|
||||
// It regenerates token and salt, as well as updates the creation time.
|
||||
func RegenerateAccessTokenByID(ctx context.Context, id, userID int64) (*AccessToken, error) {
|
||||
t := &AccessToken{}
|
||||
found, err := db.GetEngine(ctx).Where("id = ? AND uid = ?", id, userID).Get(t)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
} else if !found {
|
||||
return nil, ErrAccessTokenNotExist{}
|
||||
}
|
||||
|
||||
err = generateAccessToken(t)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Reset the creation time, token is unused
|
||||
t.UpdatedUnix = timeutil.TimeStampNow()
|
||||
|
||||
return t, UpdateAccessToken(ctx, t)
|
||||
}
|
||||
|
|
|
@ -131,3 +131,28 @@ func TestDeleteAccessTokenByID(t *testing.T) {
|
|||
require.Error(t, err)
|
||||
assert.True(t, auth_model.IsErrAccessTokenNotExist(err))
|
||||
}
|
||||
|
||||
func TestRegenerateAccessTokenByID(t *testing.T) {
|
||||
require.NoError(t, unittest.PrepareTestDatabase())
|
||||
|
||||
token, err := auth_model.GetAccessTokenBySHA(db.DefaultContext, "4c6f36e6cf498e2a448662f915d932c09c5a146c")
|
||||
require.NoError(t, err)
|
||||
|
||||
newToken, err := auth_model.RegenerateAccessTokenByID(db.DefaultContext, token.ID, 1)
|
||||
require.NoError(t, err)
|
||||
unittest.AssertNotExistsBean(t, &auth_model.AccessToken{ID: token.ID, UID: token.UID, TokenHash: token.TokenHash})
|
||||
newToken = &auth_model.AccessToken{
|
||||
ID: newToken.ID,
|
||||
UID: newToken.UID,
|
||||
TokenHash: newToken.TokenHash,
|
||||
}
|
||||
unittest.AssertExistsAndLoadBean(t, newToken)
|
||||
|
||||
// Token has been recreated, new salt and hash, but should retain the same ID, UID, Name and Scope
|
||||
assert.Equal(t, token.ID, newToken.ID)
|
||||
assert.NotEqual(t, token.TokenHash, newToken.TokenHash)
|
||||
assert.NotEqual(t, token.TokenSalt, newToken.TokenSalt)
|
||||
assert.Equal(t, token.UID, newToken.UID)
|
||||
assert.Equal(t, token.Name, newToken.Name)
|
||||
assert.Equal(t, token.Scope, newToken.Scope)
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue