feat: admin routes + better auth routing

This commit is contained in:
2025-05-30 18:17:12 +02:00
parent db2cb36f54
commit 51b7e6b3f9
10 changed files with 133 additions and 59 deletions

View File

@ -6,7 +6,7 @@ import (
"net/http" "net/http"
"os" "os"
"gitea.local/admin/hspguard/internal/apiservices" "gitea.local/admin/hspguard/internal/admin"
"gitea.local/admin/hspguard/internal/auth" "gitea.local/admin/hspguard/internal/auth"
"gitea.local/admin/hspguard/internal/config" "gitea.local/admin/hspguard/internal/config"
imiddleware "gitea.local/admin/hspguard/internal/middleware" imiddleware "gitea.local/admin/hspguard/internal/middleware"
@ -45,17 +45,15 @@ func (s *APIServer) Run() error {
oauthHandler := oauth.NewOAuthHandler(s.repo, s.cfg) oauthHandler := oauth.NewOAuthHandler(s.repo, s.cfg)
router.Route("/api/v1", func(r chi.Router) { router.Route("/api/v1", func(r chi.Router) {
am := imiddleware.New(s.cfg) authMiddleware := imiddleware.NewAuthMiddleware(s.cfg)
r.Use(imiddleware.WithSkipper( r.Use(imiddleware.WithSkipper(
am.Runner, authMiddleware.Runner,
"/api/v1/auth/login",
"/api/v1/register", "/api/v1/register",
"/api/v1/auth/refresh",
"/api/v1/oauth/token", "/api/v1/oauth/token",
"/api/v1/avatar", "/api/v1/avatar",
)) ))
userHandler := user.NewUserHandler(s.repo, s.storage) userHandler := user.NewUserHandler(s.repo, s.storage, s.cfg)
userHandler.RegisterRoutes(r) userHandler.RegisterRoutes(r)
authHandler := auth.NewAuthHandler(s.repo, s.cfg) authHandler := auth.NewAuthHandler(s.repo, s.cfg)
@ -63,8 +61,8 @@ func (s *APIServer) Run() error {
oauthHandler.RegisterRoutes(r) oauthHandler.RegisterRoutes(r)
apiServicesHandler := apiservices.New(s.repo, s.cfg) adminHandler := admin.New(s.repo, s.cfg)
apiServicesHandler.RegisterRoutes(r) adminHandler.RegisterRoutes(r)
}) })
router.Get("/.well-known/jwks.json", oauthHandler.WriteJWKS) router.Get("/.well-known/jwks.json", oauthHandler.WriteJWKS)

View File

@ -1,31 +1,49 @@
package apiservices package admin
import ( import (
"encoding/json" "encoding/json"
"log"
"net/http" "net/http"
"gitea.local/admin/hspguard/internal/config" "gitea.local/admin/hspguard/internal/config"
imiddleware "gitea.local/admin/hspguard/internal/middleware"
"gitea.local/admin/hspguard/internal/repository" "gitea.local/admin/hspguard/internal/repository"
"gitea.local/admin/hspguard/internal/util" "gitea.local/admin/hspguard/internal/util"
"gitea.local/admin/hspguard/internal/web" "gitea.local/admin/hspguard/internal/web"
"github.com/go-chi/chi/v5" "github.com/go-chi/chi/v5"
"github.com/google/uuid"
) )
type ApiServicesHandler struct { type AdminHandler struct {
repo *repository.Queries repo *repository.Queries
cfg *config.AppConfig cfg *config.AppConfig
} }
func New(repo *repository.Queries, cfg *config.AppConfig) *ApiServicesHandler { func New(repo *repository.Queries, cfg *config.AppConfig) *AdminHandler {
return &ApiServicesHandler{ return &AdminHandler{
repo, repo,
cfg, cfg,
} }
} }
func (h *ApiServicesHandler) RegisterRoutes(router chi.Router) { func (h *AdminHandler) RegisterRoutes(router chi.Router) {
router.Post("/api-services/create", h.Add) router.Route("/admin", func(r chi.Router) {
authMiddleware := imiddleware.NewAuthMiddleware(h.cfg)
adminMiddleware := imiddleware.NewAdminMiddleware(h.repo)
r.Use(authMiddleware.Runner, adminMiddleware.Runner)
r.Post("/api-services/create", h.AddApiService)
})
}
func (h *AdminHandler) GetApiServices(w http.ResponseWriter, r *http.Request) {
services, err := h.repo.ListApiServices(r.Context())
if err != nil {
log.Println("ERR: Failed to list api services from db:", err)
web.Error(w, "failed to get api services", http.StatusInternalServerError)
return
}
} }
type AddServiceRequest struct { type AddServiceRequest struct {
@ -35,24 +53,7 @@ type AddServiceRequest struct {
GrantTypes []string `json:"grant_types"` GrantTypes []string `json:"grant_types"`
} }
func (h *ApiServicesHandler) Add(w http.ResponseWriter, r *http.Request) { func (h *AdminHandler) AddApiService(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
}
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
}
if !user.IsAdmin {
web.Error(w, "you cannot create api services", http.StatusForbidden)
return
}
var req AddServiceRequest var req AddServiceRequest
decoder := json.NewDecoder(r.Body) decoder := json.NewDecoder(r.Body)

View File

@ -8,6 +8,7 @@ import (
"time" "time"
"gitea.local/admin/hspguard/internal/config" "gitea.local/admin/hspguard/internal/config"
imiddleware "gitea.local/admin/hspguard/internal/middleware"
"gitea.local/admin/hspguard/internal/repository" "gitea.local/admin/hspguard/internal/repository"
"gitea.local/admin/hspguard/internal/types" "gitea.local/admin/hspguard/internal/types"
"gitea.local/admin/hspguard/internal/util" "gitea.local/admin/hspguard/internal/util"
@ -34,7 +35,7 @@ func (h *AuthHandler) signTokens(user *repository.User) (string, string, error)
}, },
} }
accessToken, err := SignJwtToken(accessClaims, h.cfg.Jwt.PrivateKey) accessToken, err := util.SignJwtToken(accessClaims, h.cfg.Jwt.PrivateKey)
if err != nil { if err != nil {
return "", "", err return "", "", err
} }
@ -50,7 +51,7 @@ func (h *AuthHandler) signTokens(user *repository.User) (string, string, error)
}, },
} }
refreshToken, err := SignJwtToken(refreshClaims, h.cfg.Jwt.PrivateKey) refreshToken, err := util.SignJwtToken(refreshClaims, h.cfg.Jwt.PrivateKey)
if err != nil { if err != nil {
return "", "", err return "", "", err
} }
@ -66,9 +67,17 @@ func NewAuthHandler(repo *repository.Queries, cfg *config.AppConfig) *AuthHandle
} }
func (h *AuthHandler) RegisterRoutes(api chi.Router) { func (h *AuthHandler) RegisterRoutes(api chi.Router) {
api.Get("/auth/profile", h.getProfile) api.Route("/auth", func(r chi.Router) {
api.Post("/auth/login", h.login) r.Group(func(protected chi.Router) {
api.Post("/auth/refresh", h.refreshToken) authMiddleware := imiddleware.NewAuthMiddleware(h.cfg)
protected.Use(authMiddleware.Runner)
protected.Get("/profile", h.getProfile)
})
r.Post("/login", h.login)
r.Post("/refresh", h.refreshToken)
})
} }
func (h *AuthHandler) refreshToken(w http.ResponseWriter, r *http.Request) { func (h *AuthHandler) refreshToken(w http.ResponseWriter, r *http.Request) {
@ -85,7 +94,7 @@ func (h *AuthHandler) refreshToken(w http.ResponseWriter, r *http.Request) {
} }
tokenStr := parts[1] tokenStr := parts[1]
token, userClaims, err := VerifyToken(tokenStr, h.cfg.Jwt.PublicKey) token, userClaims, err := util.VerifyToken(tokenStr, h.cfg.Jwt.PublicKey)
if err != nil || !token.Valid { if err != nil || !token.Valid {
http.Error(w, fmt.Sprintf("invalid token: %v", err), http.StatusUnauthorized) http.Error(w, fmt.Sprintf("invalid token: %v", err), http.StatusUnauthorized)
return return

View File

@ -0,0 +1,47 @@
package middleware
import (
"log"
"net/http"
"gitea.local/admin/hspguard/internal/repository"
"gitea.local/admin/hspguard/internal/util"
"gitea.local/admin/hspguard/internal/web"
"github.com/google/uuid"
)
type AdminMiddleware struct {
repo *repository.Queries
}
func NewAdminMiddleware(repo *repository.Queries) *AdminMiddleware {
return &AdminMiddleware{
repo,
}
}
func (m *AdminMiddleware) Runner(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
userId, ok := util.GetRequestUserId(r.Context())
if !ok {
log.Println("ERR: Could not get user id from request")
web.Error(w, "not authenticated", http.StatusUnauthorized)
return
}
user, err := m.repo.FindUserId(r.Context(), uuid.MustParse(userId))
if err != nil {
log.Println("ERR: User with provided id does not exist:", userId)
web.Error(w, "not authenticated", http.StatusUnauthorized)
return
}
if !user.IsAdmin {
log.Println("INFO: User is not admin")
web.Error(w, "no priviligies to access this resource", http.StatusForbidden)
return
}
next.ServeHTTP(w, r)
})
}

View File

@ -6,9 +6,9 @@ import (
"net/http" "net/http"
"strings" "strings"
"gitea.local/admin/hspguard/internal/auth"
"gitea.local/admin/hspguard/internal/config" "gitea.local/admin/hspguard/internal/config"
"gitea.local/admin/hspguard/internal/types" "gitea.local/admin/hspguard/internal/types"
"gitea.local/admin/hspguard/internal/util"
"gitea.local/admin/hspguard/internal/web" "gitea.local/admin/hspguard/internal/web"
) )
@ -16,7 +16,7 @@ type AuthMiddleware struct {
cfg *config.AppConfig cfg *config.AppConfig
} }
func New(cfg *config.AppConfig) *AuthMiddleware { func NewAuthMiddleware(cfg *config.AppConfig) *AuthMiddleware {
return &AuthMiddleware{ return &AuthMiddleware{
cfg, cfg,
} }
@ -37,9 +37,9 @@ func (m *AuthMiddleware) Runner(next http.Handler) http.Handler {
} }
tokenStr := parts[1] tokenStr := parts[1]
token, userClaims, err := auth.VerifyToken(tokenStr, m.cfg.Jwt.PublicKey) token, userClaims, err := util.VerifyToken(tokenStr, m.cfg.Jwt.PublicKey)
if err != nil || !token.Valid { if err != nil || !token.Valid {
http.Error(w, fmt.Sprintf("invalid token: %v", err), http.StatusUnauthorized) web.Error(w, fmt.Sprintf("invalid token: %v", err), http.StatusUnauthorized)
return return
} }

View File

@ -9,7 +9,6 @@ import (
"strings" "strings"
"time" "time"
"gitea.local/admin/hspguard/internal/auth"
"gitea.local/admin/hspguard/internal/config" "gitea.local/admin/hspguard/internal/config"
"gitea.local/admin/hspguard/internal/repository" "gitea.local/admin/hspguard/internal/repository"
"gitea.local/admin/hspguard/internal/types" "gitea.local/admin/hspguard/internal/types"
@ -32,14 +31,16 @@ func NewOAuthHandler(repo *repository.Queries, cfg *config.AppConfig) *OAuthHand
} }
} }
func (h *OAuthHandler) RegisterRoutes(r chi.Router) { func (h *OAuthHandler) RegisterRoutes(router chi.Router) {
r.Post("/oauth/token", h.tokenEndpoint) router.Route("/oauth", func(r chi.Router) {
r.Post("/token", h.tokenEndpoint)
r.Post("/oauth/code", h.getAuthCode) r.Post("/code", h.getAuthCode)
})
} }
func (h *OAuthHandler) WriteJWKS(w http.ResponseWriter, r *http.Request) { func (h *OAuthHandler) WriteJWKS(w http.ResponseWriter, r *http.Request) {
pubKey, err := auth.ParseBase64PublicKey(h.cfg.Jwt.PublicKey) pubKey, err := util.ParseBase64PublicKey(h.cfg.Jwt.PublicKey)
if err != nil { if err != nil {
web.Error(w, "failed to parse public key", http.StatusInternalServerError) web.Error(w, "failed to parse public key", http.StatusInternalServerError)
} }
@ -207,7 +208,7 @@ func (h *OAuthHandler) tokenEndpoint(w http.ResponseWriter, r *http.Request) {
}, },
} }
idToken, err := auth.SignJwtToken(claims, h.cfg.Jwt.PrivateKey) idToken, err := util.SignJwtToken(claims, h.cfg.Jwt.PrivateKey)
if err != nil { if err != nil {
web.Error(w, "failed to sign id token", http.StatusInternalServerError) web.Error(w, "failed to sign id token", http.StatusInternalServerError)
return return

View File

@ -11,6 +11,8 @@ import (
"strings" "strings"
"time" "time"
"gitea.local/admin/hspguard/internal/config"
imiddleware "gitea.local/admin/hspguard/internal/middleware"
"gitea.local/admin/hspguard/internal/repository" "gitea.local/admin/hspguard/internal/repository"
"gitea.local/admin/hspguard/internal/storage" "gitea.local/admin/hspguard/internal/storage"
"gitea.local/admin/hspguard/internal/util" "gitea.local/admin/hspguard/internal/util"
@ -24,18 +26,26 @@ import (
type UserHandler struct { type UserHandler struct {
repo *repository.Queries repo *repository.Queries
minio *storage.FileStorage minio *storage.FileStorage
cfg *config.AppConfig
} }
func NewUserHandler(repo *repository.Queries, minio *storage.FileStorage) *UserHandler { func NewUserHandler(repo *repository.Queries, minio *storage.FileStorage, cfg *config.AppConfig) *UserHandler {
return &UserHandler{ return &UserHandler{
repo: repo, repo,
minio: minio, minio,
cfg,
} }
} }
func (h *UserHandler) RegisterRoutes(api chi.Router) { func (h *UserHandler) RegisterRoutes(api chi.Router) {
api.Group(func(protected chi.Router) {
authMiddleware := imiddleware.NewAuthMiddleware(h.cfg)
protected.Use(authMiddleware.Runner)
protected.Put("/avatar", h.uploadAvatar)
})
api.Post("/register", h.register) api.Post("/register", h.register)
api.Put("/avatar", h.uploadAvatar)
api.Get("/avatar/{avatar}", h.getAvatar) api.Get("/avatar/{avatar}", h.getAvatar)
} }

View File

@ -1,4 +1,4 @@
package auth package util
import ( import (
"crypto/rsa" "crypto/rsa"

View File

@ -3,16 +3,16 @@ INSERT INTO api_services (
client_id, client_secret, name, redirect_uris, scopes, grant_types client_id, client_secret, name, redirect_uris, scopes, grant_types
) VALUES ( ) VALUES (
$1, $2, $3, $4, $5, $6 $1, $2, $3, $4, $5, $6
) RETURNING *; ) RETURNING id,client_id,name,redirect_uris,scopes,grant_types,created_at,updated_at,is_active;
-- name: GetApiServiceCID :one -- name: GetApiServiceCID :one
SELECT * FROM api_services SELECT id,client_id,name,redirect_uris,scopes,grant_types,created_at,updated_at,is_active FROM api_services
WHERE client_id = $1 WHERE client_id = $1
AND is_active = true AND is_active = true
LIMIT 1; LIMIT 1;
-- name: ListApiServices :many -- name: ListApiServices :many
SELECT * FROM api_services SELECT id,client_id,name,redirect_uris,scopes,grant_types,created_at,updated_at,is_active FROM api_services
ORDER BY created_at DESC; ORDER BY created_at DESC;
-- name: UpdateApiService :one -- name: UpdateApiService :one
@ -24,7 +24,7 @@ SET
grant_types = $5, grant_types = $5,
updated_at = NOW() updated_at = NOW()
WHERE client_id = $1 WHERE client_id = $1
RETURNING *; RETURNING id,client_id,name,redirect_uris,scopes,grant_types,created_at,updated_at,is_active;
-- name: DeactivateApiService :exec -- name: DeactivateApiService :exec
UPDATE api_services UPDATE api_services

View File

@ -0,0 +1,8 @@
import { axios } from ".."
export interface FetchApiServicesRequest {}
export const fetchApiServices = async (req: FetchApiServicesRequest) => {
const response = await axios.get()
}