From d171c5940bbdf274fb84bdeb56be49c0d9146c27 Mon Sep 17 00:00:00 2001 From: Stavros Date: Wed, 30 Apr 2025 19:49:35 +0300 Subject: [PATCH] feat: get claims from github and google --- frontend/src/index.css | 4 ++ frontend/src/main.tsx | 6 ++- internal/auth/auth.go | 6 +++ internal/handlers/handlers.go | 34 ++++++++++++---- internal/providers/github.go | 74 +++++++++++++++++++++++++++++------ internal/providers/google.go | 19 ++++++++- 6 files changed, 121 insertions(+), 22 deletions(-) create mode 100644 frontend/src/index.css diff --git a/frontend/src/index.css b/frontend/src/index.css new file mode 100644 index 0000000..2ddf63c --- /dev/null +++ b/frontend/src/index.css @@ -0,0 +1,4 @@ +span, +p { + word-break: break-word; +} diff --git a/frontend/src/main.tsx b/frontend/src/main.tsx index c5a39d6..2efd797 100644 --- a/frontend/src/main.tsx +++ b/frontend/src/main.tsx @@ -19,6 +19,7 @@ import { TotpPage } from "./pages/totp-page.tsx"; import { AppContextProvider } from "./context/app-context.tsx"; import "./lib/i18n/i18n.ts"; import { ForgotPasswordPage } from "./pages/forgot-password-page.tsx"; +import "./index.css"; const queryClient = new QueryClient(); @@ -38,7 +39,10 @@ createRoot(document.getElementById("root")!).render( } /> } /> } /> - } /> + } + /> } /> diff --git a/internal/auth/auth.go b/internal/auth/auth.go index d6dfbc3..d593f2f 100644 --- a/internal/auth/auth.go +++ b/internal/auth/auth.go @@ -283,6 +283,12 @@ func (auth *Auth) OAuthGroup(c *gin.Context, context types.UserContext, labels t return true } + // Check if we are using the generic oauth provider + if context.Provider != "generic" { + log.Debug().Msg("Not using generic provider, skipping group check") + return true + } + // Split the groups by comma (no need to parse since they are from the API response) oauthGroups := strings.Split(context.OAuthGroups, ",") diff --git a/internal/handlers/handlers.go b/internal/handlers/handlers.go index 4a8f625..31418f6 100644 --- a/internal/handlers/handlers.go +++ b/internal/handlers/handlers.go @@ -150,11 +150,20 @@ func (h *Handlers) AuthHandler(c *gin.Context) { return } - // Build query - queries, err := query.Values(types.UnauthorizedQuery{ - Username: userContext.Username, + // Values + values := types.UnauthorizedQuery{ Resource: strings.Split(host, ".")[0], - }) + } + + // Use either username or email + if userContext.OAuth { + values.Username = userContext.Email + } else { + values.Username = userContext.Username + } + + // Build query + queries, err := query.Values(values) // Handle error (no need to check for nginx/headers since we are sure we are using caddy/traefik) if err != nil { @@ -190,12 +199,21 @@ func (h *Handlers) AuthHandler(c *gin.Context) { return } - // Build query - queries, err := query.Values(types.UnauthorizedQuery{ - Username: userContext.Username, + // Values + values := types.UnauthorizedQuery{ Resource: strings.Split(host, ".")[0], GroupErr: true, - }) + } + + // Use either username or email + if userContext.OAuth { + values.Username = userContext.Email + } else { + values.Username = userContext.Username + } + + // Build query + queries, err := query.Values(values) // Handle error (no need to check for nginx/headers since we are sure we are using caddy/traefik) if err != nil { diff --git a/internal/providers/github.go b/internal/providers/github.go index 2f8862e..b80c4b1 100644 --- a/internal/providers/github.go +++ b/internal/providers/github.go @@ -10,30 +10,36 @@ import ( "github.com/rs/zerolog/log" ) -// Github has a different response than the generic provider -type GithubUserInfoResponse []struct { +// Response for the github email endpoint +type GithubEmailResponse []struct { Email string `json:"email"` Primary bool `json:"primary"` } +// Response for the github user endpoint +type GithubUserInfoResponse struct { + Login string `json:"login"` + Name string `json:"name"` +} + // The scopes required for the github provider func GithubScopes() []string { - return []string{"user:email"} + return []string{"user:email", "read:user"} } func GetGithubUser(client *http.Client) (constants.Claims, error) { // Create user struct var user constants.Claims - // Get the user emails from github using the oauth http client - res, err := client.Get("https://api.github.com/user/emails") + // Get the user info from github using the oauth http client + res, err := client.Get("https://api.github.com/user") // Check if there was an error if err != nil { return user, err } - log.Debug().Msg("Got response from github") + log.Debug().Msg("Got user response from github") // Read the body of the response body, err := io.ReadAll(res.Body) @@ -43,10 +49,41 @@ func GetGithubUser(client *http.Client) (constants.Claims, error) { return user, err } - log.Debug().Msg("Read body from github") + log.Debug().Msg("Read user body from github") // Parse the body into a user struct - var emails GithubUserInfoResponse + var userInfo GithubUserInfoResponse + + // Unmarshal the body into the user struct + err = json.Unmarshal(body, &userInfo) + + // Check if there was an error + if err != nil { + return user, err + } + + // Get the user emails from github using the oauth http client + res, err = client.Get("https://api.github.com/user/emails") + + // Check if there was an error + if err != nil { + return user, err + } + + log.Debug().Msg("Got email response from github") + + // Read the body of the response + body, err = io.ReadAll(res.Body) + + // Check if there was an error + if err != nil { + return user, err + } + + log.Debug().Msg("Read email body from github") + + // Parse the body into a user struct + var emails GithubEmailResponse // Unmarshal the body into the user struct err = json.Unmarshal(body, &emails) @@ -61,11 +98,26 @@ func GetGithubUser(client *http.Client) (constants.Claims, error) { // Find and return the primary email for _, email := range emails { if email.Primary { + // Set the email then exit user.Email = email.Email - return user, nil + break } } - // User does not have a primary email? - return user, errors.New("no primary email found") + // If no primary email was found, use the first available email + if len(emails) == 0 { + return user, errors.New("no emails found") + } + + // Set the email if it is not set picking the first one + if user.Email == "" { + user.Email = emails[0].Email + } + + // Set the username and name + user.PreferredUsername = userInfo.Login + user.Name = userInfo.Name + + // Return + return user, nil } diff --git a/internal/providers/google.go b/internal/providers/google.go index f8ba30d..417de6f 100644 --- a/internal/providers/google.go +++ b/internal/providers/google.go @@ -4,14 +4,21 @@ import ( "encoding/json" "io" "net/http" + "strings" "tinyauth/internal/constants" "github.com/rs/zerolog/log" ) +// Response for the google user endpoint +type GoogleUserInfoResponse struct { + Email string `json:"email"` + Name string `json:"name"` +} + // The scopes required for the google provider func GoogleScopes() []string { - return []string{"https://www.googleapis.com/auth/userinfo.email"} + return []string{"https://www.googleapis.com/auth/userinfo.email", "https://www.googleapis.com/auth/userinfo.profile"} } func GetGoogleUser(client *http.Client) (constants.Claims, error) { @@ -38,8 +45,11 @@ func GetGoogleUser(client *http.Client) (constants.Claims, error) { log.Debug().Msg("Read body from google") + // Create a new user info struct + var userInfo GoogleUserInfoResponse + // Unmarshal the body into the user struct - err = json.Unmarshal(body, &user) + err = json.Unmarshal(body, &userInfo) // Check if there was an error if err != nil { @@ -48,6 +58,11 @@ func GetGoogleUser(client *http.Client) (constants.Claims, error) { log.Debug().Msg("Parsed user from google") + // Map the user info to the user struct + user.PreferredUsername = strings.Split(userInfo.Email, "@")[0] + user.Name = userInfo.Name + user.Email = userInfo.Email + // Return the user return user, nil }