refactor(server): slim the Server and dedupe issuer-URL helpers - #4929
Merged
Conversation
Follow-up cleanup after the handler-extraction refactor: - oauth2.IssuerURL: a small url.URL wrapper with AbsPath/AbsURL. Handlers hold it instead of a bare url.URL, so the four copies of absPath and three of absURL scattered across authflow, consent, mfa, logout and Server are gone in favor of one implementation. - Drop Server.OpenConnector and Server.CloseConnector. Both were thin pass-throughs to the connector cache; CloseConnector had no callers left after the cache was extracted (the gRPC API invalidates it directly), and OpenConnector's only users now call s.connectors.Open. - Build the discovery handler once and store it, instead of reconstructing it from scattered Server fields on every call. The mounted HTTP route and the gRPC API's ConstructDiscovery serve the same handler, and the response-types/grant-types/pkce fields no longer need to live on Server. Signed-off-by: maksim.nabokikh <max.nabokih@gmail.com>
Contributor
There was a problem hiding this comment.
Pull request overview
Refactors the server package to reduce Server surface area and centralize issuer-relative URL/path construction, while keeping discovery generation consistent across HTTP and gRPC.
Changes:
- Introduces
server/oauth2.IssuerURL(wrappingurl.URL) to provide sharedAbsPath/AbsURLhelpers used by handlers and tests. - Removes unused
Server.OpenConnector/Server.CloseConnectormethods and updates call sites to use the connector cache directly. - Builds the OIDC discovery handler once during server construction and reuses it for both HTTP routing and gRPC discovery construction.
Reviewed changes
Copilot reviewed 25 out of 25 changed files in this pull request and generated no comments.
Show a summary per file
| File | Description |
|---|---|
| server/session/session.go | Switches session manager issuer field to oauth2.IssuerURL. |
| server/server.go | Stores issuer as oauth2.IssuerURL, removes dead connector methods, and builds/caches discovery handler once. |
| server/server_test.go | Updates tests to use IssuerURL.AbsPath/AbsURL and validates grants via srv.discovery. |
| server/server_oauth2_test.go | Updates server test setup to wrap issuer URL and pass raw url.URL to token issuer. |
| server/server_login_test.go | Updates tests to open connectors via s.connectors.Open. |
| server/server_introspection_test.go | Updates introspection helpers to use the embedded url.URL where needed. |
| server/server_grant_password_test.go | Updates tests to open connectors via s.connectors.Open. |
| server/server_authorize_test.go | Updates tests to open connectors via s.connectors.Open. |
| server/oauth2/issuer.go | Adds IssuerURL helper type with AbsPath/AbsURL. |
| server/mfa/handler.go | Replaces per-handler absPath helper with IssuerURL.AbsPath. |
| server/mfa/handler_test.go | Updates MFA handler tests to construct oauth2.IssuerURL. |
| server/logout/logout.go | Replaces per-handler absURL helper with IssuerURL.AbsURL. |
| server/home/home.go | Uses IssuerURL.AbsURL for logout link construction. |
| server/device/device.go | Uses IssuerURL.AbsPath for device verification and callback path building. |
| server/device/device_test.go | Updates device handler tests to construct oauth2.IssuerURL. |
| server/consent/consent.go | Replaces per-handler absPath helper with IssuerURL.AbsPath. |
| server/authflow/urls.go | Switches flow redirect construction to IssuerURL.AbsPath. |
| server/authflow/sessionlogin_test.go | Updates session test server setup to use oauth2.IssuerURL for handlers and sessions. |
| server/authflow/request.go | Updates device callback redirect handling to use IssuerURL.AbsPath. |
| server/authflow/request_test.go | Updates authflow handler setup to construct oauth2.IssuerURL. |
| server/authflow/render.go | Removes per-handler absPath/absURL helpers in favor of IssuerURL. |
| server/authflow/login.go | Updates connector/login URL construction to use IssuerURL.AbsPath/AbsURL. |
| server/authflow/handler.go | Changes handler issuer field type to oauth2.IssuerURL. |
| server/authflow/handler_test.go | Updates flow assembly tests to construct oauth2.IssuerURL for all handlers. |
| server/authflow/authorize.go | Updates connector selection redirects to use IssuerURL.AbsPath. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
… fields, make routeMux concrete Follow-ups on the same theme: - The gRPC API took a func(ctx) Document that called back into the Server. Since it just needs the discovery handler, NewAPI now takes *discovery.Handler directly (symmetric with the *connectors.Cache it already holds) and calls Construct itself. Server.ConstructDiscovery becomes a plain Discovery() accessor. - Demote six construction-only fields (alwaysShowLogin, passwordConnector, auth/deviceRequestsValidFor, mfaProviders, defaultMFAChain) to locals / direct config reads; they only ever fed a handler at wiring time. Server is down to the fields actually read after construction plus the few in-package tests reach into. - routeMux was a bundle of four closures with four delegating methods over a fifth header-wrapping closure. It is now a concrete router.Mux: the path prefixing, header/context wrapping, and CORS live in its methods, and the direct routes (/healthz, /static, /robots.txt) go through it too. Signed-off-by: maksim.nabokikh <max.nabokih@gmail.com>
…e fields The Server struct kept several construction-only fields alive only because in-package tests reached into them. Rewire those tests so the fields can go: - oauth2_test built a throwaway Server just to get a token issuer; build the tokens.Issuer directly instead. - introspection_test read s.refreshTokenPolicy; it already has the policy as a local, so use that. - the multi-connector test helper set s.skipApproval after construction; set SkipApprovalScreen in its config like the others do. With those readers gone, drop signer, now, idTokensValidFor, refreshTokenPolicy and skipApproval from the struct — they only fed handlers at construction and are now locals / direct config reads. Server is down from 20 fields to 9: the six read after construction (connectors, storage, mux, templates, logger, discovery) plus issuer, issuerURL and sessionConfig, which the tests still use as fixture handles to mint tokens, build URLs and session cookies. Signed-off-by: maksim.nabokikh <max.nabokih@gmail.com>
Contributor
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 28 out of 28 changed files in this pull request and generated 1 comment.
Comments suppressed due to low confidence (2)
server/server.go:219
- The routeMux doc comment says every route is "wrapped" with common headers/context/instrumentation, but HandlePrefix mounts prefix handlers without calling wrap (so it only prefixes/strips). This makes the comment misleading for static/theme prefix routes.
// routeMux implements router.Mux over a gorilla mux.Router: every route is
// prefixed with the issuer path and wrapped with the common response headers,
// request-id/real-ip context, and instrumentation; HandleCORS additionally adds
// CORS for the public endpoints. instrument and realIP are the server's
// (prometheus- and config-bound) closures.
server/server.go:398
- This comment still references the removed ConstructDiscovery method (it now passes the shared discovery.Handler to the gRPC API). Updating it avoids confusion about how discovery is served over gRPC.
// Build the discovery handler once from config; both the mounted HTTP route
// and the gRPC API's ConstructDiscovery serve this same handler.
Address review + lint: - The router package held only interfaces while the sole implementation lived in server. Move it there as router.New(router.Config) so the package owns both the contract and its default implementation; server just wires config into it. Handlers are unchanged (still Mount(router.Mux)). - Fix the routeMux doc: HandlePrefix mounts static trees directly, it does not apply the header/instrumentation wrap (unchanged behavior). - Update the discovery comment that still named the removed ConstructDiscovery. - GetDiscovery returns an error instead of panicking when no discovery handler is configured (some tests pass nil). - Drop the stale rate-limit TODO. - gci import grouping. Signed-off-by: maksim.nabokikh <max.nabokih@gmail.com>
Signed-off-by: maksim.nabokikh <max.nabokih@gmail.com>
…resolver from Server Extract the connector registry (ConnectorsConfig, openConnector), the local password DB, and the resolver into server/connector.go. resolveConnector was a Server method reading s.storage/s.logger; it becomes connectorResolver(storage, logger) returning the connectors.ResolveFunc the cache already injects, so connector construction no longer depends on the Server. server.go sheds all the connector/* imports along with it. Signed-off-by: maksim.nabokikh <max.nabokih@gmail.com>
…tor map The connector registry, config parsing and the local password DB moved into the connectors package as connectors.Resolver(storage, logger, configs), which returns the ResolveFunc the cache already takes. The config map is injected by the caller, so the connectors package imports no connector implementation and a library consumer can supply its own set. server keeps only ConnectorsConfig (the built-in map) plus ConnectorConfig/LocalConnector aliases for compatibility. NewPasswordDB is exported so a custom resolver can reuse the local connector. Signed-off-by: maksim.nabokikh <max.nabokih@gmail.com>
…he server aliases Split passwordDB into connectors/password.go, leaving resolve.go with just the resolver and the config-map contract. Remove the server-package type/const aliases to connectors.ConnectorConfig / connectors.LocalConnector; cmd now references those symbols in the connectors package directly. server keeps the built-in ConnectorsConfig map (typed with connectors.ConnectorConfig) that it hands to connectors.Resolver. Signed-off-by: maksim.nabokikh <max.nabokih@gmail.com>
Contributor
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 33 out of 33 changed files in this pull request and generated 3 comments.
Comments suppressed due to low confidence (1)
server/router/router.go:75
- mountMux.wrap always calls Config.Instrument; if Instrument is nil, this will panic. Since router.New is exported and Config doesn’t document Instrument as required, it’s safer to fall back to calling the handler directly when Instrument isn’t provided.
m.c.Instrument(name, h)(w, r.WithContext(rCtx))
…ig directly Address review: router.wrap only calls RealIP when both RealIPHeader is set and a resolver was provided (a library caller could set one without the other); openConnector unmarshals conn.Config ([]byte) directly instead of round-tripping through string. Signed-off-by: maksim.nabokikh <max.nabokih@gmail.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Overview
Housekeeping on the
serverpackage after the handler-extraction refactor: theServerstruct had accumulated fields and methods that no longer earn their place, the issuer-URL path helpers were copy-pasted across every handler, and the gRPC API reached back into the Server for discovery.What this PR does / why we need it
oauth2.IssuerURLwrapsurl.URLwithAbsPath/AbsURL. Handlers hold it instead of a bareurl.URL, replacing four identicalabsPathand threeabsURLimplementations (authflow, consent, mfa, logout, andServer) with a single one.Server.CloseConnectorhad no callers left — the connector cache was extracted intoconnectors.Cache, and the gRPC API invalidates it directly (d.connectors.Close).Server.OpenConnectorwas a thin pass-through; its callers now uses.connectors.Open. Both removed.Serverfields on every call; it is now built once and stored.apiserver.NewAPItakes the*discovery.Handlerdirectly (symmetric with the*connectors.Cacheit already holds) instead of afunc(ctx) Documentthat called back into the Server, andServer.ConstructDiscoverybecomes a plainDiscovery()accessor.alwaysShowLogin,passwordConnector,auth/deviceRequestsValidFor,mfaProviders,defaultMFAChain) only ever fed a handler at wiring time and are now locals / direct config reads. Together with the discovery change, the struct drops from 20 fields to 14 — the ones actually read after construction, plus a few the in-package tests use.routeMux. It was a bundle of four closures with four delegating methods over a fifth header-wrapping closure. It is now a realrouter.Muximplementation: issuer-path prefixing, header/context wrapping, and CORS live in its methods, and the direct routes (/healthz,/static,/robots.txt) go through it too.No behavior change; existing tests cover the affected paths.
Special notes for your reviewer
Tests that constructed handlers with a raw
url.URLwere updated tooauth2.IssuerURL{...}.