2022-04-19 21:50:15 +01:00
|
|
|
// Copyright (C) 2022 Storj Labs, Inc.
|
|
|
|
// See LICENSE for copying information.
|
|
|
|
|
|
|
|
package consoleauth
|
|
|
|
|
|
|
|
import (
|
|
|
|
"context"
|
2022-06-05 23:41:38 +01:00
|
|
|
"crypto/subtle"
|
2022-04-19 21:50:15 +01:00
|
|
|
"encoding/base64"
|
|
|
|
"time"
|
|
|
|
|
|
|
|
"github.com/spacemonkeygo/monkit/v3"
|
|
|
|
|
|
|
|
"storj.io/common/uuid"
|
|
|
|
)
|
|
|
|
|
|
|
|
var mon = monkit.Package()
|
|
|
|
|
|
|
|
// Config contains configuration parameters for console auth.
|
|
|
|
type Config struct {
|
2022-10-11 22:36:29 +01:00
|
|
|
TokenExpirationTime time.Duration `help:"expiration time for account recovery and activation tokens" default:"30m"`
|
2022-04-19 21:50:15 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
// Service handles creating, signing, and checking the expiration of auth tokens.
|
|
|
|
type Service struct {
|
|
|
|
config Config
|
|
|
|
Signer
|
|
|
|
}
|
|
|
|
|
|
|
|
// NewService creates a new consoleauth service.
|
|
|
|
func NewService(config Config, signer Signer) *Service {
|
|
|
|
return &Service{
|
|
|
|
config: config,
|
|
|
|
Signer: signer,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Signer creates signature for provided data.
|
|
|
|
type Signer interface {
|
|
|
|
Sign(data []byte) ([]byte, error)
|
|
|
|
}
|
|
|
|
|
|
|
|
// CreateToken creates a new auth token.
|
|
|
|
func (s *Service) CreateToken(ctx context.Context, id uuid.UUID, email string) (_ string, err error) {
|
|
|
|
defer mon.Task()(&ctx)(&err)
|
|
|
|
claims := &Claims{
|
|
|
|
ID: id,
|
|
|
|
Expiration: time.Now().Add(s.config.TokenExpirationTime),
|
|
|
|
}
|
|
|
|
if email != "" {
|
|
|
|
claims.Email = email
|
|
|
|
}
|
|
|
|
|
|
|
|
return s.createToken(ctx, claims)
|
|
|
|
}
|
|
|
|
|
|
|
|
// createToken creates string representation.
|
|
|
|
func (s *Service) createToken(ctx context.Context, claims *Claims) (_ string, err error) {
|
|
|
|
defer mon.Task()(&ctx)(&err)
|
|
|
|
|
|
|
|
json, err := claims.JSON()
|
|
|
|
if err != nil {
|
|
|
|
return "", err
|
|
|
|
}
|
|
|
|
|
|
|
|
token := Token{Payload: json}
|
2022-06-05 23:41:38 +01:00
|
|
|
signature, err := s.SignToken(token)
|
2022-04-19 21:50:15 +01:00
|
|
|
if err != nil {
|
|
|
|
return "", err
|
|
|
|
}
|
2022-06-05 23:41:38 +01:00
|
|
|
token.Signature = signature
|
2022-04-19 21:50:15 +01:00
|
|
|
|
|
|
|
return token.String(), nil
|
|
|
|
}
|
|
|
|
|
2022-06-05 23:41:38 +01:00
|
|
|
// SignToken returns token signature.
|
|
|
|
func (s *Service) SignToken(token Token) ([]byte, error) {
|
2022-04-19 21:50:15 +01:00
|
|
|
encoded := base64.URLEncoding.EncodeToString(token.Payload)
|
|
|
|
|
|
|
|
signature, err := s.Signer.Sign([]byte(encoded))
|
|
|
|
if err != nil {
|
2022-06-05 23:41:38 +01:00
|
|
|
return nil, err
|
2022-04-19 21:50:15 +01:00
|
|
|
}
|
|
|
|
|
2022-06-05 23:41:38 +01:00
|
|
|
return signature, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// ValidateToken determines token validity using its signature.
|
|
|
|
func (s *Service) ValidateToken(token Token) (bool, error) {
|
|
|
|
signature, err := s.SignToken(token)
|
|
|
|
if err != nil {
|
|
|
|
return false, err
|
|
|
|
}
|
|
|
|
|
|
|
|
return subtle.ConstantTimeCompare(signature, token.Signature) == 1, nil
|
2022-04-19 21:50:15 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
// IsExpired returns whether token is expired.
|
|
|
|
func (s *Service) IsExpired(now, tokenCreatedAt time.Time) bool {
|
|
|
|
return now.Sub(tokenCreatedAt) > s.config.TokenExpirationTime
|
|
|
|
}
|