Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
190 changes: 190 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,190 @@
# Claude Development Guide for River Guide

This file contains project-specific instructions for Claude when working on River Guide.

## Code Quality and Linting

### Running Linters

Always run linting before committing changes:

```bash
# Run golangci-lint with auto-fix
golangci-lint run --fix

# Build and test
go build ./...
go test ./...
```

### Key Linting Rules

- Use `golangci-lint run --fix` to automatically fix most issues
- Replace string concatenation with `+=` operator: `path += "/"` not `path = path + "/"`
- Use `http.NoBody` instead of `nil` for HTTP request bodies
- Define constants for magic numbers (session timeouts, key sizes, etc.)
- Use proper context key types (custom type, not string)
- Mark unused parameters with `_` prefix
- Follow struct field alignment recommendations
- Always run `npx prettier --write` on modified files

### Testing Requirements

- Always run tests after changes: `go test ./...`
- Add test coverage for new functionality
- Use proper mocking and table-driven tests
- Test both success and error scenarios

## OIDC Implementation

### Security Principles

- Store minimal session data to avoid cookie size limits
- Clear invalid sessions gracefully - redirect to login, don't show technical errors
- Use cryptographically secure random keys
- Validate all configuration parameters together
- Log detailed errors server-side but show friendly messages to users

### Session Management

- Session cookies should be HttpOnly, Secure (for HTTPS), SameSite=Lax
- Store only: `user_groups`, `token_expiry`, `authenticated` flag, and individual claim keys
- Never store full ID tokens in sessions (too large)
- Store claims as individual session keys (e.g., `user_claim_sub`, `user_claim_email`) to avoid gob serialization issues with maps
- Clear corrupted sessions and redirect to login

### Configuration

- All OIDC params (issuer, client-id, client-secret, redirect-url) must be provided together
- Scopes are configurable with sensible defaults
- Only request "groups" scope if group filtering is configured
- Validate redirect URL format for cookie security settings

## Error Handling Patterns

### Session Errors

Use the `clearSessionAndRedirectToLogin()` helper for any session-related errors:

```go
if err != nil {
clearSessionAndRedirectToLogin(w, r, fmt.Sprintf("Handler: session error: %v", err))
return
}
```

### User-Friendly Messages

- Log technical details server-side
- Show user-friendly messages in browser
- For OIDC errors, display provider error messages when available
- Clear invalid state gracefully without exposing internals

## Logging

### User-Aware Logging

The application includes user identification in request logs:

- Anonymous: `"GET / -> 200 OK in 5ms"`
- Authenticated: `"GET /toggle user=john.doe@company.com -> 200 OK in 12ms"`

### Context Usage

Add user info to request context for logging:

```go
userSubject, _ := session.Values["user_subject"].(string)
ctx := context.WithValue(r.Context(), userSubjectKey, userSubject)
next.ServeHTTP(w, r.WithContext(ctx))
```

## Development Workflow

### Before Committing

1. Run `golangci-lint run --fix` to fix code quality issues
2. Run `go build ./...` to ensure compilation
3. Run `go test ./...` to ensure tests pass
4. Check that changes work with both AWS and Azure providers
5. Test OIDC flows if authentication code was modified

### Git Practices

- Write descriptive commit messages
- Include "Co-Authored-By: Claude <noreply@anthropic.com>" in commits (though the user's config overrides this)
- Group related changes into single commits
- Test functionality before pushing

## Architecture Notes

### Provider Pattern

- CloudProvider interface supports AWS and Azure
- Each provider handles its own authentication and resource management
- RDS support is optional and AWS-specific

### Middleware Stack

1. Negroni recovery middleware
2. Static file serving
3. UserAwareLogger (custom request logging)
4. AuthMiddleware (OIDC authentication)
5. Router (gorilla/mux)

### Security Considerations

- Session keys are generated using crypto/rand
- Cookie security flags are set based on redirect URL scheme
- Group-based authorization is enforced after authentication
- All session errors result in clean logout and redirect to login

## Common Patterns

### Constants

Define constants for magic numbers:

```go
const (
sessionKeySize = 32
sessionMaxAge = 86400 // 24 hours
)
```

### Context Keys

Use typed context keys:

```go
type contextKey string
const userSubjectKey contextKey = "user_subject"
```

### Error Handling

Always handle errors gracefully and provide user-friendly messages while logging technical details.

### Session Storage

Avoid storing maps in sessions due to gob serialization issues. Store complex data as individual keys:

```go
// Don't do this (gob serialization issues)
session.Values["user_claims"] = map[string]string{"sub": "user123", "email": "user@example.com"}

// Do this instead
session.Values["user_claim_sub"] = "user123"
session.Values["user_claim_email"] = "user@example.com"

// Reconstruct when needed
claims := make(map[string]string)
for key, value := range session.Values {
if strings.HasPrefix(key, "user_claim_") {
claimName := strings.TrimPrefix(key, "user_claim_")
if claimValue, ok := value.(string); ok {
claims[claimName] = claimValue
}
}
}
```
44 changes: 44 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,13 @@ The application accepts several flags:
- `--primary-color`: primary color for text (default is "#333").
- `--favicon`: path to favicon (default is embedded favicon).
- `--rds`: enable support to control RDS instances (default is `false`).
- `--oidc-issuer`: OIDC issuer URL (optional)
- `--oidc-client-id`: OIDC client ID (optional)
- `--oidc-client-secret`: OIDC client secret (optional)
- `--oidc-redirect-url`: OIDC redirect URL (optional)
- `--oidc-groups`: comma-separated list of allowed OIDC groups (optional)
- `--oidc-scopes`: comma-separated list of OIDC scopes to request (optional, defaults to "openid,profile,email" plus "groups" if --oidc-groups is set)
- `--oidc-log-claims`: comma-separated list of OIDC claims to include in request logs (optional, defaults to "sub")

### Configuration file

Expand Down Expand Up @@ -174,6 +181,43 @@ instance, to set the title, you could use the following command:
export RIVER_GUIDE_TITLE="My Custom Title"
```

### Optional OIDC login

River Guide can optionally protect the UI with an OIDC login. Set the following flags (or their configuration equivalents) to enable authentication:

- `--oidc-issuer`: the OIDC issuer URL
- `--oidc-client-id`: the client ID registered with the issuer
- `--oidc-client-secret`: the client secret for the client ID
- `--oidc-redirect-url`: redirect URL configured for the client
- `--oidc-groups`: comma-separated list of groups allowed to access the UI (optional)
- `--oidc-scopes`: comma-separated list of OIDC scopes to request (optional)
- `--oidc-log-claims`: comma-separated list of OIDC claims to include in request logs (optional)

All four of the issuer, client ID, client secret, and redirect URL must be provided for authentication to be enabled. The redirect URL must exactly match the value configured for your OIDC client.

If `--oidc-groups` is omitted, users from any group are allowed. If `--oidc-scopes` is omitted, the default scopes are "openid,profile,email" (plus "groups" if --oidc-groups is set). You can override the scopes entirely by providing custom values. If `--oidc-log-claims` is omitted, only the "sub" (subject) claim is logged with requests.

Example YAML configuration:

```yaml
oidc-issuer: https://auth.example.com
oidc-client-id: my-app
oidc-client-secret: super-secret
oidc-redirect-url: https://my-app.example.com/callback
oidc-groups:
- admins
- operators
# Optional: override default scopes
oidc-scopes:
- openid
- profile
- email
# Optional: customize claims shown in logs (defaults to sub)
oidc-log-claims:
- email
- name
```

## API

The application provides the following endpoints:
Expand Down
30 changes: 30 additions & 0 deletions cmd/assets/landing.gohtml
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
<!DOCTYPE html>
<html>
<head>
<link href="https://fonts.googleapis.com/css2?family=IBM+Plex+Sans:wght@400;700&display=swap" rel="stylesheet">
<title>{{ .Title }}</title>
<style>
body {
font-family: 'IBM Plex Sans', sans-serif;
background-color: #f1f1f1;
color: {{ .PrimaryColor }};
text-align: center;
padding-top: 100px;
}
a.button {
display: inline-block;
padding: 10px 20px;
background-color: {{ .PrimaryColor }};
color: #fff;
text-decoration: none;
border-radius: 4px;
font-weight: bold;
}
</style>
</head>
<body>
<h1>{{ .Title }}</h1>
<p>You need to log in to continue.</p>
<a href="{{ .LoginPath }}" class="button">Log in</a>
</body>
</html>
Loading
Loading