feat: oauth endpoints: code and token

This commit is contained in:
2025-05-25 14:16:20 +02:00
parent d46e296ce1
commit 6e2d67ad24

View File

@ -1,49 +1,210 @@
package oauth
import (
"encoding/base64"
"encoding/json"
"fmt"
"log"
"net/http"
"strings"
"time"
"gitea.local/admin/hspguard/internal/auth"
"gitea.local/admin/hspguard/internal/repository"
"gitea.local/admin/hspguard/internal/types"
"gitea.local/admin/hspguard/internal/util"
"gitea.local/admin/hspguard/internal/web"
"github.com/go-chi/chi/v5"
"github.com/golang-jwt/jwt/v5"
"github.com/google/uuid"
)
type OAuthHandler struct{}
type OAuthHandler struct {
repo *repository.Queries
}
func NewOAuthHandler() *OAuthHandler {
return &OAuthHandler{}
func NewOAuthHandler(repo *repository.Queries) *OAuthHandler {
return &OAuthHandler{
repo,
}
}
func (h *OAuthHandler) RegisterRoutes(r chi.Router) {
r.Get("/oauth/authorize", h.authorizeEndpoint)
r.Get("/oauth/token", h.tokenEndpoint)
r.Post("/oauth/token", h.tokenEndpoint)
r.Post("/oauth/code", h.getAuthCode)
}
func (h *OAuthHandler) tokenEndpoint(w http.ResponseWriter, r *http.Request) {
log.Println("[OAUTH] New request to token endpoint")
w.WriteHeader(http.StatusOK)
w.Write([]byte("OK"))
}
func (h *OAuthHandler) getAuthCode(w http.ResponseWriter, r *http.Request) {
userId, ok := util.GetRequestUserId(r.Context())
if !ok {
web.Error(w, "failed to get user id from auth session", http.StatusInternalServerError)
return
}
func (h *OAuthHandler) authorizeEndpoint(w http.ResponseWriter, r *http.Request) {
log.Println("[OAUTH] New request to authorize endpoint")
w.WriteHeader(http.StatusOK)
w.Write([]byte("OK"))
}
user, err := h.repo.FindUserId(r.Context(), uuid.MustParse(userId))
if err != nil {
web.Error(w, "user with provided id does not exist", http.StatusUnauthorized)
return
}
type Request struct {
Nonce string `json:"nonce"`
}
var req Request
decoder := json.NewDecoder(r.Body)
if err := decoder.Decode(&req); err != nil {
web.Error(w, "nonce field is required in request", http.StatusBadRequest)
return
}
// TODO: Create real authorization code
func (h *OAuthHandler) Metadata(w http.ResponseWriter, r *http.Request) {
type Response struct {
TokenEndpoint string `json:"token_endpoint"`
AuthEndpoint string `json:"authorization_endpoint"`
Code string `json:"code"`
}
encoder := json.NewEncoder(w)
w.Header().Set("Content-Type", "application/json")
if err := encoder.Encode(Response{
TokenEndpoint: "http://192.168.178.21:3001/api/v1/oauth/token",
AuthEndpoint: "http://192.168.178.21:5173/authorize",
Code: fmt.Sprintf("%s,%s", user.ID.String(), req.Nonce),
}); err != nil {
web.Error(w, "failed to encode response", http.StatusInternalServerError)
}
}
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.String,
Nonce: nonce,
Roles: []string{"user"},
RegisteredClaims: jwt.RegisteredClaims{
Issuer: "https://cb5f-2a00-10-5b00-c801-e955-5c68-63d0-b777.ngrok-free.app",
// 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 := auth.SignJwtToken(claims)
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)
}
}
// func (h *OAuthHandler) Metadata(w http.ResponseWriter, r *http.Request) {
// type Response struct {
// TokenEndpoint string `json:"token_endpoint"`
// AuthEndpoint string `json:"authorization_endpoint"`
// Issuer string `json:"issuer"`
// JwksUri string `json:"jwks_uri"`
// }
// encoder := json.NewEncoder(w)
// if err := encoder.Encode(Response{
// TokenEndpoint: "http://192.168.178.21:3001/api/v1/oauth/token",
// AuthEndpoint: "https://147f-2a00-10-5b00-c801-4882-7ef0-5e68-d360.ngrok-free.app/authorize",
// JwksUri: "http://192.168.178.21:3001/.well-known/jwks.json",
// Issuer: "http://192.168.178.21:3001",
// }); err != nil {
// web.Error(w, "failed to encode response", http.StatusInternalServerError)
// }
// }