diff --git a/backend/go.mod b/backend/go.mod index 3c78345f..bb9f22f7 100644 --- a/backend/go.mod +++ b/backend/go.mod @@ -22,8 +22,7 @@ require ( github.com/go-playground/validator/v10 v10.30.3 github.com/go-webauthn/webauthn v0.17.4 github.com/golang-migrate/migrate/v4 v4.19.1 - github.com/google/uuid v1.6.0 - github.com/italypaleale/francis v0.1.0-rc.1 + github.com/italypaleale/francis v0.1.0-rc.2 github.com/italypaleale/go-kit v1.0.0 github.com/italypaleale/go-sql-utils v0.3.6 github.com/jackc/pgx/v5 v5.10.0 @@ -122,6 +121,7 @@ require ( github.com/google/go-github/v39 v39.2.0 // indirect github.com/google/go-querystring v1.2.0 // indirect github.com/google/go-tpm v0.9.8 // indirect + github.com/google/uuid v1.6.0 // indirect github.com/gorilla/websocket v1.5.3 // indirect github.com/grpc-ecosystem/grpc-gateway/v2 v2.29.0 // indirect github.com/h2non/filetype v1.1.3 // indirect diff --git a/backend/go.sum b/backend/go.sum index 0ad7368e..d9aa2475 100644 --- a/backend/go.sum +++ b/backend/go.sum @@ -241,8 +241,8 @@ github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= -github.com/italypaleale/francis v0.1.0-rc.1 h1:zRDkHPxiAYOQTuGegMlWVs/TCIsoLblcd/xoM/ItNP0= -github.com/italypaleale/francis v0.1.0-rc.1/go.mod h1:4ce0qAv9OoDY3FxsIVD46aHzMbqyixlucsNKcSyaNl4= +github.com/italypaleale/francis v0.1.0-rc.2 h1:GD8M9x4gVXWic5W3QwbA+NvhAl3uLW1/gg2OGje7Ix0= +github.com/italypaleale/francis v0.1.0-rc.2/go.mod h1:vtO32inBhTtYQ+2SjnLX81hIy+x7n8xnUESj7XK4yeQ= github.com/italypaleale/go-kit v1.0.0 h1:c+SaYHTaoZzTgbPXhSbNVdNRkNdNhE0YljBWWoPTdKI= github.com/italypaleale/go-kit v1.0.0/go.mod h1:wg4UsIbsbtDiVqUjJdo/tO9lXo0OXBuwPOpSJ+u+1jI= github.com/italypaleale/go-sql-utils v0.3.6 h1:ND14osePZFhn717qqI+nuH2blxKLTcYqWYSAG9anVDg= diff --git a/backend/internal/instanceid/instanceid.go b/backend/internal/instanceid/instanceid.go index b4b6e0e5..a084fb70 100644 --- a/backend/internal/instanceid/instanceid.go +++ b/backend/internal/instanceid/instanceid.go @@ -5,8 +5,8 @@ import ( "errors" "fmt" "time" + "uuid" - "github.com/google/uuid" "gorm.io/gorm" ) @@ -14,7 +14,7 @@ import ( // If no instance ID exists yet, a new one is generated and persisted atomically func Load(parentCtx context.Context, db *gorm.DB) (string, error) { // Candidate value used only if there's no instance ID stored yet - newInstanceID := uuid.NewString() + newInstanceID := uuid.NewV4().String() // We use a raw query because gorm can't build it for us in this atomic way // The syntax is valid for both SQLite and Postgres diff --git a/backend/internal/instanceid/instanceid_test.go b/backend/internal/instanceid/instanceid_test.go index c335a0d7..9c816f95 100644 --- a/backend/internal/instanceid/instanceid_test.go +++ b/backend/internal/instanceid/instanceid_test.go @@ -3,8 +3,8 @@ package instanceid import ( "sync" "testing" + "uuid" - "github.com/google/uuid" "github.com/stretchr/testify/require" "gorm.io/gorm" @@ -138,7 +138,7 @@ func TestMigrateFromAppConfig(t *testing.T) { } t.Run("moves an existing instance ID from app_config_variables into the kv table", func(t *testing.T) { - legacyID := uuid.NewString() + legacyID := uuid.NewV4().String() db := testutils.NewDatabaseForTestWithMigrationSeed(t, versionBeforeMove, seedAppConfigInstanceID(legacyID)) // The migration should have copied the value into the kv table under the "instance_id" key @@ -160,7 +160,7 @@ func TestMigrateFromAppConfig(t *testing.T) { }) t.Run("keeps the existing kv value when both tables have an instance ID", func(t *testing.T) { - legacyID := uuid.NewString() + legacyID := uuid.NewV4().String() db := testutils.NewDatabaseForTestWithMigrationSeed(t, versionBeforeMove, func(t *testing.T, db *gorm.DB) { t.Helper() // An instance ID is already present in the kv table before the move runs diff --git a/backend/internal/ldapsync/service.go b/backend/internal/ldapsync/service.go index 7e661cb1..8bd64abc 100644 --- a/backend/internal/ldapsync/service.go +++ b/backend/internal/ldapsync/service.go @@ -14,9 +14,9 @@ import ( "strings" "time" "unicode/utf8" + "uuid" "github.com/go-ldap/ldap/v3" - "github.com/google/uuid" "golang.org/x/text/unicode/norm" "gorm.io/gorm" @@ -735,9 +735,7 @@ func convertLdapIdToString(ldapId string) string { // Try to parse as binary UUID (16 bytes) if len(ldapId) == 16 { - if parsedUUID, err := uuid.FromBytes([]byte(ldapId)); err == nil { - return parsedUUID.String() - } + return uuid.UUID([]byte(ldapId)).String() } // As a last resort, encode as base64 to make it UTF-8 safe diff --git a/backend/internal/middleware/error_handler.go b/backend/internal/middleware/error_handler.go index 401cb5b7..fd1949d2 100644 --- a/backend/internal/middleware/error_handler.go +++ b/backend/internal/middleware/error_handler.go @@ -10,14 +10,15 @@ import ( "strings" "time" "unicode" + "uuid" "github.com/gin-gonic/gin" "github.com/gin-gonic/gin/binding" "github.com/go-playground/validator/v10" - "github.com/google/uuid" + "go.opentelemetry.io/otel/trace" + "github.com/pocket-id/pocket-id/backend/internal/apperror" "github.com/pocket-id/pocket-id/backend/internal/dto" - "go.opentelemetry.io/otel/trace" ) const requestIDHeader = "X-Request-ID" @@ -81,7 +82,7 @@ type classifiedError struct { // Add records a request ID before executing the request and serializes the first returned error afterward func (m *ErrorHandlerMiddleware) Add() gin.HandlerFunc { return func(c *gin.Context) { - requestID := uuid.NewString() + requestID := uuid.NewV4().String() c.Set(requestIDContextKey{}, requestID) c.Header(requestIDHeader, requestID) diff --git a/backend/internal/model/base.go b/backend/internal/model/base.go index d4a32121..0cf565a9 100644 --- a/backend/internal/model/base.go +++ b/backend/internal/model/base.go @@ -2,8 +2,8 @@ package model import ( "time" + "uuid" - "github.com/google/uuid" datatype "github.com/pocket-id/pocket-id/backend/internal/model/types" "gorm.io/gorm" ) @@ -16,7 +16,7 @@ type Base struct { func (b *Base) BeforeCreate(_ *gorm.DB) (err error) { if b.ID == "" { - b.ID = uuid.New().String() + b.ID = uuid.NewV4().String() } b.CreatedAt = datatype.DateTime(time.Now()) return diff --git a/backend/internal/oidc/session.go b/backend/internal/oidc/session.go index 4c0c0e09..e02e9881 100644 --- a/backend/internal/oidc/session.go +++ b/backend/internal/oidc/session.go @@ -3,8 +3,8 @@ package oidc import ( "encoding/json" "time" + "uuid" - "github.com/google/uuid" "github.com/ory/fosite" fositeoauth2 "github.com/ory/fosite/handler/oauth2" "github.com/ory/fosite/handler/openid" @@ -48,7 +48,7 @@ func NewAuthenticatedSession(subject, authenticationMethod string, authenticatio session.Claims.Subject = subject session.Claims.AuthTime = authenticationTime.UTC() session.Claims.RequestedAt = requestedAt.UTC() - session.Claims.JTI = uuid.NewString() + session.Claims.JTI = uuid.NewV4().String() return session } diff --git a/backend/internal/service/e2etest_service.go b/backend/internal/service/e2etest_service.go index 5201faf4..939daa9a 100644 --- a/backend/internal/service/e2etest_service.go +++ b/backend/internal/service/e2etest_service.go @@ -12,9 +12,9 @@ import ( "log/slog" "path" "time" + "uuid" "github.com/go-webauthn/webauthn/protocol" - "github.com/google/uuid" "github.com/italypaleale/francis/actor" "github.com/italypaleale/francis/host/local" "github.com/lestrrat-go/jwx/v3/jwa" @@ -920,7 +920,7 @@ func (s *TestService) SignAccessToken(ctx context.Context, userID, clientID stri session := fositeTokenSession{ Kind: "access_token", - RequestID: "e2e-access-" + uuid.NewString(), + RequestID: "e2e-access-" + uuid.NewV4().String(), UserID: userID, ClientID: clientID, AuthenticationMethod: AuthenticationMethodPhishingResistant, @@ -1022,7 +1022,7 @@ func (s *TestService) SignExternalIdPToken(iss, sub, aud string) (string, error) Subject(sub). Expiration(now.Add(time.Hour)). IssuedAt(now). - JwtID(uuid.NewString()). + JwtID(uuid.NewV4().String()). Issuer(iss). Audience([]string{aud}). Build() diff --git a/backend/internal/service/jwt_service.go b/backend/internal/service/jwt_service.go index fcc926da..39350a50 100644 --- a/backend/internal/service/jwt_service.go +++ b/backend/internal/service/jwt_service.go @@ -6,8 +6,8 @@ import ( "errors" "fmt" "time" + "uuid" - "github.com/google/uuid" "github.com/lestrrat-go/jwx/v3/jwa" "github.com/lestrrat-go/jwx/v3/jwk" "github.com/lestrrat-go/jwx/v3/jwt" @@ -188,7 +188,7 @@ func (s *JwtService) GenerateAccessToken(user model.User, authenticationMethod s Expiration(now.Add(sessionDuration)). IssuedAt(now). Issuer(s.envConfig.AppURL). - JwtID(uuid.New().String()). + JwtID(uuid.NewV4().String()). Build() if err != nil { return "", fmt.Errorf("failed to build token: %w", err) diff --git a/backend/internal/service/oidc_service.go b/backend/internal/service/oidc_service.go index ac4c4ae8..d339c446 100644 --- a/backend/internal/service/oidc_service.go +++ b/backend/internal/service/oidc_service.go @@ -13,8 +13,8 @@ import ( "slices" "strings" "time" + "uuid" - "github.com/google/uuid" "gorm.io/gorm" "gorm.io/gorm/clause" @@ -387,7 +387,7 @@ func (s *OidcService) CreateClientSecret(ctx context.Context, clientID string, i // Only the hash and a short prefix are persisted, so this is the last time the value is available secret := model.OidcClientSecret{ - ID: uuid.New().String(), + ID: uuid.NewV4().String(), Algorithm: model.OidcClientSecretHashSHA256, Hash: utils.CreateSha256Hash(clientSecret), Prefix: clientSecretPrefix(clientSecret), diff --git a/backend/internal/service/user_service.go b/backend/internal/service/user_service.go index 38d7f0fc..806a44bd 100644 --- a/backend/internal/service/user_service.go +++ b/backend/internal/service/user_service.go @@ -11,8 +11,8 @@ import ( "log/slog" "path" "time" + "uuid" - "github.com/google/uuid" "gorm.io/gorm" "gorm.io/gorm/clause" @@ -88,7 +88,8 @@ func (s *UserService) getUserInternal(ctx context.Context, userID string, tx *go func (s *UserService) GetProfilePicture(ctx context.Context, userID string) (io.ReadCloser, int64, error) { // Validate the user ID to prevent directory traversal - if err := uuid.Validate(userID); err != nil { + _, err := uuid.Parse(userID) + if err != nil { return nil, 0, apperror.InvalidUserID() } @@ -167,7 +168,7 @@ func (s *UserService) GetUserGroups(ctx context.Context, userID string) ([]model func (s *UserService) UpdateProfilePicture(ctx context.Context, userID string, file io.ReadSeeker) error { // Validate the user ID to prevent directory traversal - err := uuid.Validate(userID) + _, err := uuid.Parse(userID) if err != nil { return apperror.InvalidUserID() } @@ -639,16 +640,19 @@ func (s *UserService) checkDuplicatedFields(ctx context.Context, user model.User // ResetProfilePicture deletes a user's custom profile picture func (s *UserService) ResetProfilePicture(ctx context.Context, userID string) error { // Validate the user ID to prevent directory traversal - if err := uuid.Validate(userID); err != nil { + _, err := uuid.Parse(userID) + if err != nil { return apperror.InvalidUserID() } - if _, err := s.GetUser(ctx, userID); err != nil { + _, err = s.GetUser(ctx, userID) + if err != nil { return err } profilePicturePath := path.Join("profile-pictures", userID+".png") - if err := s.fileStorage.Delete(ctx, profilePicturePath); err != nil { + err = s.fileStorage.Delete(ctx, profilePicturePath) + if err != nil { return fmt.Errorf("failed to delete profile picture: %w", err) } return nil diff --git a/backend/internal/service/user_service_test.go b/backend/internal/service/user_service_test.go index 785a29a7..e5a10b09 100644 --- a/backend/internal/service/user_service_test.go +++ b/backend/internal/service/user_service_test.go @@ -4,8 +4,8 @@ import ( "encoding/json" "strings" "testing" + "uuid" - "github.com/google/uuid" "github.com/stretchr/testify/require" "github.com/pocket-id/pocket-id/backend/internal/appconfig" @@ -54,7 +54,7 @@ func TestUpdateProfilePictureRejectsInvalidImageData(t *testing.T) { userService, _ := newTestUserService(t) config := &appconfig.AppConfigModel{RequireUserEmail: "false"} user, err := userService.CreateUser(t.Context(), config, dto.UserCreateDto{ - ID: uuid.NewString(), + ID: uuid.NewV4().String(), Username: "image-test", }) require.NoError(t, err) @@ -70,7 +70,7 @@ func TestUpdateProfilePictureRejectsInvalidImageData(t *testing.T) { func TestProfilePictureUpdatesRejectMissingUser(t *testing.T) { userService, _ := newTestUserService(t) - missingUserID := uuid.NewString() + missingUserID := uuid.NewV4().String() err := userService.UpdateProfilePicture(t.Context(), missingUserID, strings.NewReader("not an image")) require.True(t, apperror.IsCode(err, apperror.CodeUserNotFound)) diff --git a/backend/internal/storage/filesystem.go b/backend/internal/storage/filesystem.go index f9f3f2fc..98f64c24 100644 --- a/backend/internal/storage/filesystem.go +++ b/backend/internal/storage/filesystem.go @@ -9,8 +9,7 @@ import ( "os" "path/filepath" "strings" - - "github.com/google/uuid" + "uuid" ) type filesystemStorage struct { @@ -51,7 +50,7 @@ func (s *filesystemStorage) Save(_ context.Context, path string, data io.Reader) } // Our strategy is to save to a separate file and then rename it to override the original file - tmpName := path + "." + uuid.NewString() + "-tmp" + tmpName := path + "." + uuid.NewV4().String() + "-tmp" // Write to the temporary file tmpFile, err := s.root.Create(tmpName) diff --git a/backend/internal/usersignup/service.go b/backend/internal/usersignup/service.go index b7a32bc1..6a11e1d4 100644 --- a/backend/internal/usersignup/service.go +++ b/backend/internal/usersignup/service.go @@ -7,8 +7,8 @@ import ( "sort" "strings" "time" + "uuid" - "github.com/google/uuid" "github.com/italypaleale/francis/actor" "gorm.io/gorm" @@ -347,7 +347,7 @@ func (s *Service) CreateSignupToken(ctx context.Context, ttl time.Duration, usag now := time.Now().Round(time.Second) state := SignupTokenState{ - ID: uuid.NewString(), + ID: uuid.NewV4().String(), ExpiresAt: now.Add(ttl), UsageLimit: usageLimit, UsageCount: 0,