package oauth import ( "encoding/base64" "encoding/json" "fmt" "log" "net/http" "strings" "time" "gitea.local/admin/hspguard/internal/types" "gitea.local/admin/hspguard/internal/util" "gitea.local/admin/hspguard/internal/web" "github.com/golang-jwt/jwt/v5" "github.com/google/uuid" ) func (h *OAuthHandler) tokenEndpoint(w http.ResponseWriter, r *http.Request) { log.Println("[OAUTH] New request to token endpoint") authHeader := r.Header.Get("Authorization") if authHeader == "" || !strings.HasPrefix(authHeader, "Basic ") { w.Header().Set("WWW-Authenticate", `Basic realm="Restricted"`) http.Error(w, "Unauthorized", http.StatusUnauthorized) return } // Decode credentials payload, err := base64.StdEncoding.DecodeString(strings.TrimPrefix(authHeader, "Basic ")) if err != nil { http.Error(w, "Invalid auth encoding", http.StatusBadRequest) return } var clientId string var clientSecret string parts := strings.SplitN(string(payload), ":", 2) if len(parts) != 2 { http.Error(w, "Unauthorized", http.StatusForbidden) return } clientId = parts[0] clientSecret = parts[1] log.Printf("Some client is trying to exchange code with id: %s and secret: %s\n", clientId, clientSecret) // Parse the form data err = r.ParseForm() if err != nil { http.Error(w, "Failed to parse form", http.StatusBadRequest) return } grantType := r.FormValue("grant_type") redirectUri := r.FormValue("redirect_uri") log.Printf("Redirect URI is %s\n", redirectUri) switch grantType { case "authorization_code": code := r.FormValue("code") fmt.Printf("Code received: %s\n", code) // TODO: Verify code from another db table nonce := strings.Split(code, ",")[1] userId := strings.Split(code, ",")[0] user, err := h.repo.FindUserId(r.Context(), uuid.MustParse(userId)) if err != nil { web.Error(w, "requested user not found", http.StatusNotFound) return } claims := types.ApiClaims{ Email: user.Email, // TODO: EmailVerified: true, Name: user.FullName, Picture: user.ProfilePicture, Nonce: nonce, Roles: []string{"user", "admin"}, RegisteredClaims: jwt.RegisteredClaims{ Issuer: h.cfg.Jwt.Issuer, // TODO: use dedicated API id that is in local DB and bind to user there Subject: user.ID.String(), Audience: jwt.ClaimStrings{clientId}, IssuedAt: jwt.NewNumericDate(time.Now()), ExpiresAt: jwt.NewNumericDate(time.Now().Add(15 * time.Minute)), }, } idToken, err := util.SignJwtToken(claims, h.cfg.Jwt.PrivateKey) if err != nil { web.Error(w, "failed to sign id token", http.StatusInternalServerError) return } type Response struct { IdToken string `json:"id_token"` TokenType string `json:"token_type"` AccessToken string `json:"access_token"` Email string `json:"email"` // TODO: add expires_in, refresh_token, scope (RFC 8693 $2) } response := Response{ IdToken: idToken, TokenType: "Bearer", // FIXME: AccessToken: idToken, Email: user.Email, } log.Printf("sending following response: %#v\n", response) w.Header().Set("Content-Type", "application/json") encoder := json.NewEncoder(w) if err := encoder.Encode(response); err != nil { web.Error(w, "failed to encode response", http.StatusInternalServerError) } default: web.Error(w, "unsupported grant type", http.StatusBadRequest) } }