Files
gitea-nostr-auth/internal/handler/userinfo.go
mleku 896a7599a0 Release v0.0.1 - Initial OAuth2 server implementation
- Add Nostr OAuth2 server with NIP-98 authentication support
- Implement OAuth2 authorization and token endpoints
- Add .well-known/openid-configuration discovery endpoint
- Include Dockerfile for containerized deployment
- Add Claude Code release command for version management
- Create example configuration file

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-19 09:37:26 +01:00

93 lines
2.8 KiB
Go

package handler
import (
"encoding/json"
"log"
"net/http"
"strings"
"git.mleku.dev/mleku/gitea-nostr-auth/internal/nostr"
)
type UserInfoResponse struct {
Sub string `json:"sub"`
Name string `json:"name,omitempty"`
PreferredUsername string `json:"preferred_username"`
Email string `json:"email,omitempty"`
EmailVerified bool `json:"email_verified,omitempty"`
Picture string `json:"picture,omitempty"`
Profile string `json:"profile,omitempty"`
Website string `json:"website,omitempty"`
Error string `json:"error,omitempty"`
ErrorDesc string `json:"error_description,omitempty"`
}
func (h *Handler) UserInfo(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
// Extract Bearer token
authHeader := r.Header.Get("Authorization")
if !strings.HasPrefix(authHeader, "Bearer ") {
w.WriteHeader(http.StatusUnauthorized)
json.NewEncoder(w).Encode(UserInfoResponse{
Error: "invalid_token",
ErrorDesc: "missing or invalid Authorization header",
})
return
}
token := strings.TrimPrefix(authHeader, "Bearer ")
// Look up access token
accessToken, err := h.store.GetAccessToken(token)
if err != nil || accessToken == nil {
w.WriteHeader(http.StatusUnauthorized)
json.NewEncoder(w).Encode(UserInfoResponse{
Error: "invalid_token",
ErrorDesc: "token is invalid or expired",
})
return
}
pubkey := accessToken.Pubkey
npub := nostr.PubkeyToNpub(pubkey)
// Fetch profile from relays (this also fetches relay list first)
log.Printf("Fetching profile for %s from relays...", nostr.TruncateNpub(npub))
profile := h.fetcher.FetchProfile(r.Context(), pubkey)
// Build response with profile data or fallbacks
response := UserInfoResponse{
Sub: pubkey,
}
if profile != nil {
log.Printf("Got profile for %s: name=%s, nip05=%s", nostr.TruncateNpub(npub), profile.Name, profile.Nip05)
// Use profile data
response.Name = profile.GetDisplayName()
response.PreferredUsername = profile.GetUsername()
response.Picture = profile.Picture
response.Website = profile.Website
response.Profile = profile.About
// Use NIP-05 as email if available (it's verified in the Nostr sense)
if profile.Nip05 != "" {
// NIP-05 format: name@domain.com or _@domain.com
response.Email = profile.Nip05
response.EmailVerified = false // We haven't verified it ourselves
} else {
response.Email = nostr.GeneratePlaceholderEmail(pubkey)
}
} else {
log.Printf("No profile found for %s, using defaults", nostr.TruncateNpub(npub))
// Fallback to generated values
response.PreferredUsername = nostr.GenerateUsername(pubkey)
response.Name = nostr.TruncateNpub(npub)
response.Email = nostr.GeneratePlaceholderEmail(pubkey)
}
json.NewEncoder(w).Encode(response)
}