From 2d724763e184b0453a862586400256718643d21f Mon Sep 17 00:00:00 2001 From: Christian Galo Date: Tue, 25 Feb 2025 19:02:27 -0600 Subject: [PATCH] Add registration handler and update routing for OIDC integration --- CLAUDE.md | 27 ++++++++++++++++++++++ internal/auth/auth.go | 53 ++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 79 insertions(+), 1 deletion(-) create mode 100644 CLAUDE.md diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..c0fa912 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,27 @@ +# CLAUDE.md - Member Console Guidelines + +## Build & Run Commands +- Build: `go build -o member-console` +- Run: `go run main.go start` +- Run with config: `go run main.go start --config path/to/config.yaml` +- Test: `go test ./...` +- Test specific: `go test ./internal/auth` +- Lint: `golangci-lint run` + +## Code Style Guidelines +- **Formatting**: Standard Go formatting with `gofmt` +- **Imports**: Group by standard lib, then external, then internal +- **Naming**: + - Packages: lowercase, single word + - Exported functions: PascalCase + - Internal functions: camelCase + - Variables: camelCase +- **Error Handling**: Check errors with `if err != nil`, use descriptive messages +- **Security**: Implement secure headers, CSRF protection, proper session management +- **Documentation**: Comment all exported functions and types +- **Configuration**: Use Viper with env vars (WCMC prefix) + +## Architecture +- Clean separation of concerns (middleware, auth, handlers) +- Follow Go project layout standards +- Protected routes should implement CSRF protection \ No newline at end of file diff --git a/internal/auth/auth.go b/internal/auth/auth.go index 4667db0..a9d80c3 100644 --- a/internal/auth/auth.go +++ b/internal/auth/auth.go @@ -72,6 +72,7 @@ func (c *Config) RegisterHandlers(mux *http.ServeMux) { mux.HandleFunc("/callback", c.CallbackHandler) mux.HandleFunc("/logout", c.LogoutHandler) mux.HandleFunc("/logout-callback", c.LogoutCallbackHandler) + mux.HandleFunc("/register", c.RegistrationHandler) } // Middleware returns an auth middleware function @@ -80,7 +81,8 @@ func (c *Config) Middleware() func(http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { // Skip authentication for public routes if r.URL.Path == "/login" || r.URL.Path == "/callback" || - r.URL.Path == "/logout" || r.URL.Path == "/logout-callback" { + r.URL.Path == "/logout" || r.URL.Path == "/logout-callback" || + r.URL.Path == "/register" { next.ServeHTTP(w, r) return } @@ -306,6 +308,55 @@ func (c *Config) LogoutCallbackHandler(w http.ResponseWriter, r *http.Request) { http.Redirect(w, r, "/login", http.StatusFound) } +// RegistrationHandler redirects to the OIDC registration page +func (c *Config) RegistrationHandler(w http.ResponseWriter, r *http.Request) { + // Generate random state, nonce, and code verifier for security + state := generateRandomString(32) + nonce := generateRandomString(32) + codeVerifier := generateRandomString(32) + hash := sha256.Sum256([]byte(codeVerifier)) + codeChallenge := base64.RawURLEncoding.EncodeToString(hash[:]) + + // Store state, nonce, and code verifier in session for verification + session, _ := c.Store.Get(r, c.SessionName) + session.Values["state"] = state + session.Values["nonce"] = nonce + session.Values["code_verifier"] = codeVerifier + if err := session.Save(r, w); err != nil { + log.Printf("Error saving session: %v", err) + http.Error(w, "Server error", http.StatusInternalServerError) + return + } + + // Build the registration URL using the specified registrations endpoint + baseURL := viper.GetString("issuer-url") + registrationURL, err := url.Parse(baseURL + "/protocol/openid-connect/registrations") + if err != nil { + log.Printf("Error parsing registration URL: %v", err) + http.Error(w, "Internal Server Error", http.StatusInternalServerError) + return + } + + // Add query parameters + q := registrationURL.Query() + q.Set("client_id", viper.GetString("client-id")) + q.Set("response_type", "code") + q.Set("scope", "openid email profile") + q.Set("redirect_uri", viper.GetString("hostname")+"/callback") + q.Set("state", state) + q.Set("nonce", nonce) + q.Set("code_challenge", codeChallenge) + q.Set("code_challenge_method", "S256") + + registrationURL.RawQuery = q.Encode() + + // Log for debugging + log.Printf("Redirecting to registration URL: %s", registrationURL.String()) + + // Redirect to registration page + http.Redirect(w, r, registrationURL.String(), http.StatusFound) +} + // Helper function to generate random strings func generateRandomString(n int) string { b := make([]byte, n)