userinfo.go raw
1 package handler
2
3 import (
4 "encoding/json"
5 "log"
6 "net/http"
7 "strings"
8
9 "github.com/mlekudev/gitea-nostr-auth/internal/nostr"
10 )
11
12 type UserInfoResponse struct {
13 Sub string `json:"sub"`
14 Name string `json:"name,omitempty"`
15 PreferredUsername string `json:"preferred_username"`
16 Email string `json:"email,omitempty"`
17 EmailVerified bool `json:"email_verified,omitempty"`
18 Picture string `json:"picture,omitempty"`
19 Profile string `json:"profile,omitempty"`
20 Website string `json:"website,omitempty"`
21 Error string `json:"error,omitempty"`
22 ErrorDesc string `json:"error_description,omitempty"`
23 }
24
25 func (h *Handler) UserInfo(w http.ResponseWriter, r *http.Request) {
26 w.Header().Set("Content-Type", "application/json")
27
28 // Extract Bearer token
29 authHeader := r.Header.Get("Authorization")
30 if !strings.HasPrefix(authHeader, "Bearer ") {
31 w.WriteHeader(http.StatusUnauthorized)
32 json.NewEncoder(w).Encode(UserInfoResponse{
33 Error: "invalid_token",
34 ErrorDesc: "missing or invalid Authorization header",
35 })
36 return
37 }
38
39 token := strings.TrimPrefix(authHeader, "Bearer ")
40
41 // Look up access token
42 accessToken, err := h.store.GetAccessToken(token)
43 if err != nil || accessToken == nil {
44 w.WriteHeader(http.StatusUnauthorized)
45 json.NewEncoder(w).Encode(UserInfoResponse{
46 Error: "invalid_token",
47 ErrorDesc: "token is invalid or expired",
48 })
49 return
50 }
51
52 pubkey := accessToken.Pubkey
53 npub := nostr.PubkeyToNpub(pubkey)
54
55 // Fetch profile from relays (this also fetches relay list first)
56 log.Printf("Fetching profile for %s from relays...", nostr.TruncateNpub(npub))
57 profile := h.fetcher.FetchProfile(r.Context(), pubkey)
58
59 // Build response with profile data or fallbacks
60 response := UserInfoResponse{
61 Sub: pubkey,
62 }
63
64 if profile != nil {
65 log.Printf("Got profile for %s: name=%s, nip05=%s", nostr.TruncateNpub(npub), profile.Name, profile.Nip05)
66
67 // Use profile data
68 response.Name = profile.GetDisplayName()
69 response.PreferredUsername = profile.GetUsername()
70 response.Picture = profile.Picture
71 response.Website = profile.Website
72 response.Profile = profile.About
73
74 // Use NIP-05 as email if available (it's verified in the Nostr sense)
75 if profile.Nip05 != "" {
76 // NIP-05 format: name@domain.com or _@domain.com
77 response.Email = profile.Nip05
78 response.EmailVerified = false // We haven't verified it ourselves
79 } else {
80 response.Email = nostr.GeneratePlaceholderEmail(pubkey)
81 }
82 } else {
83 log.Printf("No profile found for %s, using defaults", nostr.TruncateNpub(npub))
84
85 // Fallback to generated values
86 response.PreferredUsername = nostr.GenerateUsername(pubkey)
87 response.Name = nostr.TruncateNpub(npub)
88 response.Email = nostr.GeneratePlaceholderEmail(pubkey)
89 }
90
91 json.NewEncoder(w).Encode(response)
92 }
93