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
2 changes: 2 additions & 0 deletions cmd/internal/migrations/lists.go
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,8 @@ var Migrations = []Migration{
v3migrations.MigrateSessionExtractor,
v3migrations.MigrateSessionStore,
v3migrations.MigrateKeyAuthConfig,
v3migrations.MigrateJWTExtractor,
v3migrations.MigratePasetoExtractor,
v3migrations.MigrateTimeoutConfig,
v3migrations.MigrateBasicauthAuthorizer,
v3migrations.MigrateBasicauthConfig,
Expand Down
59 changes: 48 additions & 11 deletions cmd/internal/migrations/v3/common.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package v3
import (
"fmt"
"regexp"
"sort"
"strconv"
"strings"
)
Expand Down Expand Up @@ -152,7 +153,19 @@ func removeConfigField(src, field string) string {
// fn with the parsed components. If fn returns an empty string, the field is
// removed entirely.
func replaceKeyLookup(src string, fn func(indent, val, comma, comment, newline string) string) string {
re := regexp.MustCompile(`(?m)(\s*)KeyLookup:\s*([^\n]+)(\n?)`)
return replaceStringField(src, "KeyLookup", fn)
}

func replaceStringField(src, field string, fn func(indent, val, comma, comment, newline string) string) string {
return replaceFieldImpl(src, field, true, fn)
}

func replaceField(src, field string, fn func(indent, val, comma, comment, newline string) string) string {
return replaceFieldImpl(src, field, false, fn)
}

func replaceFieldImpl(src, field string, unquote bool, fn func(indent, val, comma, comment, newline string) string) string {
re := regexp.MustCompile(`(?m)^(\s*)` + regexp.QuoteMeta(field) + `:\s*([^\n]+)(\n?)`)
return re.ReplaceAllStringFunc(src, func(s string) string {
sub := re.FindStringSubmatch(s)
indent := sub[1]
Expand All @@ -174,25 +187,49 @@ func replaceKeyLookup(src string, fn func(indent, val, comma, comment, newline s
val = strings.TrimSpace(strings.TrimSuffix(val, ","))
}

if uq, err := strconv.Unquote(val); err == nil {
val = uq
repl := fn(indent, val, comma, comment, newline)
if repl == "" {
if unquote {
uq, err := strconv.Unquote(val)
if err != nil {
if comment != "" {
return fmt.Sprintf("%s%s%s", indent, comment, newline)
return fmt.Sprintf("%s// TODO: migrate %s: %s %s%s", indent, field, val, comment, newline)
}
return newline
return fmt.Sprintf("%s// TODO: migrate %s: %s%s", indent, field, val, newline)
}
return repl
val = uq
}

if comment != "" {
return fmt.Sprintf("%s// TODO: migrate KeyLookup: %s %s%s", indent, val, comment, newline)
repl := fn(indent, val, comma, comment, newline)
if repl == "" {
if comment != "" {
return fmt.Sprintf("%s%s%s", indent, comment, newline)
}
return newline
}
return fmt.Sprintf("%s// TODO: migrate KeyLookup: %s%s", indent, val, newline)
return repl
})
}

func collectAliases(content string, reImport *regexp.Regexp, defaults []string) []string {
aliases := map[string]struct{}{}
for _, m := range reImport.FindAllStringSubmatch(content, -1) {
alias := strings.TrimSpace(m[1])
if alias == "" {
for _, d := range defaults {
aliases[d] = struct{}{}
}
continue
}
aliases[alias] = struct{}{}
}

result := make([]string, 0, len(aliases))
for alias := range aliases {
result = append(result, alias)
}
sort.Strings(result)
return result
}

// splitArgs splits a comma-separated argument list into its individual arguments
// while respecting nested parentheses, brackets, and braces as well as quoted
// strings. It returns the trimmed arguments without altering inner spacing.
Expand Down
112 changes: 112 additions & 0 deletions cmd/internal/migrations/v3/jwt_extractor.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
package v3

import (
"fmt"
"regexp"
"strconv"
"strings"

semver "github.com/Masterminds/semver/v3"
"github.com/spf13/cobra"

"github.com/gofiber/cli/cmd/internal"
)

func MigrateJWTExtractor(cmd *cobra.Command, cwd string, _, _ *semver.Version) error {
reImport := regexp.MustCompile(`(?m)^\s*(?:import\s+)?(?:([\w\.]+)\s+)?"github\.com/gofiber/contrib/jwt(?:/v\d+)?"`)
reAuthScheme := regexp.MustCompile(`(?m)^\s*AuthScheme:\s*([^,\n]+)`)
reAuthLine := regexp.MustCompile(`(?m)^\s*AuthScheme:\s*[^\n]+\n?`)
reFilter := regexp.MustCompile(`(?m)^(\s*)Filter:\s*`)

changed, err := internal.ChangeFileContent(cwd, func(content string) string {
aliases := collectAliases(content, reImport, []string{"jwtware", "jwt"})
if len(aliases) == 0 {
return content
}

updated := content
for _, alias := range aliases {
reConfig := regexp.MustCompile(regexp.QuoteMeta(alias) + `\.Config{(?:[^{}]|{[^{}]*})*}`)
updated = reConfig.ReplaceAllStringFunc(updated, func(cfg string) string {
schemeArg := "\"Bearer\""
if am := reAuthScheme.FindStringSubmatch(cfg); len(am) > 1 {
raw := strings.TrimSpace(am[1])
if uq, err := strconv.Unquote(raw); err == nil {
schemeArg = fmt.Sprintf("%q", uq)
} else {
schemeArg = raw
}
}

cfg = replaceStringField(cfg, "TokenLookup", func(indent, val, comma, comment, newline string) string {
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("extractors.FromAuthHeader(%s)", schemeArg))
} else {
extractors = append(extractors, fmt.Sprintf("extractors.FromHeader(%q)", header))
}
case strings.HasPrefix(p, "query:"):
extractors = append(extractors, fmt.Sprintf("extractors.FromQuery(%q)", strings.TrimPrefix(p, "query:")))
case strings.HasPrefix(p, "param:"):
extractors = append(extractors, fmt.Sprintf("extractors.FromParam(%q)", strings.TrimPrefix(p, "param:")))
case strings.HasPrefix(p, "cookie:"):
extractors = append(extractors, fmt.Sprintf("extractors.FromCookie(%q)", strings.TrimPrefix(p, "cookie:")))
case strings.HasPrefix(p, "form:"):
extractors = append(extractors, fmt.Sprintf("extractors.FromForm(%q)", strings.TrimPrefix(p, "form:")))
default:
if comment != "" {
comment = " " + comment
}
return fmt.Sprintf("%s// TODO: migrate TokenLookup: %s%s%s", indent, val, comment, newline)
}
}

extractor := ""
switch len(extractors) {
case 1:
extractor = extractors[0]
case 0:
default:
extractor = fmt.Sprintf("extractors.Chain(%s)", strings.Join(extractors, ", "))
}

if extractor == "" {
if comment != "" {
comment = " " + comment
}
return fmt.Sprintf("%s// TODO: migrate TokenLookup: %s%s%s", indent, val, comment, newline)
}

if comment != "" {
comment = " " + comment
}
return fmt.Sprintf("%sExtractor: %s%s%s%s", indent, extractor, comma, comment, newline)
})

cfg = reAuthLine.ReplaceAllString(cfg, "")
cfg = reFilter.ReplaceAllString(cfg, "${1}Next: ")
return cfg
})
}

if updated != content && strings.Contains(updated, "extractors.") {
updated = addImport(updated, "github.com/gofiber/fiber/v3/extractors")
}
return updated
})
if err != nil {
return fmt.Errorf("failed to migrate jwt extractor config: %w", err)
}
if !changed {
return nil
}

cmd.Println("Migrating jwt middleware configs")
return nil
}
118 changes: 118 additions & 0 deletions cmd/internal/migrations/v3/jwt_extractor_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
package v3_test

import (
"bytes"
"os"
"testing"

"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"

"github.com/gofiber/cli/cmd/internal/migrations/v3"
)

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

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

file := writeTempFile(t, dir, `package main
import (
"github.com/gofiber/fiber/v3"
jwtware "github.com/gofiber/contrib/jwt"
)
var _ = jwtware.New(jwtware.Config{
TokenLookup: "header:Authorization, cookie:jwt",
AuthScheme: "Token",
Filter: func(c fiber.Ctx) bool { return false },
})`)

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

content := readFile(t, file)
assert.NotContains(t, content, "TokenLookup")
assert.NotContains(t, content, "AuthScheme")
assert.Contains(t, content, `Extractor: extractors.Chain(extractors.FromAuthHeader("Token"), extractors.FromCookie("jwt"))`)
assert.Contains(t, content, "Next:")
assert.Contains(t, content, "func(c fiber.Ctx) bool { return false },")
assert.Contains(t, content, `"github.com/gofiber/fiber/v3/extractors"`)
assert.Contains(t, buf.String(), "Migrating jwt middleware configs")
}

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

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

file := writeTempFile(t, dir, `package main
import (
jwtware "github.com/gofiber/contrib/jwt"
"strings"
)
var _ = jwtware.New(jwtware.Config{
TokenLookup: strings.Join([]string{"header:Authorization"}, ","),
AuthScheme: "Bearer",
})`)

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

content := readFile(t, file)
assert.Contains(t, content, "// TODO: migrate TokenLookup: strings.Join([]string{\"header:Authorization\"}, \",\")")
assert.NotContains(t, content, "AuthScheme")
assert.Contains(t, buf.String(), "Migrating jwt middleware configs")
}

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

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

file := writeTempFile(t, dir, `package main
import authjwt "github.com/gofiber/contrib/jwt/v3"
var _ = authjwt.New(authjwt.Config{
TokenLookup: "cookie:session",
})`)

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

content := readFile(t, file)
assert.NotContains(t, content, "TokenLookup")
assert.Contains(t, content, `Extractor: extractors.FromCookie("session")`)
assert.Contains(t, content, `"github.com/gofiber/fiber/v3/extractors"`)
assert.Contains(t, buf.String(), "Migrating jwt middleware configs")
}

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

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

original := `package main
import jwtware "example.com/jwtware"
var _ = jwtware.Config{
TokenLookup: "header:Authorization",
}`
file := writeTempFile(t, dir, original)

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

content := readFile(t, file)
assert.Equal(t, original, content)
assert.Empty(t, buf.String())
}
Loading
Loading