feat: integration v2 and API key PART 1/3
This commit is contained in:
1 parent
7e9bd9ba30
commit
844842fdae
40 files changed
+3782
-64
No files matched your search
@@ -0,0 +1,67 @@
|
||||
package useCases
|
||||
|
||||
import (
|
||||
"RenewCMS/internal/domain/apikey"
|
||||
"RenewCMS/internal/domain/user"
|
||||
"errors"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
const lastUsedWriteInterval = time.Minute
|
||||
|
||||
type AuthenticateAPIKeyUseCase struct {
|
||||
apiKeyRepository apikey.Repository
|
||||
keyGenerator apikey.KeyGenerator
|
||||
userRepository user.Repository
|
||||
now func() time.Time
|
||||
}
|
||||
|
||||
type AuthenticatedAPIKey struct {
|
||||
Key apikey.APIKey
|
||||
User user.User
|
||||
}
|
||||
|
||||
func NewAuthenticateAPIKeyUseCase(apiKeyRepository apikey.Repository, keyGenerator apikey.KeyGenerator, userRepository user.Repository, now func() time.Time) *AuthenticateAPIKeyUseCase {
|
||||
return &AuthenticateAPIKeyUseCase{apiKeyRepository, keyGenerator, userRepository, now}
|
||||
}
|
||||
|
||||
func (a *AuthenticateAPIKeyUseCase) AuthenticateAPIKey(rawKey string) (AuthenticatedAPIKey, error) {
|
||||
if !strings.HasPrefix(rawKey, apikey.RawKeyPrefix) {
|
||||
return AuthenticatedAPIKey{}, apikey.ErrAPIKeyInvalid
|
||||
}
|
||||
|
||||
key, err := a.apiKeyRepository.GetByHashedKey(a.keyGenerator.Hash(rawKey))
|
||||
if err != nil {
|
||||
if errors.Is(err, apikey.ErrAPIKeyNotFound) {
|
||||
return AuthenticatedAPIKey{}, apikey.ErrAPIKeyInvalid
|
||||
}
|
||||
return AuthenticatedAPIKey{}, err
|
||||
}
|
||||
|
||||
now := a.now()
|
||||
if key.StatusAt(now) != apikey.StatusActive {
|
||||
return AuthenticatedAPIKey{}, apikey.ErrAPIKeyInvalid
|
||||
}
|
||||
|
||||
owner, err := a.userRepository.Get(key.UserID)
|
||||
if err != nil {
|
||||
if errors.Is(err, user.ErrUserNotFound) {
|
||||
return AuthenticatedAPIKey{}, apikey.ErrAPIKeyInvalid
|
||||
}
|
||||
return AuthenticatedAPIKey{}, err
|
||||
}
|
||||
if !owner.IsVerified {
|
||||
return AuthenticatedAPIKey{}, apikey.ErrAPIKeyInvalid
|
||||
}
|
||||
|
||||
a.recordUse(key, now)
|
||||
return AuthenticatedAPIKey{Key: key, User: owner}, nil
|
||||
}
|
||||
|
||||
func (a *AuthenticateAPIKeyUseCase) recordUse(key apikey.APIKey, now time.Time) {
|
||||
if key.LastUsedAt != nil && now.Sub(*key.LastUsedAt) < lastUsedWriteInterval {
|
||||
return
|
||||
}
|
||||
_ = a.apiKeyRepository.TouchLastUsed(key.ID, now)
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
package useCases
|
||||
|
||||
import (
|
||||
"RenewCMS/internal/domain/apikey"
|
||||
"time"
|
||||
)
|
||||
|
||||
type CreateAPIKeyUseCase struct {
|
||||
apiKeyRepository apikey.Repository
|
||||
keyGenerator apikey.KeyGenerator
|
||||
now func() time.Time
|
||||
}
|
||||
|
||||
type CreateAPIKeyCommand struct {
|
||||
UserID uint32
|
||||
Name string
|
||||
ExpiresAt *time.Time
|
||||
}
|
||||
|
||||
type CreatedAPIKey struct {
|
||||
Key apikey.APIKey
|
||||
RawKey string
|
||||
}
|
||||
|
||||
func NewCreateAPIKeyUseCase(apiKeyRepository apikey.Repository, keyGenerator apikey.KeyGenerator, now func() time.Time) *CreateAPIKeyUseCase {
|
||||
return &CreateAPIKeyUseCase{apiKeyRepository, keyGenerator, now}
|
||||
}
|
||||
|
||||
func (c *CreateAPIKeyUseCase) CreateAPIKey(command CreateAPIKeyCommand) (CreatedAPIKey, error) {
|
||||
now := c.now()
|
||||
if command.ExpiresAt != nil && !command.ExpiresAt.After(now) {
|
||||
return CreatedAPIKey{}, apikey.ErrExpiryNotInFuture
|
||||
}
|
||||
|
||||
rawKey, prefix, hashedKey := c.keyGenerator.Generate()
|
||||
stored, err := c.apiKeyRepository.Create(apikey.APIKey{
|
||||
UserID: command.UserID,
|
||||
Name: command.Name,
|
||||
Prefix: prefix,
|
||||
HashedKey: hashedKey,
|
||||
ExpiresAt: command.ExpiresAt,
|
||||
CreatedAt: now,
|
||||
})
|
||||
if err != nil {
|
||||
return CreatedAPIKey{}, err
|
||||
}
|
||||
|
||||
return CreatedAPIKey{Key: stored, RawKey: rawKey}, nil
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
package useCases
|
||||
|
||||
import (
|
||||
"RenewCMS/internal/domain/apikey"
|
||||
"RenewCMS/internal/domain/user"
|
||||
"sort"
|
||||
"time"
|
||||
)
|
||||
|
||||
type ListAPIKeysUseCase struct {
|
||||
apiKeyRepository apikey.Repository
|
||||
userRepository user.Repository
|
||||
now func() time.Time
|
||||
}
|
||||
|
||||
type APIKeyEntry struct {
|
||||
Key apikey.APIKey
|
||||
Status apikey.Status
|
||||
}
|
||||
|
||||
func NewListAPIKeysUseCase(apiKeyRepository apikey.Repository, userRepository user.Repository, now func() time.Time) *ListAPIKeysUseCase {
|
||||
return &ListAPIKeysUseCase{apiKeyRepository, userRepository, now}
|
||||
}
|
||||
|
||||
func (l *ListAPIKeysUseCase) ListAPIKeys(requesterId uint32) ([]APIKeyEntry, error) {
|
||||
requester, err := l.userRepository.Get(requesterId)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
keys, err := l.keysVisibleTo(requester)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
sort.SliceStable(keys, func(i, j int) bool {
|
||||
if keys[i].CreatedAt.Equal(keys[j].CreatedAt) {
|
||||
return keys[i].ID > keys[j].ID
|
||||
}
|
||||
return keys[i].CreatedAt.After(keys[j].CreatedAt)
|
||||
})
|
||||
|
||||
now := l.now()
|
||||
entries := make([]APIKeyEntry, 0, len(keys))
|
||||
for _, key := range keys {
|
||||
entries = append(entries, APIKeyEntry{Key: key, Status: key.StatusAt(now)})
|
||||
}
|
||||
return entries, nil
|
||||
}
|
||||
|
||||
func (l *ListAPIKeysUseCase) keysVisibleTo(requester user.User) ([]apikey.APIKey, error) {
|
||||
if requester.Role == user.RoleOwner {
|
||||
return l.apiKeyRepository.ListAll()
|
||||
}
|
||||
return l.apiKeyRepository.ListByUser(requester.ID)
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
package useCases
|
||||
|
||||
import (
|
||||
"RenewCMS/internal/domain/apikey"
|
||||
"RenewCMS/internal/domain/hash"
|
||||
"RenewCMS/internal/domain/user"
|
||||
"time"
|
||||
@@ -9,10 +10,12 @@ import (
|
||||
type ResetPasswordUseCase struct {
|
||||
userRepository user.Repository
|
||||
hasher hash.Hasher
|
||||
apiKeys apikey.Repository
|
||||
now func() time.Time
|
||||
}
|
||||
|
||||
func NewResetPasswordUseCase(userRepository user.Repository, hasher hash.Hasher) *ResetPasswordUseCase {
|
||||
return &ResetPasswordUseCase{userRepository, hasher}
|
||||
func NewResetPasswordUseCase(userRepository user.Repository, hasher hash.Hasher, apiKeys apikey.Repository, now func() time.Time) *ResetPasswordUseCase {
|
||||
return &ResetPasswordUseCase{userRepository, hasher, apiKeys, now}
|
||||
}
|
||||
|
||||
func (r *ResetPasswordUseCase) ResetPassword(email string, code string, newPassword string) error {
|
||||
@@ -25,7 +28,7 @@ func (r *ResetPasswordUseCase) ResetPassword(email string, code string, newPassw
|
||||
return user.ErrInvalidPasswordResetCode
|
||||
}
|
||||
|
||||
if time.Now().After(dbUser.PasswordResetExpiration) {
|
||||
if r.now().After(dbUser.PasswordResetExpiration) {
|
||||
return user.ErrPasswordResetCodeExpired
|
||||
}
|
||||
|
||||
@@ -34,6 +37,9 @@ func (r *ResetPasswordUseCase) ResetPassword(email string, code string, newPassw
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = r.userRepository.ResetPassword(dbUser.ID, hashedPassword)
|
||||
return err
|
||||
if _, err = r.userRepository.ResetPassword(dbUser.ID, hashedPassword); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return r.apiKeys.RevokeAllByUser(dbUser.ID, r.now())
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
package useCases
|
||||
|
||||
import (
|
||||
"RenewCMS/internal/domain/apikey"
|
||||
"RenewCMS/internal/domain/user"
|
||||
"time"
|
||||
)
|
||||
|
||||
type RevokeAPIKeyUseCase struct {
|
||||
apiKeyRepository apikey.Repository
|
||||
userRepository user.Repository
|
||||
now func() time.Time
|
||||
}
|
||||
|
||||
func NewRevokeAPIKeyUseCase(apiKeyRepository apikey.Repository, userRepository user.Repository, now func() time.Time) *RevokeAPIKeyUseCase {
|
||||
return &RevokeAPIKeyUseCase{apiKeyRepository, userRepository, now}
|
||||
}
|
||||
|
||||
func (r *RevokeAPIKeyUseCase) RevokeAPIKey(requesterId uint32, keyId uint32) error {
|
||||
key, err := r.apiKeyRepository.Get(keyId)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
requester, err := r.userRepository.Get(requesterId)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if requester.Role != user.RoleOwner && key.UserID != requester.ID {
|
||||
return apikey.ErrAPIKeyNotFound
|
||||
}
|
||||
|
||||
if key.RevokedAt != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
return r.apiKeyRepository.Revoke(key.ID, r.now())
|
||||
}
|
||||
Reference in new issue
Block a user