Add AT Protocol OAuth 2.0 authentication for dashboard

- Implement full OAuth 2.0 with PKCE using haileyok/atproto-oauth-golang
- Backend For Frontend (BFF) pattern: tokens stored server-side only
- AES-256-GCM encrypted session cookies
- Auto token refresh when near expiry
- Restrict access to allowed handles (1440.news, wehrv.bsky.social)
- Add genkey utility for generating OAuth configuration
- Generic error messages to prevent handle enumeration
- Server-side logging of failed login attempts for security monitoring

New files:
- oauth.go: OAuth client wrapper and DID/handle resolution
- oauth_session.go: Session management with encrypted cookies
- oauth_middleware.go: RequireAuth middleware for route protection
- oauth_handlers.go: Login, callback, logout, metadata endpoints
- cmd/genkey/main.go: Generate OAuth secrets and JWK keypair
- oauth.env.example: Configuration template

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
primal
2026-01-30 15:16:51 -05:00
parent 1a2f6c15a9
commit 8192bce301
11 changed files with 1446 additions and 84 deletions
+45
View File
@@ -0,0 +1,45 @@
// genkey generates an ES256 JWK keypair for OAuth client authentication
package main
import (
"crypto/rand"
"encoding/hex"
"encoding/json"
"fmt"
"os"
"github.com/haileyok/atproto-oauth-golang/helpers"
)
func main() {
fmt.Println("Generating OAuth configuration...")
fmt.Println()
// Generate cookie secret
cookieSecret := make([]byte, 32)
if _, err := rand.Read(cookieSecret); err != nil {
fmt.Fprintf(os.Stderr, "Error generating cookie secret: %v\n", err)
os.Exit(1)
}
// Generate ES256 JWK
key, err := helpers.GenerateKey(nil)
if err != nil {
fmt.Fprintf(os.Stderr, "Error generating JWK: %v\n", err)
os.Exit(1)
}
// Marshal JWK to JSON
keyJSON, err := json.Marshal(key)
if err != nil {
fmt.Fprintf(os.Stderr, "Error marshaling JWK: %v\n", err)
os.Exit(1)
}
fmt.Println("Add these values to your oauth.env file:")
fmt.Println()
fmt.Printf("OAUTH_COOKIE_SECRET=%s\n", hex.EncodeToString(cookieSecret))
fmt.Printf("OAUTH_PRIVATE_JWK=%s\n", string(keyJSON))
fmt.Println()
fmt.Println("Keep these values secret!")
}