From a90c8abe513a72aaa527d5f9aed91e5891bd1187 Mon Sep 17 00:00:00 2001 From: Kyle Mendell Date: Mon, 23 Feb 2026 12:50:44 -0600 Subject: [PATCH 1/2] chore(deps): upgrade to node 24 and go 1.26.0 (#1328) Co-authored-by: ItalyPaleAle <43508+ItalyPaleAle@users.noreply.github.com> Co-authored-by: Elias Schneider --- .devcontainer/devcontainer.json | 4 +- .github/workflows/backend-linter.yml | 4 +- .github/workflows/build-next.yml | 2 +- .github/workflows/e2e-tests.yml | 2 +- .github/workflows/release.yml | 2 +- .github/workflows/svelte-check.yml | 2 +- CONTRIBUTING.md | 5 +- backend/go.mod | 2 +- .../bootstrap/observability_boostrap.go | 5 +- backend/internal/common/env_config.go | 10 +-- backend/internal/dto/dto_mapper_test.go | 23 +++-- backend/internal/dto/dto_normalize.go | 4 +- backend/internal/dto/scim_dto.go | 2 +- backend/internal/dto/user_dto_test.go | 15 ++-- backend/internal/model/app_config_test.go | 11 +-- backend/internal/model/webauthn.go | 4 +- backend/internal/service/api_key_service.go | 2 +- .../internal/service/app_config_service.go | 3 +- backend/internal/service/e2etest_service.go | 22 ++--- backend/internal/service/export_service.go | 20 ++--- backend/internal/service/jwt_service_test.go | 33 ++++--- backend/internal/service/ldap_service.go | 2 +- backend/internal/service/oidc_service.go | 2 +- backend/internal/service/scim_service.go | 9 +- .../internal/service/user_group_service.go | 4 +- backend/internal/service/user_service.go | 8 +- .../internal/utils/callback_url_util_test.go | 15 +++- backend/internal/utils/ip_util.go | 4 +- backend/internal/utils/json_util.go | 2 +- backend/internal/utils/list_request_util.go | 15 ++-- backend/internal/utils/ptr_util.go | 5 -- backend/internal/utils/slogfanout.go | 85 ------------------- backend/internal/utils/string_util.go | 5 -- docker/Dockerfile | 4 +- package.json | 2 +- 35 files changed, 121 insertions(+), 218 deletions(-) delete mode 100644 backend/internal/utils/slogfanout.go diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index 2f3a86eb..55bf1674 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -2,7 +2,9 @@ "name": "pocket-id", "image": "mcr.microsoft.com/devcontainers/typescript-node:1-22-bookworm", "features": { - "ghcr.io/devcontainers/features/go:1": {} + "ghcr.io/devcontainers/features/go:1": { + "version": "1.26" + } }, "customizations": { "vscode": { diff --git a/.github/workflows/backend-linter.yml b/.github/workflows/backend-linter.yml index ec6f18d4..c29f2efb 100644 --- a/.github/workflows/backend-linter.yml +++ b/.github/workflows/backend-linter.yml @@ -32,9 +32,9 @@ jobs: go-version-file: backend/go.mod - name: Run Golangci-lint - uses: golangci/golangci-lint-action@v8.0.0 + uses: golangci/golangci-lint-action@v9.0.0 with: - version: v2.4.0 + version: v2.9.0 args: --build-tags=exclude_frontend working-directory: backend only-new-issues: ${{ github.event_name == 'pull_request' }} diff --git a/.github/workflows/build-next.yml b/.github/workflows/build-next.yml index 403ca5bf..d531ff81 100644 --- a/.github/workflows/build-next.yml +++ b/.github/workflows/build-next.yml @@ -27,7 +27,7 @@ jobs: - name: Setup Node.js uses: actions/setup-node@v5 with: - node-version: 22 + node-version: 24 - name: Setup Go uses: actions/setup-go@v6 diff --git a/.github/workflows/e2e-tests.yml b/.github/workflows/e2e-tests.yml index e8ae44a6..c0f46ca9 100644 --- a/.github/workflows/e2e-tests.yml +++ b/.github/workflows/e2e-tests.yml @@ -78,7 +78,7 @@ jobs: - name: Setup Node.js uses: actions/setup-node@v5 with: - node-version: 22 + node-version: 24 - name: Cache Playwright Browsers uses: actions/cache@v4 diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 062a5208..cfd94006 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -21,7 +21,7 @@ jobs: - name: Setup Node.js uses: actions/setup-node@v5 with: - node-version: 22 + node-version: 24 - uses: actions/setup-go@v6 with: go-version-file: "backend/go.mod" diff --git a/.github/workflows/svelte-check.yml b/.github/workflows/svelte-check.yml index b49deddc..9a0c761c 100644 --- a/.github/workflows/svelte-check.yml +++ b/.github/workflows/svelte-check.yml @@ -42,7 +42,7 @@ jobs: - name: Setup Node.js uses: actions/setup-node@v5 with: - node-version: 22 + node-version: 24 - name: Install dependencies run: pnpm --filter pocket-id-frontend install --frozen-lockfile diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 3702ae7d..7d4ef66c 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -21,7 +21,6 @@ Before you submit the pull request for review please ensure that ``` Where `TYPE` can be: - - **feat** - is a new feature - **doc** - documentation only changes - **fix** - a bug fix @@ -51,8 +50,8 @@ If you use [Dev Containers](https://code.visualstudio.com/docs/remote/containers If you don't use Dev Containers, you need to install the following tools manually: -- [Node.js](https://nodejs.org/en/download/) >= 22 -- [Go](https://golang.org/doc/install) >= 1.25 +- [Node.js](https://nodejs.org/en/download/) >= 24 +- [Go](https://golang.org/doc/install) >= 1.26 - [Git](https://git-scm.com/downloads) ### 2. Setup diff --git a/backend/go.mod b/backend/go.mod index d56298d5..9013dc1e 100644 --- a/backend/go.mod +++ b/backend/go.mod @@ -1,6 +1,6 @@ module github.com/pocket-id/pocket-id/backend -go 1.25.0 +go 1.26.0 require ( github.com/aws/aws-sdk-go-v2 v1.41.1 diff --git a/backend/internal/bootstrap/observability_boostrap.go b/backend/internal/bootstrap/observability_boostrap.go index 7228b36b..6c89861e 100644 --- a/backend/internal/bootstrap/observability_boostrap.go +++ b/backend/internal/bootstrap/observability_boostrap.go @@ -118,11 +118,10 @@ func initOtelLogging(ctx context.Context, resource *resource.Resource) error { // Set the logger provider globally globallog.SetLoggerProvider(provider) - // Wrap the handler in a "fanout" one - handler = utils.LogFanoutHandler{ + handler = slog.NewMultiHandler( handler, otelslog.NewHandler(common.Name, otelslog.WithLoggerProvider(provider)), - } + ) // Set the default slog to send logs to OTel and add the app name log := slog.New(handler). diff --git a/backend/internal/common/env_config.go b/backend/internal/common/env_config.go index dfd45a0e..93d68dbb 100644 --- a/backend/internal/common/env_config.go +++ b/backend/internal/common/env_config.go @@ -106,7 +106,7 @@ func defaultConfig() EnvConfigSchema { func parseEnvConfig() error { parsers := map[reflect.Type]env.ParserFunc{ - reflect.TypeOf([]byte{}): func(value string) (interface{}, error) { + reflect.TypeFor[[]byte](): func(value string) (any, error) { return []byte(value), nil }, } @@ -184,8 +184,8 @@ func ValidateEnvConfig(config *EnvConfigSchema) error { } // Validate LOCAL_IPV6_RANGES - ranges := strings.Split(config.LocalIPv6Ranges, ",") - for _, rangeStr := range ranges { + ranges := strings.SplitSeq(config.LocalIPv6Ranges, ",") + for rangeStr := range ranges { rangeStr = strings.TrimSpace(rangeStr) if rangeStr == "" { continue @@ -235,9 +235,9 @@ func prepareEnvConfig(config *EnvConfigSchema) error { fieldType := typ.Field(i) optionsTag := fieldType.Tag.Get("options") - options := strings.Split(optionsTag, ",") + options := strings.SplitSeq(optionsTag, ",") - for _, option := range options { + for option := range options { switch option { case "toLower": if field.Kind() == reflect.String { diff --git a/backend/internal/dto/dto_mapper_test.go b/backend/internal/dto/dto_mapper_test.go index 43b86b14..f244b9ee 100644 --- a/backend/internal/dto/dto_mapper_test.go +++ b/backend/internal/dto/dto_mapper_test.go @@ -9,7 +9,6 @@ import ( "github.com/pocket-id/pocket-id/backend/internal/model" datatype "github.com/pocket-id/pocket-id/backend/internal/model/types" - "github.com/pocket-id/pocket-id/backend/internal/utils" ) type sourceStruct struct { @@ -60,11 +59,11 @@ type embeddedStruct struct { func TestMapStruct(t *testing.T) { src := sourceStruct{ AString: "abcd", - AStringPtr: utils.Ptr("xyz"), + AStringPtr: new("xyz"), ABool: true, - ABoolPtr: utils.Ptr(false), + ABoolPtr: new(false), ACustomDateTime: datatype.DateTime(time.Date(2025, 1, 2, 3, 4, 5, 0, time.UTC)), - ACustomDateTimePtr: utils.Ptr(datatype.DateTime(time.Date(2024, 1, 2, 3, 4, 5, 0, time.UTC))), + ACustomDateTimePtr: new(datatype.DateTime(time.Date(2024, 1, 2, 3, 4, 5, 0, time.UTC))), ANilStringPtr: nil, ASlice: []string{"a", "b", "c"}, AMap: map[string]int{ @@ -80,8 +79,8 @@ func TestMapStruct(t *testing.T) { Bar: 111, }, - StringPtrToString: utils.Ptr("foobar"), - EmptyStringPtrToString: utils.Ptr(""), + StringPtrToString: new("foobar"), + EmptyStringPtrToString: new(""), NilStringPtrToString: nil, IntToInt64: 99, AuditLogEventToString: model.AuditLogEventAccountCreated, @@ -118,11 +117,11 @@ func TestMapStructList(t *testing.T) { sources := []sourceStruct{ { AString: "first", - AStringPtr: utils.Ptr("one"), + AStringPtr: new("one"), ABool: true, - ABoolPtr: utils.Ptr(false), + ABoolPtr: new(false), ACustomDateTime: datatype.DateTime(time.Date(2025, 1, 2, 3, 4, 5, 0, time.UTC)), - ACustomDateTimePtr: utils.Ptr(datatype.DateTime(time.Date(2024, 1, 2, 3, 4, 5, 0, time.UTC))), + ACustomDateTimePtr: new(datatype.DateTime(time.Date(2024, 1, 2, 3, 4, 5, 0, time.UTC))), ASlice: []string{"a", "b"}, AMap: map[string]int{ "a": 1, @@ -136,11 +135,11 @@ func TestMapStructList(t *testing.T) { }, { AString: "second", - AStringPtr: utils.Ptr("two"), + AStringPtr: new("two"), ABool: false, - ABoolPtr: utils.Ptr(true), + ABoolPtr: new(true), ACustomDateTime: datatype.DateTime(time.Date(2026, 6, 7, 8, 9, 10, 0, time.UTC)), - ACustomDateTimePtr: utils.Ptr(datatype.DateTime(time.Date(2023, 6, 7, 8, 9, 10, 0, time.UTC))), + ACustomDateTimePtr: new(datatype.DateTime(time.Date(2023, 6, 7, 8, 9, 10, 0, time.UTC))), ASlice: []string{"c", "d", "e"}, AMap: map[string]int{ "c": 3, diff --git a/backend/internal/dto/dto_normalize.go b/backend/internal/dto/dto_normalize.go index d366f1d3..b73910c4 100644 --- a/backend/internal/dto/dto_normalize.go +++ b/backend/internal/dto/dto_normalize.go @@ -12,7 +12,7 @@ import ( // Normalize iterates through an object and performs Unicode normalization on all string fields with the `unorm` tag. func Normalize(obj any) { v := reflect.ValueOf(obj) - if v.Kind() != reflect.Ptr || v.IsNil() { + if v.Kind() != reflect.Pointer || v.IsNil() { return } v = v.Elem() @@ -21,7 +21,7 @@ func Normalize(obj any) { if v.Kind() == reflect.Slice { for i := 0; i < v.Len(); i++ { elem := v.Index(i) - if elem.Kind() == reflect.Ptr && !elem.IsNil() && elem.Elem().Kind() == reflect.Struct { + if elem.Kind() == reflect.Pointer && !elem.IsNil() && elem.Elem().Kind() == reflect.Struct { Normalize(elem.Interface()) } else if elem.Kind() == reflect.Struct && elem.CanAddr() { Normalize(elem.Addr().Interface()) diff --git a/backend/internal/dto/scim_dto.go b/backend/internal/dto/scim_dto.go index dc98e1be..c04c8948 100644 --- a/backend/internal/dto/scim_dto.go +++ b/backend/internal/dto/scim_dto.go @@ -67,7 +67,7 @@ type ScimResourceData struct { type ScimResourceMeta struct { Location string `json:"location,omitempty"` ResourceType string `json:"resourceType,omitempty"` - Created time.Time `json:"created,omitempty"` + Created time.Time `json:"created"` LastModified time.Time `json:"lastModified,omitempty"` Version string `json:"version,omitempty"` } diff --git a/backend/internal/dto/user_dto_test.go b/backend/internal/dto/user_dto_test.go index a037218f..251dfece 100644 --- a/backend/internal/dto/user_dto_test.go +++ b/backend/internal/dto/user_dto_test.go @@ -3,7 +3,6 @@ package dto import ( "testing" - "github.com/pocket-id/pocket-id/backend/internal/utils" "github.com/stretchr/testify/require" ) @@ -17,7 +16,7 @@ func TestUserCreateDto_Validate(t *testing.T) { name: "valid input", input: UserCreateDto{ Username: "testuser", - Email: utils.Ptr("test@example.com"), + Email: new("test@example.com"), FirstName: "John", LastName: "Doe", DisplayName: "John Doe", @@ -27,7 +26,7 @@ func TestUserCreateDto_Validate(t *testing.T) { { name: "missing username", input: UserCreateDto{ - Email: utils.Ptr("test@example.com"), + Email: new("test@example.com"), FirstName: "John", LastName: "Doe", DisplayName: "John Doe", @@ -37,7 +36,7 @@ func TestUserCreateDto_Validate(t *testing.T) { { name: "missing display name", input: UserCreateDto{ - Email: utils.Ptr("test@example.com"), + Email: new("test@example.com"), FirstName: "John", LastName: "Doe", }, @@ -47,7 +46,7 @@ func TestUserCreateDto_Validate(t *testing.T) { name: "username contains invalid characters", input: UserCreateDto{ Username: "test/ser", - Email: utils.Ptr("test@example.com"), + Email: new("test@example.com"), FirstName: "John", LastName: "Doe", DisplayName: "John Doe", @@ -58,7 +57,7 @@ func TestUserCreateDto_Validate(t *testing.T) { name: "invalid email", input: UserCreateDto{ Username: "testuser", - Email: utils.Ptr("not-an-email"), + Email: new("not-an-email"), FirstName: "John", LastName: "Doe", DisplayName: "John Doe", @@ -69,7 +68,7 @@ func TestUserCreateDto_Validate(t *testing.T) { name: "first name too short", input: UserCreateDto{ Username: "testuser", - Email: utils.Ptr("test@example.com"), + Email: new("test@example.com"), FirstName: "", LastName: "Doe", DisplayName: "John Doe", @@ -80,7 +79,7 @@ func TestUserCreateDto_Validate(t *testing.T) { name: "last name too long", input: UserCreateDto{ Username: "testuser", - Email: utils.Ptr("test@example.com"), + Email: new("test@example.com"), FirstName: "John", LastName: "abcdfghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz", DisplayName: "John Doe", diff --git a/backend/internal/model/app_config_test.go b/backend/internal/model/app_config_test.go index 7290617b..aac70a62 100644 --- a/backend/internal/model/app_config_test.go +++ b/backend/internal/model/app_config_test.go @@ -70,13 +70,12 @@ func TestAppConfigVariable_AsMinutesDuration(t *testing.T) { // - dto.AppConfigDto should not include "internal" fields from model.AppConfig // This test is primarily meant to catch discrepancies between the two structs as fields are added or removed over time func TestAppConfigStructMatchesUpdateDto(t *testing.T) { - appConfigType := reflect.TypeOf(model.AppConfig{}) - updateDtoType := reflect.TypeOf(dto.AppConfigUpdateDto{}) + appConfigType := reflect.TypeFor[model.AppConfig]() + updateDtoType := reflect.TypeFor[dto.AppConfigUpdateDto]() // Process AppConfig fields appConfigFields := make(map[string]string) - for i := 0; i < appConfigType.NumField(); i++ { - field := appConfigType.Field(i) + for field := range appConfigType.Fields() { if field.Tag.Get("key") == "" { // Skip internal fields continue @@ -91,9 +90,7 @@ func TestAppConfigStructMatchesUpdateDto(t *testing.T) { // Process AppConfigUpdateDto fields dtoFields := make(map[string]string) - for i := 0; i < updateDtoType.NumField(); i++ { - field := updateDtoType.Field(i) - + for field := range updateDtoType.Fields() { // Extract the json name from the tag (takes the part before any binding constraints) jsonTag := field.Tag.Get("json") jsonName, _, _ := strings.Cut(jsonTag, ",") diff --git a/backend/internal/model/webauthn.go b/backend/internal/model/webauthn.go index 4cd19520..60f1913f 100644 --- a/backend/internal/model/webauthn.go +++ b/backend/internal/model/webauthn.go @@ -58,7 +58,7 @@ type ReauthenticationToken struct { type AuthenticatorTransportList []protocol.AuthenticatorTransport //nolint:recvcheck // Scan and Value methods for GORM to handle the custom type -func (atl *AuthenticatorTransportList) Scan(value interface{}) error { +func (atl *AuthenticatorTransportList) Scan(value any) error { return utils.UnmarshalJSONFromDatabase(atl, value) } @@ -69,7 +69,7 @@ func (atl AuthenticatorTransportList) Value() (driver.Value, error) { type CredentialParameters []protocol.CredentialParameter //nolint:recvcheck // Scan and Value methods for GORM to handle the custom type -func (cp *CredentialParameters) Scan(value interface{}) error { +func (cp *CredentialParameters) Scan(value any) error { return utils.UnmarshalJSONFromDatabase(cp, value) } diff --git a/backend/internal/service/api_key_service.go b/backend/internal/service/api_key_service.go index bc8de9f5..0d175a92 100644 --- a/backend/internal/service/api_key_service.go +++ b/backend/internal/service/api_key_service.go @@ -170,7 +170,7 @@ func (s *ApiKeyService) ValidateApiKey(ctx context.Context, apiKey string) (mode Clauses(clause.Returning{}). Where("key = ? AND expires_at > ?", hashedKey, datatype.DateTime(now)). Updates(&model.ApiKey{ - LastUsedAt: utils.Ptr(datatype.DateTime(now)), + LastUsedAt: new(datatype.DateTime(now)), }). Preload("User"). First(&key). diff --git a/backend/internal/service/app_config_service.go b/backend/internal/service/app_config_service.go index e05427f6..613fdcf8 100644 --- a/backend/internal/service/app_config_service.go +++ b/backend/internal/service/app_config_service.go @@ -186,8 +186,7 @@ func (s *AppConfigService) UpdateAppConfig(ctx context.Context, input dto.AppCon rt := reflect.ValueOf(input).Type() rv := reflect.ValueOf(input) dbUpdate := make([]model.AppConfigVariable, 0, rt.NumField()) - for i := range rt.NumField() { - field := rt.Field(i) + for field := range rt.Fields() { value := rv.FieldByName(field.Name).String() // Get the value of the json tag, taking only what's before the comma diff --git a/backend/internal/service/e2etest_service.go b/backend/internal/service/e2etest_service.go index 4e65064d..7df15681 100644 --- a/backend/internal/service/e2etest_service.go +++ b/backend/internal/service/e2etest_service.go @@ -81,7 +81,7 @@ func (s *TestService) SeedDatabase(baseURL string) error { ID: "f4b89dc2-62fb-46bf-9f5f-c34f4eafe93e", }, Username: "tim", - Email: utils.Ptr("tim.cook@test.com"), + Email: new("tim.cook@test.com"), EmailVerified: true, FirstName: "Tim", LastName: "Cook", @@ -93,7 +93,7 @@ func (s *TestService) SeedDatabase(baseURL string) error { ID: "1cd19686-f9a6-43f4-a41f-14a0bf5b4036", }, Username: "craig", - Email: utils.Ptr("craig.federighi@test.com"), + Email: new("craig.federighi@test.com"), EmailVerified: false, FirstName: "Craig", LastName: "Federighi", @@ -105,7 +105,7 @@ func (s *TestService) SeedDatabase(baseURL string) error { ID: "d9256384-98ad-49a7-bc58-99ad0b4dc23c", }, Username: "eddy", - Email: utils.Ptr("eddy.cue@test.com"), + Email: new("eddy.cue@test.com"), FirstName: "Eddy", LastName: "Cue", DisplayName: "Eddy Cue", @@ -171,12 +171,12 @@ func (s *TestService) SeedDatabase(baseURL string) error { ID: "3654a746-35d4-4321-ac61-0bdcff2b4055", }, Name: "Nextcloud", - LaunchURL: utils.Ptr("https://nextcloud.local"), + LaunchURL: new("https://nextcloud.local"), Secret: "$2a$10$9dypwot8nGuCjT6wQWWpJOckZfRprhe2EkwpKizxS/fpVHrOLEJHC", // w2mUeZISmEvIDMEDvpY0PnxQIpj1m3zY CallbackURLs: model.UrlList{"http://nextcloud/auth/callback"}, LogoutCallbackURLs: model.UrlList{"http://nextcloud/auth/logout/callback"}, - ImageType: utils.StringPointer("png"), - CreatedByID: utils.Ptr(users[0].ID), + ImageType: new("png"), + CreatedByID: new(users[0].ID), }, { Base: model.Base{ @@ -185,7 +185,7 @@ func (s *TestService) SeedDatabase(baseURL string) error { Name: "Immich", Secret: "$2a$10$Ak.FP8riD1ssy2AGGbG.gOpnp/rBpymd74j0nxNMtW0GG1Lb4gzxe", // PYjrE9u4v9GVqXKi52eur0eb2Ci4kc0x CallbackURLs: model.UrlList{"http://immich/auth/callback"}, - CreatedByID: utils.Ptr(users[1].ID), + CreatedByID: new(users[1].ID), IsGroupRestricted: true, AllowedUserGroups: []model.UserGroup{ userGroups[1], @@ -200,7 +200,7 @@ func (s *TestService) SeedDatabase(baseURL string) error { CallbackURLs: model.UrlList{"http://tailscale/auth/callback"}, LogoutCallbackURLs: model.UrlList{"http://tailscale/auth/logout/callback"}, IsGroupRestricted: true, - CreatedByID: utils.Ptr(users[0].ID), + CreatedByID: new(users[0].ID), }, { Base: model.Base{ @@ -209,7 +209,7 @@ func (s *TestService) SeedDatabase(baseURL string) error { Name: "Federated", Secret: "$2a$10$Ak.FP8riD1ssy2AGGbG.gOpnp/rBpymd74j0nxNMtW0GG1Lb4gzxe", // PYjrE9u4v9GVqXKi52eur0eb2Ci4kc0x CallbackURLs: model.UrlList{"http://federated/auth/callback"}, - CreatedByID: utils.Ptr(users[1].ID), + CreatedByID: new(users[1].ID), AllowedUserGroups: []model.UserGroup{}, Credentials: model.OidcClientCredentials{ FederatedIdentities: []model.OidcClientFederatedIdentity{ @@ -229,7 +229,7 @@ func (s *TestService) SeedDatabase(baseURL string) error { Name: "SCIM Client", Secret: "$2a$10$h4wfa8gI7zavDAxwzSq1sOwYU4e8DwK1XZ8ZweNnY5KzlJ3Iz.qdK", // nQbiuMRG7FpdK2EnDd5MBivWQeKFXohn CallbackURLs: model.UrlList{"http://scimclient/auth/callback"}, - CreatedByID: utils.Ptr(users[0].ID), + CreatedByID: new(users[0].ID), IsGroupRestricted: true, AllowedUserGroups: []model.UserGroup{ userGroups[0], @@ -458,7 +458,7 @@ func (s *TestService) SeedDatabase(baseURL string) error { { Key: jwkutils.PrivateKeyDBKey, // {"alg":"RS256","d":"mvMDWSdPPvcum0c0iEHE2gbqtV2NKMmLwrl9E6K7g8lTV95SePLnW_bwyMPV7EGp7PQk3l17I5XRhFjze7GqTnFIOgKzMianPs7jv2ELtBMGK0xOPATgu1iGb70xZ6vcvuEfRyY3dJ0zr4jpUdVuXwKmx9rK4IdZn2dFCKfvSuspqIpz11RhF1ALrqDLkxGVv7ZwNh0_VhJZU9hcjG5l6xc7rQEKpPRkZp0IdjkGS8Z0FskoVaiRIWAbZuiVFB9WCW8k1czC4HQTPLpII01bUQx2ludbm0UlXRgVU9ptUUbU7GAImQqTOW8LfPGklEvcgzlIlR_oqw4P9yBxLi-yMQ","dp":"pvNCSnnhbo8Igw9psPR-DicxFnkXlu_ix4gpy6efTrxA-z1VDFDioJ814vKQNioYDzpyAP1gfMPhRkvG_q0hRZsJah3Sb9dfA-WkhSWY7lURQP4yIBTMU0PF_rEATuS7lRciYk1SOx5fqXZd3m_LP0vpBC4Ujlq6NAq6CIjCnms","dq":"TtUVGCCkPNgfOLmkYXu7dxxUCV5kB01-xAEK2OY0n0pG8vfDophH4_D_ZC7nvJ8J9uDhs_3JStexq1lIvaWtG99RNTChIEDzpdn6GH9yaVcb_eB4uJjrNm64FhF8PGCCwxA-xMCZMaARKwhMB2_IOMkxUbWboL3gnhJ2rDO_QO0","e":"AQAB","kid":"8uHDw3M6rf8","kty":"RSA","n":"yaeEL0VKoPBXIAaWXsUgmu05lAvEIIdJn0FX9lHh4JE5UY9B83C5sCNdhs9iSWzpeP11EVjWp8i3Yv2CF7c7u50BXnVBGtxpZpFC-585UXacoJ0chUmarL9GRFJcM1nPHBTFu68aRrn1rIKNHUkNaaxFo0NFGl_4EDDTO8HwawTjwkPoQlRzeByhlvGPVvwgB3Fn93B8QJ_cZhXKxJvjjrC_8Pk76heC_ntEMru71Ix77BoC3j2TuyiN7m9RNBW8BU5q6lKoIdvIeZfTFLzi37iufyfvMrJTixp9zhNB1NxlLCeOZl2MXegtiGqd2H3cbAyqoOiv9ihUWTfXj7SxJw","p":"_Yylc9e07CKdqNRD2EosMC2mrhrEa9j5oY_l00Qyy4-jmCA59Q9viyqvveRo0U7cRvFA5BWgWN6GGLh1DG3X-QBqVr0dnk3uzbobb55RYUXyPLuBZI2q6w2oasbiDwPdY7KpkVv_H-bpITQlyDvO8hhucA6rUV7F6KTQVz8M3Ms","q":"y5p3hch-7jJ21TkAhp_Vk1fLCAuD4tbErwQs2of9ja8sB4iJOs5Wn6HD3P7Mc8Plye7qaLHvzc8I5g0tPKWvC0DPd_FLPXiWwMVAzee3NUX_oGeJNOQp11y1w_KqdO9qZqHSEPZ3NcFL_SZMFgggxhM1uzRiPzsVN0lnD_6prZU","qi":"2Grt6uXHm61ji3xSdkBWNtUnj19vS1-7rFJp5SoYztVQVThf_W52BAiXKBdYZDRVoItC_VS2NvAOjeJjhYO_xQ_q3hK7MdtuXfEPpLnyXKkmWo3lrJ26wbeF6l05LexCkI7ShsOuSt-dsyaTJTszuKDIA6YOfWvfo3aVZmlWRaI","use":"sig"} - Value: utils.Ptr("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"), + Value: new("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"), }, } diff --git a/backend/internal/service/export_service.go b/backend/internal/service/export_service.go index 67d18111..1ce5b259 100644 --- a/backend/internal/service/export_service.go +++ b/backend/internal/service/export_service.go @@ -129,39 +129,39 @@ func (s *ExportService) getScanValuesForTable(cols []string, types utils.DBSchem case "boolean", "bool": var x bool if types[col].Nullable { - res[i] = utils.Ptr(utils.Ptr(x)) + res[i] = new(new(x)) } else { - res[i] = utils.Ptr(x) + res[i] = new(x) } case "blob", "bytea", "jsonb": // Treat jsonb columns as binary too var x []byte if types[col].Nullable { - res[i] = utils.Ptr(utils.Ptr(x)) + res[i] = new(new(x)) } else { - res[i] = utils.Ptr(x) + res[i] = new(x) } case "timestamp", "timestamptz", "timestamp with time zone", "datetime": var x datatype.DateTime if types[col].Nullable { - res[i] = utils.Ptr(utils.Ptr(x)) + res[i] = new(new(x)) } else { - res[i] = utils.Ptr(x) + res[i] = new(x) } case "integer", "int", "bigint": var x int64 if types[col].Nullable { - res[i] = utils.Ptr(utils.Ptr(x)) + res[i] = new(new(x)) } else { - res[i] = utils.Ptr(x) + res[i] = new(x) } default: // Treat everything else as a string (including the "numeric" type) var x string if types[col].Nullable { - res[i] = utils.Ptr(utils.Ptr(x)) + res[i] = new(new(x)) } else { - res[i] = utils.Ptr(x) + res[i] = new(x) } } } diff --git a/backend/internal/service/jwt_service_test.go b/backend/internal/service/jwt_service_test.go index 5b08f1c7..3adba7e0 100644 --- a/backend/internal/service/jwt_service_test.go +++ b/backend/internal/service/jwt_service_test.go @@ -20,7 +20,6 @@ import ( "github.com/pocket-id/pocket-id/backend/internal/common" "github.com/pocket-id/pocket-id/backend/internal/model" - "github.com/pocket-id/pocket-id/backend/internal/utils" jwkutils "github.com/pocket-id/pocket-id/backend/internal/utils/jwk" testutils "github.com/pocket-id/pocket-id/backend/internal/utils/testing" ) @@ -305,7 +304,7 @@ func TestGenerateVerifyAccessToken(t *testing.T) { user := model.User{ Base: model.Base{ID: "user123"}, - Email: utils.Ptr("user@example.com"), + Email: new("user@example.com"), IsAdmin: false, } @@ -341,7 +340,7 @@ func TestGenerateVerifyAccessToken(t *testing.T) { adminUser := model.User{ Base: model.Base{ID: "admin123"}, - Email: utils.Ptr("admin@example.com"), + Email: new("admin@example.com"), IsAdmin: true, } @@ -393,7 +392,7 @@ func TestGenerateVerifyAccessToken(t *testing.T) { user := model.User{ Base: model.Base{ID: "eddsauser123"}, - Email: utils.Ptr("eddsauser@example.com"), + Email: new("eddsauser@example.com"), IsAdmin: true, } @@ -430,7 +429,7 @@ func TestGenerateVerifyAccessToken(t *testing.T) { user := model.User{ Base: model.Base{ID: "ecdsauser123"}, - Email: utils.Ptr("ecdsauser@example.com"), + Email: new("ecdsauser@example.com"), IsAdmin: true, } @@ -467,7 +466,7 @@ func TestGenerateVerifyAccessToken(t *testing.T) { user := model.User{ Base: model.Base{ID: "rsauser123"}, - Email: utils.Ptr("rsauser@example.com"), + Email: new("rsauser@example.com"), IsAdmin: true, } @@ -502,7 +501,7 @@ func TestGenerateVerifyIdToken(t *testing.T) { t.Run("generates and verifies ID token with standard claims", func(t *testing.T) { service, _, _ := setupJwtService(t, mockConfig) - userClaims := map[string]interface{}{ + userClaims := map[string]any{ "sub": "user123", "name": "Test User", "email": "user@example.com", @@ -539,7 +538,7 @@ func TestGenerateVerifyIdToken(t *testing.T) { t.Run("can accept expired tokens if told so", func(t *testing.T) { service, _, _ := setupJwtService(t, mockConfig) - userClaims := map[string]interface{}{ + userClaims := map[string]any{ "sub": "user123", "name": "Test User", "email": "user@example.com", @@ -587,7 +586,7 @@ func TestGenerateVerifyIdToken(t *testing.T) { t.Run("generates and verifies ID token with nonce", func(t *testing.T) { service, _, _ := setupJwtService(t, mockConfig) - userClaims := map[string]interface{}{ + userClaims := map[string]any{ "sub": "user456", "name": "Another User", } @@ -612,7 +611,7 @@ func TestGenerateVerifyIdToken(t *testing.T) { t.Run("fails verification with incorrect issuer", func(t *testing.T) { service, _, _ := setupJwtService(t, mockConfig) - userClaims := map[string]interface{}{ + userClaims := map[string]any{ "sub": "user789", } tokenString, err := service.GenerateIDToken(userClaims, "client-789", "") @@ -634,7 +633,7 @@ func TestGenerateVerifyIdToken(t *testing.T) { require.True(t, ok) assert.Equal(t, origKeyID, loadedKeyID, "Loaded key should have the same ID as the original") - userClaims := map[string]interface{}{ + userClaims := map[string]any{ "sub": "eddsauser456", "name": "EdDSA User", "email": "eddsauser@example.com", @@ -672,7 +671,7 @@ func TestGenerateVerifyIdToken(t *testing.T) { require.True(t, ok) assert.Equal(t, origKeyID, loadedKeyID, "Loaded key should have the same ID as the original") - userClaims := map[string]interface{}{ + userClaims := map[string]any{ "sub": "ecdsauser456", "email": "ecdsauser@example.com", } @@ -709,7 +708,7 @@ func TestGenerateVerifyIdToken(t *testing.T) { require.True(t, ok) assert.Equal(t, origKeyID, loadedKeyID, "Loaded key should have the same ID as the original") - userClaims := map[string]interface{}{ + userClaims := map[string]any{ "sub": "rsauser456", "name": "RSA User", "email": "rsauser@example.com", @@ -742,7 +741,7 @@ func TestGenerateVerifyOAuthAccessToken(t *testing.T) { user := model.User{ Base: model.Base{ID: "user123"}, - Email: utils.Ptr("user@example.com"), + Email: new("user@example.com"), } const clientID = "test-client-123" @@ -825,7 +824,7 @@ func TestGenerateVerifyOAuthAccessToken(t *testing.T) { user := model.User{ Base: model.Base{ID: "eddsauser789"}, - Email: utils.Ptr("eddsaoauth@example.com"), + Email: new("eddsaoauth@example.com"), } const clientID = "eddsa-oauth-client" @@ -862,7 +861,7 @@ func TestGenerateVerifyOAuthAccessToken(t *testing.T) { user := model.User{ Base: model.Base{ID: "ecdsauser789"}, - Email: utils.Ptr("ecdsaoauth@example.com"), + Email: new("ecdsaoauth@example.com"), } const clientID = "ecdsa-oauth-client" @@ -899,7 +898,7 @@ func TestGenerateVerifyOAuthAccessToken(t *testing.T) { user := model.User{ Base: model.Base{ID: "rsauser789"}, - Email: utils.Ptr("rsaoauth@example.com"), + Email: new("rsaoauth@example.com"), } const clientID = "rsa-oauth-client" diff --git a/backend/internal/service/ldap_service.go b/backend/internal/service/ldap_service.go index 01a8ff3d..ac6042b2 100644 --- a/backend/internal/service/ldap_service.go +++ b/backend/internal/service/ldap_service.go @@ -529,7 +529,7 @@ func getDNProperty(property string, str string) string { // First we split at the comma property = strings.ToLower(property) l := len(property) + 1 - for _, v := range strings.Split(str, ",") { + for v := range strings.SplitSeq(str, ",") { v = strings.TrimSpace(v) if len(v) > l && strings.ToLower(v)[0:l] == property+"=" { return v[l:] diff --git a/backend/internal/service/oidc_service.go b/backend/internal/service/oidc_service.go index f633c151..828e0710 100644 --- a/backend/internal/service/oidc_service.go +++ b/backend/internal/service/oidc_service.go @@ -731,7 +731,7 @@ func (s *OidcService) CreateClient(ctx context.Context, input dto.OidcClientCrea Base: model.Base{ ID: input.ID, }, - CreatedByID: utils.Ptr(userID), + CreatedByID: new(userID), } updateOIDCClientModelFromDto(&client, &input.OidcClientUpdateDto) diff --git a/backend/internal/service/scim_service.go b/backend/internal/service/scim_service.go index 1d8a8736..976e2cd5 100644 --- a/backend/internal/service/scim_service.go +++ b/backend/internal/service/scim_service.go @@ -11,6 +11,7 @@ import ( "net/http" "net/url" "path" + "slices" "strconv" "strings" "time" @@ -243,7 +244,7 @@ func (s *ScimService) SyncServiceProvider(ctx context.Context, serviceProviderID return errors.Join(errs...) } - provider.LastSyncedAt = utils.Ptr(datatype.DateTime(time.Now())) + provider.LastSyncedAt = new(datatype.DateTime(time.Now())) if err := s.db.WithContext(ctx).Save(&provider).Error; err != nil { return err } @@ -788,10 +789,8 @@ func ensureScimStatus( resp *http.Response, provider model.ScimServiceProvider, allowedStatuses ...int) error { - for _, status := range allowedStatuses { - if resp.StatusCode == status { - return nil - } + if slices.Contains(allowedStatuses, resp.StatusCode) { + return nil } body := readScimErrorBody(resp.Body) diff --git a/backend/internal/service/user_group_service.go b/backend/internal/service/user_group_service.go index cc6cb7eb..e55c9085 100644 --- a/backend/internal/service/user_group_service.go +++ b/backend/internal/service/user_group_service.go @@ -162,7 +162,7 @@ func (s *UserGroupService) updateInternal(ctx context.Context, id string, input group.Name = input.Name group.FriendlyName = input.FriendlyName - group.UpdatedAt = utils.Ptr(datatype.DateTime(time.Now())) + group.UpdatedAt = new(datatype.DateTime(time.Now())) err = tx. WithContext(ctx). @@ -228,7 +228,7 @@ func (s *UserGroupService) updateUsersInternal(ctx context.Context, id string, u } // Save the updated group - group.UpdatedAt = utils.Ptr(datatype.DateTime(time.Now())) + group.UpdatedAt = new(datatype.DateTime(time.Now())) err = tx. WithContext(ctx). diff --git a/backend/internal/service/user_service.go b/backend/internal/service/user_service.go index b02dcab3..f0ad2369 100644 --- a/backend/internal/service/user_service.go +++ b/backend/internal/service/user_service.go @@ -435,7 +435,7 @@ func (s *UserService) updateUserInternal(ctx context.Context, userID string, upd } } - user.UpdatedAt = utils.Ptr(datatype.DateTime(time.Now())) + user.UpdatedAt = new(datatype.DateTime(time.Now())) err = tx. WithContext(ctx). @@ -501,9 +501,9 @@ func (s *UserService) UpdateUserGroups(ctx context.Context, id string, userGroup } // Update the UpdatedAt field for all affected groups - now := time.Now() + now := datatype.DateTime(time.Now()) for _, group := range groups { - group.UpdatedAt = utils.Ptr(datatype.DateTime(now)) + group.UpdatedAt = &now err = tx.WithContext(ctx).Save(&group).Error if err != nil { return model.User{}, err @@ -636,7 +636,7 @@ func (s *UserService) VerifyEmail(ctx context.Context, userID string, token stri } user.EmailVerified = true - user.UpdatedAt = utils.Ptr(datatype.DateTime(time.Now())) + user.UpdatedAt = new(datatype.DateTime(time.Now())) err = tx.WithContext(ctx).Save(&user).Error if err != nil { return err diff --git a/backend/internal/utils/callback_url_util_test.go b/backend/internal/utils/callback_url_util_test.go index 9691bdb4..959a8789 100644 --- a/backend/internal/utils/callback_url_util_test.go +++ b/backend/internal/utils/callback_url_util_test.go @@ -414,10 +414,10 @@ func TestGetCallbackURLFromList_LoopbackSpecialHandling(t *testing.T) { expectMatch: true, }, { - name: "IPv6 loopback without brackets in input", - urls: []string{"http://[::1]/callback"}, - inputCallbackURL: "http://::1:8080/callback", - expectedURL: "http://::1:8080/callback", + name: "IPv6 loopback with wildcard path", + urls: []string{"http://[::1]/auth/*"}, + inputCallbackURL: "http://[::1]:8080/auth/callback", + expectedURL: "http://[::1]:8080/auth/callback", expectMatch: true, }, { @@ -462,6 +462,13 @@ func TestGetCallbackURLFromList_LoopbackSpecialHandling(t *testing.T) { expectedURL: "http://127.0.0.1:8080/callback", expectMatch: true, }, + { + name: "wildcard matches IPv6 loopback", + urls: []string{"*"}, + inputCallbackURL: "http://[::1]:8080/callback", + expectedURL: "http://[::1]:8080/callback", + expectMatch: true, + }, } for _, tt := range tests { diff --git a/backend/internal/utils/ip_util.go b/backend/internal/utils/ip_util.go index be2ab83c..4136658f 100644 --- a/backend/internal/utils/ip_util.go +++ b/backend/internal/utils/ip_util.go @@ -87,9 +87,9 @@ func listContainsIP(ipNets []*net.IPNet, ip net.IP) bool { func loadLocalIPv6Ranges() { localIPv6Ranges = nil - ranges := strings.Split(common.EnvConfig.LocalIPv6Ranges, ",") + ranges := strings.SplitSeq(common.EnvConfig.LocalIPv6Ranges, ",") - for _, rangeStr := range ranges { + for rangeStr := range ranges { rangeStr = strings.TrimSpace(rangeStr) if rangeStr == "" { continue diff --git a/backend/internal/utils/json_util.go b/backend/internal/utils/json_util.go index f7dd55f4..476fdd9a 100644 --- a/backend/internal/utils/json_util.go +++ b/backend/internal/utils/json_util.go @@ -42,7 +42,7 @@ func (d *JSONDuration) UnmarshalJSON(b []byte) error { } } -func UnmarshalJSONFromDatabase(data interface{}, value any) error { +func UnmarshalJSONFromDatabase(data any, value any) error { switch v := value.(type) { case []byte: return json.Unmarshal(v, data) diff --git a/backend/internal/utils/list_request_util.go b/backend/internal/utils/list_request_util.go index e52773bc..b0bb34c8 100644 --- a/backend/internal/utils/list_request_util.go +++ b/backend/internal/utils/list_request_util.go @@ -43,7 +43,7 @@ func ParseListRequestOptions(ctx *gin.Context) (listRequestOptions ListRequestOp return listRequestOptions } -func PaginateFilterAndSort(params ListRequestOptions, query *gorm.DB, result interface{}) (PaginationResponse, error) { +func PaginateFilterAndSort(params ListRequestOptions, query *gorm.DB, result any) (PaginationResponse, error) { meta := extractModelMetadata(result) query = applyFilters(params.Filters, query, meta) @@ -52,7 +52,7 @@ func PaginateFilterAndSort(params ListRequestOptions, query *gorm.DB, result int return Paginate(params.Pagination.Page, params.Pagination.Limit, query, result) } -func Paginate(page int, pageSize int, query *gorm.DB, result interface{}) (PaginationResponse, error) { +func Paginate(page int, pageSize int, query *gorm.DB, result any) (PaginationResponse, error) { if page < 1 { page = 1 } @@ -117,8 +117,8 @@ func parseNestedFilters(ctx *gin.Context) map[string][]any { // Keys can be "filters[field]" or "filters[field][0]" raw := strings.TrimPrefix(key, "filters[") // Take everything up to the first closing bracket - if idx := strings.IndexByte(raw, ']'); idx != -1 { - field := raw[:idx] + if before, _, ok := strings.Cut(raw, "]"); ok { + field := before for _, v := range values { result[field] = append(result[field], ConvertStringToType(v)) } @@ -165,12 +165,12 @@ func applySorting(sortColumn string, sortDirection string, query *gorm.DB, meta } // extractModelMetadata extracts FieldMeta from the model struct using reflection -func extractModelMetadata(model interface{}) map[string]FieldMeta { +func extractModelMetadata(model any) map[string]FieldMeta { meta := make(map[string]FieldMeta) // Unwrap pointers and slices to get the element struct type t := reflect.TypeOf(model) - for t.Kind() == reflect.Ptr || t.Kind() == reflect.Slice { + for t.Kind() == reflect.Pointer || t.Kind() == reflect.Slice { t = t.Elem() if t == nil { return meta @@ -180,8 +180,7 @@ func extractModelMetadata(model interface{}) map[string]FieldMeta { // recursive parser that merges fields from embedded structs var parseStruct func(reflect.Type) parseStruct = func(st reflect.Type) { - for i := 0; i < st.NumField(); i++ { - field := st.Field(i) + for field := range st.Fields() { ft := field.Type // If the field is an embedded/anonymous struct, recurse into it diff --git a/backend/internal/utils/ptr_util.go b/backend/internal/utils/ptr_util.go index dae0bb5b..6c5ec1cb 100644 --- a/backend/internal/utils/ptr_util.go +++ b/backend/internal/utils/ptr_util.go @@ -1,10 +1,5 @@ package utils -// Ptr returns a pointer to the given value. -func Ptr[T any](v T) *T { - return &v -} - // PtrOrNil returns a pointer to v if v is not the zero value of its type, // otherwise it returns nil. func PtrOrNil[T comparable](v T) *T { diff --git a/backend/internal/utils/slogfanout.go b/backend/internal/utils/slogfanout.go deleted file mode 100644 index ac6777d7..00000000 --- a/backend/internal/utils/slogfanout.go +++ /dev/null @@ -1,85 +0,0 @@ -package utils - -import ( - "context" - "errors" - "fmt" - "log/slog" - "slices" -) - -// This file contains code adapted from https://github.com/samber/slog-multi -// Source: https://github.com/samber/slog-multi/blob/ced84707f45ec9848138349ed58de178eedaa6f2/pipe.go -// Copyright (C) 2023 Samuel Berthe -// License: MIT (https://github.com/samber/slog-multi/blob/ced84707f45ec9848138349ed58de178eedaa6f2/LICENSE) - -// LogFanoutHandler is a slog.Handler that sends logs to multiple destinations -type LogFanoutHandler []slog.Handler - -// Implements slog.Handler -func (h LogFanoutHandler) Enabled(ctx context.Context, l slog.Level) bool { - for i := range h { - if h[i].Enabled(ctx, l) { - return true - } - } - - return false -} - -// Implements slog.Handler -func (h LogFanoutHandler) Handle(ctx context.Context, r slog.Record) error { - errs := make([]error, 0) - for i := range h { - if h[i].Enabled(ctx, r.Level) { - err := try(func() error { - return h[i].Handle(ctx, r.Clone()) - }) - if err != nil { - errs = append(errs, err) - } - } - } - - return errors.Join(errs...) -} - -// Implements slog.Handler -func (h LogFanoutHandler) WithAttrs(attrs []slog.Attr) slog.Handler { - res := make(LogFanoutHandler, len(h)) - for i, v := range h { - res[i] = v.WithAttrs(slices.Clone(attrs)) - } - return res -} - -// Implements slog.Handler -func (h LogFanoutHandler) WithGroup(name string) slog.Handler { - // https://cs.opensource.google/go/x/exp/+/46b07846:slog/handler.go;l=247 - if name == "" { - return h - } - - res := make(LogFanoutHandler, len(h)) - for i, v := range h { - res[i] = v.WithGroup(name) - } - return res -} - -func try(callback func() error) (err error) { - defer func() { - r := recover() - if r != nil { - if e, ok := r.(error); ok { - err = e - } else { - err = fmt.Errorf("unexpected error: %+v", r) - } - } - }() - - err = callback() - - return -} diff --git a/backend/internal/utils/string_util.go b/backend/internal/utils/string_util.go index b20f083f..13281903 100644 --- a/backend/internal/utils/string_util.go +++ b/backend/internal/utils/string_util.go @@ -70,11 +70,6 @@ func GetHostnameFromURL(rawURL string) string { return parsedURL.Hostname() } -// StringPointer creates a string pointer from a string value -func StringPointer(s string) *string { - return &s -} - func CapitalizeFirstLetter(str string) string { if str == "" { return "" diff --git a/docker/Dockerfile b/docker/Dockerfile index 2dd396b6..3a1f38d0 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -4,7 +4,7 @@ ARG BUILD_TAGS="" # Stage 1: Build Frontend -FROM node:22-alpine AS frontend-builder +FROM node:24-alpine AS frontend-builder RUN corepack enable WORKDIR /build @@ -18,7 +18,7 @@ COPY ./frontend ./frontend/ RUN BUILD_OUTPUT_PATH=dist pnpm --filter pocket-id-frontend run build # Stage 2: Build Backend -FROM golang:1.25-alpine AS backend-builder +FROM golang:1.26-alpine AS backend-builder ARG BUILD_TAGS WORKDIR /build COPY ./backend/go.mod ./backend/go.sum ./ diff --git a/package.json b/package.json index 0f7cc837..e3b3e5a2 100644 --- a/package.json +++ b/package.json @@ -12,5 +12,5 @@ "test": "pnpm --filter pocket-id-tests test", "format": "pnpm --filter pocket-id-frontend format" }, - "packageManager": "pnpm@10.27.0+sha512.72d699da16b1179c14ba9e64dc71c9a40988cbdc65c264cb0e489db7de917f20dcf4d64d8723625f2969ba52d4b7e2a1170682d9ac2a5dcaeaab732b7e16f04a" + "packageManager": "pnpm@10.30.1+sha512.3590e550d5384caa39bd5c7c739f72270234b2f6059e13018f975c313b1eb9fefcc09714048765d4d9efe961382c312e624572c0420762bdc5d5940cdf9be73a" } From b3fe14313684f9d8c389ed93ea8e479e3681b5c6 Mon Sep 17 00:00:00 2001 From: Elias Schneider Date: Mon, 23 Feb 2026 19:54:32 +0100 Subject: [PATCH 2/2] fix: left align input error messages --- frontend/src/lib/components/form/form-input.svelte | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/src/lib/components/form/form-input.svelte b/frontend/src/lib/components/form/form-input.svelte index e3a995be..ab76404c 100644 --- a/frontend/src/lib/components/form/form-input.svelte +++ b/frontend/src/lib/components/form/form-input.svelte @@ -86,6 +86,6 @@ {/if} {/if} {#if input?.error} - {input.error} + {input.error} {/if}