Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions cmd/internal/migrations/lists.go
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ var Migrations = []Migration{
v3migrations.MigrateEnvVarConfig,
v3migrations.MigrateSessionConfig,
v3migrations.MigrateSessionExtractor,
v3migrations.MigrateKeyAuthConfig,
v3migrations.MigrateTimeoutConfig,
v3migrations.MigrateBasicauthAuthorizer,
v3migrations.MigrateBasicauthConfig,
Expand Down
84 changes: 84 additions & 0 deletions cmd/internal/migrations/v3/common.go
Original file line number Diff line number Diff line change
Expand Up @@ -1004,3 +1004,87 @@ func MigrateSessionExtractor(cmd *cobra.Command, cwd string, _, _ *semver.Versio
cmd.Println("Migrating session KeyLookup config")
return nil
}

// MigrateKeyAuthConfig updates keyauth middleware configuration to use Extractor
// instead of KeyLookup/AuthScheme and removes the deprecated fields.
func MigrateKeyAuthConfig(cmd *cobra.Command, cwd string, _, _ *semver.Version) error {
reConfig := regexp.MustCompile(`keyauth\.Config{[^}]*}`)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

The regex keyauth\.Config{[^}]*} is not robust enough to handle struct literals that contain nested blocks with braces, such as function literals (e.g., for a Validator field). The [^}]* pattern will stop at the first closing brace } it encounters, which can lead to an incomplete match and broken code after migration.

For example, this valid configuration would be migrated incorrectly:

keyauth.New(keyauth.Config{
    KeyLookup: "header:X-API-Key",
    Validator: func(c fiber.Ctx, key string) (bool, error) {
        if key == "secret" { // This '}' would break the regex
            return true, nil
        }
        return false, nil
    },
})

To fix this, you should use a more robust method to find the entire keyauth.Config struct literal. Instead of a single regex, you could find the start keyauth.Config{ and then programmatically scan for the matching closing brace, taking into account nested braces and strings, similar to the logic in the removeConfigField function.

reKeyLookup := regexp.MustCompile(`(?m)(\s*)KeyLookup:\s*("[^"]+")(,?)(\n?)`)
reAuthScheme := regexp.MustCompile(`(?m)\s*AuthScheme:\s*([^,\n]+)`)

err := internal.ChangeFileContent(cwd, func(content string) string {
return reConfig.ReplaceAllStringFunc(content, func(cfg string) string {
keyMatch := reKeyLookup.FindStringSubmatch(cfg)
if len(keyMatch) < 5 {
// remove AuthScheme if present
return removeConfigField(cfg, "AuthScheme")
}

indent := keyMatch[1]
val := strings.TrimSpace(keyMatch[2])
comma := keyMatch[3]
newline := keyMatch[4]

if uq, err := strconv.Unquote(val); err == nil {
val = uq
}

scheme := "Bearer"
if am := reAuthScheme.FindStringSubmatch(cfg); len(am) > 1 {
scheme = strings.TrimSpace(am[1])
if uq, err := strconv.Unquote(scheme); err == nil {
scheme = uq
}
}

Comment on lines +1032 to +1039
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Do not force-quote AuthScheme; preserve raw expressions to avoid breaking variable-based schemes

If AuthScheme is provided as a variable or const (not a string literal), unquoting then re-quoting with %q will turn the identifier into a literal string of its name. This changes behavior. Instead, keep the raw matched expression (quoted or not) and only default to a quoted "Bearer" when missing.

Apply this diff to preserve AuthScheme expressions and fix the FromAuthHeader call:

-            scheme := "Bearer"
-            if am := reAuthScheme.FindStringSubmatch(cfg); len(am) > 1 {
-                scheme = strings.TrimSpace(am[1])
-                if uq, err := strconv.Unquote(scheme); err == nil {
-                    scheme = uq
-                }
-            }
+            // Preserve raw AuthScheme expression if present (supports literals and identifiers).
+            // Default to a quoted "Bearer" when not provided.
+            schemeArg := strconv.Quote("Bearer")
+            if am := reAuthScheme.FindStringSubmatch(cfg); len(am) > 1 {
+                schemeArg = strings.TrimSpace(am[1])
+            }
@@
-                    if strings.EqualFold(header, "Authorization") {
-                        extractors = append(extractors, fmt.Sprintf("keyauth.FromAuthHeader(%q, %q)", header, scheme))
+                    if strings.EqualFold(header, "Authorization") {
+                        extractors = append(extractors, fmt.Sprintf("keyauth.FromAuthHeader(%q, %s)", header, schemeArg))

Also applies to: 1045-1052

parts := strings.Split(val, ",")
var extractors []string
for _, p := range parts {
p = strings.TrimSpace(p)
switch {
case strings.HasPrefix(p, "header:"):
header := strings.TrimPrefix(p, "header:")
if strings.EqualFold(header, "Authorization") {
extractors = append(extractors, fmt.Sprintf("keyauth.FromAuthHeader(%q, %q)", header, scheme))
} else {
extractors = append(extractors, fmt.Sprintf("keyauth.FromHeader(%q)", header))
}
case strings.HasPrefix(p, "query:"):
extractors = append(extractors, fmt.Sprintf("keyauth.FromQuery(%q)", strings.TrimPrefix(p, "query:")))
case strings.HasPrefix(p, "param:"):
extractors = append(extractors, fmt.Sprintf("keyauth.FromParam(%q)", strings.TrimPrefix(p, "param:")))
case strings.HasPrefix(p, "form:"):
extractors = append(extractors, fmt.Sprintf("keyauth.FromForm(%q)", strings.TrimPrefix(p, "form:")))
case strings.HasPrefix(p, "cookie:"):
extractors = append(extractors, fmt.Sprintf("keyauth.FromCookie(%q)", strings.TrimPrefix(p, "cookie:")))
default:
// unrecognized source; remove field and return
cfg = removeConfigField(cfg, "AuthScheme")
return removeConfigField(cfg, "KeyLookup")
}
}

extractor := ""
if len(extractors) == 1 {
extractor = extractors[0]
} else if len(extractors) > 1 {
extractor = fmt.Sprintf("keyauth.Chain(%s)", strings.Join(extractors, ", "))
}

cfg = removeConfigField(cfg, "AuthScheme")
if extractor == "" {
return removeConfigField(cfg, "KeyLookup")
}

newField := fmt.Sprintf("%sExtractor: %s%s%s", indent, extractor, comma, newline)
cfg = reKeyLookup.ReplaceAllString(cfg, newField)
return cfg
})
})
if err != nil {
return fmt.Errorf("failed to migrate keyauth configs: %w", err)
}

cmd.Println("Migrating keyauth middleware configs")
return nil
}
96 changes: 96 additions & 0 deletions cmd/internal/migrations/v3/common_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1300,3 +1300,99 @@ var _ = session.New(session.Config{
assert.NotContains(t, content, "KeyLookup")
assert.Contains(t, buf.String(), "Migrating session KeyLookup config")
}

func Test_MigrateKeyAuthConfig_HeaderAuth(t *testing.T) {
t.Parallel()

dir, err := os.MkdirTemp("", "mkeyauth_header")
require.NoError(t, err)
defer func() { require.NoError(t, os.RemoveAll(dir)) }()

file := writeTempFile(t, dir, `package main
import "github.com/gofiber/fiber/v2/middleware/keyauth"
var _ = keyauth.New(keyauth.Config{
KeyLookup: "header:Authorization",
AuthScheme: "Bearer",
})`)

var buf bytes.Buffer
cmd := newCmd(&buf)
require.NoError(t, v3.MigrateKeyAuthConfig(cmd, dir, nil, nil))

content := readFile(t, file)
assert.NotContains(t, content, "KeyLookup")
assert.NotContains(t, content, "AuthScheme")
assert.Contains(t, content, `Extractor: keyauth.FromAuthHeader("Authorization", "Bearer")`)
assert.Contains(t, buf.String(), "Migrating keyauth middleware configs")
}

func Test_MigrateKeyAuthConfig_Cookie(t *testing.T) {
t.Parallel()

dir, err := os.MkdirTemp("", "mkeyauth_cookie")
require.NoError(t, err)
defer func() { require.NoError(t, os.RemoveAll(dir)) }()

file := writeTempFile(t, dir, `package main
import "github.com/gofiber/fiber/v2/middleware/keyauth"
var _ = keyauth.New(keyauth.Config{
KeyLookup: "cookie:token",
})`)

var buf bytes.Buffer
cmd := newCmd(&buf)
require.NoError(t, v3.MigrateKeyAuthConfig(cmd, dir, nil, nil))

content := readFile(t, file)
assert.NotContains(t, content, "KeyLookup")
assert.Contains(t, content, `Extractor: keyauth.FromCookie("token")`)
assert.Contains(t, buf.String(), "Migrating keyauth middleware configs")
}

func Test_MigrateKeyAuthConfig_Chain(t *testing.T) {
t.Parallel()

dir, err := os.MkdirTemp("", "mkeyauth_chain")
require.NoError(t, err)
defer func() { require.NoError(t, os.RemoveAll(dir)) }()

file := writeTempFile(t, dir, `package main
import "github.com/gofiber/fiber/v2/middleware/keyauth"
var _ = keyauth.New(keyauth.Config{
KeyLookup: "query:token,header:X-API-Key",
})`)

var buf bytes.Buffer
cmd := newCmd(&buf)
require.NoError(t, v3.MigrateKeyAuthConfig(cmd, dir, nil, nil))

content := readFile(t, file)
assert.NotContains(t, content, "KeyLookup")
assert.Contains(t, content, `Extractor: keyauth.Chain(keyauth.FromQuery("token"), keyauth.FromHeader("X-API-Key"))`)
assert.Contains(t, buf.String(), "Migrating keyauth middleware configs")
}

func Test_MigrateKeyAuthConfig_Unknown(t *testing.T) {
t.Parallel()

dir, err := os.MkdirTemp("", "mkeyauth_unknown")
require.NoError(t, err)
defer func() { require.NoError(t, os.RemoveAll(dir)) }()

file := writeTempFile(t, dir, `package main
import "github.com/gofiber/fiber/v2/middleware/keyauth"
var _ = keyauth.New(keyauth.Config{
KeyLookup: "unknown:token",
AuthScheme: "Bearer",
})`)

var buf bytes.Buffer
cmd := newCmd(&buf)
require.NoError(t, v3.MigrateKeyAuthConfig(cmd, dir, nil, nil))

content := readFile(t, file)
assert.NotContains(t, content, "KeyLookup")
assert.NotContains(t, content, "AuthScheme")
assert.NotContains(t, content, "Extractor")
assert.Contains(t, buf.String(), "Migrating keyauth middleware configs")
}
Loading