1package middleware
2
3import (
4 "encoding/json"
5 "fmt"
6 "io"
7 "net/http"
8 "strings"
9
10 "github.com/bnema/gordon/internal/db/queries"
11 "github.com/bnema/gordon/internal/server"
12 "github.com/charmbracelet/log"
13 "github.com/labstack/echo/v4"
14)
15
16// GithubUserInfo is an alias for the type defined in the queries package
17type GithubUserInfo = queries.GithubUserInfo
18
19// RequireToken is a middleware that checks for a valid GitHub token in the request
20func RequireToken(a *server.App) echo.MiddlewareFunc {
21 return func(next echo.HandlerFunc) echo.HandlerFunc {
22 return func(c echo.Context) error {
23 log.Debug("Starting RequireToken middleware")
24
25 githubUser, err := validateGitHubToken(c)
26 if err != nil {
27 log.Error("Token validation failed", "error", err)
28 return c.JSON(http.StatusUnauthorized, map[string]string{"error": "Invalid or missing token"})
29 }
30 log.Debug("GitHub token validated successfully", "user", githubUser.Login)
31
32 // Check if the user exists and matches the GitHub user
33 isValid, err := queries.CheckDBUserIsGood(a, githubUser)
34 if err != nil {
35 log.Error("Error checking user authorization", "error", err)
36 return c.JSON(http.StatusInternalServerError, map[string]string{"error": "Error checking user authorization"})
37 }
38
39 if !isValid {
40 log.Warn("User not authorized", "user", githubUser.Login)
41 return c.JSON(http.StatusForbidden, map[string]string{"error": "User not authorized"})
42 }
43 log.Debug("User authorization check passed", "user", githubUser.Login)
44
45 // Update or create session
46 err = queries.CreateOrUpdateDBSession(a, c.Request().Header.Get("Authorization"), c.Request().UserAgent())
47 if err != nil {
48 log.Error("Error updating session", "error", err)
49 return c.JSON(http.StatusInternalServerError, map[string]string{"error": "Error updating session"})
50 }
51 log.Debug("Session created or updated successfully", "user", githubUser.Login)
52
53 // Store the user information in the context for later use
54 c.Set("user", githubUser)
55 log.Debug("User information stored in context", "user", githubUser.Login)
56
57 return next(c)
58 }
59 }
60}
61
62func validateGitHubToken(c echo.Context) (*GithubUserInfo, error) {
63 token := c.Request().Header.Get("Authorization")
64 if token == "" {
65 log.Warn("Token is missing in the request")
66 return nil, echo.NewHTTPError(http.StatusUnauthorized, "Token is missing")
67 }
68
69 token = strings.TrimPrefix(token, "Bearer ")
70 log.Debug("Validating GitHub token")
71
72 // Validate the token against GitHub API
73 client := &http.Client{}
74 req, err := http.NewRequest("GET", "https://api.github.com/user", nil)
75 if err != nil {
76 log.Error("Failed to create request", "error", err)
77 return nil, fmt.Errorf("failed to create request: %w", err)
78 }
79
80 req.Header.Set("Authorization", "token "+token)
81 req.Header.Set("Accept", "application/vnd.github.v3+json")
82
83 resp, err := client.Do(req)
84 if err != nil {
85 log.Error("Failed to send request to GitHub API", "error", err)
86 return nil, fmt.Errorf("failed to send request: %w", err)
87 }
88 defer resp.Body.Close()
89
90 if resp.StatusCode != http.StatusOK {
91 log.Warn("Invalid token", "status", resp.Status)
92 return nil, echo.NewHTTPError(http.StatusUnauthorized, "Invalid token")
93 }
94
95 body, err := io.ReadAll(resp.Body)
96 if err != nil {
97 log.Error("Failed to read response body", "error", err)
98 return nil, fmt.Errorf("failed to read response body: %w", err)
99 }
100
101 var githubUser GithubUserInfo
102 if err := json.Unmarshal(body, &githubUser); err != nil {
103 log.Error("Failed to parse user data", "error", err)
104 return nil, fmt.Errorf("failed to parse user data: %w", err)
105 }
106
107 log.Debug("GitHub user info fetched successfully", "user", githubUser.Login)
108
109 // Fetch user's email
110 emails, err := fetchGitHubUserEmails(token)
111 if err != nil {
112 log.Error("Failed to fetch user emails", "error", err)
113 return nil, fmt.Errorf("failed to fetch user emails: %w", err)
114 }
115 githubUser.Emails = emails
116 log.Debug("GitHub user emails fetched successfully", "user", githubUser.Login, "emailCount", len(emails))
117
118 return &githubUser, nil
119}
120
121func fetchGitHubUserEmails(token string) ([]string, error) {
122 log.Debug("Fetching GitHub user emails")
123 client := &http.Client{}
124 req, err := http.NewRequest("GET", "https://api.github.com/user/emails", nil)
125 if err != nil {
126 log.Error("Failed to create request for emails", "error", err)
127 return nil, fmt.Errorf("failed to create request: %w", err)
128 }
129
130 req.Header.Set("Authorization", "token "+token)
131 req.Header.Set("Accept", "application/vnd.github.v3+json")
132
133 resp, err := client.Do(req)
134 if err != nil {
135 log.Error("Failed to send request for emails", "error", err)
136 return nil, fmt.Errorf("failed to send request: %w", err)
137 }
138 defer resp.Body.Close()
139
140 if resp.StatusCode != http.StatusOK {
141 log.Warn("Failed to fetch user emails", "status", resp.Status)
142 return nil, fmt.Errorf("failed to fetch user emails: %s", resp.Status)
143 }
144
145 var emailsResp []struct {
146 Email string `json:"email"`
147 }
148 if err := json.NewDecoder(resp.Body).Decode(&emailsResp); err != nil {
149 log.Error("Failed to parse email response", "error", err)
150 return nil, fmt.Errorf("failed to parse email response: %w", err)
151 }
152
153 emails := make([]string, len(emailsResp))
154 for i, e := range emailsResp {
155 emails[i] = e.Email
156 }
157
158 log.Debug("GitHub user emails fetched successfully", "emailCount", len(emails))
159 return emails, nil
160}