From 5805ab5433c5fd02205847984a02bab8c6bc1fa9 Mon Sep 17 00:00:00 2001 From: Takanori Hirano Date: Mon, 25 Aug 2025 13:21:03 +0000 Subject: [PATCH 1/2] feat(auth/oidc): add glob pattern support for allowed users - Add OIDC_ALLOWED_USERS_GLOB flag/env to allow user authorization via glob patterns - Compile and evaluate patterns with github.com/gobwas/glob - Preserve exact match checks (OIDC_ALLOWED_USERS) and fall back to globs - Introduce splitWithEscapes to parse comma-separated values with escaped delimiters - Wire through CLI flags and pkg/mcp-proxy - Add tests for glob matching and helper parsing - Update README and docs to document new option Backward compatible: when no exact or glob rules are set, all users are allowed (as before). --- README.md | 3 +- docs/docs/configuration.md | 40 +++++-- docs/docs/examples.md | 5 + docs/docs/oauth-setup.md | 16 +++ go.mod | 1 + go.sum | 2 + main.go | 47 ++++++++ main_test.go | 219 +++++++++++++++++++++++++++++++++++++ pkg/auth/oidc.go | 47 ++++++-- pkg/auth/oidc_test.go | 69 ++++++++++++ pkg/mcp-proxy/main.go | 2 + 11 files changed, 430 insertions(+), 21 deletions(-) create mode 100644 main_test.go diff --git a/README.md b/README.md index cb7d642..5c469b2 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,8 @@ - **Drop-in OAuth 2.1/OIDC gateway for MCP servers — put it in front, no code changes.** - **Your IdP, your choice**: Google, GitHub, or any OIDC provider — e.g. Okta, Auth0, Azure AD, Keycloak — plus optional password. -- **Publish local MCP servers safely**: Supports all stdio, SSE, and HTTP transports. For stdio, traffic is converted to `/mcp`. For SSE/HTTP, it’s proxied as-is. Of course, with authentication. +- **Flexible user matching**: Support exact matching and glob patterns for user authorization (e.g., `*@company.com`) +- **Publish local MCP servers safely**: Supports all stdio, SSE, and HTTP transports. For stdio, traffic is converted to `/mcp`. For SSE/HTTP, it's proxied as-is. Of course, with authentication. - **Verified across major MCP clients**: Claude, Claude Code, ChatGPT, GitHub Copilot, Cursor, etc. — the proxy smooths client-specific quirks for consistent auth. --- diff --git a/docs/docs/configuration.md b/docs/docs/configuration.md index a5a8c99..56bf941 100644 --- a/docs/docs/configuration.md +++ b/docs/docs/configuration.md @@ -52,15 +52,37 @@ Complete reference for all MCP Auth Proxy configuration options. #### Generic OIDC -| Option | Environment Variable | Default | Description | -| -------------------------- | ------------------------ | ---------------------- | ----------------------------------------------------------- | -| `--oidc-configuration-url` | `OIDC_CONFIGURATION_URL` | - | OIDC configuration URL | -| `--oidc-client-id` | `OIDC_CLIENT_ID` | - | OIDC client ID | -| `--oidc-client-secret` | `OIDC_CLIENT_SECRET` | - | OIDC client secret | -| `--oidc-allowed-users` | `OIDC_ALLOWED_USERS` | - | Comma-separated list of allowed OIDC users | -| `--oidc-provider-name` | `OIDC_PROVIDER_NAME` | `OIDC` | Display name for OIDC provider | -| `--oidc-scopes` | `OIDC_SCOPES` | `openid,profile,email` | Comma-separated list of OIDC scopes | -| `--oidc-user-id-field` | `OIDC_USER_ID_FIELD` | `/email` | JSON pointer to user ID field in userinfo endpoint response | +| Option | Environment Variable | Default | Description | +| --------------------------- | ------------------------- | ---------------------- | ------------------------------------------------------------ | +| `--oidc-configuration-url` | `OIDC_CONFIGURATION_URL` | - | OIDC configuration URL | +| `--oidc-client-id` | `OIDC_CLIENT_ID` | - | OIDC client ID | +| `--oidc-client-secret` | `OIDC_CLIENT_SECRET` | - | OIDC client secret | +| `--oidc-allowed-users` | `OIDC_ALLOWED_USERS` | - | Comma-separated list of allowed OIDC users (exact match) | +| `--oidc-allowed-users-glob` | `OIDC_ALLOWED_USERS_GLOB` | - | Comma-separated list of glob patterns for allowed OIDC users | +| `--oidc-provider-name` | `OIDC_PROVIDER_NAME` | `OIDC` | Display name for OIDC provider | +| `--oidc-scopes` | `OIDC_SCOPES` | `openid,profile,email` | Comma-separated list of OIDC scopes | +| `--oidc-user-id-field` | `OIDC_USER_ID_FIELD` | `/email` | JSON pointer to user ID field in userinfo endpoint response | + +##### OIDC User Matching + +You can use both exact matching and glob patterns for OIDC user authorization: + +- **Exact matching** (`--oidc-allowed-users`): Users must match exactly +- **Glob patterns** (`--oidc-allowed-users-glob`): Users are matched against [glob patterns](https://github.com/gobwas/glob) + +**Examples:** + +```bash +# Exact matching +--oidc-allowed-users "user1@example.com,admin@company.org" + +# Glob patterns - allow all users from example.com +--oidc-allowed-users-glob "*@example.com" + +# Combined exact and glob matching +--oidc-allowed-users "specific@user.com" \ +--oidc-allowed-users-glob "*@example.com" +``` ### Server Options diff --git a/docs/docs/examples.md b/docs/docs/examples.md index 8f68305..47fec2e 100644 --- a/docs/docs/examples.md +++ b/docs/docs/examples.md @@ -42,6 +42,11 @@ services: - GITHUB_CLIENT_SECRET=your-github-client-secret - GITHUB_ALLOWED_USERS=username1,username2 - GITHUB_ALLOWED_ORGS=org1,org2:team1 + - OIDC_CONFIGURATION_URL=https://your-oidc-provider/.well-known/openid_configuration + - OIDC_CLIENT_ID=your-oidc-client-id + - OIDC_CLIENT_SECRET=your-oidc-client-secret + - OIDC_ALLOWED_USERS=specific@user.com + - OIDC_ALLOWED_USERS_GLOB=*@example.com - TRUSTED_PROXIES=10.0.0.0/8,172.16.0.0/12,192.168.0.0/16 volumes: - ./data:/data diff --git a/docs/docs/oauth-setup.md b/docs/docs/oauth-setup.md index 7b45246..309ab8b 100644 --- a/docs/docs/oauth-setup.md +++ b/docs/docs/oauth-setup.md @@ -99,6 +99,8 @@ Configure OAuth providers to enable secure authentication for your MCP server. ### 2. Configure MCP Auth Proxy +#### Exact user matching: + ```bash ./mcp-auth-proxy \ --external-url https://{your-domain} \ @@ -110,6 +112,19 @@ Configure OAuth providers to enable secure authentication for your MCP server. -- your-mcp-command ``` +#### Glob pattern matching: + +```bash +./mcp-auth-proxy \ + --external-url https://{your-domain} \ + --tls-accept-tos \ + --oidc-configuration-url "https://your-provider.com/.well-known/openid-configuration" \ + --oidc-client-id "your-oidc-client-id" \ + --oidc-client-secret "your-oidc-client-secret" \ + --oidc-allowed-users-glob "*@example.com" \ + -- your-mcp-command +``` + ### Provider-Specific Examples #### Okta @@ -167,6 +182,7 @@ export OIDC_CONFIGURATION_URL="https://provider.com/.well-known/openid-configura export OIDC_CLIENT_ID="your-oidc-client-id" export OIDC_CLIENT_SECRET="your-oidc-client-secret" export OIDC_ALLOWED_USERS="user1@example.com,user2@example.com" +export OIDC_ALLOWED_USERS_GLOB="*@example.com" ./mcp-auth-proxy --external-url https://{your-domain} --tls-accept-tos -- your-mcp-command ``` diff --git a/go.mod b/go.mod index ebc09d1..b9787f5 100644 --- a/go.mod +++ b/go.mod @@ -46,6 +46,7 @@ require ( github.com/go-playground/universal-translator v0.18.1 // indirect github.com/go-playground/validator/v10 v10.26.0 // indirect github.com/gobuffalo/pop/v6 v6.1.1 // indirect + github.com/gobwas/glob v0.2.3 // indirect github.com/goccy/go-json v0.10.5 // indirect github.com/gogo/protobuf v1.3.2 // indirect github.com/golang/mock v1.6.0 // indirect diff --git a/go.sum b/go.sum index 55c4b2d..a42034b 100644 --- a/go.sum +++ b/go.sum @@ -155,6 +155,8 @@ github.com/gobuffalo/pop/v6 v6.1.1 h1:eUDBaZcb0gYrmFnKwpuTEUA7t5ZHqNfvS4POqJYXDZ github.com/gobuffalo/pop/v6 v6.1.1/go.mod h1:1n7jAmI1i7fxuXPZjZb0VBPQDbksRtCoFnrDV5IsvaI= github.com/gobuffalo/tags/v3 v3.1.4/go.mod h1:ArRNo3ErlHO8BtdA0REaZxijuWnWzF6PUXngmMXd2I0= github.com/gobuffalo/validate/v3 v3.3.3/go.mod h1:YC7FsbJ/9hW/VjQdmXPvFqvRis4vrRYFxr69WiNZw6g= +github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y= +github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8= github.com/goccy/go-json v0.10.5 h1:Fq85nIqj+gXn/S5ahsiTlK3TmC85qgirsdTP/+DeaC4= github.com/goccy/go-json v0.10.5/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M= github.com/gofrs/uuid v4.0.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= diff --git a/main.go b/main.go index f622bfa..a369213 100644 --- a/main.go +++ b/main.go @@ -25,6 +25,45 @@ func getEnvBoolWithDefault(key string, defaultValue bool) bool { return defaultValue } +// splitWithEscapes splits a string by delimiter, respecting escape sequences +// e.g., "a,b\,c,d" with delimiter "," returns ["a", "b,c", "d"] +func splitWithEscapes(s, delimiter string) []string { + if s == "" { + return []string{} + } + + var result []string + var current strings.Builder + escaped := false + + for i := 0; i < len(s); i++ { + if escaped { + current.WriteByte(s[i]) + escaped = false + } else if s[i] == '\\' && i+1 < len(s) { + // Check if next character is the delimiter + if strings.HasPrefix(s[i+1:], delimiter) { + // Skip the backslash and add the delimiter character + escaped = true + } else { + // Not escaping delimiter, keep the backslash + current.WriteByte(s[i]) + } + } else if strings.HasPrefix(s[i:], delimiter) { + // Found unescaped delimiter + result = append(result, strings.TrimSpace(current.String())) + current.Reset() + i += len(delimiter) - 1 // -1 because loop will increment + } else { + current.WriteByte(s[i]) + } + } + + // Add the last part + result = append(result, strings.TrimSpace(current.String())) + return result +} + func main() { var listen string var tlsListen string @@ -49,6 +88,7 @@ func main() { var oidcUserIDField string var oidcProviderName string var oidcAllowedUsers string + var oidcAllowedUsersGlob string var password string var passwordHash string var proxyBearerToken string @@ -98,6 +138,11 @@ func main() { } } + var oidcAllowedUsersGlobList []string + if oidcAllowedUsersGlob != "" { + oidcAllowedUsersGlobList = splitWithEscapes(oidcAllowedUsersGlob, ",") + } + var oidcScopesList []string if oidcScopes != "" { oidcScopesList = strings.Split(oidcScopes, ",") @@ -149,6 +194,7 @@ func main() { oidcUserIDField, oidcProviderName, oidcAllowedUsersList, + oidcAllowedUsersGlobList, password, passwordHash, trustedProxiesList, @@ -190,6 +236,7 @@ func main() { rootCmd.Flags().StringVar(&oidcUserIDField, "oidc-user-id-field", getEnvWithDefault("OIDC_USER_ID_FIELD", "/email"), "JSON pointer to user ID field in userinfo endpoint response") rootCmd.Flags().StringVar(&oidcProviderName, "oidc-provider-name", getEnvWithDefault("OIDC_PROVIDER_NAME", "OIDC"), "Display name for OIDC provider") rootCmd.Flags().StringVar(&oidcAllowedUsers, "oidc-allowed-users", getEnvWithDefault("OIDC_ALLOWED_USERS", ""), "Comma-separated list of allowed OIDC users") + rootCmd.Flags().StringVar(&oidcAllowedUsersGlob, "oidc-allowed-users-glob", getEnvWithDefault("OIDC_ALLOWED_USERS_GLOB", ""), "Comma-separated list of glob patterns for allowed OIDC users") // Password authentication rootCmd.Flags().StringVar(&password, "password", getEnvWithDefault("PASSWORD", ""), "Plain text password for authentication (will be hashed with bcrypt)") diff --git a/main_test.go b/main_test.go new file mode 100644 index 0000000..6295889 --- /dev/null +++ b/main_test.go @@ -0,0 +1,219 @@ +package main + +import ( + "reflect" + "testing" +) + +func TestSplitWithEscapes(t *testing.T) { + testCases := []struct { + name string + input string + delimiter string + expected []string + }{ + { + name: "simple comma split", + input: "a,b,c", + delimiter: ",", + expected: []string{"a", "b", "c"}, + }, + { + name: "escaped comma", + input: "a,b\\,c,d", + delimiter: ",", + expected: []string{"a", "b,c", "d"}, + }, + { + name: "email with escaped comma", + input: "user@domain.com\\,backup,*@example.org", + delimiter: ",", + expected: []string{"user@domain.com,backup", "*@example.org"}, + }, + { + name: "glob pattern with escaped comma", + input: "admin.*@company\\,inc.*,*@example.com", + delimiter: ",", + expected: []string{"admin.*@company,inc.*", "*@example.com"}, + }, + { + name: "empty string", + input: "", + delimiter: ",", + expected: []string{}, + }, + { + name: "single item", + input: "single", + delimiter: ",", + expected: []string{"single"}, + }, + { + name: "no escapes needed", + input: "user1@example.com,user2@test.org", + delimiter: ",", + expected: []string{"user1@example.com", "user2@test.org"}, + }, + { + name: "multiple escaped commas", + input: "a\\,b\\,c,d,e\\,f", + delimiter: ",", + expected: []string{"a,b,c", "d", "e,f"}, + }, + { + name: "whitespace trimming", + input: "a , b\\,c , d", + delimiter: ",", + expected: []string{"a", "b,c", "d"}, + }, + { + name: "different delimiter", + input: "a;b\\;c;d", + delimiter: ";", + expected: []string{"a", "b;c", "d"}, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + result := splitWithEscapes(tc.input, tc.delimiter) + + if !reflect.DeepEqual(result, tc.expected) { + t.Errorf("Expected %v, got %v", tc.expected, result) + } + }) + } +} + +func TestGetEnvWithDefault(t *testing.T) { + testCases := []struct { + name string + key string + def string + expected string + setEnv bool + envValue string + }{ + { + name: "env var not set", + key: "TEST_KEY_NOT_SET", + def: "default_value", + expected: "default_value", + setEnv: false, + }, + { + name: "env var set", + key: "TEST_KEY_SET", + def: "default_value", + expected: "env_value", + setEnv: true, + envValue: "env_value", + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + // Setup + if tc.setEnv { + t.Setenv(tc.key, tc.envValue) + } + + // Test + result := getEnvWithDefault(tc.key, tc.def) + + if result != tc.expected { + t.Errorf("Expected %s, got %s", tc.expected, result) + } + }) + } +} + +func TestGetEnvBoolWithDefault(t *testing.T) { + testCases := []struct { + name string + key string + def bool + expected bool + setEnv bool + envValue string + }{ + { + name: "env var not set - default false", + key: "TEST_BOOL_NOT_SET", + def: false, + expected: false, + setEnv: false, + }, + { + name: "env var not set - default true", + key: "TEST_BOOL_NOT_SET2", + def: true, + expected: true, + setEnv: false, + }, + { + name: "env var set to 'true'", + key: "TEST_BOOL_TRUE", + def: false, + expected: true, + setEnv: true, + envValue: "true", + }, + { + name: "env var set to 'TRUE'", + key: "TEST_BOOL_TRUE_UPPER", + def: false, + expected: true, + setEnv: true, + envValue: "TRUE", + }, + { + name: "env var set to '1'", + key: "TEST_BOOL_ONE", + def: false, + expected: true, + setEnv: true, + envValue: "1", + }, + { + name: "env var set to 'false'", + key: "TEST_BOOL_FALSE", + def: true, + expected: false, + setEnv: true, + envValue: "false", + }, + { + name: "env var set to '0'", + key: "TEST_BOOL_ZERO", + def: true, + expected: false, + setEnv: true, + envValue: "0", + }, + { + name: "env var set to other value", + key: "TEST_BOOL_OTHER", + def: true, + expected: false, + setEnv: true, + envValue: "other", + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + // Setup + if tc.setEnv { + t.Setenv(tc.key, tc.envValue) + } + + // Test + result := getEnvBoolWithDefault(tc.key, tc.def) + + if result != tc.expected { + t.Errorf("Expected %t, got %t", tc.expected, result) + } + }) + } +} diff --git a/pkg/auth/oidc.go b/pkg/auth/oidc.go index 767d86e..6f02d64 100644 --- a/pkg/auth/oidc.go +++ b/pkg/auth/oidc.go @@ -9,21 +9,23 @@ import ( "slices" "github.com/gin-gonic/gin" + "github.com/gobwas/glob" "github.com/mattn/go-jsonpointer" "golang.org/x/oauth2" ) type oidcProvider struct { - oauth2 oauth2.Config - providerName string - userInfoURL string - userIDField string - allowedUsers []string + oauth2 oauth2.Config + providerName string + userInfoURL string + userIDField string + allowedUsers []string + allowedUsersGlob []glob.Glob } func NewOIDCProvider( configurationURL string, scopes []string, userIDField string, - providerName, externalURL, clientID, clientSecret string, allowedUsers []string, + providerName, externalURL, clientID, clientSecret string, allowedUsers []string, allowedUsersGlob []string, ) (Provider, error) { resp, err := http.Get(configurationURL) if err != nil { @@ -42,6 +44,19 @@ func NewOIDCProvider( if err != nil { return nil, err } + + // Compile glob patterns + var compiledGlobs []glob.Glob + for _, pattern := range allowedUsersGlob { + if pattern != "" { + g, err := glob.Compile(pattern) + if err != nil { + return nil, err + } + compiledGlobs = append(compiledGlobs, g) + } + } + return &oidcProvider{ oauth2: oauth2.Config{ ClientID: clientID, @@ -53,10 +68,11 @@ func NewOIDCProvider( TokenURL: cfg.TokenEndpoint, }, }, - providerName: providerName, - userInfoURL: cfg.UserInfo, - userIDField: userIDField, - allowedUsers: allowedUsers, + providerName: providerName, + userInfoURL: cfg.UserInfo, + userIDField: userIDField, + allowedUsers: allowedUsers, + allowedUsersGlob: compiledGlobs, }, nil } @@ -113,13 +129,22 @@ func (p *oidcProvider) Authorization(ctx context.Context, token *oauth2.Token) ( return false, "", errors.New("user ID field is not a string") } - if len(p.allowedUsers) == 0 { + // If no restrictions are set, allow all users + if len(p.allowedUsers) == 0 && len(p.allowedUsersGlob) == 0 { return true, userID, nil } + // Check exact matches first if slices.Contains(p.allowedUsers, userID) { return true, userID, nil } + // Check glob patterns + for _, g := range p.allowedUsersGlob { + if g.Match(userID) { + return true, userID, nil + } + } + return false, userID, nil } diff --git a/pkg/auth/oidc_test.go b/pkg/auth/oidc_test.go index f4a0101..1fb70ad 100644 --- a/pkg/auth/oidc_test.go +++ b/pkg/auth/oidc_test.go @@ -45,6 +45,7 @@ func setupOIDCTest(allowedUsers []string, userIDField string) (Provider, gin.IRo TestOIDCClientID, TestOIDCClientSecret, allowedUsers, + []string{}, ) if err != nil { panic(err) @@ -182,6 +183,7 @@ func TestOIDCProviderErrors(t *testing.T) { TestOIDCClientID, TestOIDCClientSecret, []string{}, + []string{}, ) require.Error(t, err) }) @@ -203,6 +205,7 @@ func TestOIDCProviderErrors(t *testing.T) { TestOIDCClientID, TestOIDCClientSecret, []string{}, + []string{}, ) require.Error(t, err) }) @@ -246,3 +249,69 @@ func TestOIDCProviderErrors(t *testing.T) { require.False(t, ok) }) } + +func TestOIDCProviderGlobPatterns(t *testing.T) { + // Setup test server with OIDC configuration + configServer := gin.New() + configServer.GET("/.well-known/openid_configuration", func(c *gin.Context) { + c.JSON(200, map[string]interface{}{ + "authorization_endpoint": "http://localhost/auth", + "token_endpoint": "http://localhost/token", + "userinfo_endpoint": "http://localhost/userinfo", + }) + }) + tsConfig := httptest.NewServer(configServer) + defer tsConfig.Close() + + // Create provider with glob patterns + p, err := NewOIDCProvider( + tsConfig.URL+"/.well-known/openid_configuration", + []string{"openid", "profile"}, + "/email", + "TestOIDC", + TestOIDCExternalURL, + TestOIDCClientID, + TestOIDCClientSecret, + []string{}, // no exact matches + []string{"*@example.com", "admin.*@company.*"}, + ) + require.NoError(t, err) + + // Test glob matching + testCases := []struct { + email string + expected bool + }{ + {"user@example.com", true}, // matches *@example.com + {"test@example.com", true}, // matches *@example.com + {"admin.user@company.org", true}, // matches admin.*@company.* + {"admin.test@company.co.uk", true}, // matches admin.*@company.* + {"user@other.com", false}, // no match + {"regular@company.org", false}, // no match (not admin.*) + {"admin@example.com", true}, // matches *@example.com + } + + for _, tc := range testCases { + t.Run(tc.email, func(t *testing.T) { + // Mock userinfo endpoint + userServer := gin.New() + userServer.GET("/userinfo", func(c *gin.Context) { + c.JSON(200, map[string]interface{}{ + "email": tc.email, + }) + }) + tsUser := httptest.NewServer(userServer) + defer tsUser.Close() + + // Update provider's userinfo URL for this test + provider := p.(*oidcProvider) + provider.userInfoURL = tsUser.URL + "/userinfo" + + // Test authorization + authorized, userID, err := provider.Authorization(context.Background(), &oauth2.Token{AccessToken: "test"}) + require.NoError(t, err) + require.Equal(t, tc.email, userID) + require.Equal(t, tc.expected, authorized, "Expected %v for email %s", tc.expected, tc.email) + }) + } +} diff --git a/pkg/mcp-proxy/main.go b/pkg/mcp-proxy/main.go index 13b04b9..c88fbbb 100644 --- a/pkg/mcp-proxy/main.go +++ b/pkg/mcp-proxy/main.go @@ -56,6 +56,7 @@ func Run( oidcUserIDField string, oidcProviderName string, oidcAllowedUsers []string, + oidcAllowedUsersGlob []string, password string, passwordHash string, trustedProxy []string, @@ -176,6 +177,7 @@ func Run( oidcClientID, oidcClientSecret, oidcAllowedUsers, + oidcAllowedUsersGlob, ) if err != nil { return fmt.Errorf("failed to create OIDC provider: %w", err) From 0e02a3b921ea3274d7ca0b87b86c37aa11b3792d Mon Sep 17 00:00:00 2001 From: Takanori Hirano Date: Mon, 25 Aug 2025 15:50:11 +0000 Subject: [PATCH 2/2] build: promote github.com/gobwas/glob to direct dependency Also tidy OIDC glob pattern test formatting. --- go.mod | 2 +- pkg/auth/oidc_test.go | 12 ++++++------ 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/go.mod b/go.mod index b9787f5..164b5de 100644 --- a/go.mod +++ b/go.mod @@ -7,6 +7,7 @@ require ( github.com/gin-contrib/sessions v1.0.4 github.com/gin-contrib/zap v1.1.5 github.com/gin-gonic/gin v1.10.1 + github.com/gobwas/glob v0.2.3 github.com/golang-jwt/jwt/v5 v5.3.0 github.com/mark3labs/mcp-go v0.37.0 github.com/mattn/go-jsonpointer v0.0.1 @@ -46,7 +47,6 @@ require ( github.com/go-playground/universal-translator v0.18.1 // indirect github.com/go-playground/validator/v10 v10.26.0 // indirect github.com/gobuffalo/pop/v6 v6.1.1 // indirect - github.com/gobwas/glob v0.2.3 // indirect github.com/goccy/go-json v0.10.5 // indirect github.com/gogo/protobuf v1.3.2 // indirect github.com/golang/mock v1.6.0 // indirect diff --git a/pkg/auth/oidc_test.go b/pkg/auth/oidc_test.go index 1fb70ad..d80f766 100644 --- a/pkg/auth/oidc_test.go +++ b/pkg/auth/oidc_test.go @@ -282,13 +282,13 @@ func TestOIDCProviderGlobPatterns(t *testing.T) { email string expected bool }{ - {"user@example.com", true}, // matches *@example.com - {"test@example.com", true}, // matches *@example.com - {"admin.user@company.org", true}, // matches admin.*@company.* + {"user@example.com", true}, // matches *@example.com + {"test@example.com", true}, // matches *@example.com + {"admin.user@company.org", true}, // matches admin.*@company.* {"admin.test@company.co.uk", true}, // matches admin.*@company.* - {"user@other.com", false}, // no match - {"regular@company.org", false}, // no match (not admin.*) - {"admin@example.com", true}, // matches *@example.com + {"user@other.com", false}, // no match + {"regular@company.org", false}, // no match (not admin.*) + {"admin@example.com", true}, // matches *@example.com } for _, tc := range testCases {