149 lines
4.1 KiB
Go
149 lines
4.1 KiB
Go
package tokenservice
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"strings"
|
|
|
|
"git.maronato.dev/maronato/goshort/internal/errs"
|
|
"git.maronato.dev/maronato/goshort/internal/storage"
|
|
"git.maronato.dev/maronato/goshort/internal/storage/models"
|
|
randomutil "git.maronato.dev/maronato/goshort/internal/util/random"
|
|
)
|
|
|
|
const (
|
|
// DefaultTokenLength is the default length of a token.
|
|
TokenLength = 32
|
|
// TokenPrefix is the prefix of a token.
|
|
TokenPrefix = "gst_"
|
|
// MinTokenNameLength is the minimum length of a token name.
|
|
MinTokenNameLength = 4
|
|
// MaxTokenNameLength is the maximum length of a token name.
|
|
MaxTokenNameLength = 32
|
|
)
|
|
|
|
// TokenService is the service that handles tokens.
|
|
type TokenService struct {
|
|
// db is the storage used by the service.
|
|
db storage.Storage
|
|
}
|
|
|
|
// NewTokenService creates a new TokenService.
|
|
func NewTokenService(db storage.Storage) *TokenService {
|
|
return &TokenService{
|
|
db: db,
|
|
}
|
|
}
|
|
|
|
// FindToken finds a token in the storage using its value.
|
|
func (s *TokenService) FindToken(ctx context.Context, value string) (*models.Token, error) {
|
|
// Check if the token has the prefix
|
|
if !strings.HasPrefix(value, TokenPrefix) {
|
|
return &models.Token{}, errs.ErrInvalidToken
|
|
}
|
|
|
|
if len(value) != TokenLength+len(TokenPrefix) {
|
|
return &models.Token{}, errs.ErrInvalidToken
|
|
}
|
|
|
|
// Get the token from storage
|
|
token, err := s.db.FindToken(ctx, value)
|
|
if err != nil {
|
|
return token, fmt.Errorf("could not get token from storage: %w", err)
|
|
}
|
|
|
|
return token, nil
|
|
}
|
|
|
|
// FindTokenByID finds a token in the storage using its ID.
|
|
func (s *TokenService) FindTokenByID(ctx context.Context, id string) (*models.Token, error) {
|
|
// Check if the ID is valid
|
|
if !models.LooksLikeID(id) {
|
|
return &models.Token{}, errs.ErrInvalidTokenID
|
|
}
|
|
|
|
// Get the token from storage
|
|
token, err := s.db.FindTokenByID(ctx, id)
|
|
if err != nil {
|
|
return token, fmt.Errorf("could not get token from storage: %w", err)
|
|
}
|
|
|
|
return token, nil
|
|
}
|
|
|
|
func (s *TokenService) ListTokens(ctx context.Context, user *models.User) ([]*models.Token, error) {
|
|
// Get the tokens from storage
|
|
tokens, err := s.db.ListTokens(ctx, user)
|
|
if err != nil {
|
|
return tokens, fmt.Errorf("could not get tokens from storage: %w", err)
|
|
}
|
|
|
|
return tokens, nil
|
|
}
|
|
|
|
// CreateToken creates a new token for a user.
|
|
func (s *TokenService) CreateToken(ctx context.Context, user *models.User, name string) (*models.Token, error) {
|
|
if name == "" {
|
|
// Make sure the name is valid
|
|
name = fmt.Sprintf("%s's token", user.Username)
|
|
}
|
|
|
|
// Generate a new token
|
|
token := &models.Token{
|
|
Name: name,
|
|
Value: TokenPrefix + randomutil.GenerateSecureToken(TokenLength),
|
|
UserID: &user.ID,
|
|
}
|
|
|
|
// Create the token in storage
|
|
newToken, err := s.db.CreateToken(ctx, token)
|
|
if err != nil {
|
|
return &models.Token{}, fmt.Errorf("could not create token in storage: %w", err)
|
|
}
|
|
|
|
return newToken, nil
|
|
}
|
|
|
|
// DeleteToken deletes a token from the storage.
|
|
func (s *TokenService) DeleteToken(ctx context.Context, token *models.Token) error {
|
|
// Delete the token from storage
|
|
err := s.db.DeleteToken(ctx, token)
|
|
if err != nil {
|
|
return fmt.Errorf("could not delete token from storage: %w", err)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (s *TokenService) ChangeTokenName(ctx context.Context, token *models.Token, name string) (*models.Token, error) {
|
|
// Make sure the name is valid
|
|
if len(name) < MinTokenNameLength || len(name) > MaxTokenNameLength {
|
|
return &models.Token{}, errs.ErrInvalidTokenName
|
|
}
|
|
|
|
// Update it
|
|
newToken, err := s.db.ChangeTokenName(ctx, token, name)
|
|
if err != nil {
|
|
return &models.Token{}, fmt.Errorf("could not rename token: %w", err)
|
|
}
|
|
|
|
return newToken, nil
|
|
}
|
|
|
|
// FindTokenUserFromValue finds the user that owns a token.
|
|
func (s *TokenService) FindTokenUserFromValue(ctx context.Context, value string) (*models.User, error) {
|
|
// Get the token from storage
|
|
token, err := s.FindToken(ctx, value)
|
|
if err != nil {
|
|
return &models.User{}, errs.Errorf("could not get token from storage", err)
|
|
}
|
|
|
|
// Get the user from storage
|
|
user, err := s.db.FindUserByID(ctx, *token.UserID)
|
|
if err != nil {
|
|
return &models.User{}, errs.Errorf("could not get token user from storage", err)
|
|
}
|
|
|
|
return user, nil
|
|
}
|