The refreshtoken package encapsulates domain logic surrouding the refresh token. A refresh token is used both for paginating through a collection and for requesting any updates to that collection.
157 lines
5.6 KiB
Go
157 lines
5.6 KiB
Go
// Copyright (c) HashiCorp, Inc.
|
|
// SPDX-License-Identifier: BUSL-1.1
|
|
|
|
// Package refreshtoken encapsulates domain logic surrounding
|
|
// list endpoint refresh tokens. Refresh tokens are used when users
|
|
// paginate through results in our list endpoints, and also to
|
|
// allow users to request new, updated and deleted resources.
|
|
package refreshtoken
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"time"
|
|
|
|
"github.com/hashicorp/boundary/internal/boundary"
|
|
"github.com/hashicorp/boundary/internal/errors"
|
|
"github.com/hashicorp/boundary/internal/types/resource"
|
|
)
|
|
|
|
// UpdatedTimeBuffer is used to automatically adjust the updated
|
|
// time of a refresh token to account for delays between overlapping
|
|
// database transactions.
|
|
const UpdatedTimeBuffer = 30 * time.Second
|
|
|
|
// A Token is returned in list endpoints for the purposes of pagination
|
|
type Token struct {
|
|
CreatedTime time.Time
|
|
UpdatedTime time.Time
|
|
ResourceType resource.Type
|
|
GrantsHash []byte
|
|
LastItemId string
|
|
LastItemUpdatedTime time.Time
|
|
}
|
|
|
|
// New creates a new refresh token from a createdTime, resource type, grants hash, and last item information
|
|
func New(ctx context.Context, createdTime time.Time, updatedTime time.Time, typ resource.Type, grantsHash []byte, lastItemId string, lastItemUpdatedTime time.Time) (*Token, error) {
|
|
const op = "refreshtoken.New"
|
|
|
|
if len(grantsHash) == 0 {
|
|
return nil, errors.New(ctx, errors.InvalidParameter, op, "missing grants hash")
|
|
}
|
|
if createdTime.After(time.Now()) {
|
|
return nil, errors.New(ctx, errors.InvalidParameter, op, "created time is in the future")
|
|
}
|
|
if createdTime.Before(time.Now().AddDate(0, 0, -30)) {
|
|
return nil, errors.New(ctx, errors.InvalidParameter, op, "created time is too old")
|
|
}
|
|
if updatedTime.Before(createdTime) {
|
|
return nil, errors.New(ctx, errors.InvalidParameter, op, "updated time is older than created time")
|
|
}
|
|
if updatedTime.After(time.Now()) {
|
|
return nil, errors.New(ctx, errors.InvalidParameter, op, "updated time is in the future")
|
|
}
|
|
if lastItemId == "" {
|
|
return nil, errors.New(ctx, errors.InvalidParameter, op, "missing last item ID")
|
|
}
|
|
if lastItemUpdatedTime.After(time.Now()) {
|
|
return nil, errors.New(ctx, errors.InvalidParameter, op, "last item updated time is in the future")
|
|
}
|
|
|
|
return &Token{
|
|
CreatedTime: createdTime,
|
|
UpdatedTime: updatedTime,
|
|
ResourceType: typ,
|
|
GrantsHash: grantsHash,
|
|
LastItemId: lastItemId,
|
|
LastItemUpdatedTime: lastItemUpdatedTime,
|
|
}, nil
|
|
}
|
|
|
|
// FromResource creates a new refresh token from a resource and grants hash.
|
|
func FromResource(res boundary.Resource, grantsHash []byte) *Token {
|
|
t := time.Now()
|
|
return &Token{
|
|
CreatedTime: t,
|
|
UpdatedTime: t,
|
|
ResourceType: res.GetResourceType(),
|
|
GrantsHash: grantsHash,
|
|
LastItemId: res.GetPublicId(),
|
|
LastItemUpdatedTime: res.GetUpdateTime().AsTime(),
|
|
}
|
|
}
|
|
|
|
// LastItem returns the last item stored in the token.
|
|
func (rt *Token) LastItem() *Item {
|
|
return &Item{
|
|
publicId: rt.LastItemId,
|
|
updateTime: rt.LastItemUpdatedTime,
|
|
resourceType: rt.ResourceType,
|
|
}
|
|
}
|
|
|
|
// Refresh refreshes a token's updated time. It accounts for overlapping
|
|
// database transactions by subtracting UpdatedTimeBuffer from the
|
|
// provided timestamp while ensuring that the updated time is never
|
|
// before the created time of the token.
|
|
func (rt *Token) Refresh(updatedTime time.Time) *Token {
|
|
rt.UpdatedTime = updatedTime.Add(-UpdatedTimeBuffer)
|
|
if rt.UpdatedTime.Before(rt.CreatedTime) {
|
|
rt.UpdatedTime = rt.CreatedTime
|
|
}
|
|
return rt
|
|
}
|
|
|
|
// RefreshLastItem refreshes a token's updated time and last item.
|
|
// It accounts for overlapping database transactions by subtracting
|
|
// UpdatedTimeBuffer from the provided timestamp while ensuring that
|
|
// the updated time is never before the created time of the token.
|
|
func (rt *Token) RefreshLastItem(res boundary.Resource, updatedTime time.Time) *Token {
|
|
rt.LastItemId = res.GetPublicId()
|
|
rt.LastItemUpdatedTime = res.GetUpdateTime().AsTime()
|
|
rt = rt.Refresh(updatedTime)
|
|
return rt
|
|
}
|
|
|
|
// Validate validates the refresh token.
|
|
func (rt *Token) Validate(
|
|
ctx context.Context,
|
|
expectedResourceType resource.Type,
|
|
expectedGrantsHash []byte,
|
|
) error {
|
|
const op = "refreshtoken.Validate"
|
|
if rt == nil {
|
|
return errors.New(ctx, errors.InvalidParameter, op, "refresh token was missing")
|
|
}
|
|
if len(rt.GrantsHash) == 0 {
|
|
return errors.New(ctx, errors.InvalidParameter, op, "refresh token was missing its grants hash")
|
|
}
|
|
if !bytes.Equal(rt.GrantsHash, expectedGrantsHash) {
|
|
return errors.New(ctx, errors.InvalidParameter, op, "grants have changed since refresh token was issued")
|
|
}
|
|
if rt.CreatedTime.After(time.Now()) {
|
|
return errors.New(ctx, errors.InvalidParameter, op, "refresh token was created in the future")
|
|
}
|
|
// Tokens older than 30 days have expired
|
|
if rt.CreatedTime.Before(time.Now().AddDate(0, 0, -30)) {
|
|
return errors.New(ctx, errors.InvalidParameter, op, "refresh token was expired")
|
|
}
|
|
if rt.UpdatedTime.Before(rt.CreatedTime) {
|
|
return errors.New(ctx, errors.InvalidParameter, op, "refresh token was updated before its creation time")
|
|
}
|
|
if rt.UpdatedTime.After(time.Now()) {
|
|
return errors.New(ctx, errors.InvalidParameter, op, "refresh token was updated in the future")
|
|
}
|
|
if rt.LastItemId == "" {
|
|
return errors.New(ctx, errors.InvalidParameter, op, "refresh token missing last item ID")
|
|
}
|
|
if rt.LastItemUpdatedTime.After(time.Now()) {
|
|
return errors.New(ctx, errors.InvalidParameter, op, "refresh token last item was updated in the future")
|
|
}
|
|
if rt.ResourceType != expectedResourceType {
|
|
return errors.New(ctx, errors.InvalidParameter, op, "refresh token resource type does not match expected resource type")
|
|
}
|
|
|
|
return nil
|
|
}
|