Skip to content

Commit 339389a

Browse files
authored
Merge pull request #232 from gofiber/codex/2025-11-30-11-20-48
2 parents 580cce6 + 21b10fe commit 339389a

File tree

6 files changed

+547
-11
lines changed

6 files changed

+547
-11
lines changed

cmd/internal/migrations/lists.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,8 @@ var Migrations = []Migration{
7171
v3migrations.MigrateSessionExtractor,
7272
v3migrations.MigrateSessionStore,
7373
v3migrations.MigrateKeyAuthConfig,
74+
v3migrations.MigrateJWTExtractor,
75+
v3migrations.MigratePasetoExtractor,
7476
v3migrations.MigrateTimeoutConfig,
7577
v3migrations.MigrateBasicauthAuthorizer,
7678
v3migrations.MigrateBasicauthConfig,

cmd/internal/migrations/v3/common.go

Lines changed: 48 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package v3
33
import (
44
"fmt"
55
"regexp"
6+
"sort"
67
"strconv"
78
"strings"
89
)
@@ -152,7 +153,19 @@ func removeConfigField(src, field string) string {
152153
// fn with the parsed components. If fn returns an empty string, the field is
153154
// removed entirely.
154155
func replaceKeyLookup(src string, fn func(indent, val, comma, comment, newline string) string) string {
155-
re := regexp.MustCompile(`(?m)(\s*)KeyLookup:\s*([^\n]+)(\n?)`)
156+
return replaceStringField(src, "KeyLookup", fn)
157+
}
158+
159+
func replaceStringField(src, field string, fn func(indent, val, comma, comment, newline string) string) string {
160+
return replaceFieldImpl(src, field, true, fn)
161+
}
162+
163+
func replaceField(src, field string, fn func(indent, val, comma, comment, newline string) string) string {
164+
return replaceFieldImpl(src, field, false, fn)
165+
}
166+
167+
func replaceFieldImpl(src, field string, unquote bool, fn func(indent, val, comma, comment, newline string) string) string {
168+
re := regexp.MustCompile(`(?m)^(\s*)` + regexp.QuoteMeta(field) + `:\s*([^\n]+)(\n?)`)
156169
return re.ReplaceAllStringFunc(src, func(s string) string {
157170
sub := re.FindStringSubmatch(s)
158171
indent := sub[1]
@@ -174,25 +187,49 @@ func replaceKeyLookup(src string, fn func(indent, val, comma, comment, newline s
174187
val = strings.TrimSpace(strings.TrimSuffix(val, ","))
175188
}
176189

177-
if uq, err := strconv.Unquote(val); err == nil {
178-
val = uq
179-
repl := fn(indent, val, comma, comment, newline)
180-
if repl == "" {
190+
if unquote {
191+
uq, err := strconv.Unquote(val)
192+
if err != nil {
181193
if comment != "" {
182-
return fmt.Sprintf("%s%s%s", indent, comment, newline)
194+
return fmt.Sprintf("%s// TODO: migrate %s: %s %s%s", indent, field, val, comment, newline)
183195
}
184-
return newline
196+
return fmt.Sprintf("%s// TODO: migrate %s: %s%s", indent, field, val, newline)
185197
}
186-
return repl
198+
val = uq
187199
}
188200

189-
if comment != "" {
190-
return fmt.Sprintf("%s// TODO: migrate KeyLookup: %s %s%s", indent, val, comment, newline)
201+
repl := fn(indent, val, comma, comment, newline)
202+
if repl == "" {
203+
if comment != "" {
204+
return fmt.Sprintf("%s%s%s", indent, comment, newline)
205+
}
206+
return newline
191207
}
192-
return fmt.Sprintf("%s// TODO: migrate KeyLookup: %s%s", indent, val, newline)
208+
return repl
193209
})
194210
}
195211

212+
func collectAliases(content string, reImport *regexp.Regexp, defaults []string) []string {
213+
aliases := map[string]struct{}{}
214+
for _, m := range reImport.FindAllStringSubmatch(content, -1) {
215+
alias := strings.TrimSpace(m[1])
216+
if alias == "" {
217+
for _, d := range defaults {
218+
aliases[d] = struct{}{}
219+
}
220+
continue
221+
}
222+
aliases[alias] = struct{}{}
223+
}
224+
225+
result := make([]string, 0, len(aliases))
226+
for alias := range aliases {
227+
result = append(result, alias)
228+
}
229+
sort.Strings(result)
230+
return result
231+
}
232+
196233
// splitArgs splits a comma-separated argument list into its individual arguments
197234
// while respecting nested parentheses, brackets, and braces as well as quoted
198235
// strings. It returns the trimmed arguments without altering inner spacing.
Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
package v3
2+
3+
import (
4+
"fmt"
5+
"regexp"
6+
"strconv"
7+
"strings"
8+
9+
semver "github.com/Masterminds/semver/v3"
10+
"github.com/spf13/cobra"
11+
12+
"github.com/gofiber/cli/cmd/internal"
13+
)
14+
15+
func MigrateJWTExtractor(cmd *cobra.Command, cwd string, _, _ *semver.Version) error {
16+
reImport := regexp.MustCompile(`(?m)^\s*(?:import\s+)?(?:([\w\.]+)\s+)?"github\.com/gofiber/contrib/jwt(?:/v\d+)?"`)
17+
reAuthScheme := regexp.MustCompile(`(?m)^\s*AuthScheme:\s*([^,\n]+)`)
18+
reAuthLine := regexp.MustCompile(`(?m)^\s*AuthScheme:\s*[^\n]+\n?`)
19+
reFilter := regexp.MustCompile(`(?m)^(\s*)Filter:\s*`)
20+
21+
changed, err := internal.ChangeFileContent(cwd, func(content string) string {
22+
aliases := collectAliases(content, reImport, []string{"jwtware", "jwt"})
23+
if len(aliases) == 0 {
24+
return content
25+
}
26+
27+
updated := content
28+
for _, alias := range aliases {
29+
reConfig := regexp.MustCompile(regexp.QuoteMeta(alias) + `\.Config{(?:[^{}]|{[^{}]*})*}`)
30+
updated = reConfig.ReplaceAllStringFunc(updated, func(cfg string) string {
31+
schemeArg := "\"Bearer\""
32+
if am := reAuthScheme.FindStringSubmatch(cfg); len(am) > 1 {
33+
raw := strings.TrimSpace(am[1])
34+
if uq, err := strconv.Unquote(raw); err == nil {
35+
schemeArg = fmt.Sprintf("%q", uq)
36+
} else {
37+
schemeArg = raw
38+
}
39+
}
40+
41+
cfg = replaceStringField(cfg, "TokenLookup", func(indent, val, comma, comment, newline string) string {
42+
parts := strings.Split(val, ",")
43+
var extractors []string
44+
for _, p := range parts {
45+
p = strings.TrimSpace(p)
46+
switch {
47+
case strings.HasPrefix(p, "header:"):
48+
header := strings.TrimPrefix(p, "header:")
49+
if strings.EqualFold(header, "Authorization") {
50+
extractors = append(extractors, fmt.Sprintf("extractors.FromAuthHeader(%s)", schemeArg))
51+
} else {
52+
extractors = append(extractors, fmt.Sprintf("extractors.FromHeader(%q)", header))
53+
}
54+
case strings.HasPrefix(p, "query:"):
55+
extractors = append(extractors, fmt.Sprintf("extractors.FromQuery(%q)", strings.TrimPrefix(p, "query:")))
56+
case strings.HasPrefix(p, "param:"):
57+
extractors = append(extractors, fmt.Sprintf("extractors.FromParam(%q)", strings.TrimPrefix(p, "param:")))
58+
case strings.HasPrefix(p, "cookie:"):
59+
extractors = append(extractors, fmt.Sprintf("extractors.FromCookie(%q)", strings.TrimPrefix(p, "cookie:")))
60+
case strings.HasPrefix(p, "form:"):
61+
extractors = append(extractors, fmt.Sprintf("extractors.FromForm(%q)", strings.TrimPrefix(p, "form:")))
62+
default:
63+
if comment != "" {
64+
comment = " " + comment
65+
}
66+
return fmt.Sprintf("%s// TODO: migrate TokenLookup: %s%s%s", indent, val, comment, newline)
67+
}
68+
}
69+
70+
extractor := ""
71+
switch len(extractors) {
72+
case 1:
73+
extractor = extractors[0]
74+
case 0:
75+
default:
76+
extractor = fmt.Sprintf("extractors.Chain(%s)", strings.Join(extractors, ", "))
77+
}
78+
79+
if extractor == "" {
80+
if comment != "" {
81+
comment = " " + comment
82+
}
83+
return fmt.Sprintf("%s// TODO: migrate TokenLookup: %s%s%s", indent, val, comment, newline)
84+
}
85+
86+
if comment != "" {
87+
comment = " " + comment
88+
}
89+
return fmt.Sprintf("%sExtractor: %s%s%s%s", indent, extractor, comma, comment, newline)
90+
})
91+
92+
cfg = reAuthLine.ReplaceAllString(cfg, "")
93+
cfg = reFilter.ReplaceAllString(cfg, "${1}Next: ")
94+
return cfg
95+
})
96+
}
97+
98+
if updated != content && strings.Contains(updated, "extractors.") {
99+
updated = addImport(updated, "github.com/gofiber/fiber/v3/extractors")
100+
}
101+
return updated
102+
})
103+
if err != nil {
104+
return fmt.Errorf("failed to migrate jwt extractor config: %w", err)
105+
}
106+
if !changed {
107+
return nil
108+
}
109+
110+
cmd.Println("Migrating jwt middleware configs")
111+
return nil
112+
}
Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
package v3_test
2+
3+
import (
4+
"bytes"
5+
"os"
6+
"testing"
7+
8+
"github.com/stretchr/testify/assert"
9+
"github.com/stretchr/testify/require"
10+
11+
"github.com/gofiber/cli/cmd/internal/migrations/v3"
12+
)
13+
14+
func Test_MigrateJWTExtractor(t *testing.T) {
15+
t.Parallel()
16+
17+
dir, err := os.MkdirTemp("", "mjwt")
18+
require.NoError(t, err)
19+
defer func() { require.NoError(t, os.RemoveAll(dir)) }()
20+
21+
file := writeTempFile(t, dir, `package main
22+
import (
23+
"github.com/gofiber/fiber/v3"
24+
jwtware "github.com/gofiber/contrib/jwt"
25+
)
26+
var _ = jwtware.New(jwtware.Config{
27+
TokenLookup: "header:Authorization, cookie:jwt",
28+
AuthScheme: "Token",
29+
Filter: func(c fiber.Ctx) bool { return false },
30+
})`)
31+
32+
var buf bytes.Buffer
33+
cmd := newCmd(&buf)
34+
require.NoError(t, v3.MigrateJWTExtractor(cmd, dir, nil, nil))
35+
36+
content := readFile(t, file)
37+
assert.NotContains(t, content, "TokenLookup")
38+
assert.NotContains(t, content, "AuthScheme")
39+
assert.Contains(t, content, `Extractor: extractors.Chain(extractors.FromAuthHeader("Token"), extractors.FromCookie("jwt"))`)
40+
assert.Contains(t, content, "Next:")
41+
assert.Contains(t, content, "func(c fiber.Ctx) bool { return false },")
42+
assert.Contains(t, content, `"github.com/gofiber/fiber/v3/extractors"`)
43+
assert.Contains(t, buf.String(), "Migrating jwt middleware configs")
44+
}
45+
46+
func Test_MigrateJWTExtractor_TokenLookupExpr(t *testing.T) {
47+
t.Parallel()
48+
49+
dir, err := os.MkdirTemp("", "mjwt_expr")
50+
require.NoError(t, err)
51+
defer func() { require.NoError(t, os.RemoveAll(dir)) }()
52+
53+
file := writeTempFile(t, dir, `package main
54+
import (
55+
jwtware "github.com/gofiber/contrib/jwt"
56+
"strings"
57+
)
58+
var _ = jwtware.New(jwtware.Config{
59+
TokenLookup: strings.Join([]string{"header:Authorization"}, ","),
60+
AuthScheme: "Bearer",
61+
})`)
62+
63+
var buf bytes.Buffer
64+
cmd := newCmd(&buf)
65+
require.NoError(t, v3.MigrateJWTExtractor(cmd, dir, nil, nil))
66+
67+
content := readFile(t, file)
68+
assert.Contains(t, content, "// TODO: migrate TokenLookup: strings.Join([]string{\"header:Authorization\"}, \",\")")
69+
assert.NotContains(t, content, "AuthScheme")
70+
assert.Contains(t, buf.String(), "Migrating jwt middleware configs")
71+
}
72+
73+
func Test_MigrateJWTExtractor_CustomAlias(t *testing.T) {
74+
t.Parallel()
75+
76+
dir, err := os.MkdirTemp("", "mjwt_alias")
77+
require.NoError(t, err)
78+
defer func() { require.NoError(t, os.RemoveAll(dir)) }()
79+
80+
file := writeTempFile(t, dir, `package main
81+
import authjwt "github.com/gofiber/contrib/jwt/v3"
82+
var _ = authjwt.New(authjwt.Config{
83+
TokenLookup: "cookie:session",
84+
})`)
85+
86+
var buf bytes.Buffer
87+
cmd := newCmd(&buf)
88+
require.NoError(t, v3.MigrateJWTExtractor(cmd, dir, nil, nil))
89+
90+
content := readFile(t, file)
91+
assert.NotContains(t, content, "TokenLookup")
92+
assert.Contains(t, content, `Extractor: extractors.FromCookie("session")`)
93+
assert.Contains(t, content, `"github.com/gofiber/fiber/v3/extractors"`)
94+
assert.Contains(t, buf.String(), "Migrating jwt middleware configs")
95+
}
96+
97+
func Test_MigrateJWTExtractor_SkipUnrelatedPackage(t *testing.T) {
98+
t.Parallel()
99+
100+
dir, err := os.MkdirTemp("", "mjwt_skip")
101+
require.NoError(t, err)
102+
defer func() { require.NoError(t, os.RemoveAll(dir)) }()
103+
104+
original := `package main
105+
import jwtware "example.com/jwtware"
106+
var _ = jwtware.Config{
107+
TokenLookup: "header:Authorization",
108+
}`
109+
file := writeTempFile(t, dir, original)
110+
111+
var buf bytes.Buffer
112+
cmd := newCmd(&buf)
113+
require.NoError(t, v3.MigrateJWTExtractor(cmd, dir, nil, nil))
114+
115+
content := readFile(t, file)
116+
assert.Equal(t, original, content)
117+
assert.Empty(t, buf.String())
118+
}

0 commit comments

Comments
 (0)