From eeba467bd0eb6733e3b1b676bf105121e33be846 Mon Sep 17 00:00:00 2001 From: Steven Masley Date: Thu, 21 Aug 2025 11:57:34 -0500 Subject: [PATCH 1/5] >DO NOT MERGE< use ory/fosite to implment oauth2 server --- coderd/coderd.go | 109 ++++----- coderd/database/models.go | 2 +- coderd/database/querier.go | 2 +- coderd/database/queries.sql.go | 2 +- coderd/fositeprovider/authorize.go | 141 ++++++++++++ coderd/fositeprovider/fositestorage/client.go | 55 +++++ .../fositeprovider/fositestorage/storage.go | 72 ++++++ coderd/fositeprovider/hasher.go | 62 ++++++ coderd/fositeprovider/provider.go | 84 +++++++ coderd/fositeprovider/token.go | 54 +++++ go.mod | 27 +++ go.sum | 209 ++++++++++++++++++ scripts/oauth2/setup-test-app.sh | 2 +- scripts/oauth2/test-manual-flow.sh | 4 +- site/package.json | 3 +- 15 files changed, 769 insertions(+), 59 deletions(-) create mode 100644 coderd/fositeprovider/authorize.go create mode 100644 coderd/fositeprovider/fositestorage/client.go create mode 100644 coderd/fositeprovider/fositestorage/storage.go create mode 100644 coderd/fositeprovider/hasher.go create mode 100644 coderd/fositeprovider/provider.go create mode 100644 coderd/fositeprovider/token.go diff --git a/coderd/coderd.go b/coderd/coderd.go index 5debc13d21431..08c48aa31cbad 100644 --- a/coderd/coderd.go +++ b/coderd/coderd.go @@ -20,7 +20,7 @@ import ( "sync/atomic" "time" - "github.com/coder/coder/v2/coderd/oauth2provider" + "github.com/coder/coder/v2/coderd/fositeprovider" "github.com/coder/coder/v2/coderd/pproflabel" "github.com/coder/coder/v2/coderd/prebuilds" "github.com/coder/coder/v2/coderd/usage" @@ -211,11 +211,6 @@ type Options struct { HealthcheckRefresh time.Duration WorkspaceProxiesFetchUpdater *atomic.Pointer[healthcheck.WorkspaceProxiesFetchUpdater] - // OAuthSigningKey is the crypto key used to sign and encrypt state strings - // related to OAuth. This is a symmetric secret key using hmac to sign payloads. - // So this secret should **never** be exposed to the client. - OAuthSigningKey [32]byte - // APIRateLimit is the minutely throughput rate limit per user or ip. // Setting a rate limit <0 will disable the rate limiter across the entire // app. Some specific routes have their own configurable rate limits. @@ -590,6 +585,7 @@ func New(options *Options) *API { Authorizer: options.Authorizer, Logger: options.Logger, }, + OAuth2Provider: fositeprovider.New(ctx, options.Logger, options.Database), metricsCache: metricsCache, Auditor: atomic.Pointer[audit.Auditor]{}, ConnectionLogger: atomic.Pointer[connectionlog.ConnectionLogger]{}, @@ -950,55 +946,65 @@ func New(options *Options) *API { // OAuth2 protected resource metadata endpoint for RFC 9728 discovery r.Get("/.well-known/oauth-protected-resource", api.oauth2ProtectedResourceMetadata()) - // OAuth2 linking routes do not make sense under the /api/v2 path. These are - // for an external application to use Coder as an OAuth2 provider, not for - // logging into Coder with an external OAuth2 provider. + // fosite oauth2 replacement r.Route("/oauth2", func(r chi.Router) { - r.Use( - httpmw.RequireExperimentWithDevBypass(api.Experiments, codersdk.ExperimentOAuth2), - ) - r.Route("/authorize", func(r chi.Router) { - r.Use( - // Fetch the app as system for the authorize endpoint - httpmw.AsAuthzSystem(httpmw.ExtractOAuth2ProviderAppWithOAuth2Errors(options.Database)), - apiKeyMiddlewareRedirect, - ) - // GET shows the consent page, POST processes the consent - r.Get("/", api.getOAuth2ProviderAppAuthorize()) - r.Post("/", api.postOAuth2ProviderAppAuthorize()) - }) - r.Route("/tokens", func(r chi.Router) { - r.Use( - // Use OAuth2-compliant error responses for the tokens endpoint - httpmw.AsAuthzSystem(httpmw.ExtractOAuth2ProviderAppWithOAuth2Errors(options.Database)), - ) - r.Group(func(r chi.Router) { - r.Use(apiKeyMiddleware) - // DELETE on /tokens is not part of the OAuth2 spec. It is our own - // route used to revoke permissions from an application. It is here for - // parity with POST on /tokens. - r.Delete("/", api.deleteOAuth2ProviderAppTokens()) - }) - // The POST /tokens endpoint will be called from an unauthorized client so - // we cannot require an API key. - r.Post("/", api.postOAuth2ProviderAppToken()) - }) - - // RFC 7591 Dynamic Client Registration - Public endpoint - r.Post("/register", api.postOAuth2ClientRegistration()) - - // RFC 7592 Client Configuration Management - Protected by registration access token - r.Route("/clients/{client_id}", func(r chi.Router) { - r.Use( - // Middleware to validate registration access token - oauth2provider.RequireRegistrationAccessToken(api.Database), - ) - r.Get("/", api.oauth2ClientConfiguration()) // Read client configuration - r.Put("/", api.putOAuth2ClientConfiguration()) // Update client configuration - r.Delete("/", api.deleteOAuth2ClientConfiguration()) // Delete client + r.Group(func(r chi.Router) { + r.Use(apiKeyMiddlewareRedirect) + r.Get("/authorize", api.OAuth2Provider.ShowAuthorizationPage(api.AccessURL)) + r.Post("/authorize", api.OAuth2Provider.AuthEndpoint) }) + r.Post("/tokens", api.OAuth2Provider.TokenEndpoint) }) + // OAuth2 linking routes do not make sense under the /api/v2 path. These are + // for an external application to use Coder as an OAuth2 provider, not for + // logging into Coder with an external OAuth2 provider. + //r.Route("/oauth2", func(r chi.Router) { + // r.Use( + // httpmw.RequireExperimentWithDevBypass(api.Experiments, codersdk.ExperimentOAuth2), + // ) + // r.Route("/authorize", func(r chi.Router) { + // r.Use( + // // Fetch the app as system for the authorize endpoint + // httpmw.AsAuthzSystem(httpmw.ExtractOAuth2ProviderAppWithOAuth2Errors(options.Database)), + // apiKeyMiddlewareRedirect, + // ) + // // GET shows the consent page, POST processes the consent + // r.Get("/", api.getOAuth2ProviderAppAuthorize()) + // r.Post("/", api.postOAuth2ProviderAppAuthorize()) + // }) + // r.Route("/tokens", func(r chi.Router) { + // r.Use( + // // Use OAuth2-compliant error responses for the tokens endpoint + // httpmw.AsAuthzSystem(httpmw.ExtractOAuth2ProviderAppWithOAuth2Errors(options.Database)), + // ) + // r.Group(func(r chi.Router) { + // r.Use(apiKeyMiddleware) + // // DELETE on /tokens is not part of the OAuth2 spec. It is our own + // // route used to revoke permissions from an application. It is here for + // // parity with POST on /tokens. + // r.Delete("/", api.deleteOAuth2ProviderAppTokens()) + // }) + // // The POST /tokens endpoint will be called from an unauthorized client so + // // we cannot require an API key. + // r.Post("/", api.postOAuth2ProviderAppToken()) + // }) + // + // // RFC 7591 Dynamic Client Registration - Public endpoint + // r.Post("/register", api.postOAuth2ClientRegistration()) + // + // // RFC 7592 Client Configuration Management - Protected by registration access token + // r.Route("/clients/{client_id}", func(r chi.Router) { + // r.Use( + // // Middleware to validate registration access token + // oauth2provider.RequireRegistrationAccessToken(api.Database), + // ) + // r.Get("/", api.oauth2ClientConfiguration()) // Read client configuration + // r.Put("/", api.putOAuth2ClientConfiguration()) // Update client configuration + // r.Delete("/", api.deleteOAuth2ClientConfiguration()) // Delete client + // }) + //}) + // Experimental routes are not guaranteed to be stable and may change at any time. r.Route("/api/experimental", func(r chi.Router) { r.Use(apiKeyMiddleware) @@ -1705,6 +1711,7 @@ type API struct { UsageInserter *atomic.Pointer[usage.Inserter] UpdatesProvider tailnet.WorkspaceUpdatesProvider + OAuth2Provider *fositeprovider.Provider HTTPAuth *HTTPAuthorizer diff --git a/coderd/database/models.go b/coderd/database/models.go index effd436f4d18d..023f703a48ed1 100644 --- a/coderd/database/models.go +++ b/coderd/database/models.go @@ -1,6 +1,6 @@ // Code generated by sqlc. DO NOT EDIT. // versions: -// sqlc v1.27.0 +// sqlc v1.29.0 package database diff --git a/coderd/database/querier.go b/coderd/database/querier.go index c490a04d2b653..84d60620fd5ac 100644 --- a/coderd/database/querier.go +++ b/coderd/database/querier.go @@ -1,6 +1,6 @@ // Code generated by sqlc. DO NOT EDIT. // versions: -// sqlc v1.27.0 +// sqlc v1.29.0 package database diff --git a/coderd/database/queries.sql.go b/coderd/database/queries.sql.go index 11d129b435e3e..7ba4fe3b3b563 100644 --- a/coderd/database/queries.sql.go +++ b/coderd/database/queries.sql.go @@ -1,6 +1,6 @@ // Code generated by sqlc. DO NOT EDIT. // versions: -// sqlc v1.27.0 +// sqlc v1.29.0 package database diff --git a/coderd/fositeprovider/authorize.go b/coderd/fositeprovider/authorize.go new file mode 100644 index 0000000000000..471c32e032bfd --- /dev/null +++ b/coderd/fositeprovider/authorize.go @@ -0,0 +1,141 @@ +package fositeprovider + +import ( + "fmt" + "net/http" + "net/url" + + "cdr.dev/slog" + "github.com/coder/coder/v2/coderd/httpmw" + "github.com/coder/coder/v2/site" +) + +func (p *Provider) ShowAuthorizationPage(accessURL *url.URL) http.HandlerFunc { + // TODO: Unsure how correct ths is. + return func(rw http.ResponseWriter, r *http.Request) { + logger := p.logger.With(slog.F("handler", "get_auth_endpoint")) + + ctx := r.Context() + + // TODO: Do we do coderd auth here? + ua := httpmw.UserAuthorization(r.Context()) + + // Let's create an AuthorizeRequest object! + // It will analyze the request and extract important information like scopes, response type and others. + ar, err := p.provider.NewAuthorizeRequest(ctx, r) + if err != nil { + logger.Error(ctx, "error occurred in ShowAuthorizationPage", slog.Error(err)) + p.provider.WriteAuthorizeError(ctx, rw, ar, err) + return + } + + app := ar.GetClient() + // primary redirect URI is always the first one + appRedirects := app.GetRedirectURIs() + if len(appRedirects) == 0 { + site.RenderStaticErrorPage(rw, r, site.ErrorPageData{Status: http.StatusInternalServerError, HideStatus: false, Title: "Internal Server Error", + Description: fmt.Sprintf("No redirect URIs configured for app %s", app.GetID()), + RetryEnabled: false, DashboardURL: accessURL.String(), Warnings: nil}) + return + } + + // TODO: Probably only needed if there is no redirect URI in the request + callbackURL, err := url.Parse(appRedirects[0]) + if err != nil { + site.RenderStaticErrorPage(rw, r, site.ErrorPageData{Status: http.StatusInternalServerError, HideStatus: false, Title: "Internal Server Error", Description: err.Error(), RetryEnabled: false, DashboardURL: accessURL.String(), Warnings: nil}) + return + } + + redirectURL := ar.GetRedirectURI() + if redirectURL == nil { + redirectURL = callbackURL + } + + cancel := redirectURL + cancelQuery := redirectURL.Query() + cancelQuery.Add("error", "access_denied") + cancel.RawQuery = cancelQuery.Encode() + + site.RenderOAuthAllowPage(rw, r, site.RenderOAuthAllowData{ + // TODO: Extend fosite.DefaultClient to have our information + AppIcon: "", //app.Icon, + AppName: app.GetID(), // app.Name, + CancelURI: cancel.String(), + RedirectURI: r.URL.String(), + Username: ua.FriendlyName, + }) + } +} + +// https://github.com/ory/fosite-example/blob/master/authorizationserver/oauth2_auth.go#L9 +func (p *Provider) AuthEndpoint(rw http.ResponseWriter, r *http.Request) { + // This context will be passed to all methods. + ctx := r.Context() + logger := p.logger.With(slog.F("handler", "post_auth_endpoint")) + + // Let's create an AuthorizeRequest object! + // It will analyze the request and extract important information like scopes, response type and others. + ar, err := p.provider.NewAuthorizeRequest(ctx, r) + if err != nil { + logger.Error(ctx, "error occurred in NewAuthorizeRequest", slog.Error(err)) + p.provider.WriteAuthorizeError(ctx, rw, ar, err) + return + } + // You have now access to authorizeRequest, Code ResponseTypes, Scopes ... + + var requestedScopes string + for _, this := range ar.GetRequestedScopes() { + requestedScopes += fmt.Sprintf(`
  • %s
  • `, this, this) + } + + // This verifies the user is authenticated + ua := httpmw.APIKey(r) + + // TODO: When we support scopes, this is how we can handle them. + // let's see what scopes the user gave consent to + //for _, scope := range r.PostForm["scopes"] { + // ar.GrantScope(scope) + //} + + // Now that the user is authorized, we set up a session: + mySessionData := p.newSession(ua) + + // When using the HMACSHA strategy you must use something that implements the HMACSessionContainer. + // It brings you the power of overriding the default values. + // + // mySessionData.HMACSession = &strategy.HMACSession{ + // AccessTokenExpiry: time.Now().Add(time.Day), + // AuthorizeCodeExpiry: time.Now().Add(time.Day), + // } + // + + // If you're using the JWT strategy, there's currently no distinction between access token and authorize code claims. + // Therefore, you both access token and authorize code will have the same "exp" claim. If this is something you + // need let us know on github. + // + // mySessionData.JWTClaims.ExpiresAt = time.Now().Add(time.Day) + + // It's also wise to check the requested scopes, e.g.: + // if ar.GetRequestedScopes().Has("admin") { + // http.Error(rw, "you're not allowed to do that", http.StatusForbidden) + // return + // } + + // Now we need to get a response. This is the place where the AuthorizeEndpointHandlers kick in and start processing the request. + // NewAuthorizeResponse is capable of running multiple response type handlers which in turn enables this library + // to support open id connect. + response, err := p.provider.NewAuthorizeResponse(ctx, ar, mySessionData) + + // Catch any errors, e.g.: + // * unknown client + // * invalid redirect + // * ... + if err != nil { + logger.Error(ctx, "error occurred in NewAuthorizeResponse", slog.Error(err)) + p.provider.WriteAuthorizeError(ctx, rw, ar, err) + return + } + + // Last but not least, send the response! + p.provider.WriteAuthorizeResponse(ctx, rw, ar, response) +} diff --git a/coderd/fositeprovider/fositestorage/client.go b/coderd/fositeprovider/fositestorage/client.go new file mode 100644 index 0000000000000..bba21f18f361b --- /dev/null +++ b/coderd/fositeprovider/fositestorage/client.go @@ -0,0 +1,55 @@ +package fositestorage + +import ( + "github.com/ory/fosite" + + "github.com/coder/coder/v2/coderd/database" +) + +var _ fosite.Client = (*Client)(nil) + +// TODO: We can implement more client interfaces if needed. +//var _ fosite.ClientWithSecretRotation = (*Client)(nil) +//var _ fosite.OpenIDConnectClient = (*Client)(nil) +//var _ fosite.ResponseModeClient = (*Client)(nil) + +// Client +// See fosite.DefaultClient for default implementation of most methods. +type Client struct { + App database.OAuth2ProviderApp + Secrets []database.OAuth2ProviderAppSecret + _ fosite.DefaultClient +} + +func (c Client) GetID() string { + return c.App.ID.String() +} + +func (c Client) GetHashedSecret() []byte { + // TODO: Why do we have more than one secret? + return c.Secrets[0].HashedSecret +} + +func (c Client) GetRedirectURIs() []string { + return c.App.RedirectUris +} + +func (c Client) GetGrantTypes() fosite.Arguments { + return c.App.GrantTypes +} + +func (c Client) GetResponseTypes() fosite.Arguments { + return c.App.ResponseTypes +} + +func (c Client) GetScopes() fosite.Arguments { + return []string{} +} + +func (c Client) IsPublic() bool { + return false // Is this right? +} + +func (c Client) GetAudience() fosite.Arguments { + return []string{"https://coder.com"} // TODO: should be access url +} diff --git a/coderd/fositeprovider/fositestorage/storage.go b/coderd/fositeprovider/fositestorage/storage.go new file mode 100644 index 0000000000000..114247ec89110 --- /dev/null +++ b/coderd/fositeprovider/fositestorage/storage.go @@ -0,0 +1,72 @@ +package fositestorage + +import ( + "context" + "database/sql" + "time" + + "github.com/google/uuid" + "github.com/ory/fosite" + "github.com/ory/fosite/handler/oauth2" + "github.com/ory/fosite/storage" + "golang.org/x/xerrors" + + "github.com/coder/coder/v2/coderd/database" + "github.com/coder/coder/v2/coderd/database/dbauthz" +) + +type fositeStorage interface { + fosite.ClientManager + oauth2.CoreStorage +} + +var _ fositeStorage = (*Storage)(nil) + +type Storage struct { + db database.Store + + // TODO: Remove the memory store entirely and implement all methods to use the database. + storage.MemoryStore +} + +func New(db database.Store) *Storage { + return &Storage{ + db: db, + MemoryStore: *storage.NewMemoryStore(), + } +} + +func (s Storage) GetClient(ctx context.Context, id string) (fosite.Client, error) { + uid, err := uuid.Parse(id) + if err != nil { + return nil, fosite.ErrNotFound + } + + app, err := s.db.GetOAuth2ProviderAppByID(dbauthz.AsSystemRestricted(ctx), uid) + if err != nil { + if xerrors.Is(err, sql.ErrNoRows) { + return nil, fosite.ErrNotFound + } + return nil, fosite.ErrorToRFC6749Error(err) + } + + secrets, err := s.db.GetOAuth2ProviderAppSecretsByAppID(dbauthz.AsSystemRestricted(ctx), app.ID) + if err != nil { + return nil, fosite.ErrorToRFC6749Error(err) + } + + return Client{ + App: app, + Secrets: secrets, + }, nil +} + +func (s Storage) ClientAssertionJWTValid(ctx context.Context, jti string) error { + //TODO implement me + panic("implement me") +} + +func (s Storage) SetClientAssertionJWT(ctx context.Context, jti string, exp time.Time) error { + //TODO implement me + panic("implement me") +} diff --git a/coderd/fositeprovider/hasher.go b/coderd/fositeprovider/hasher.go new file mode 100644 index 0000000000000..d49d19ba30d3c --- /dev/null +++ b/coderd/fositeprovider/hasher.go @@ -0,0 +1,62 @@ +package fositeprovider + +import ( + "context" + "strings" + + "github.com/ory/fosite" + "golang.org/x/xerrors" + + "github.com/coder/coder/v2/coderd/userpassword" +) + +var _ fosite.Hasher = (*clientSecretHasher)(nil) + +type clientSecretHasher struct { +} + +func (c clientSecretHasher) Compare(ctx context.Context, hashedSecret, data []byte) error { + // TODO: Maybe there is a better place to do this parsing? + parsed, err := parseFormattedSecret(string(data)) + if err != nil { + return xerrors.Errorf("parse formatted secret: %w", err) + } + + equal, err := userpassword.Compare(string(hashedSecret), parsed.secret) + if err != nil { + return xerrors.Errorf("compare hashed secret: %w", err) + } + + if !equal { + return xerrors.New("hashes do not match") + } + return nil +} + +func (c clientSecretHasher) Hash(ctx context.Context, data []byte) ([]byte, error) { + hashed, err := userpassword.Hash(string(data)) + if err != nil { + return nil, xerrors.Errorf("hash secret: %w", err) + } + return []byte(hashed), nil +} + +type parsedSecret struct { + prefix string + secret string +} + +// parseFormattedSecret parses a formatted secret like "coder_prefix_secret" +func parseFormattedSecret(secret string) (parsedSecret, error) { + parts := strings.Split(secret, "_") + if len(parts) != 3 { + return parsedSecret{}, xerrors.Errorf("incorrect number of parts: %d", len(parts)) + } + if parts[0] != "coder" { + return parsedSecret{}, xerrors.Errorf("incorrect scheme: %s", parts[0]) + } + return parsedSecret{ + prefix: parts[1], + secret: parts[2], + }, nil +} diff --git a/coderd/fositeprovider/provider.go b/coderd/fositeprovider/provider.go new file mode 100644 index 0000000000000..194da9e925bcd --- /dev/null +++ b/coderd/fositeprovider/provider.go @@ -0,0 +1,84 @@ +package fositeprovider + +import ( + "context" + "crypto/rand" + "crypto/rsa" + "time" + + "github.com/ory/fosite" + "github.com/ory/fosite/compose" + "github.com/ory/fosite/handler/openid" + "github.com/ory/fosite/token/jwt" + + "cdr.dev/slog" + "github.com/coder/coder/v2/coderd/database" + "github.com/coder/coder/v2/coderd/fositeprovider/fositestorage" +) + +type Provider struct { + // TODO: Make a subset interface for database.Store with only the methods needed for OAuth2 provider functionality. + //DB database.Store + + logger slog.Logger + + storage *fositestorage.Storage + config *fosite.Config + provider fosite.OAuth2Provider +} + +func New(ctx context.Context, logger slog.Logger, db database.Store) *Provider { + // privateKey is used to sign JWT tokens. The default strategy uses RS256 (RSA Signature with SHA-256) + // TODO: Pass in this secret and persist it + privateKey, _ := rsa.GenerateKey(rand.Reader, 2048) + + // TODO: This is unused right now? + //secret, err := db.GetOAuthSigningKey(ctx) + //if err != nil { + // panic(err) + //} + + config := &fosite.Config{ + GlobalSecret: []byte("some-cool-secret-that-is-32bytes"), + ClientSecretsHasher: clientSecretHasher{}, + // TODO: Configure http client + } + + // TODO: Persist storage in the database + store := fositestorage.New(db) + provider := compose.ComposeAllEnabled(config, store, privateKey) + + return &Provider{ + logger: logger.Named("oauth2_provider"), + provider: provider, + config: config, + storage: store, + } +} + +// A session is passed from the `/auth` to the `/token` endpoint. You probably want to store data like: "Who made the request", +// "What organization does that person belong to" and so on. +// For our use case, the session will meet the requirements imposed by JWT access tokens, HMAC access tokens and OpenID Connect +// ID Tokens plus a custom field +// +// newSession is a helper function for creating a new session. This may look like a lot of code but since we are +// setting up multiple strategies it is a bit longer. +// Usually, you could do: +// +// session = new(fosite.DefaultSession) +func (p *Provider) newSession(key database.APIKey) *openid.DefaultSession { + return &openid.DefaultSession{ + Claims: &jwt.IDTokenClaims{ + Issuer: "https://fosite.my-application.com", + Subject: key.UserID.String(), + Audience: []string{"https://my-client.my-application.com"}, + ExpiresAt: time.Now().Add(time.Hour * 6), + IssuedAt: time.Now(), + RequestedAt: time.Now(), + AuthTime: time.Now(), + }, + Headers: &jwt.Headers{ + Extra: make(map[string]interface{}), + }, + } +} diff --git a/coderd/fositeprovider/token.go b/coderd/fositeprovider/token.go new file mode 100644 index 0000000000000..c2c786ea35b6f --- /dev/null +++ b/coderd/fositeprovider/token.go @@ -0,0 +1,54 @@ +package fositeprovider + +import ( + "log" + "net/http" + + "github.com/coder/coder/v2/coderd/database" +) + +// TODO: Not sure how TokenEndpoint works with sessions. +func (p *Provider) TokenEndpoint(rw http.ResponseWriter, req *http.Request) { + // This context will be passed to all methods. + ctx := req.Context() + + // Create an empty session object which will be passed to the request handlers + // TODO: Why do we need an empty session here? + mySessionData := p.newSession(database.APIKey{}) + + // This will create an access request object and iterate through the registered TokenEndpointHandlers to validate the request. + accessRequest, err := p.provider.NewAccessRequest(ctx, req, mySessionData) + + // Catch any errors, e.g.: + // * unknown client + // * invalid redirect + // * ... + if err != nil { + log.Printf("Error occurred in NewAccessRequest: %+v", err) + p.provider.WriteAccessError(ctx, rw, accessRequest, err) + return + } + + // If this is a client_credentials grant, grant all requested scopes + // NewAccessRequest validated that all requested scopes the client is allowed to perform + // based on configured scope matching strategy. + if accessRequest.GetGrantTypes().ExactOne("client_credentials") { + for _, scope := range accessRequest.GetRequestedScopes() { + accessRequest.GrantScope(scope) + } + } + + // Next we create a response for the access request. Again, we iterate through the TokenEndpointHandlers + // and aggregate the result in response. + response, err := p.provider.NewAccessResponse(ctx, accessRequest) + if err != nil { + log.Printf("Error occurred in NewAccessResponse: %+v", err) + p.provider.WriteAccessError(ctx, rw, accessRequest, err) + return + } + + // All done, send the response. + p.provider.WriteAccessResponse(ctx, rw, accessRequest, response) + + // The client now has a valid access token +} diff --git a/go.mod b/go.mod index 7c2dd7bc02f48..5727085b75ff2 100644 --- a/go.mod +++ b/go.mod @@ -485,6 +485,7 @@ require ( github.com/fsnotify/fsnotify v1.9.0 github.com/go-git/go-git/v5 v5.16.2 github.com/mark3labs/mcp-go v0.37.0 + github.com/ory/fosite v0.49.0 ) require ( @@ -503,34 +504,53 @@ require ( github.com/Masterminds/semver/v3 v3.3.1 // indirect github.com/aquasecurity/go-version v0.0.1 // indirect github.com/aquasecurity/trivy v0.58.2 // indirect + github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 // indirect github.com/aws/aws-sdk-go v1.55.7 // indirect github.com/bahlo/generic-list-go v0.2.0 // indirect github.com/bgentry/go-netrc v0.0.0-20140422174119-9fd32a8b3d3d // indirect github.com/buger/jsonparser v1.1.1 // indirect github.com/charmbracelet/x/exp/slice v0.0.0-20250327172914-2fdc97757edf // indirect github.com/cncf/xds/go v0.0.0-20250501225837-2ac532fd4443 // indirect + github.com/cristalhq/jwt/v4 v4.0.2 // indirect + github.com/dgraph-io/ristretto v1.0.0 // indirect github.com/dgryski/go-farm v0.0.0-20240924180020-3414d57e47da // indirect github.com/envoyproxy/go-control-plane/envoy v1.32.4 // indirect github.com/envoyproxy/protoc-gen-validate v1.2.1 // indirect github.com/esiqveland/notify v0.13.3 // indirect github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect github.com/go-git/go-billy/v5 v5.6.2 // indirect + github.com/go-jose/go-jose/v3 v3.0.3 // indirect + github.com/gobuffalo/pop/v6 v6.1.1 // indirect + github.com/golang/mock v1.7.0-rc.1 // indirect github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674 // indirect github.com/hashicorp/go-getter v1.7.9 // indirect github.com/hashicorp/go-safetemp v1.0.0 // indirect + github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/invopop/jsonschema v0.13.0 // indirect github.com/jackmordaunt/icns/v3 v3.0.1 // indirect github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect github.com/klauspost/cpuid/v2 v2.2.10 // indirect + github.com/mattn/goveralls v0.0.12 // indirect github.com/moby/sys/user v0.4.0 // indirect + github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 // indirect github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 // indirect github.com/openai/openai-go v1.7.0 // indirect + github.com/openzipkin/zipkin-go v0.4.2 // indirect + github.com/ory/go-acc v0.2.9-0.20230103102148-6b1c9a70dbbe // indirect + github.com/ory/go-convenience v0.1.0 // indirect + github.com/ory/x v0.0.665 // indirect github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 // indirect github.com/puzpuzpuz/xsync/v3 v3.5.1 // indirect + github.com/sagikazarmark/locafero v0.7.0 // indirect github.com/samber/lo v1.50.0 // indirect + github.com/seatgeek/logrus-gelf-formatter v0.0.0-20210414080842-5b05eb8ff761 // indirect github.com/sergeymakinen/go-bmp v1.0.0 // indirect github.com/sergeymakinen/go-ico v1.0.0-beta.0 // indirect + github.com/sourcegraph/conc v0.3.0 // indirect + github.com/spf13/cobra v1.9.1 // indirect + github.com/spf13/viper v1.20.1 // indirect github.com/spiffe/go-spiffe/v2 v2.5.0 // indirect + github.com/subosito/gotenv v1.6.0 // indirect github.com/tidwall/sjson v1.2.5 // indirect github.com/tmaxmax/go-sse v0.10.0 // indirect github.com/ulikunitz/xz v0.5.12 // indirect @@ -539,6 +559,13 @@ require ( github.com/zeebo/xxh3 v1.0.2 // indirect go.opentelemetry.io/contrib/detectors/gcp v1.36.0 // indirect go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.61.0 // indirect + go.opentelemetry.io/contrib/instrumentation/net/http/httptrace/otelhttptrace v0.46.1 // indirect + go.opentelemetry.io/contrib/propagators/b3 v1.21.0 // indirect + go.opentelemetry.io/contrib/propagators/jaeger v1.21.1 // indirect + go.opentelemetry.io/contrib/samplers/jaegerremote v0.15.1 // indirect + go.opentelemetry.io/otel/exporters/jaeger v1.17.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.35.0 // indirect + go.opentelemetry.io/otel/exporters/zipkin v1.21.0 // indirect go.opentelemetry.io/otel/sdk/metric v1.37.0 // indirect google.golang.org/genai v1.12.0 // indirect gopkg.in/warnings.v0 v0.1.2 // indirect diff --git a/go.sum b/go.sum index bf33f1772dcd0..27a1a29b917e0 100644 --- a/go.sum +++ b/go.sum @@ -679,6 +679,7 @@ github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapp github.com/JohnCGriffin/overflow v0.0.0-20211019200055-46fa312c352c/go.mod h1:X0CRv0ky0k6m906ixxpzmDRLvX58TFUKS2eePweuyxk= github.com/KyleBanks/depth v1.2.1 h1:5h8fQADFrWtarTdtDudMmGsC7GPbOAu6RVB3ffsVFHc= github.com/KyleBanks/depth v1.2.1/go.mod h1:jzSb9d0L43HxTQfT+oSA1EEp2q+ne2uh6XgeJcm8brE= +github.com/Masterminds/semver/v3 v3.1.1/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs= github.com/Masterminds/semver/v3 v3.3.1 h1:QtNSWtVZ3nBfk8mAOu/B6v7FMJ+NHTIgUPi7rj+4nv4= github.com/Masterminds/semver/v3 v3.3.1/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM= github.com/Microsoft/go-winio v0.5.0/go.mod h1:JPGBdM1cNvN/6ISo+n8V5iA4v8pBzdOpzfwIujj1a84= @@ -745,6 +746,8 @@ github.com/armon/circbuf v0.0.0-20190214190532-5111143e8da2 h1:7Ip0wMmLHLRJdrloD github.com/armon/circbuf v0.0.0-20190214190532-5111143e8da2/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= github.com/armon/go-radix v1.0.1-0.20221118154546-54df44f2176c h1:651/eoCRnQ7YtSjAnSzRucrJz+3iGEFt+ysraELS81M= github.com/armon/go-radix v1.0.1-0.20221118154546-54df44f2176c/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= +github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 h1:DklsrG3dyBCFEj5IhUbnKptjxatkF07cF2ak3yi77so= +github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw= github.com/aslilac/afero v0.0.0-20250403163713-f06e86036696 h1:7hAl/81gNUjmSCqJYKe1aTIVY4myjapaSALdCko19tI= github.com/aslilac/afero v0.0.0-20250403163713-f06e86036696/go.mod h1:acJQ8t0ohCGuMN3O+Pv0V0hgMxNYDlvdk+VTfyZmbYo= github.com/atotto/clipboard v0.1.4 h1:EH0zSVneZPSuFR11BlR9YppQTVDbh5+16AmcJi4g1z4= @@ -901,6 +904,7 @@ github.com/cncf/xds/go v0.0.0-20230105202645-06c439db220b/go.mod h1:eXthEFrGJvWH github.com/cncf/xds/go v0.0.0-20230607035331-e9ce68804cb4/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/cncf/xds/go v0.0.0-20250501225837-2ac532fd4443 h1:aQ3y1lwWyqYPiWZThqv1aFbZMiM9vblcSArJRf2Irls= github.com/cncf/xds/go v0.0.0-20250501225837-2ac532fd4443/go.mod h1:W+zGtBO5Y1IgJhy4+A9GOqVhqLpfZi+vwmdNXUehLA8= +github.com/cockroachdb/apd v1.1.0/go.mod h1:8Sl8LxpKi29FqWXR16WEFZRNSz3SoPzUzeMeY4+DwBQ= github.com/coder/agentapi-sdk-go v0.0.0-20250505131810-560d1d88d225 h1:tRIViZ5JRmzdOEo5wUWngaGEFBG8OaE1o2GIHN5ujJ8= github.com/coder/agentapi-sdk-go v0.0.0-20250505131810-560d1d88d225/go.mod h1:rNLVpYgEVeu1Zk29K64z6Od8RBP9DwqCu9OfCzh8MR4= github.com/coder/aisdk-go v0.0.9 h1:Vzo/k2qwVGLTR10ESDeP2Ecek1SdPfZlEjtTfMveiVo= @@ -956,13 +960,20 @@ github.com/coreos/go-iptables v0.6.0 h1:is9qnZMPYjLd8LYqmm/qlE+wwEgJIkTYdhV3rfZo github.com/coreos/go-iptables v0.6.0/go.mod h1:Qe8Bv2Xik5FyTXwgIbLAnv2sWSBmvWdFETJConOQ//Q= github.com/coreos/go-oidc/v3 v3.15.0 h1:R6Oz8Z4bqWR7VFQ+sPSvZPQv4x8M+sJkDO5ojgwlyAg= github.com/coreos/go-oidc/v3 v3.15.0/go.mod h1:HaZ3szPaZ0e4r6ebqvsLWlk2Tn+aejfmrfah6hnSYEU= +github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= +github.com/coreos/go-systemd v0.0.0-20190719114852-fd7a80b32e1f/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= github.com/coreos/go-systemd v0.0.0-20191104093116-d3cd4ed1dbcf h1:iW4rZ826su+pqaw19uhpSCzhj44qo35pNgKFGqzDKkU= github.com/coreos/go-systemd v0.0.0-20191104093116-d3cd4ed1dbcf/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= github.com/cpuguy83/dockercfg v0.3.2 h1:DlJTyZGBDlXqUZ2Dk2Q3xHs/FtnooJJVaad2S9GKorA= github.com/cpuguy83/dockercfg v0.3.2/go.mod h1:sugsbF4//dDlL/i+S+rtpIWp+5h0BHJHfjj5/jFyUJc= +github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= +github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g= +github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/creack/pty v1.1.21 h1:1/QdRyBaHHJP61QkWMXlOIBfsgdDeeKfK8SYVUWJKf0= github.com/creack/pty v1.1.21/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4= +github.com/cristalhq/jwt/v4 v4.0.2 h1:g/AD3h0VicDamtlM70GWGElp8kssQEv+5wYd7L9WOhU= +github.com/cristalhq/jwt/v4 v4.0.2/go.mod h1:HnYraSNKDRag1DZP92rYHyrjyQHnVEHPNqesmzs+miQ= github.com/cyphar/filepath-securejoin v0.4.1 h1:JyxxyPEaktOD+GAnqIqTf9A8tHyAG22rowi7HkoSU1s= github.com/cyphar/filepath-securejoin v0.4.1/go.mod h1:Sdj7gXlvMcPZsbhwhQ33GguGLDGQL7h7bg04C/+u9jI= github.com/dave/dst v0.27.2 h1:4Y5VFTkhGLC1oddtNwuxxe36pnyLxMFXT51FOzH8Ekc= @@ -977,6 +988,8 @@ github.com/dblohm7/wingoes v0.0.0-20240820181039-f2b84150679e h1:L+XrFvD0vBIBm+W github.com/dblohm7/wingoes v0.0.0-20240820181039-f2b84150679e/go.mod h1:SUxUaAK/0UG5lYyZR1L1nC4AaYYvSSYTWQSH3FPcxKU= github.com/dgraph-io/badger/v4 v4.7.0 h1:Q+J8HApYAY7UMpL8d9owqiB+odzEc0zn/aqOD9jhc6Y= github.com/dgraph-io/badger/v4 v4.7.0/go.mod h1:He7TzG3YBy3j4f5baj5B7Zl2XyfNe5bl4Udl0aPemVA= +github.com/dgraph-io/ristretto v1.0.0 h1:SYG07bONKMlFDUYu5pEu3DGAh8c2OFNzKm6G9J4Si84= +github.com/dgraph-io/ristretto v1.0.0/go.mod h1:jTi2FiYEhQ1NsMmA7DeBykizjOuY88NhKBkepyu1jPc= github.com/dgraph-io/ristretto/v2 v2.2.0 h1:bkY3XzJcXoMuELV8F+vS8kzNgicwQFAaGINAEJdWGOM= github.com/dgraph-io/ristretto/v2 v2.2.0/go.mod h1:RZrm63UmcBAaYWC1DotLYBmTvgkrs0+XhBd7Npn7/zI= github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw= @@ -1113,12 +1126,16 @@ github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2 github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-ini/ini v1.67.0 h1:z6ZrTEZqSWOTyH2FlglNbNgARyHG8oLW9gMELqKr06A= github.com/go-ini/ini v1.67.0/go.mod h1:ByCAeIL28uOIIG0E3PJtZPDL8WnHpFKFOtgjp+3Ies8= +github.com/go-jose/go-jose/v3 v3.0.3 h1:fFKWeig/irsp7XD2zBxvnmA/XaRWp5V3CBsZXJF7G7k= +github.com/go-jose/go-jose/v3 v3.0.3/go.mod h1:5b+7YgP7ZICgJDBdfjZaIt+H/9L9T/YQrVfLAMboGkQ= github.com/go-jose/go-jose/v4 v4.1.0 h1:cYSYxd3pw5zd2FSXk2vGdn9igQU2PS8MuxrCOCl0FdY= github.com/go-jose/go-jose/v4 v4.1.0/go.mod h1:GG/vqmYm3Von2nYiB2vGTXzdoNKE5tix5tuc6iAd+sw= github.com/go-json-experiment/json v0.0.0-20250725192818-e39067aee2d2 h1:iizUGZ9pEquQS5jTGkh4AqeeHCMbfbjeb0zMt0aEFzs= github.com/go-json-experiment/json v0.0.0-20250725192818-e39067aee2d2/go.mod h1:TiCD2a1pcmjd7YnhGH0f/zKNcCD06B029pHhzV23c2M= +github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY= github.com/go-latex/latex v0.0.0-20210118124228-b3d85cf34e07/go.mod h1:CO1AlKB2CSIqUrmQPqA0gdRIlnLEY0gK5JGjh37zN5U= github.com/go-latex/latex v0.0.0-20210823091927-c0d11ff05a81/go.mod h1:SX0U8uGpxhq9o2S/CELCSUxEWWAuoCUcVCQWv7G2OCk= +github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A= github.com/go-logr/logr v1.2.0/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.2.1/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= @@ -1150,14 +1167,34 @@ github.com/go-playground/validator/v10 v10.27.0 h1:w8+XrWVMhGkxOaaowyKH35gFydVHO github.com/go-playground/validator/v10 v10.27.0/go.mod h1:I5QpIEbmr8On7W0TktmJAumgzX4CA1XNl4ZmDuVHKKo= github.com/go-sourcemap/sourcemap v2.1.3+incompatible h1:W1iEw64niKVGogNgBN3ePyLFfuisuzeidWPMPWmECqU= github.com/go-sourcemap/sourcemap v2.1.3+incompatible/go.mod h1:F8jJfvm2KbVjc5NqelyYJmf/v5J0dwNLS2mL4sNA1Jg= +github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= +github.com/go-sql-driver/mysql v1.7.0/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI= github.com/go-sql-driver/mysql v1.8.1 h1:LedoTUt/eveggdHS9qUFC1EFSa8bU2+1pZjSRpvNJ1Y= github.com/go-sql-driver/mysql v1.8.1/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqwuVSjsCm7DZg= +github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/go-test/deep v1.1.0 h1:WOcxcdHcvdgThNXjw0t76K42FXTU7HpNQWHpA2HHNlg= github.com/go-test/deep v1.1.0/go.mod h1:5C2ZWiW0ErCdrYzpqxLbTX7MG14M9iiw8DgHncVwcsE= github.com/go-viper/mapstructure/v2 v2.3.0 h1:27XbWsHIqhbdR5TIC911OfYvgSaW93HM+dX7970Q7jk= github.com/go-viper/mapstructure/v2 v2.3.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM= +github.com/gobuffalo/attrs v1.0.3/go.mod h1:KvDJCE0avbufqS0Bw3UV7RQynESY0jjod+572ctX4t8= +github.com/gobuffalo/envy v1.10.2/go.mod h1:qGAGwdvDsaEtPhfBzb3o0SfDea8ByGn9j8bKmVft9z8= +github.com/gobuffalo/fizz v1.14.4/go.mod h1:9/2fGNXNeIFOXEEgTPJwiK63e44RjG+Nc4hfMm1ArGM= +github.com/gobuffalo/flect v0.3.0/go.mod h1:5pf3aGnsvqvCj50AVni7mJJF8ICxGZ8HomberC3pXLE= +github.com/gobuffalo/flect v1.0.0/go.mod h1:l9V6xSb4BlXwsxEMj3FVEub2nkdQjWhPvD8XTTlHPQc= github.com/gobuffalo/flect v1.0.3 h1:xeWBM2nui+qnVvNM4S3foBhCAL2XgPU+a7FdpelbTq4= github.com/gobuffalo/flect v1.0.3/go.mod h1:A5msMlrHtLqh9umBSnvabjsMrCcCpAyzglnDvkbYKHs= +github.com/gobuffalo/genny/v2 v2.1.0/go.mod h1:4yoTNk4bYuP3BMM6uQKYPvtP6WsXFGm2w2EFYZdRls8= +github.com/gobuffalo/github_flavored_markdown v1.1.3/go.mod h1:IzgO5xS6hqkDmUh91BW/+Qxo/qYnvfzoz3A7uLkg77I= +github.com/gobuffalo/helpers v0.6.7/go.mod h1:j0u1iC1VqlCaJEEVkZN8Ia3TEzfj/zoXANqyJExTMTA= +github.com/gobuffalo/logger v1.0.7/go.mod h1:u40u6Bq3VVvaMcy5sRBclD8SXhBYPS0Qk95ubt+1xJM= +github.com/gobuffalo/nulls v0.4.2/go.mod h1:EElw2zmBYafU2R9W4Ii1ByIj177wA/pc0JdjtD0EsH8= +github.com/gobuffalo/packd v1.0.2/go.mod h1:sUc61tDqGMXON80zpKGp92lDb86Km28jfvX7IAyxFT8= +github.com/gobuffalo/plush/v4 v4.1.16/go.mod h1:6t7swVsarJ8qSLw1qyAH/KbrcSTwdun2ASEQkOznakg= +github.com/gobuffalo/plush/v4 v4.1.18/go.mod h1:xi2tJIhFI4UdzIL8sxZtzGYOd2xbBpcFbLZlIPGGZhU= +github.com/gobuffalo/pop/v6 v6.1.1 h1:eUDBaZcb0gYrmFnKwpuTEUA7t5ZHqNfvS4POqJYXDZY= +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/gobwas/httphead v0.1.0 h1:exrUm0f4YX0L7EBwZHuCF4GDp8aJfVeBrlLQrs6NqWU= @@ -1171,6 +1208,9 @@ github.com/godbus/dbus/v5 v5.1.0 h1:4KLkAxT3aOY8Li4FRJe/KvhoNFFxo0m6fNuFUO8QJUk= github.com/godbus/dbus/v5 v5.1.0/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/gofrs/flock v0.12.0 h1:xHW8t8GPAiGtqz7KxiSqfOEXwpOaqhpYZrTE2MQBgXY= github.com/gofrs/flock v0.12.0/go.mod h1:FirDy1Ing0mI2+kB6wk+vyyAH+e6xiE+EYA0jnzV9jc= +github.com/gofrs/uuid v4.0.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= +github.com/gofrs/uuid v4.2.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= +github.com/gofrs/uuid v4.3.1+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= github.com/gofrs/uuid v4.4.0+incompatible h1:3qXRTX8/NbyulANqlc0lchS1gqAVxRgsuW1YrTJupqA= github.com/gofrs/uuid v4.4.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= @@ -1331,6 +1371,7 @@ github.com/googleapis/gax-go/v2 v2.7.1/go.mod h1:4orTrqY6hXxxaUL4LHIPl6lGo8vAE38 github.com/googleapis/gax-go/v2 v2.15.0 h1:SyjDc1mGgZU5LncH8gimWo9lW1DtIfPibOG81vgd/bo= github.com/googleapis/gax-go/v2 v2.15.0/go.mod h1:zVVkkxAQHa1RQpg9z2AUCMnKhi0Qld9rcmyfL1OZhoc= github.com/googleapis/go-type-adapters v1.0.0/go.mod h1:zHW75FOG2aur7gAO2B+MLby+cLsWGBF62rFAi7WjWO4= +github.com/gorilla/css v1.0.0/go.mod h1:Dn721qIggHpt4+EFCcTLTU/vk5ySda2ReITrtgBl60c= github.com/gorilla/css v1.0.1 h1:ntNaBIghp6JmvWnxbZKANoLyuXTPZ4cAMlo6RyhlbO8= github.com/gorilla/css v1.0.1/go.mod h1:BvnYkspnSzMmwRK+b8/xgNPLiIuNZr6vbZBTPQ2A3b0= github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY= @@ -1417,12 +1458,55 @@ github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1: github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/illarion/gonotify v1.0.1 h1:F1d+0Fgbq/sDWjj/r66ekjDG+IDeecQKUFH4wNwsoio= github.com/illarion/gonotify v1.0.1/go.mod h1:zt5pmDofZpU1f8aqlK0+95eQhoEAn/d4G4B/FjVW4jE= +github.com/inconshreveable/mousetrap v1.0.1/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= +github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= +github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= github.com/insomniacslk/dhcp v0.0.0-20231206064809-8c70d406f6d2 h1:9K06NfxkBh25x56yVhWWlKFE8YpicaSfHwoV8SFbueA= github.com/insomniacslk/dhcp v0.0.0-20231206064809-8c70d406f6d2/go.mod h1:3A9PQ1cunSDF/1rbTq99Ts4pVnycWg+vlPkfeD2NLFI= github.com/invopop/jsonschema v0.13.0 h1:KvpoAJWEjR3uD9Kbm2HWJmqsEaHt8lBUpd0qHcIi21E= github.com/invopop/jsonschema v0.13.0/go.mod h1:ffZ5Km5SWWRAIN6wbDXItl95euhFz2uON45H2qjYt+0= +github.com/jackc/chunkreader v1.0.0/go.mod h1:RT6O25fNZIuasFJRyZ4R/Y2BbhasbmZXF9QQ7T3kePo= +github.com/jackc/chunkreader/v2 v2.0.0/go.mod h1:odVSm741yZoC3dpHEUXIqA9tQRhFrgOHwnPIn9lDKlk= +github.com/jackc/chunkreader/v2 v2.0.1/go.mod h1:odVSm741yZoC3dpHEUXIqA9tQRhFrgOHwnPIn9lDKlk= +github.com/jackc/pgconn v0.0.0-20190420214824-7e0022ef6ba3/go.mod h1:jkELnwuX+w9qN5YIfX0fl88Ehu4XC3keFuOJJk9pcnA= +github.com/jackc/pgconn v0.0.0-20190824142844-760dd75542eb/go.mod h1:lLjNuW/+OfW9/pnVKPazfWOgNfH2aPem8YQ7ilXGvJE= +github.com/jackc/pgconn v0.0.0-20190831204454-2fabfa3c18b7/go.mod h1:ZJKsE/KZfsUgOEh9hBm+xYTstcNHg7UPMVJqRfQxq4s= +github.com/jackc/pgconn v1.8.0/go.mod h1:1C2Pb36bGIP9QHGBYCjnyhqu7Rv3sGshaQUvmfGIB/o= +github.com/jackc/pgconn v1.9.0/go.mod h1:YctiPyvzfU11JFxoXokUOOKQXQmDMoJL9vJzHH8/2JY= +github.com/jackc/pgconn v1.9.1-0.20210724152538-d89c8390a530/go.mod h1:4z2w8XhRbP1hYxkpTuBjTS3ne3J48K83+u0zoyvg2pI= +github.com/jackc/pgconn v1.13.0/go.mod h1:AnowpAqO4CMIIJNZl2VJp+KrkAZciAkhEl0W0JIobpI= +github.com/jackc/pgio v1.0.0/go.mod h1:oP+2QK2wFfUWgr+gxjoBH9KGBb31Eio69xUb0w5bYf8= +github.com/jackc/pgmock v0.0.0-20190831213851-13a1b77aafa2/go.mod h1:fGZlG77KXmcq05nJLRkk0+p82V8B8Dw8KN2/V9c/OAE= +github.com/jackc/pgmock v0.0.0-20201204152224-4fe30f7445fd/go.mod h1:hrBW0Enj2AZTNpt/7Y5rr2xe/9Mn757Wtb2xeBzPv2c= +github.com/jackc/pgmock v0.0.0-20210724152146-4ad1a8207f65/go.mod h1:5R2h2EEX+qri8jOWMbJCtaPWkrrNc7OHwsp2TCqp7ak= +github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg= +github.com/jackc/pgproto3 v1.1.0/go.mod h1:eR5FA3leWg7p9aeAqi37XOTgTIbkABlvcPB3E5rlc78= +github.com/jackc/pgproto3/v2 v2.0.0-alpha1.0.20190420180111-c116219b62db/go.mod h1:bhq50y+xrl9n5mRYyCBFKkpRVTLYJVWeCc+mEAI3yXA= +github.com/jackc/pgproto3/v2 v2.0.0-alpha1.0.20190609003834-432c2951c711/go.mod h1:uH0AWtUmuShn0bcesswc4aBTWGvw0cAxIJp+6OB//Wg= +github.com/jackc/pgproto3/v2 v2.0.0-rc3/go.mod h1:ryONWYqW6dqSg1Lw6vXNMXoBJhpzvWKnT95C46ckYeM= +github.com/jackc/pgproto3/v2 v2.0.0-rc3.0.20190831210041-4c03ce451f29/go.mod h1:ryONWYqW6dqSg1Lw6vXNMXoBJhpzvWKnT95C46ckYeM= +github.com/jackc/pgproto3/v2 v2.0.6/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA= +github.com/jackc/pgproto3/v2 v2.1.1/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA= +github.com/jackc/pgproto3/v2 v2.3.1/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA= +github.com/jackc/pgservicefile v0.0.0-20200714003250-2b9c44734f2b/go.mod h1:vsD4gTJCa9TptPL8sPkXrLZ+hDuNrZCnj29CQpr4X1E= +github.com/jackc/pgtype v0.0.0-20190421001408-4ed0de4755e0/go.mod h1:hdSHsc1V01CGwFsrv11mJRHWJ6aifDLfdV3aVjFF0zg= +github.com/jackc/pgtype v0.0.0-20190824184912-ab885b375b90/go.mod h1:KcahbBH1nCMSo2DXpzsoWOAfFkdEtEJpPbVLq8eE+mc= +github.com/jackc/pgtype v0.0.0-20190828014616-a8802b16cc59/go.mod h1:MWlu30kVJrUS8lot6TQqcg7mtthZ9T0EoIBFiJcmcyw= +github.com/jackc/pgtype v1.8.1-0.20210724151600-32e20a603178/go.mod h1:C516IlIV9NKqfsMCXTdChteoXmwgUceqaLfjg2e3NlM= +github.com/jackc/pgtype v1.12.0/go.mod h1:LUMuVrfsFfdKGLw+AFFVv6KtHOFMwRgDDzBt76IqCA4= +github.com/jackc/pgx/v4 v4.0.0-20190420224344-cc3461e65d96/go.mod h1:mdxmSJJuR08CZQyj1PVQBHy9XOp5p8/SHH6a0psbY9Y= +github.com/jackc/pgx/v4 v4.0.0-20190421002000-1b8f0016e912/go.mod h1:no/Y67Jkk/9WuGR0JG/JseM9irFbnEPbuWV2EELPNuM= +github.com/jackc/pgx/v4 v4.0.0-pre1.0.20190824185557-6972a5742186/go.mod h1:X+GQnOEnf1dqHGpw7JmHqHc1NxDoalibchSk9/RWuDc= +github.com/jackc/pgx/v4 v4.12.1-0.20210724153913-640aa07df17c/go.mod h1:1QD0+tgSXP7iUjYm9C1NxKhny7lq6ee99u/z+IHFcgs= +github.com/jackc/pgx/v4 v4.17.2/go.mod h1:lcxIZN44yMIrWI78a5CpucdD14hX0SBDbNRvjDBItsw= +github.com/jackc/puddle v0.0.0-20190413234325-e4ced69a3a2b/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= +github.com/jackc/puddle v0.0.0-20190608224051-11cab39313c9/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= +github.com/jackc/puddle v1.1.3/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= +github.com/jackc/puddle v1.3.0/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= github.com/jackmordaunt/icns/v3 v3.0.1 h1:xxot6aNuGrU+lNgxz5I5H0qSeCjNKp8uTXB1j8D4S3o= github.com/jackmordaunt/icns/v3 v3.0.1/go.mod h1:5sHL59nqTd2ynTnowxB/MDQFhKNqkK8X687uKNygaSQ= +github.com/jandelgado/gcov2lcov v1.0.5 h1:rkBt40h0CVK4oCb8Dps950gvfd1rYvQ8+cWa346lVU0= +github.com/jandelgado/gcov2lcov v1.0.5/go.mod h1:NnSxK6TMlg1oGDBfGelGbjgorT5/L3cchlbtgFYZSss= github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A= github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo= github.com/jdkato/prose v1.2.1 h1:Fp3UnJmLVISmlc57BgKUzdjr0lOtjqTZicL3PaYy6cU= @@ -1435,8 +1519,10 @@ github.com/jmespath/go-jmespath v0.4.1-0.20220621161143-b0104c826a24 h1:liMMTbpW github.com/jmespath/go-jmespath v0.4.1-0.20220621161143-b0104c826a24/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo= github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8= github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U= +github.com/jmoiron/sqlx v1.3.5/go.mod h1:nRVWtLre0KfCLJvgxzCsLVMogSvQ1zNJtpYr2Ccp0mQ= github.com/jmoiron/sqlx v1.4.0 h1:1PLqN7S1UYp5t4SrVVnt4nUVNemrDAtxlulVe+Qgm3o= github.com/jmoiron/sqlx v1.4.0/go.mod h1:ZrZ7UsYB/weZdl2Bxg6jCRO9c3YHl8r3ahlKmRT4JLY= +github.com/joho/godotenv v1.4.0/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= github.com/josharian/native v1.1.1-0.20230202152459-5c7d0dd6ab86 h1:elKwZS1OcdQ0WwEDBeqxKwb7WB62QX8bvZ/FJnVXIfk= @@ -1467,6 +1553,16 @@ github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYW github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= github.com/klauspost/cpuid/v2 v2.2.10 h1:tBs3QSyvjDyFTq3uoc/9xFpCuOsJQFNPiAhYdw2skhE= github.com/klauspost/cpuid/v2 v2.2.10/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0= +github.com/knadh/koanf/maps v0.1.1 h1:G5TjmUh2D7G2YWf5SQQqSiHRJEjaicvU0KpypqB3NIs= +github.com/knadh/koanf/maps v0.1.1/go.mod h1:npD/QZY3V6ghQDdcQzl1W4ICNVTkohC8E73eI2xW4yI= +github.com/knadh/koanf/parsers/json v0.1.0 h1:dzSZl5pf5bBcW0Acnu20Djleto19T0CfHcvZ14NJ6fU= +github.com/knadh/koanf/parsers/json v0.1.0/go.mod h1:ll2/MlXcZ2BfXD6YJcjVFzhG9P0TdJ207aIBKQhV2hY= +github.com/knadh/koanf/providers/rawbytes v0.1.0 h1:dpzgu2KO6uf6oCb4aP05KDmKmAmI51k5pe8RYKQ0qME= +github.com/knadh/koanf/providers/rawbytes v0.1.0/go.mod h1:mMTB1/IcJ/yE++A2iEZbY1MLygX7vttU+C+S/YmPu9c= +github.com/knadh/koanf/v2 v2.0.1 h1:1dYGITt1I23x8cfx8ZnldtezdyaZtfAuRtIFOiRzK7g= +github.com/knadh/koanf/v2 v2.0.1/go.mod h1:ZeiIlIDXTE7w1lMT6UVcNiRAS2/rCeLn/GdLNvY1Dus= +github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/kortschak/wol v0.0.0-20200729010619-da482cc4850a h1:+RR6SqnTkDLWyICxS1xpjCi/3dhyV+TgZwA6Ww3KncQ= github.com/kortschak/wol v0.0.0-20200729010619-da482cc4850a/go.mod h1:YTtCCM3ryyfiu4F7t8HQ1mxvp1UBdWM2r6Xa+nGWvDk= github.com/kr/fs v0.1.0 h1:Jskdu9ieNAYnjxsi0LbQp1ulIKZV1LAFgK1tWhpZgl8= @@ -1477,6 +1573,7 @@ github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NB github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/pty v1.1.8/go.mod h1:O1sed60cT9XZ5uDucP5qwvh+TE3NnUj51EiZO/lmSfw= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= @@ -1500,6 +1597,7 @@ github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69 github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0= github.com/lufia/plan9stats v0.0.0-20250317134145-8bc96cf8fc35 h1:PpXWgLPs+Fqr325bN2FD2ISlRRztXibcX6e8f5FR5Dc= github.com/lufia/plan9stats v0.0.0-20250317134145-8bc96cf8fc35/go.mod h1:autxFIvghDt3jPTLoqZ9OZ7s9qTGNAWmYCjVFWPX/zg= +github.com/luna-duclos/instrumentedsql v1.1.3/go.mod h1:9J1njvFds+zN7y85EDhN9XNQLANWwZt2ULeIC8yMNYs= github.com/lyft/protoc-gen-star v0.6.0/go.mod h1:TGAoBVkt8w7MPG72TrKIu85MIdXwDuzJYeZuUPFPNwA= github.com/lyft/protoc-gen-star v0.6.1/go.mod h1:TGAoBVkt8w7MPG72TrKIu85MIdXwDuzJYeZuUPFPNwA= github.com/lyft/protoc-gen-star/v2 v2.0.1/go.mod h1:RcCdONR2ScXaYnQC5tUzxzlpA3WVYF7/opLeUgcQs/o= @@ -1514,11 +1612,15 @@ github.com/marekm4/color-extractor v1.2.1/go.mod h1:90VjmiHI6M8ez9eYUaXLdcKnS+BA github.com/mark3labs/mcp-go v0.37.0 h1:BywvZLPRT6Zx6mMG/MJfxLSZQkTGIcJSEGKsvr4DsoQ= github.com/mark3labs/mcp-go v0.37.0/go.mod h1:T7tUa2jO6MavG+3P25Oy/jR7iCeJPHImCZHRymCn39g= github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= +github.com/mattn/go-colorable v0.1.1/go.mod h1:FuOcm+DKB9mbwrcAfNl7/TZVBZ6rcnceauSikq3lYCQ= github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= +github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4= github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE= github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8= +github.com/mattn/go-isatty v0.0.5/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= +github.com/mattn/go-isatty v0.0.7/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= @@ -1531,9 +1633,14 @@ github.com/mattn/go-runewidth v0.0.4/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzp github.com/mattn/go-runewidth v0.0.12/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk= github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc= github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= +github.com/mattn/go-sqlite3 v1.14.6/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU= github.com/mattn/go-sqlite3 v1.14.14/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU= +github.com/mattn/go-sqlite3 v1.14.15/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg= +github.com/mattn/go-sqlite3 v1.14.16/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg= github.com/mattn/go-sqlite3 v1.14.22 h1:2gZY6PC6kBnID23Tichd1K+Z0oS6nE/XwU+Vz/5o4kU= github.com/mattn/go-sqlite3 v1.14.22/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y= +github.com/mattn/goveralls v0.0.12 h1:PEEeF0k1SsTjOBQ8FOmrOAoCu4ytuMaWCnWe94zxbCg= +github.com/mattn/goveralls v0.0.12/go.mod h1:44ImGEUfmqH8bBtaMrYKsM65LXfNLWmwaxFGjZwgMSQ= github.com/mdlayher/genetlink v1.3.2 h1:KdrNKe+CTu+IbZnm/GVUMXSqBBLqcGpRDa0xkQy56gw= github.com/mdlayher/genetlink v1.3.2/go.mod h1:tcC3pkCrPUGIKKsCsp0B3AdaaKuHtaxoJRz3cc+528o= github.com/mdlayher/netlink v1.7.2 h1:/UtM3ofJap7Vl4QWCPDGXY8d3GIY2UGSDbK+QWmY8/g= @@ -1542,6 +1649,7 @@ github.com/mdlayher/sdnotify v1.0.0 h1:Ma9XeLVN/l0qpyx1tNeMSeTjCPH6NtuD6/N9XdTlQ github.com/mdlayher/sdnotify v1.0.0/go.mod h1:HQUmpM4XgYkhDLtd+Uad8ZFK1T9D5+pNxnXQjCeJlGE= github.com/mdlayher/socket v0.5.0 h1:ilICZmJcQz70vrWVes1MFera4jGiWNocSkykwwoy3XI= github.com/mdlayher/socket v0.5.0/go.mod h1:WkcBFfvyG8QENs5+hfQPl1X6Jpd2yeLIYgrGFmJiJxI= +github.com/microcosm-cc/bluemonday v1.0.20/go.mod h1:yfBmMi8mxvaZut3Yytv+jTXRY8mxyjJ0/kQBTElld50= github.com/microcosm-cc/bluemonday v1.0.27 h1:MpEUotklkwCSLeH+Qdx1VJgNqLlpY2KXwXFM08ygZfk= github.com/microcosm-cc/bluemonday v1.0.27/go.mod h1:jFi9vgW+H7c3V0lb6nR74Ib/DIB5OBs92Dimizgw2cA= github.com/miekg/dns v1.1.57 h1:Jzi7ApEIzwEPLHWRcafCN9LZSBbqQpxjt/wpgvg7wcM= @@ -1607,12 +1715,16 @@ github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 h1:zYyBkD/k9seD2A7fsi6 github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646/go.mod h1:jpp1/29i3P1S/RLdc7JQKbRpFeM1dOBd8T9ki5s+AY8= github.com/niklasfasching/go-org v1.8.0 h1:WyGLaajLLp8JbQzkmapZ1y0MOzKuKV47HkZRloi+HGY= github.com/niklasfasching/go-org v1.8.0/go.mod h1:e2A9zJs7cdONrEGs3gvxCcaAEpwwPNPG7csDpXckMNg= +github.com/nyaruka/phonenumbers v1.1.6 h1:DcueYq7QrOArAprAYNoQfDgp0KetO4LqtnBtQC6Wyes= +github.com/nyaruka/phonenumbers v1.1.6/go.mod h1:yShPJHDSH3aTKzCbXyVxNpbl2kA+F+Ne5Pun/MvFRos= github.com/oasdiff/yaml v0.0.0-20250309154309-f31be36b4037 h1:G7ERwszslrBzRxj//JalHPu/3yz+De2J+4aLtSRlHiY= github.com/oasdiff/yaml v0.0.0-20250309154309-f31be36b4037/go.mod h1:2bpvgLBZEtENV5scfDFEtB/5+1M4hkQhDQrccEJ/qGw= github.com/oasdiff/yaml3 v0.0.0-20250309153720-d2182401db90 h1:bQx3WeLcUWy+RletIKwUIt4x3t8n2SxavmoclizMb8c= github.com/oasdiff/yaml3 v0.0.0-20250309153720-d2182401db90/go.mod h1:y5+oSEHCPT/DGrS++Wc/479ERge0zTFxaF8PbGKcg2o= github.com/oklog/run v1.1.0 h1:GEenZ1cK0+q0+wsJew9qUg/DyD8k3JzYsZAi5gYi2mA= github.com/oklog/run v1.1.0/go.mod h1:sVPdnTZT1zYwAJeCMu2Th4T21pA3FPOQRfWjQlk7DVU= +github.com/oleiade/reflections v1.0.1 h1:D1XO3LVEYroYskEsoSiGItp9RUxG6jWnCVvrqH0HHQM= +github.com/oleiade/reflections v1.0.1/go.mod h1:rdFxbxq4QXVZWj0F+e9jqjDkc7dbp97vkRixKo2JR60= github.com/olekukonko/errors v0.0.0-20250405072817-4e6d85265da6 h1:r3FaAI0NZK3hSmtTDrBVREhKULp8oUeqLT5Eyl2mSPo= github.com/olekukonko/errors v0.0.0-20250405072817-4e6d85265da6/go.mod h1:ppzxA5jBKcO1vIpCXQ9ZqgDh8iwODz6OXIGKU8r5m4Y= github.com/olekukonko/ll v0.0.8 h1:sbGZ1Fx4QxJXEqL/6IG8GEFnYojUSQ45dJVwN2FH2fc= @@ -1635,10 +1747,24 @@ github.com/opencontainers/runc v1.2.3 h1:fxE7amCzfZflJO2lHXf4y/y8M1BoAqp+FVmG19o github.com/opencontainers/runc v1.2.3/go.mod h1:nSxcWUydXrsBZVYNSkTjoQ/N6rcyTtn+1SD5D4+kRIM= github.com/opentracing/opentracing-go v1.2.0 h1:uEJPy/1a5RIPAJ0Ov+OIO8OxWu77jEv+1B0VhjKrZUs= github.com/opentracing/opentracing-go v1.2.0/go.mod h1:GxEUsuufX4nBwe+T+Wl9TAgYrxe9dPLANfrWvHYVTgc= +github.com/openzipkin/zipkin-go v0.4.2 h1:zjqfqHjUpPmB3c1GlCvvgsM1G4LkvqQbBDueDOCg/jA= +github.com/openzipkin/zipkin-go v0.4.2/go.mod h1:ZeVkFjuuBiSy13y8vpSDCjMi9GoI3hPpCJSBx/EYFhY= github.com/orisano/pixelmatch v0.0.0-20220722002657-fb0b55479cde h1:x0TT0RDC7UhAVbbWWBzr41ElhJx5tXPWkIHA2HWPRuw= github.com/orisano/pixelmatch v0.0.0-20220722002657-fb0b55479cde/go.mod h1:nZgzbfBr3hhjoZnS66nKrHmduYNpc34ny7RK4z5/HM0= github.com/ory/dockertest/v3 v3.12.0 h1:3oV9d0sDzlSQfHtIaB5k6ghUCVMVLpAY8hwrqoCyRCw= github.com/ory/dockertest/v3 v3.12.0/go.mod h1:aKNDTva3cp8dwOWwb9cWuX84aH5akkxXRvO7KCwWVjE= +github.com/ory/fosite v0.49.0 h1:KNqO7RVt/1X8F08/UI0Y+GRvcpscCWgjqvpLBQPRovo= +github.com/ory/fosite v0.49.0/go.mod h1:FAn7IY+I6DjT1r29wMouPeRYq63DWUuBj++96uOS4mE= +github.com/ory/go-acc v0.2.9-0.20230103102148-6b1c9a70dbbe h1:rvu4obdvqR0fkSIJ8IfgzKOWwZ5kOT2UNfLq81Qk7rc= +github.com/ory/go-acc v0.2.9-0.20230103102148-6b1c9a70dbbe/go.mod h1:z4n3u6as84LbV4YmgjHhnwtccQqzf4cZlSk9f1FhygI= +github.com/ory/go-convenience v0.1.0 h1:zouLKfF2GoSGnJwGq+PE/nJAE6dj2Zj5QlTgmMTsTS8= +github.com/ory/go-convenience v0.1.0/go.mod h1:uEY/a60PL5c12nYz4V5cHY03IBmwIAEm8TWB0yn9KNs= +github.com/ory/herodot v0.10.2 h1:gGvNMHgAwWzdP/eo+roSiT5CGssygHSjDU7MSQNlJ4E= +github.com/ory/herodot v0.10.2/go.mod h1:MMNmY6MG1uB6fnXYFaHoqdV23DTWctlPsmRCeq/2+wc= +github.com/ory/jsonschema/v3 v3.0.8 h1:Ssdb3eJ4lDZ/+XnGkvQS/te0p+EkolqwTsDOCxr/FmU= +github.com/ory/jsonschema/v3 v3.0.8/go.mod h1:ZPzqjDkwd3QTnb2Z6PAS+OTvBE2x5i6m25wCGx54W/0= +github.com/ory/x v0.0.665 h1:61vv0ObCDSX1vOQYbxBeqDiv4YiPmMT91lYxDaaKX08= +github.com/ory/x v0.0.665/go.mod h1:7SCTki3N0De3ZpqlxhxU/94ZrOCfNEnXwVtd0xVt+L8= github.com/outcaste-io/ristretto v0.2.3 h1:AK4zt/fJ76kjlYObOeNwh4T3asEuaCmp26pOvUOL9w0= github.com/outcaste-io/ristretto v0.2.3/go.mod h1:W8HywhmtlopSB1jeMg3JtdIhf+DYkLAr0VN/s4+MHac= github.com/pbnjay/memory v0.0.0-20210728143218-7b4eea64cf58 h1:onHthvaw9LFnH4t2DcNVpwGmV9E1BkGknEliJkfwQj0= @@ -1717,23 +1843,38 @@ github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTE github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= +github.com/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ= +github.com/rs/zerolog v1.13.0/go.mod h1:YbFCdg8HfsridGWAh22vktObvhZbQsZXe4/zB0OKkWU= +github.com/rs/zerolog v1.15.0/go.mod h1:xYTKnLHcpfU2225ny5qZjxnj9NvkumZYjJHlAThCjNc= +github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/ruudk/golang-pdf417 v0.0.0-20181029194003-1af4ab5afa58/go.mod h1:6lfFZQK844Gfx8o5WFuvpxWRwnSoipWe/p622j1v06w= github.com/ruudk/golang-pdf417 v0.0.0-20201230142125-a7e3863a1245/go.mod h1:pQAZKsJ8yyVxGRWYNEm9oFB8ieLgKFnamEyDmSA0BRk= +github.com/sagikazarmark/locafero v0.7.0 h1:5MqpDsTGNDhY8sGp0Aowyf0qKsPrhewaLSsFaodPcyo= +github.com/sagikazarmark/locafero v0.7.0/go.mod h1:2za3Cg5rMaTMoG/2Ulr9AwtFaIppKXTRYnozin4aB5k= github.com/samber/lo v1.50.0 h1:XrG0xOeHs+4FQ8gJR97zDz5uOFMW7OwFWiFVzqopKgY= github.com/samber/lo v1.50.0/go.mod h1:RjZyNk6WSnUFRKK6EyOhsRJMqft3G+pg7dCWHQCWvsc= +github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= github.com/satori/go.uuid v1.2.1-0.20181028125025-b2ce2384e17b h1:gQZ0qzfKHQIybLANtM3mBXNUtOfsCFXeTsnBqCsx1KM= github.com/satori/go.uuid v1.2.1-0.20181028125025-b2ce2384e17b/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= +github.com/seatgeek/logrus-gelf-formatter v0.0.0-20210414080842-5b05eb8ff761 h1:0b8DF5kR0PhRoRXDiEEdzrgBc8UqVY4JWLkQJCRsLME= +github.com/seatgeek/logrus-gelf-formatter v0.0.0-20210414080842-5b05eb8ff761/go.mod h1:/THDZYi7F/BsVEcYzYPqdcWFQ+1C2InkawTKfLOAnzg= github.com/secure-systems-lab/go-securesystemslib v0.9.0 h1:rf1HIbL64nUpEIZnjLZ3mcNEL9NBPB0iuVjyxvq3LZc= github.com/secure-systems-lab/go-securesystemslib v0.9.0/go.mod h1:DVHKMcZ+V4/woA/peqr+L0joiRXbPpQ042GgJckkFgw= github.com/sergeymakinen/go-bmp v1.0.0 h1:SdGTzp9WvCV0A1V0mBeaS7kQAwNLdVJbmHlqNWq0R+M= github.com/sergeymakinen/go-bmp v1.0.0/go.mod h1:/mxlAQZRLxSvJFNIEGGLBE/m40f3ZnUifpgVDlcUIEY= github.com/sergeymakinen/go-ico v1.0.0-beta.0 h1:m5qKH7uPKLdrygMWxbamVn+tl2HfiA3K6MFJw4GfZvQ= github.com/sergeymakinen/go-ico v1.0.0-beta.0/go.mod h1:wQ47mTczswBO5F0NoDt7O0IXgnV4Xy3ojrroMQzyhUk= +github.com/sergi/go-diff v1.2.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM= github.com/sergi/go-diff v1.4.0 h1:n/SP9D5ad1fORl+llWyN+D6qoUETXNZARKjyY2/KVCw= github.com/sergi/go-diff v1.4.0/go.mod h1:A0bzQcvG0E7Rwjx0REVgAGH58e96+X0MeOfepqsbeW4= github.com/shirou/gopsutil/v4 v4.25.4 h1:cdtFO363VEOOFrUCjZRh4XVJkb548lyF0q0uTeMqYPw= github.com/shirou/gopsutil/v4 v4.25.4/go.mod h1:xbuxyoZj+UsgnZrENu3lQivsngRR5BdjbJwf2fv4szA= +github.com/shopspring/decimal v0.0.0-20180709203117-cd690d0c9e24/go.mod h1:M+9NzErvs504Cn4c5DxATwIqPbtswREoFCre64PpcG4= +github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= +github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q= +github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= +github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= github.com/skeema/knownhosts v1.3.1 h1:X2osQ+RAjK76shCbvhHHHVl3ZlgDm8apHEHFqRjnBY8= @@ -1742,18 +1883,30 @@ github.com/skratchdot/open-golang v0.0.0-20200116055534-eef842397966 h1:JIAuq3EE github.com/skratchdot/open-golang v0.0.0-20200116055534-eef842397966/go.mod h1:sUM3LWHvSMaG192sy56D9F7CNvL7jUJVXoqM1QKLnog= github.com/sosedoff/gitkit v0.4.0 h1:opyQJ/h9xMRLsz2ca/2CRXtstePcpldiZN8DpLLF8Os= github.com/sosedoff/gitkit v0.4.0/go.mod h1:V3EpGZ0nvCBhXerPsbDeqtyReNb48cwP9KtkUYTKT5I= +github.com/sourcegraph/annotate v0.0.0-20160123013949-f4cad6c6324d/go.mod h1:UdhH50NIW0fCiwBSr0co2m7BnFLdv4fQTgdqdJTHFeE= +github.com/sourcegraph/conc v0.3.0 h1:OQTbbt6P72L20UqAkXXuLOj79LfEanQ+YQFNpLA9ySo= +github.com/sourcegraph/conc v0.3.0/go.mod h1:Sdozi7LEKbFPqYX2/J+iBAM6HpqSLTASQIKqDmF7Mt0= +github.com/sourcegraph/syntaxhighlight v0.0.0-20170531221838-bd320f5d308e/go.mod h1:HuIsMU8RRBOtsCgI77wP899iHVBQpCmg4ErYMZB+2IA= github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= github.com/spaolacci/murmur3 v1.1.0 h1:7c1g84S4BPRrfL5Xrdp6fOJ206sU9y293DDHaoy0bLI= github.com/spaolacci/murmur3 v1.1.0/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= github.com/spf13/cast v1.9.2 h1:SsGfm7M8QOFtEzumm7UZrZdLLquNdzFYfIbEXntcFbE= github.com/spf13/cast v1.9.2/go.mod h1:jNfB8QC9IA6ZuY2ZjDp0KtFO2LZZlg4S/7bzP6qqeHo= +github.com/spf13/cobra v1.6.1/go.mod h1:IOw/AERYS7UzyrGinqmz6HLUo219MORXGxhbaJUqzrY= +github.com/spf13/cobra v1.9.1 h1:CXSaggrXdbHK9CF+8ywj8Amf7PBRmPCOJugH954Nnlo= +github.com/spf13/cobra v1.9.1/go.mod h1:nDyEzZ8ogv936Cinf6g1RU9MRY64Ir93oCnqb9wxYW0= +github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/spf13/pflag v1.0.6 h1:jFzHGLGAlb3ruxLB8MhbI6A8+AQX/2eW4qeyNZXNp2o= github.com/spf13/pflag v1.0.6/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/spf13/viper v1.20.1 h1:ZMi+z/lvLyPSCoNtFCpqjy0S4kPbirhpTMwl8BkW9X4= +github.com/spf13/viper v1.20.1/go.mod h1:P9Mdzt1zoHIG8m2eZQinpiBjo6kCmZSKBClNNqjJvu4= github.com/spiffe/go-spiffe/v2 v2.5.0 h1:N2I01KCUkv1FAjZXJMwh95KK1ZIQLYbPfhaxw8WS0hE= github.com/spiffe/go-spiffe/v2 v2.5.0/go.mod h1:P+NxobPc6wXhVtINNtFjNWGBTreew1GBUCwT2wPmb7g= github.com/sqlc-dev/pqtype v0.3.0 h1:b09TewZ3cSnO5+M1Kqq05y0+OjqIptxELaSayg7bmqk= github.com/sqlc-dev/pqtype v0.3.0/go.mod h1:oyUjp5981ctiL9UYvj1bVvCKi8OXkCa0u645hce7CAs= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= @@ -1773,6 +1926,8 @@ github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXl github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8= +github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU= github.com/swaggest/assertjson v1.9.0 h1:dKu0BfJkIxv/xe//mkCrK5yZbs79jL7OVf9Ija7o2xQ= github.com/swaggest/assertjson v1.9.0/go.mod h1:b+ZKX2VRiUjxfUIal0HDN85W0nHPAYUbYH5WkkSsFsU= github.com/swaggo/files/v2 v2.0.0 h1:hmAt8Dkynw7Ssz46F6pn8ok6YmGZqHSVLZ+HQM7i0kw= @@ -1838,6 +1993,8 @@ github.com/ulikunitz/xz v0.5.12 h1:37Nm15o69RwBkXM0J6A5OlE67RZTfzUxTj8fB3dfcsc= github.com/ulikunitz/xz v0.5.12/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14= github.com/unrolled/secure v1.17.0 h1:Io7ifFgo99Bnh0J7+Q+qcMzWM6kaDPCA5FroFZEdbWU= github.com/unrolled/secure v1.17.0/go.mod h1:BmF5hyM6tXczk3MpQkFf1hpKSRqCyhqcbiQtiAF7+40= +github.com/urfave/negroni v1.0.0 h1:kIimOitoypq34K7TG7DUaJ9kq/N4Ofuwi1sjz0KipXc= +github.com/urfave/negroni v1.0.0/go.mod h1:Meg73S6kFm/4PpbYdq35yYWoCZ9mS/YSx+lKnmiohz4= github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= github.com/valyala/fasthttp v1.64.0 h1:QBygLLQmiAyiXuRhthf0tuRkqAFcrC42dckN2S+N3og= @@ -1913,6 +2070,7 @@ github.com/zeebo/errs v1.4.0 h1:XNdoD/RRMKP7HD0UhJnIzUy74ISdGGxURlYG8HSWSfM= github.com/zeebo/errs v1.4.0/go.mod h1:sgbWHsvVuTPHcqJJGQ1WhI5KbWlHYz+2+2C/LSEtCw4= github.com/zeebo/xxh3 v1.0.2 h1:xZmwmqxHZA8AI603jOQ0tMqmBr9lPeFwGg6d+xy9DC0= github.com/zeebo/xxh3 v1.0.2/go.mod h1:5NWz9Sef7zIDm2JHfFlcQvNekmcEl9ekUZQQKCYaDcA= +github.com/zenazn/goji v0.9.0/go.mod h1:7S9M489iMyHBNxwZnk9/EHS098H4/F6TATF2mIxtB1Q= go.mozilla.org/pkcs7 v0.9.0 h1:yM4/HS9dYv7ri2biPtxt8ikvB37a980dg69/pKmS+eI= go.mozilla.org/pkcs7 v0.9.0/go.mod h1:SNgMg+EgDFwmvSmLRTNKC5fegJjB7v23qTQ0XLGUNHk= go.nhat.io/otelsql v0.16.0 h1:MUKhNSl7Vk1FGyopy04FBDimyYogpRFs0DBB9frQal0= @@ -1954,11 +2112,21 @@ go.opentelemetry.io/contrib/detectors/gcp v1.36.0 h1:F7q2tNlCaHY9nMKHR6XH9/qkp8F go.opentelemetry.io/contrib/detectors/gcp v1.36.0/go.mod h1:IbBN8uAIIx734PTonTPxAxnjc2pQTxWNkwfstZ+6H2k= go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.61.0 h1:q4XOmH/0opmeuJtPsbFNivyl7bCt7yRBbeEm2sC/XtQ= go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.61.0/go.mod h1:snMWehoOh2wsEwnvvwtDyFCxVeDAODenXHtn5vzrKjo= +go.opentelemetry.io/contrib/instrumentation/net/http/httptrace/otelhttptrace v0.46.1 h1:gbhw/u49SS3gkPWiYweQNJGm/uJN5GkI/FrosxSHT7A= +go.opentelemetry.io/contrib/instrumentation/net/http/httptrace/otelhttptrace v0.46.1/go.mod h1:GnOaBaFQ2we3b9AGWJpsBa7v1S5RlQzlC3O7dRMxZhM= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0 h1:F7Jx+6hwnZ41NSFTO5q4LYDtJRXBf2PD0rNBkeB/lus= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0/go.mod h1:UHB22Z8QsdRDrnAtX4PntOl36ajSxcdUMt1sF7Y6E7Q= +go.opentelemetry.io/contrib/propagators/b3 v1.21.0 h1:uGdgDPNzwQWRwCXJgw/7h29JaRqcq9B87Iv4hJDKAZw= +go.opentelemetry.io/contrib/propagators/b3 v1.21.0/go.mod h1:D9GQXvVGT2pzyTfp1QBOnD1rzKEWzKjjwu5q2mslCUI= +go.opentelemetry.io/contrib/propagators/jaeger v1.21.1 h1:f4beMGDKiVzg9IcX7/VuWVy+oGdjx3dNJ72YehmtY5k= +go.opentelemetry.io/contrib/propagators/jaeger v1.21.1/go.mod h1:U9jhkEl8d1LL+QXY7q3kneJWJugiN3kZJV2OWz3hkBY= +go.opentelemetry.io/contrib/samplers/jaegerremote v0.15.1 h1:Qb+5A+JbIjXwO7l4HkRUhgIn4Bzz0GNS2q+qdmSx+0c= +go.opentelemetry.io/contrib/samplers/jaegerremote v0.15.1/go.mod h1:G4vNCm7fRk0kjZ6pGNLo5SpLxAUvOfSrcaegnT8TPck= go.opentelemetry.io/otel v1.3.0/go.mod h1:PWIKzi6JCp7sM0k9yZ43VX+T345uNbAkDKwHVjb2PTs= go.opentelemetry.io/otel v1.37.0 h1:9zhNfelUvx0KBfu/gb+ZgeAfAgtWrfHJZcAqFC228wQ= go.opentelemetry.io/otel v1.37.0/go.mod h1:ehE/umFRLnuLa/vSccNq9oS1ErUlkkK71gMcN34UG8I= +go.opentelemetry.io/otel/exporters/jaeger v1.17.0 h1:D7UpUy2Xc2wsi1Ras6V40q806WM07rqoCWzXu7Sqy+4= +go.opentelemetry.io/otel/exporters/jaeger v1.17.0/go.mod h1:nPCqOnEH9rNLKqH/+rrUjiMzHJdV1BlpKcTwRTyKkKI= go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.35.0 h1:1fTNlAIJZGWLP5FVu0fikVry1IsiUnXjf7QFvoNN3Xw= go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.35.0/go.mod h1:zjPK58DtkqQFn+YUMbx0M2XV3QgKU0gS9LeGohREyK4= go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.35.0 h1:m639+BofXTvcY1q8CGs4ItwQarYtJPOWmVobfM1HpVI= @@ -1969,6 +2137,8 @@ go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.37.0 h1:6VjV6Et+1Hd2iL go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.37.0/go.mod h1:u8hcp8ji5gaM/RfcOo8z9NMnf1pVLfVY7lBY2VOGuUU= go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.37.0 h1:SNhVp/9q4Go/XHBkQ1/d5u9P/U+L1yaGPoi0x+mStaI= go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.37.0/go.mod h1:tx8OOlGH6R4kLV67YaYO44GFXloEjGPZuMjEkaaqIp4= +go.opentelemetry.io/otel/exporters/zipkin v1.21.0 h1:D+Gv6lSfrFBWmQYyxKjDd0Zuld9SRXpIrEsKZvE4DO4= +go.opentelemetry.io/otel/exporters/zipkin v1.21.0/go.mod h1:83oMKR6DzmHisFOW3I+yIMGZUTjxiWaiBI8M8+TU5zE= go.opentelemetry.io/otel/metric v1.37.0 h1:mvwbQS5m0tbmqML4NqK+e3aDiO02vsf/WgbsdpcPoZE= go.opentelemetry.io/otel/metric v1.37.0/go.mod h1:04wGrZurHYKOc+RKeye86GwKiTb9FKm1WHtO+4EVr2E= go.opentelemetry.io/otel/sdk v1.3.0/go.mod h1:rIo4suHNhQwBIPg9axF8V9CA72Wz2mKF1teNrup8yzs= @@ -1984,6 +2154,10 @@ go.opentelemetry.io/proto/otlp v0.15.0/go.mod h1:H7XAot3MsfNsj7EXtrA2q5xSNQ10UqI go.opentelemetry.io/proto/otlp v0.19.0/go.mod h1:H7XAot3MsfNsj7EXtrA2q5xSNQ10UqI405h3+duxN4U= go.opentelemetry.io/proto/otlp v1.5.0 h1:xJvq7gMzB31/d406fB8U5CBdyQGw4P399D1aQWU/3i4= go.opentelemetry.io/proto/otlp v1.5.0/go.mod h1:keN8WnHxOy8PG0rQZjJJ5A2ebUoafqWp0eVQ4yIXvJ4= +go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= +go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= +go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= +go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE= go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0= @@ -1991,8 +2165,15 @@ go.uber.org/goleak v1.3.1-0.20240429205332-517bace7cc29 h1:w0QrHuh0hhUZ++UTQaBM2 go.uber.org/goleak v1.3.1-0.20240429205332-517bace7cc29/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= go.uber.org/mock v0.5.0 h1:KAMbZvZPyBPWgD14IrIQ38QCyjwpvVVV6K/bHl1IwQU= go.uber.org/mock v0.5.0/go.mod h1:ge71pBPLYDk7QIi1LupWxdAykm7KIEFchiOqd6z7qMM= +go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= +go.uber.org/multierr v1.3.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+4= +go.uber.org/multierr v1.5.0/go.mod h1:FeouvMocqHpRaaGuG9EjoKcStLC43Zu/fmqdUMPcKYU= go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= +go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA= +go.uber.org/zap v1.9.1/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= +go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= +go.uber.org/zap v1.13.0/go.mod h1:zwrFLgMcdUuIBviXEYEH1YKNaOBnKXsx2IPda5bBwHM= go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8= go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= go4.org/mem v0.0.0-20220726221520-4f986261bf13 h1:CbZeCBZ0aZj8EfVgnqQcYZgf0lpZ3H9rmp5nkDTAst8= @@ -2000,13 +2181,18 @@ go4.org/mem v0.0.0-20220726221520-4f986261bf13/go.mod h1:reUoABIJ9ikfM5sgtSF3Wus go4.org/netipx v0.0.0-20230728180743-ad4cb58a6516 h1:X66ZEoMN2SuaoI/dfZVYobB6E5zjZyyHUMWlCA7MgGE= go4.org/netipx v0.0.0-20230728180743-ad4cb58a6516/go.mod h1:TQvodOM+hJTioNQJilmLXu08JNb8i+ccq418+KWu1/Y= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20190411191339-88737f569e3a/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE= golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200117160349-530e935923ad/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20201203163018-be400aefbc4c/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.12.0/go.mod h1:NF0Gs7EO5K4qLn+Ylc+fih8BSTeIjAP05siRnAh98yw= golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc= golang.org/x/crypto v0.17.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4= @@ -2077,6 +2263,7 @@ golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91 golang.org/x/mod v0.7.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.9.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/mod v0.10.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.15.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= @@ -2093,6 +2280,7 @@ golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= @@ -2118,6 +2306,7 @@ golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96b golang.org/x/net v0.0.0-20210503060351-7fd8e65b6420/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20210813160813-60bc85c4be6d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= golang.org/x/net v0.0.0-20220325170049-de3da57026de/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= @@ -2127,7 +2316,9 @@ golang.org/x/net v0.0.0-20220607020251-c690dde0001d/go.mod h1:XRhObCWvk6IyKnWLug golang.org/x/net v0.0.0-20220617184016-355a448f1bc9/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.0.0-20220624214902-1bab6f366d9e/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/net v0.0.0-20220826154423-83b083e8dc8b/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk= golang.org/x/net v0.0.0-20220909164309-bea034e7d591/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk= +golang.org/x/net v0.0.0-20221002022538-bcab6841153b/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk= golang.org/x/net v0.0.0-20221012135044-0b7e1fb9d458/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk= golang.org/x/net v0.0.0-20221014081412-f15817d10f9b/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk= golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco= @@ -2201,14 +2392,18 @@ golang.org/x/sync v0.12.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= golang.org/x/sync v0.16.0 h1:ycBJEhp9p4vXvUZNszeOq0kGTPghopOL8q0fq3vstxw= golang.org/x/sync v0.16.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -2299,8 +2494,10 @@ golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.35.0 h1:vz1N37gP5bs89s7He8XuIYXpyY0+QlsKmzipCbUtyxI= golang.org/x/sys v0.35.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= golang.org/x/telemetry v0.0.0-20240228155512-f48c80bd79b2/go.mod h1:TeRTkGYfJXctD9OcfyVLyj2J3IxLnKwHJR8f4D8a3YE= +golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/term v0.0.0-20220722155259-a9ba230a4035/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc= golang.org/x/term v0.3.0/go.mod h1:q750SLmJuPmVoN1blW3UFBPREJfb1KmY3vwxfr+nFDA= @@ -2359,15 +2556,19 @@ golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3 golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190425163242-31fd60d6bfdc/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20190823170909-c4a336ef6a2f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20190927191325-030b2cf1153e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= @@ -2375,6 +2576,7 @@ golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtn golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200103221440-774c71fcf114/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= @@ -2407,15 +2609,19 @@ golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.3/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.4/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= +golang.org/x/tools v0.1.8/go.mod h1:nABZi5QlRsZVlzPpHl034qft6wpY4eDcsTt5AaioBiU= golang.org/x/tools v0.1.9/go.mod h1:nABZi5QlRsZVlzPpHl034qft6wpY4eDcsTt5AaioBiU= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/tools v0.3.0/go.mod h1:/rWhSS2+zyEVwoJf8YAX6L2f0ntZ7Kn/mGgAWcipA5k= golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= golang.org/x/tools v0.7.0/go.mod h1:4pg6aUX35JBAogB10C9AtvVL+qowtN4pT3CGSQex14s= +golang.org/x/tools v0.8.0/go.mod h1:JxBZ99ISMI5ViVkT1tr6tdNmXeTrcpVSD3vZ1RsRdN4= golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58= golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk= golang.org/x/tools v0.36.0 h1:kWS0uv/zsvHEle1LbV5LE8QujrxB3wfQyxHfhOk0Qkg= golang.org/x/tools v0.36.0/go.mod h1:WBDiHKJK8YgLHlcQPYQzNCkUxUypCaa5ZegCVutKm+s= +golang.org/x/xerrors v0.0.0-20190410155217-1f06c39b4373/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20190513163551-3ee3066db522/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= @@ -2713,10 +2919,12 @@ gopkg.in/DataDog/dd-trace-go.v1 v1.74.0 h1:wScziU1ff6Bnyr8MEyxATPSLJdnLxKz3p6RsA gopkg.in/DataDog/dd-trace-go.v1 v1.74.0/go.mod h1:ReNBsNfnsjVC7GsCe80zRcykL/n+nxvsNrg3NbjuleM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/cheggaaa/pb.v1 v1.0.27/go.mod h1:V/YB90LKu/1FcN3WVnfiiE5oMCibMjukxqG/qStrOgw= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= +gopkg.in/inconshreveable/log15.v2 v2.0.0-20180818164646-67afb5ed74ec/go.mod h1:aPpfJ7XW+gOuirDoZ8gHhLh3kZ1B08FtV2bbmy7Jv3s= gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA= gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/natefinch/lumberjack.v2 v2.2.1 h1:bBRl1b0OH9s/DuPhuXpNl+VtCaJXFZ5/uEFST95x9zc= @@ -2726,6 +2934,7 @@ gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRN gopkg.in/yaml.v1 v1.0.0-20140924161607-9f9df34309c0/go.mod h1:WDnlLJ4WF5VGsH/HVa3CI79GS0ol3YnhVnKP89i0kNg= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= diff --git a/scripts/oauth2/setup-test-app.sh b/scripts/oauth2/setup-test-app.sh index 5f2a7b889ad3f..c9a24634cc1c3 100755 --- a/scripts/oauth2/setup-test-app.sh +++ b/scripts/oauth2/setup-test-app.sh @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash set -e # Setup OAuth2 test app and return credentials diff --git a/scripts/oauth2/test-manual-flow.sh b/scripts/oauth2/test-manual-flow.sh index 734c3a9c5e03a..2e41b76c66b6c 100755 --- a/scripts/oauth2/test-manual-flow.sh +++ b/scripts/oauth2/test-manual-flow.sh @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash set -e # Manual OAuth2 flow test with automatic callback handling @@ -39,7 +39,7 @@ if ! command -v go &>/dev/null; then fi # Generate PKCE parameters -CODE_VERIFIER=$(openssl rand -base64 32 | tr -d "=+/" | cut -c -43) +CODE_VERIFIER=$(openssl rand -base64 32 | tr -d "=+/" | cut -c -50) export CODE_VERIFIER CODE_CHALLENGE=$(echo -n "$CODE_VERIFIER" | openssl dgst -sha256 -binary | base64 | tr -d "=" | tr '+/' '-_') export CODE_CHALLENGE diff --git a/site/package.json b/site/package.json index 5693fc5d55220..b253de43c357a 100644 --- a/site/package.json +++ b/site/package.json @@ -195,8 +195,7 @@ "semver": "7.6.2" }, "engines": { - "pnpm": ">=10.0.0 <11.0.0", - "node": ">=18.0.0 <21.0.0" + }, "pnpm": { "overrides": { From f22d07b842e776ac093894ff6f90522452ca792d Mon Sep 17 00:00:00 2001 From: Steven Masley Date: Thu, 21 Aug 2025 12:01:49 -0500 Subject: [PATCH 2/5] remove some files --- coderd/oauth2.go | 6 +- coderd/oauth2provider/authorize.go | 197 ------------ coderd/oauth2provider/tokens.go | 466 ----------------------------- 3 files changed, 3 insertions(+), 666 deletions(-) delete mode 100644 coderd/oauth2provider/authorize.go delete mode 100644 coderd/oauth2provider/tokens.go diff --git a/coderd/oauth2.go b/coderd/oauth2.go index 1e28f9b65bbb8..f9b8be5bcbff4 100644 --- a/coderd/oauth2.go +++ b/coderd/oauth2.go @@ -116,7 +116,7 @@ func (api *API) deleteOAuth2ProviderAppSecret() http.HandlerFunc { // @Success 200 "Returns HTML authorization page" // @Router /oauth2/authorize [get] func (api *API) getOAuth2ProviderAppAuthorize() http.HandlerFunc { - return oauth2provider.ShowAuthorizePage(api.AccessURL) + return api.OAuth2Provider.AuthEndpoint } // @Summary OAuth2 authorization request (POST - process authorization). @@ -131,7 +131,7 @@ func (api *API) getOAuth2ProviderAppAuthorize() http.HandlerFunc { // @Success 302 "Returns redirect with authorization code" // @Router /oauth2/authorize [post] func (api *API) postOAuth2ProviderAppAuthorize() http.HandlerFunc { - return oauth2provider.ProcessAuthorize(api.Database) + return api.OAuth2Provider.ShowAuthorizationPage(api.AccessURL) } // @Summary OAuth2 token exchange. @@ -146,7 +146,7 @@ func (api *API) postOAuth2ProviderAppAuthorize() http.HandlerFunc { // @Success 200 {object} oauth2.Token // @Router /oauth2/tokens [post] func (api *API) postOAuth2ProviderAppToken() http.HandlerFunc { - return oauth2provider.Tokens(api.Database, api.DeploymentValues.Sessions) + return api.OAuth2Provider.TokenEndpoint } // @Summary Delete OAuth2 application tokens. diff --git a/coderd/oauth2provider/authorize.go b/coderd/oauth2provider/authorize.go deleted file mode 100644 index 4100b82306384..0000000000000 --- a/coderd/oauth2provider/authorize.go +++ /dev/null @@ -1,197 +0,0 @@ -package oauth2provider - -import ( - "database/sql" - "errors" - "net/http" - "net/url" - "strings" - "time" - - "github.com/google/uuid" - "golang.org/x/xerrors" - - "github.com/coder/coder/v2/coderd/database" - "github.com/coder/coder/v2/coderd/database/dbtime" - "github.com/coder/coder/v2/coderd/httpapi" - "github.com/coder/coder/v2/coderd/httpmw" - "github.com/coder/coder/v2/codersdk" - "github.com/coder/coder/v2/site" -) - -type authorizeParams struct { - clientID string - redirectURL *url.URL - responseType codersdk.OAuth2ProviderResponseType - scope []string - state string - resource string // RFC 8707 resource indicator - codeChallenge string // PKCE code challenge - codeChallengeMethod string // PKCE challenge method -} - -func extractAuthorizeParams(r *http.Request, callbackURL *url.URL) (authorizeParams, []codersdk.ValidationError, error) { - p := httpapi.NewQueryParamParser() - vals := r.URL.Query() - - p.RequiredNotEmpty("response_type", "client_id") - - params := authorizeParams{ - clientID: p.String(vals, "", "client_id"), - redirectURL: p.RedirectURL(vals, callbackURL, "redirect_uri"), - responseType: httpapi.ParseCustom(p, vals, "", "response_type", httpapi.ParseEnum[codersdk.OAuth2ProviderResponseType]), - scope: p.Strings(vals, []string{}, "scope"), - state: p.String(vals, "", "state"), - resource: p.String(vals, "", "resource"), - codeChallenge: p.String(vals, "", "code_challenge"), - codeChallengeMethod: p.String(vals, "", "code_challenge_method"), - } - // Validate resource indicator syntax (RFC 8707): must be absolute URI without fragment - if err := validateResourceParameter(params.resource); err != nil { - p.Errors = append(p.Errors, codersdk.ValidationError{ - Field: "resource", - Detail: "must be an absolute URI without fragment", - }) - } - - p.ErrorExcessParams(vals) - if len(p.Errors) > 0 { - // Create a readable error message with validation details - var errorDetails []string - for _, err := range p.Errors { - errorDetails = append(errorDetails, err.Error()) - } - errorMsg := "Invalid query params: " + strings.Join(errorDetails, ", ") - return authorizeParams{}, p.Errors, xerrors.Errorf(errorMsg) - } - return params, nil, nil -} - -// ShowAuthorizePage handles GET /oauth2/authorize requests to display the HTML authorization page. -func ShowAuthorizePage(accessURL *url.URL) http.HandlerFunc { - return func(rw http.ResponseWriter, r *http.Request) { - app := httpmw.OAuth2ProviderApp(r) - ua := httpmw.UserAuthorization(r.Context()) - - callbackURL, err := url.Parse(app.CallbackURL) - if err != nil { - site.RenderStaticErrorPage(rw, r, site.ErrorPageData{Status: http.StatusInternalServerError, HideStatus: false, Title: "Internal Server Error", Description: err.Error(), RetryEnabled: false, DashboardURL: accessURL.String(), Warnings: nil}) - return - } - - params, validationErrs, err := extractAuthorizeParams(r, callbackURL) - if err != nil { - errStr := make([]string, len(validationErrs)) - for i, err := range validationErrs { - errStr[i] = err.Detail - } - site.RenderStaticErrorPage(rw, r, site.ErrorPageData{Status: http.StatusBadRequest, HideStatus: false, Title: "Invalid Query Parameters", Description: "One or more query parameters are missing or invalid.", RetryEnabled: false, DashboardURL: accessURL.String(), Warnings: errStr}) - return - } - - cancel := params.redirectURL - cancelQuery := params.redirectURL.Query() - cancelQuery.Add("error", "access_denied") - cancel.RawQuery = cancelQuery.Encode() - - site.RenderOAuthAllowPage(rw, r, site.RenderOAuthAllowData{ - AppIcon: app.Icon, - AppName: app.Name, - CancelURI: cancel.String(), - RedirectURI: r.URL.String(), - Username: ua.FriendlyName, - }) - } -} - -// ProcessAuthorize handles POST /oauth2/authorize requests to process the user's authorization decision -// and generate an authorization code. -func ProcessAuthorize(db database.Store) http.HandlerFunc { - return func(rw http.ResponseWriter, r *http.Request) { - ctx := r.Context() - apiKey := httpmw.APIKey(r) - app := httpmw.OAuth2ProviderApp(r) - - callbackURL, err := url.Parse(app.CallbackURL) - if err != nil { - httpapi.WriteOAuth2Error(r.Context(), rw, http.StatusInternalServerError, "server_error", "Failed to validate query parameters") - return - } - - params, _, err := extractAuthorizeParams(r, callbackURL) - if err != nil { - httpapi.WriteOAuth2Error(ctx, rw, http.StatusBadRequest, "invalid_request", err.Error()) - return - } - - // Validate PKCE for public clients (MCP requirement) - if params.codeChallenge != "" { - // If code_challenge is provided but method is not, default to S256 - if params.codeChallengeMethod == "" { - params.codeChallengeMethod = "S256" - } - if params.codeChallengeMethod != "S256" { - httpapi.WriteOAuth2Error(ctx, rw, http.StatusBadRequest, "invalid_request", "Invalid code_challenge_method: only S256 is supported") - return - } - } - - // TODO: Ignoring scope for now, but should look into implementing. - code, err := GenerateSecret() - if err != nil { - httpapi.WriteOAuth2Error(r.Context(), rw, http.StatusInternalServerError, "server_error", "Failed to generate OAuth2 app authorization code") - return - } - err = db.InTx(func(tx database.Store) error { - // Delete any previous codes. - err = tx.DeleteOAuth2ProviderAppCodesByAppAndUserID(ctx, database.DeleteOAuth2ProviderAppCodesByAppAndUserIDParams{ - AppID: app.ID, - UserID: apiKey.UserID, - }) - if err != nil && !errors.Is(err, sql.ErrNoRows) { - return xerrors.Errorf("delete oauth2 app codes: %w", err) - } - - // Insert the new code. - _, err = tx.InsertOAuth2ProviderAppCode(ctx, database.InsertOAuth2ProviderAppCodeParams{ - ID: uuid.New(), - CreatedAt: dbtime.Now(), - // TODO: Configurable expiration? Ten minutes matches GitHub. - // This timeout is only for the code that will be exchanged for the - // access token, not the access token itself. It does not need to be - // long-lived because normally it will be exchanged immediately after it - // is received. If the application does wait before exchanging the - // token (for example suppose they ask the user to confirm and the user - // has left) then they can just retry immediately and get a new code. - ExpiresAt: dbtime.Now().Add(time.Duration(10) * time.Minute), - SecretPrefix: []byte(code.Prefix), - HashedSecret: []byte(code.Hashed), - AppID: app.ID, - UserID: apiKey.UserID, - ResourceUri: sql.NullString{String: params.resource, Valid: params.resource != ""}, - CodeChallenge: sql.NullString{String: params.codeChallenge, Valid: params.codeChallenge != ""}, - CodeChallengeMethod: sql.NullString{String: params.codeChallengeMethod, Valid: params.codeChallengeMethod != ""}, - }) - if err != nil { - return xerrors.Errorf("insert oauth2 authorization code: %w", err) - } - - return nil - }, nil) - if err != nil { - httpapi.WriteOAuth2Error(ctx, rw, http.StatusInternalServerError, "server_error", "Failed to generate OAuth2 authorization code") - return - } - - newQuery := params.redirectURL.Query() - newQuery.Add("code", code.Formatted) - if params.state != "" { - newQuery.Add("state", params.state) - } - params.redirectURL.RawQuery = newQuery.Encode() - - // (ThomasK33): Use a 302 redirect as some (external) OAuth 2 apps and browsers - // do not work with the 307. - http.Redirect(rw, r, params.redirectURL.String(), http.StatusFound) - } -} diff --git a/coderd/oauth2provider/tokens.go b/coderd/oauth2provider/tokens.go deleted file mode 100644 index afbc27dd8b5a8..0000000000000 --- a/coderd/oauth2provider/tokens.go +++ /dev/null @@ -1,466 +0,0 @@ -package oauth2provider - -import ( - "context" - "database/sql" - "errors" - "fmt" - "net/http" - "net/url" - "slices" - "time" - - "github.com/google/uuid" - "golang.org/x/oauth2" - "golang.org/x/xerrors" - - "github.com/coder/coder/v2/coderd/apikey" - "github.com/coder/coder/v2/coderd/database" - "github.com/coder/coder/v2/coderd/database/dbauthz" - "github.com/coder/coder/v2/coderd/database/dbtime" - "github.com/coder/coder/v2/coderd/httpapi" - "github.com/coder/coder/v2/coderd/httpmw" - "github.com/coder/coder/v2/coderd/rbac" - "github.com/coder/coder/v2/coderd/userpassword" - "github.com/coder/coder/v2/codersdk" -) - -var ( - // errBadSecret means the user provided a bad secret. - errBadSecret = xerrors.New("Invalid client secret") - // errBadCode means the user provided a bad code. - errBadCode = xerrors.New("Invalid code") - // errBadToken means the user provided a bad token. - errBadToken = xerrors.New("Invalid token") - // errInvalidPKCE means the PKCE verification failed. - errInvalidPKCE = xerrors.New("invalid code_verifier") - // errInvalidResource means the resource parameter validation failed. - errInvalidResource = xerrors.New("invalid resource parameter") -) - -type tokenParams struct { - clientID string - clientSecret string - code string - grantType codersdk.OAuth2ProviderGrantType - redirectURL *url.URL - refreshToken string - codeVerifier string // PKCE verifier - resource string // RFC 8707 resource for token binding -} - -func extractTokenParams(r *http.Request, callbackURL *url.URL) (tokenParams, []codersdk.ValidationError, error) { - p := httpapi.NewQueryParamParser() - err := r.ParseForm() - if err != nil { - return tokenParams{}, nil, xerrors.Errorf("parse form: %w", err) - } - - vals := r.Form - p.RequiredNotEmpty("grant_type") - grantType := httpapi.ParseCustom(p, vals, "", "grant_type", httpapi.ParseEnum[codersdk.OAuth2ProviderGrantType]) - switch grantType { - case codersdk.OAuth2ProviderGrantTypeRefreshToken: - p.RequiredNotEmpty("refresh_token") - case codersdk.OAuth2ProviderGrantTypeAuthorizationCode: - p.RequiredNotEmpty("client_secret", "client_id", "code") - } - - params := tokenParams{ - clientID: p.String(vals, "", "client_id"), - clientSecret: p.String(vals, "", "client_secret"), - code: p.String(vals, "", "code"), - grantType: grantType, - redirectURL: p.RedirectURL(vals, callbackURL, "redirect_uri"), - refreshToken: p.String(vals, "", "refresh_token"), - codeVerifier: p.String(vals, "", "code_verifier"), - resource: p.String(vals, "", "resource"), - } - // Validate resource parameter syntax (RFC 8707): must be absolute URI without fragment - if err := validateResourceParameter(params.resource); err != nil { - p.Errors = append(p.Errors, codersdk.ValidationError{ - Field: "resource", - Detail: "must be an absolute URI without fragment", - }) - } - - p.ErrorExcessParams(vals) - if len(p.Errors) > 0 { - return tokenParams{}, p.Errors, xerrors.Errorf("invalid query params: %w", p.Errors) - } - return params, nil, nil -} - -// Tokens -// TODO: the sessions lifetime config passed is for coder api tokens. -// Should there be a separate config for oauth2 tokens? They are related, -// but they are not the same. -func Tokens(db database.Store, lifetimes codersdk.SessionLifetime) http.HandlerFunc { - return func(rw http.ResponseWriter, r *http.Request) { - ctx := r.Context() - app := httpmw.OAuth2ProviderApp(r) - - callbackURL, err := url.Parse(app.CallbackURL) - if err != nil { - httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{ - Message: "Failed to validate form values.", - Detail: err.Error(), - }) - return - } - - params, validationErrs, err := extractTokenParams(r, callbackURL) - if err != nil { - // Check for specific validation errors in priority order - if slices.ContainsFunc(validationErrs, func(validationError codersdk.ValidationError) bool { - return validationError.Field == "grant_type" - }) { - httpapi.WriteOAuth2Error(ctx, rw, http.StatusBadRequest, "unsupported_grant_type", "The grant type is missing or unsupported") - return - } - - // Check for missing required parameters for authorization_code grant - for _, field := range []string{"code", "client_id", "client_secret"} { - if slices.ContainsFunc(validationErrs, func(validationError codersdk.ValidationError) bool { - return validationError.Field == field - }) { - httpapi.WriteOAuth2Error(ctx, rw, http.StatusBadRequest, "invalid_request", fmt.Sprintf("Missing required parameter: %s", field)) - return - } - } - // Generic invalid request for other validation errors - httpapi.WriteOAuth2Error(ctx, rw, http.StatusBadRequest, "invalid_request", "The request is missing required parameters or is otherwise malformed") - return - } - - var token oauth2.Token - //nolint:gocritic,revive // More cases will be added later. - switch params.grantType { - // TODO: Client creds, device code. - case codersdk.OAuth2ProviderGrantTypeRefreshToken: - token, err = refreshTokenGrant(ctx, db, app, lifetimes, params) - case codersdk.OAuth2ProviderGrantTypeAuthorizationCode: - token, err = authorizationCodeGrant(ctx, db, app, lifetimes, params) - default: - // This should handle truly invalid grant types - httpapi.WriteOAuth2Error(ctx, rw, http.StatusBadRequest, "unsupported_grant_type", fmt.Sprintf("The grant type %q is not supported", params.grantType)) - return - } - - if errors.Is(err, errBadSecret) { - httpapi.WriteOAuth2Error(ctx, rw, http.StatusUnauthorized, "invalid_client", "The client credentials are invalid") - return - } - if errors.Is(err, errBadCode) { - httpapi.WriteOAuth2Error(ctx, rw, http.StatusBadRequest, "invalid_grant", "The authorization code is invalid or expired") - return - } - if errors.Is(err, errInvalidPKCE) { - httpapi.WriteOAuth2Error(ctx, rw, http.StatusBadRequest, "invalid_grant", "The PKCE code verifier is invalid") - return - } - if errors.Is(err, errInvalidResource) { - httpapi.WriteOAuth2Error(ctx, rw, http.StatusBadRequest, "invalid_target", "The resource parameter is invalid") - return - } - if errors.Is(err, errBadToken) { - httpapi.WriteOAuth2Error(ctx, rw, http.StatusBadRequest, "invalid_grant", "The refresh token is invalid or expired") - return - } - if err != nil { - httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{ - Message: "Failed to exchange token", - Detail: err.Error(), - }) - return - } - - // Some client libraries allow this to be "application/x-www-form-urlencoded". We can implement that upon - // request. The same libraries should also accept JSON. If implemented, choose based on "Accept" header. - httpapi.Write(ctx, rw, http.StatusOK, token) - } -} - -func authorizationCodeGrant(ctx context.Context, db database.Store, app database.OAuth2ProviderApp, lifetimes codersdk.SessionLifetime, params tokenParams) (oauth2.Token, error) { - // Validate the client secret. - secret, err := parseFormattedSecret(params.clientSecret) - if err != nil { - return oauth2.Token{}, errBadSecret - } - //nolint:gocritic // Users cannot read secrets so we must use the system. - dbSecret, err := db.GetOAuth2ProviderAppSecretByPrefix(dbauthz.AsSystemRestricted(ctx), []byte(secret.prefix)) - if errors.Is(err, sql.ErrNoRows) { - return oauth2.Token{}, errBadSecret - } - if err != nil { - return oauth2.Token{}, err - } - equal, err := userpassword.Compare(string(dbSecret.HashedSecret), secret.secret) - if err != nil { - return oauth2.Token{}, xerrors.Errorf("unable to compare secret: %w", err) - } - if !equal { - return oauth2.Token{}, errBadSecret - } - - // Validate the authorization code. - code, err := parseFormattedSecret(params.code) - if err != nil { - return oauth2.Token{}, errBadCode - } - //nolint:gocritic // There is no user yet so we must use the system. - dbCode, err := db.GetOAuth2ProviderAppCodeByPrefix(dbauthz.AsSystemRestricted(ctx), []byte(code.prefix)) - if errors.Is(err, sql.ErrNoRows) { - return oauth2.Token{}, errBadCode - } - if err != nil { - return oauth2.Token{}, err - } - equal, err = userpassword.Compare(string(dbCode.HashedSecret), code.secret) - if err != nil { - return oauth2.Token{}, xerrors.Errorf("unable to compare code: %w", err) - } - if !equal { - return oauth2.Token{}, errBadCode - } - - // Ensure the code has not expired. - if dbCode.ExpiresAt.Before(dbtime.Now()) { - return oauth2.Token{}, errBadCode - } - - // Verify PKCE challenge if present - if dbCode.CodeChallenge.Valid && dbCode.CodeChallenge.String != "" { - if params.codeVerifier == "" { - return oauth2.Token{}, errInvalidPKCE - } - if !VerifyPKCE(dbCode.CodeChallenge.String, params.codeVerifier) { - return oauth2.Token{}, errInvalidPKCE - } - } - - // Verify resource parameter consistency (RFC 8707) - if dbCode.ResourceUri.Valid && dbCode.ResourceUri.String != "" { - // Resource was specified during authorization - it must match in token request - if params.resource == "" { - return oauth2.Token{}, errInvalidResource - } - if params.resource != dbCode.ResourceUri.String { - return oauth2.Token{}, errInvalidResource - } - } else if params.resource != "" { - // Resource was not specified during authorization but is now provided - return oauth2.Token{}, errInvalidResource - } - - // Generate a refresh token. - refreshToken, err := GenerateSecret() - if err != nil { - return oauth2.Token{}, err - } - - // Generate the API key we will swap for the code. - // TODO: We are ignoring scopes for now. - tokenName := fmt.Sprintf("%s_%s_oauth_session_token", dbCode.UserID, app.ID) - key, sessionToken, err := apikey.Generate(apikey.CreateParams{ - UserID: dbCode.UserID, - LoginType: database.LoginTypeOAuth2ProviderApp, - DefaultLifetime: lifetimes.DefaultDuration.Value(), - // For now, we allow only one token per app and user at a time. - TokenName: tokenName, - }) - if err != nil { - return oauth2.Token{}, err - } - - // Grab the user roles so we can perform the exchange as the user. - actor, _, err := httpmw.UserRBACSubject(ctx, db, dbCode.UserID, rbac.ScopeAll) - if err != nil { - return oauth2.Token{}, xerrors.Errorf("fetch user actor: %w", err) - } - - // Do the actual token exchange in the database. - err = db.InTx(func(tx database.Store) error { - ctx := dbauthz.As(ctx, actor) - err = tx.DeleteOAuth2ProviderAppCodeByID(ctx, dbCode.ID) - if err != nil { - return xerrors.Errorf("delete oauth2 app code: %w", err) - } - - // Delete the previous key, if any. - prevKey, err := tx.GetAPIKeyByName(ctx, database.GetAPIKeyByNameParams{ - UserID: dbCode.UserID, - TokenName: tokenName, - }) - if err == nil { - err = tx.DeleteAPIKeyByID(ctx, prevKey.ID) - } - if err != nil && !errors.Is(err, sql.ErrNoRows) { - return xerrors.Errorf("delete api key by name: %w", err) - } - - newKey, err := tx.InsertAPIKey(ctx, key) - if err != nil { - return xerrors.Errorf("insert oauth2 access token: %w", err) - } - - _, err = tx.InsertOAuth2ProviderAppToken(ctx, database.InsertOAuth2ProviderAppTokenParams{ - ID: uuid.New(), - CreatedAt: dbtime.Now(), - ExpiresAt: key.ExpiresAt, - HashPrefix: []byte(refreshToken.Prefix), - RefreshHash: []byte(refreshToken.Hashed), - AppSecretID: dbSecret.ID, - APIKeyID: newKey.ID, - UserID: dbCode.UserID, - Audience: dbCode.ResourceUri, - }) - if err != nil { - return xerrors.Errorf("insert oauth2 refresh token: %w", err) - } - return nil - }, nil) - if err != nil { - return oauth2.Token{}, err - } - - return oauth2.Token{ - AccessToken: sessionToken, - TokenType: "Bearer", - RefreshToken: refreshToken.Formatted, - Expiry: key.ExpiresAt, - ExpiresIn: int64(time.Until(key.ExpiresAt).Seconds()), - }, nil -} - -func refreshTokenGrant(ctx context.Context, db database.Store, app database.OAuth2ProviderApp, lifetimes codersdk.SessionLifetime, params tokenParams) (oauth2.Token, error) { - // Validate the token. - token, err := parseFormattedSecret(params.refreshToken) - if err != nil { - return oauth2.Token{}, errBadToken - } - //nolint:gocritic // There is no user yet so we must use the system. - dbToken, err := db.GetOAuth2ProviderAppTokenByPrefix(dbauthz.AsSystemRestricted(ctx), []byte(token.prefix)) - if errors.Is(err, sql.ErrNoRows) { - return oauth2.Token{}, errBadToken - } - if err != nil { - return oauth2.Token{}, err - } - equal, err := userpassword.Compare(string(dbToken.RefreshHash), token.secret) - if err != nil { - return oauth2.Token{}, xerrors.Errorf("unable to compare token: %w", err) - } - if !equal { - return oauth2.Token{}, errBadToken - } - - // Ensure the token has not expired. - if dbToken.ExpiresAt.Before(dbtime.Now()) { - return oauth2.Token{}, errBadToken - } - - // Verify resource parameter consistency for refresh tokens (RFC 8707) - if params.resource != "" { - // If resource is provided in refresh request, it must match the original token's audience - if !dbToken.Audience.Valid || dbToken.Audience.String != params.resource { - return oauth2.Token{}, errInvalidResource - } - } - - // Grab the user roles so we can perform the refresh as the user. - //nolint:gocritic // There is no user yet so we must use the system. - prevKey, err := db.GetAPIKeyByID(dbauthz.AsSystemRestricted(ctx), dbToken.APIKeyID) - if err != nil { - return oauth2.Token{}, err - } - - actor, _, err := httpmw.UserRBACSubject(ctx, db, prevKey.UserID, rbac.ScopeAll) - if err != nil { - return oauth2.Token{}, xerrors.Errorf("fetch user actor: %w", err) - } - - // Generate a new refresh token. - refreshToken, err := GenerateSecret() - if err != nil { - return oauth2.Token{}, err - } - - // Generate the new API key. - // TODO: We are ignoring scopes for now. - tokenName := fmt.Sprintf("%s_%s_oauth_session_token", prevKey.UserID, app.ID) - key, sessionToken, err := apikey.Generate(apikey.CreateParams{ - UserID: prevKey.UserID, - LoginType: database.LoginTypeOAuth2ProviderApp, - DefaultLifetime: lifetimes.DefaultDuration.Value(), - // For now, we allow only one token per app and user at a time. - TokenName: tokenName, - }) - if err != nil { - return oauth2.Token{}, err - } - - // Replace the token. - err = db.InTx(func(tx database.Store) error { - ctx := dbauthz.As(ctx, actor) - err = tx.DeleteAPIKeyByID(ctx, prevKey.ID) // This cascades to the token. - if err != nil { - return xerrors.Errorf("delete oauth2 app token: %w", err) - } - - newKey, err := tx.InsertAPIKey(ctx, key) - if err != nil { - return xerrors.Errorf("insert oauth2 access token: %w", err) - } - - _, err = tx.InsertOAuth2ProviderAppToken(ctx, database.InsertOAuth2ProviderAppTokenParams{ - ID: uuid.New(), - CreatedAt: dbtime.Now(), - ExpiresAt: key.ExpiresAt, - HashPrefix: []byte(refreshToken.Prefix), - RefreshHash: []byte(refreshToken.Hashed), - AppSecretID: dbToken.AppSecretID, - APIKeyID: newKey.ID, - UserID: dbToken.UserID, - Audience: dbToken.Audience, - }) - if err != nil { - return xerrors.Errorf("insert oauth2 refresh token: %w", err) - } - return nil - }, nil) - if err != nil { - return oauth2.Token{}, err - } - - return oauth2.Token{ - AccessToken: sessionToken, - TokenType: "Bearer", - RefreshToken: refreshToken.Formatted, - Expiry: key.ExpiresAt, - ExpiresIn: int64(time.Until(key.ExpiresAt).Seconds()), - }, nil -} - -// validateResourceParameter validates that a resource parameter conforms to RFC 8707: -// must be an absolute URI without fragment component. -func validateResourceParameter(resource string) error { - if resource == "" { - return nil // Resource parameter is optional - } - - u, err := url.Parse(resource) - if err != nil { - return xerrors.Errorf("invalid URI syntax: %w", err) - } - - if u.Scheme == "" { - return xerrors.New("must be an absolute URI with scheme") - } - - if u.Fragment != "" { - return xerrors.New("must not contain fragment component") - } - - return nil -} From beb1d94dd7767750162f96d44c37b32d08d085dc Mon Sep 17 00:00:00 2001 From: Steven Masley Date: Fri, 22 Aug 2025 09:10:01 -0500 Subject: [PATCH 3/5] Add revocation and introspection endpoints --- coderd/coderd.go | 2 ++ coderd/fositeprovider/introspect.go | 21 +++++++++++++++++++++ coderd/fositeprovider/revoke.go | 14 ++++++++++++++ coderd/oauth2.go | 4 ++-- 4 files changed, 39 insertions(+), 2 deletions(-) create mode 100644 coderd/fositeprovider/introspect.go create mode 100644 coderd/fositeprovider/revoke.go diff --git a/coderd/coderd.go b/coderd/coderd.go index 08c48aa31cbad..a60a685ee588b 100644 --- a/coderd/coderd.go +++ b/coderd/coderd.go @@ -953,6 +953,8 @@ func New(options *Options) *API { r.Get("/authorize", api.OAuth2Provider.ShowAuthorizationPage(api.AccessURL)) r.Post("/authorize", api.OAuth2Provider.AuthEndpoint) }) + r.Get("/introspect", api.OAuth2Provider.IntrospectionEndpoint) + r.Post("/revoke", api.OAuth2Provider.RevokeEndpoint) r.Post("/tokens", api.OAuth2Provider.TokenEndpoint) }) diff --git a/coderd/fositeprovider/introspect.go b/coderd/fositeprovider/introspect.go new file mode 100644 index 0000000000000..48f99f172b05f --- /dev/null +++ b/coderd/fositeprovider/introspect.go @@ -0,0 +1,21 @@ +package fositeprovider + +import ( + "net/http" + + "cdr.dev/slog" + "github.com/coder/coder/v2/coderd/database" +) + +func (p *Provider) IntrospectionEndpoint(rw http.ResponseWriter, req *http.Request) { + ctx := req.Context() + mySessionData := p.newSession(database.APIKey{}) + ir, err := p.provider.NewIntrospectionRequest(ctx, req, mySessionData) + if err != nil { + p.logger.Error(ctx, "error occurred in NewIntrospectionRequest", slog.Error(err)) + p.provider.WriteIntrospectionError(ctx, rw, err) + return + } + + p.provider.WriteIntrospectionResponse(ctx, rw, ir) +} diff --git a/coderd/fositeprovider/revoke.go b/coderd/fositeprovider/revoke.go new file mode 100644 index 0000000000000..1c6574fb6e50f --- /dev/null +++ b/coderd/fositeprovider/revoke.go @@ -0,0 +1,14 @@ +package fositeprovider + +import "net/http" + +func (p *Provider) RevokeEndpoint(rw http.ResponseWriter, r *http.Request) { + // This context will be passed to all methods. + ctx := r.Context() + + // This will accept the token revocation request and validate various parameters. + err := p.provider.NewRevocationRequest(ctx, r) + + // All done, send the response. + p.provider.WriteRevocationResponse(ctx, rw, err) +} diff --git a/coderd/oauth2.go b/coderd/oauth2.go index f9b8be5bcbff4..c42b1edacf17c 100644 --- a/coderd/oauth2.go +++ b/coderd/oauth2.go @@ -116,7 +116,7 @@ func (api *API) deleteOAuth2ProviderAppSecret() http.HandlerFunc { // @Success 200 "Returns HTML authorization page" // @Router /oauth2/authorize [get] func (api *API) getOAuth2ProviderAppAuthorize() http.HandlerFunc { - return api.OAuth2Provider.AuthEndpoint + return api.OAuth2Provider.ShowAuthorizationPage(api.AccessURL) } // @Summary OAuth2 authorization request (POST - process authorization). @@ -131,7 +131,7 @@ func (api *API) getOAuth2ProviderAppAuthorize() http.HandlerFunc { // @Success 302 "Returns redirect with authorization code" // @Router /oauth2/authorize [post] func (api *API) postOAuth2ProviderAppAuthorize() http.HandlerFunc { - return api.OAuth2Provider.ShowAuthorizationPage(api.AccessURL) + return api.OAuth2Provider.AuthEndpoint } // @Summary OAuth2 token exchange. From d51fd08cbbbffceb898831c9ca89e252acfeea87 Mon Sep 17 00:00:00 2001 From: Steven Masley Date: Fri, 22 Aug 2025 13:36:22 -0500 Subject: [PATCH 4/5] begin making sql storage --- .../fositeprovider/fositestorage/storage.go | 79 ++++++++++++++++++- coderd/fositeprovider/introspect.go | 3 +- coderd/fositeprovider/provider.go | 16 ++++ coderd/fositeprovider/token.go | 4 +- 4 files changed, 93 insertions(+), 9 deletions(-) diff --git a/coderd/fositeprovider/fositestorage/storage.go b/coderd/fositeprovider/fositestorage/storage.go index 114247ec89110..0a3d573a63959 100644 --- a/coderd/fositeprovider/fositestorage/storage.go +++ b/coderd/fositeprovider/fositestorage/storage.go @@ -8,11 +8,11 @@ import ( "github.com/google/uuid" "github.com/ory/fosite" "github.com/ory/fosite/handler/oauth2" - "github.com/ory/fosite/storage" "golang.org/x/xerrors" "github.com/coder/coder/v2/coderd/database" "github.com/coder/coder/v2/coderd/database/dbauthz" + "github.com/coder/coder/v2/coderd/database/dbtime" ) type fositeStorage interface { @@ -26,16 +26,87 @@ type Storage struct { db database.Store // TODO: Remove the memory store entirely and implement all methods to use the database. - storage.MemoryStore + //storage.MemoryStore } func New(db database.Store) *Storage { return &Storage{ - db: db, - MemoryStore: *storage.NewMemoryStore(), + db: db, + //MemoryStore: *storage.NewMemoryStore(), } } +func (s Storage) CreateAuthorizeCodeSession(ctx context.Context, code string, request fosite.Requester) error { + _, err := s.db.InsertOAuth2ProviderAppCode(ctx, database.InsertOAuth2ProviderAppCodeParams{ + ID: uuid.New(), + CreatedAt: dbtime.Now(), + // TODO: Configurable expiration? Ten minutes matches GitHub. + // This timeout is only for the code that will be exchanged for the + // access token, not the access token itself. It does not need to be + // long-lived because normally it will be exchanged immediately after it + // is received. If the application does wait before exchanging the + // token (for example suppose they ask the user to confirm and the user + // has left) then they can just retry immediately and get a new code. + ExpiresAt: dbtime.Now().Add(time.Duration(10) * time.Minute), + SecretPrefix: nil, + HashedSecret: nil, + AppID: uuid.UUID{}, + UserID: uuid.UUID{}, + ResourceUri: sql.NullString{}, + CodeChallenge: sql.NullString{}, + CodeChallengeMethod: sql.NullString{}, + }) + if err != nil { + return xerrors.Errorf("insert oauth code: %w", err) + } + return nil +} + +func (s Storage) GetAuthorizeCodeSession(ctx context.Context, code string, session fosite.Session) (request fosite.Requester, err error) { + //TODO implement me + panic("implement me") +} + +func (s Storage) InvalidateAuthorizeCodeSession(ctx context.Context, code string) (err error) { + //TODO implement me + panic("implement me") +} + +func (s Storage) CreateAccessTokenSession(ctx context.Context, signature string, request fosite.Requester) (err error) { + //TODO implement me + panic("implement me") +} + +func (s Storage) GetAccessTokenSession(ctx context.Context, signature string, session fosite.Session) (request fosite.Requester, err error) { + //TODO implement me + panic("implement me") +} + +func (s Storage) DeleteAccessTokenSession(ctx context.Context, signature string) (err error) { + //TODO implement me + panic("implement me") +} + +func (s Storage) CreateRefreshTokenSession(ctx context.Context, signature string, accessSignature string, request fosite.Requester) (err error) { + //TODO implement me + panic("implement me") +} + +func (s Storage) GetRefreshTokenSession(ctx context.Context, signature string, session fosite.Session) (request fosite.Requester, err error) { + //TODO implement me + panic("implement me") +} + +func (s Storage) DeleteRefreshTokenSession(ctx context.Context, signature string) (err error) { + //TODO implement me + panic("implement me") +} + +func (s Storage) RotateRefreshToken(ctx context.Context, requestID string, refreshTokenSignature string) (err error) { + //TODO implement me + panic("implement me") +} + func (s Storage) GetClient(ctx context.Context, id string) (fosite.Client, error) { uid, err := uuid.Parse(id) if err != nil { diff --git a/coderd/fositeprovider/introspect.go b/coderd/fositeprovider/introspect.go index 48f99f172b05f..795167220c9da 100644 --- a/coderd/fositeprovider/introspect.go +++ b/coderd/fositeprovider/introspect.go @@ -4,12 +4,11 @@ import ( "net/http" "cdr.dev/slog" - "github.com/coder/coder/v2/coderd/database" ) func (p *Provider) IntrospectionEndpoint(rw http.ResponseWriter, req *http.Request) { ctx := req.Context() - mySessionData := p.newSession(database.APIKey{}) + mySessionData := p.EmptySession() ir, err := p.provider.NewIntrospectionRequest(ctx, req, mySessionData) if err != nil { p.logger.Error(ctx, "error occurred in NewIntrospectionRequest", slog.Error(err)) diff --git a/coderd/fositeprovider/provider.go b/coderd/fositeprovider/provider.go index 194da9e925bcd..26d7f805e6bb3 100644 --- a/coderd/fositeprovider/provider.go +++ b/coderd/fositeprovider/provider.go @@ -82,3 +82,19 @@ func (p *Provider) newSession(key database.APIKey) *openid.DefaultSession { }, } } + +func (p *Provider) EmptySession() *openid.DefaultSession { + return &openid.DefaultSession{ + Claims: &jwt.IDTokenClaims{ + Issuer: "https://fosite.my-application.com", + Audience: []string{"https://my-client.my-application.com"}, + ExpiresAt: time.Now().Add(time.Hour * 6), + IssuedAt: time.Now(), + RequestedAt: time.Now(), + AuthTime: time.Now(), + }, + Headers: &jwt.Headers{ + Extra: make(map[string]interface{}), + }, + } +} diff --git a/coderd/fositeprovider/token.go b/coderd/fositeprovider/token.go index c2c786ea35b6f..fa43bcdfa787d 100644 --- a/coderd/fositeprovider/token.go +++ b/coderd/fositeprovider/token.go @@ -3,8 +3,6 @@ package fositeprovider import ( "log" "net/http" - - "github.com/coder/coder/v2/coderd/database" ) // TODO: Not sure how TokenEndpoint works with sessions. @@ -14,7 +12,7 @@ func (p *Provider) TokenEndpoint(rw http.ResponseWriter, req *http.Request) { // Create an empty session object which will be passed to the request handlers // TODO: Why do we need an empty session here? - mySessionData := p.newSession(database.APIKey{}) + mySessionData := p.EmptySession() // This will create an access request object and iterate through the registered TokenEndpointHandlers to validate the request. accessRequest, err := p.provider.NewAccessRequest(ctx, req, mySessionData) From e11c8a7375c66edf96416f2c4a514a8a67050b99 Mon Sep 17 00:00:00 2001 From: Steven Masley Date: Mon, 25 Aug 2025 09:59:04 -0500 Subject: [PATCH 5/5] ad comment --- coderd/fositeprovider/fositestorage/storage.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/coderd/fositeprovider/fositestorage/storage.go b/coderd/fositeprovider/fositestorage/storage.go index 0a3d573a63959..51cb334294a35 100644 --- a/coderd/fositeprovider/fositestorage/storage.go +++ b/coderd/fositeprovider/fositestorage/storage.go @@ -18,6 +18,8 @@ import ( type fositeStorage interface { fosite.ClientManager oauth2.CoreStorage + // TODO: Add support for database transactions. + //storage.Transactional } var _ fositeStorage = (*Storage)(nil)