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

View File

@ -1,31 +1,49 @@
package apiservices
package admin
import (
"encoding/json"
"log"
"net/http"
"gitea.local/admin/hspguard/internal/config"
imiddleware "gitea.local/admin/hspguard/internal/middleware"
"gitea.local/admin/hspguard/internal/repository"
"gitea.local/admin/hspguard/internal/util"
"gitea.local/admin/hspguard/internal/web"
"github.com/go-chi/chi/v5"
"github.com/google/uuid"
)
type ApiServicesHandler struct {
type AdminHandler struct {
repo *repository.Queries
cfg *config.AppConfig
}
func New(repo *repository.Queries, cfg *config.AppConfig) *ApiServicesHandler {
return &ApiServicesHandler{
func New(repo *repository.Queries, cfg *config.AppConfig) *AdminHandler {
return &AdminHandler{
repo,
cfg,
}
}
func (h *ApiServicesHandler) RegisterRoutes(router chi.Router) {
router.Post("/api-services/create", h.Add)
func (h *AdminHandler) RegisterRoutes(router chi.Router) {
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 {
@ -35,24 +53,7 @@ type AddServiceRequest struct {
GrantTypes []string `json:"grant_types"`
}
func (h *ApiServicesHandler) Add(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
}
func (h *AdminHandler) AddApiService(w http.ResponseWriter, r *http.Request) {
var req AddServiceRequest
decoder := json.NewDecoder(r.Body)

View File

@ -8,6 +8,7 @@ import (
"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/types"
"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 {
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 {
return "", "", err
}
@ -66,9 +67,17 @@ func NewAuthHandler(repo *repository.Queries, cfg *config.AppConfig) *AuthHandle
}
func (h *AuthHandler) RegisterRoutes(api chi.Router) {
api.Get("/auth/profile", h.getProfile)
api.Post("/auth/login", h.login)
api.Post("/auth/refresh", h.refreshToken)
api.Route("/auth", func(r chi.Router) {
r.Group(func(protected chi.Router) {
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) {
@ -85,7 +94,7 @@ func (h *AuthHandler) refreshToken(w http.ResponseWriter, r *http.Request) {
}
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 {
http.Error(w, fmt.Sprintf("invalid token: %v", err), http.StatusUnauthorized)
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"
"strings"
"gitea.local/admin/hspguard/internal/auth"
"gitea.local/admin/hspguard/internal/config"
"gitea.local/admin/hspguard/internal/types"
"gitea.local/admin/hspguard/internal/util"
"gitea.local/admin/hspguard/internal/web"
)
@ -16,7 +16,7 @@ type AuthMiddleware struct {
cfg *config.AppConfig
}
func New(cfg *config.AppConfig) *AuthMiddleware {
func NewAuthMiddleware(cfg *config.AppConfig) *AuthMiddleware {
return &AuthMiddleware{
cfg,
}
@ -37,9 +37,9 @@ func (m *AuthMiddleware) Runner(next http.Handler) http.Handler {
}
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 {
http.Error(w, fmt.Sprintf("invalid token: %v", err), http.StatusUnauthorized)
web.Error(w, fmt.Sprintf("invalid token: %v", err), http.StatusUnauthorized)
return
}

View File

@ -9,7 +9,6 @@ import (
"strings"
"time"
"gitea.local/admin/hspguard/internal/auth"
"gitea.local/admin/hspguard/internal/config"
"gitea.local/admin/hspguard/internal/repository"
"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) {
r.Post("/oauth/token", h.tokenEndpoint)
func (h *OAuthHandler) RegisterRoutes(router chi.Router) {
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) {
pubKey, err := auth.ParseBase64PublicKey(h.cfg.Jwt.PublicKey)
pubKey, err := util.ParseBase64PublicKey(h.cfg.Jwt.PublicKey)
if err != nil {
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 {
web.Error(w, "failed to sign id token", http.StatusInternalServerError)
return

View File

@ -11,6 +11,8 @@ import (
"strings"
"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/storage"
"gitea.local/admin/hspguard/internal/util"
@ -24,18 +26,26 @@ import (
type UserHandler struct {
repo *repository.Queries
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{
repo: repo,
minio: minio,
repo,
minio,
cfg,
}
}
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.Put("/avatar", h.uploadAvatar)
api.Get("/avatar/{avatar}", h.getAvatar)
}

View File

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

View File

@ -3,16 +3,16 @@ INSERT INTO api_services (
client_id, client_secret, name, redirect_uris, scopes, grant_types
) VALUES (
$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
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
AND is_active = true
LIMIT 1;
-- 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;
-- name: UpdateApiService :one
@ -24,7 +24,7 @@ SET
grant_types = $5,
updated_at = NOW()
WHERE client_id = $1
RETURNING *;
RETURNING id,client_id,name,redirect_uris,scopes,grant_types,created_at,updated_at,is_active;
-- name: DeactivateApiService :exec
UPDATE api_services
@ -36,4 +36,4 @@ WHERE client_id = $1;
UPDATE api_services
SET client_secret = $2,
updated_at = NOW()
WHERE client_id = $1;
WHERE client_id = $1;

View File

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