1
0
mirror of https://github.com/pocket-id/pocket-id.git synced 2026-02-04 13:21:45 +00:00

feat: add healthz endpoint (#494)

This commit is contained in:
Alessandro (Ale) Segala
2025-05-06 13:14:18 -07:00
committed by GitHub
parent c55fef057c
commit 3c87e4ec14
6 changed files with 66 additions and 34 deletions

View File

@@ -49,19 +49,18 @@ func initRouterInternal(db *gorm.DB, svc *services) (utils.Service, error) {
r.Use(otelgin.Middleware("pocket-id-backend"))
}
rateLimitMiddleware := middleware.NewRateLimitMiddleware()
rateLimitMiddleware := middleware.NewRateLimitMiddleware().Add(rate.Every(time.Second), 60)
// Setup global middleware
r.Use(middleware.NewCorsMiddleware().Add())
r.Use(middleware.NewErrorHandlerMiddleware().Add())
r.Use(rateLimitMiddleware.Add(rate.Every(time.Second), 60))
// Initialize middleware for specific routes
authMiddleware := middleware.NewAuthMiddleware(svc.apiKeyService, svc.userService, svc.jwtService)
fileSizeLimitMiddleware := middleware.NewFileSizeLimitMiddleware()
// Set up API routes
apiGroup := r.Group("/api")
apiGroup := r.Group("/api", rateLimitMiddleware)
controller.NewApiKeyController(apiGroup, authMiddleware, svc.apiKeyService)
controller.NewWebauthnController(apiGroup, authMiddleware, middleware.NewRateLimitMiddleware(), svc.webauthnService, svc.appConfigService)
controller.NewOidcController(apiGroup, authMiddleware, fileSizeLimitMiddleware, svc.oidcService, svc.jwtService)
@@ -79,9 +78,13 @@ func initRouterInternal(db *gorm.DB, svc *services) (utils.Service, error) {
}
// Set up base routes
baseGroup := r.Group("/")
baseGroup := r.Group("/", rateLimitMiddleware)
controller.NewWellKnownController(baseGroup, svc.jwtService)
// Set up healthcheck routes
// These are not rate-limited
controller.NewHealthzController(r)
// Set up the server
srv := &http.Server{
Addr: net.JoinHostPort(common.EnvConfig.Host, common.EnvConfig.Port),

View File

@@ -0,0 +1,29 @@
package controller
import (
"net/http"
"github.com/gin-gonic/gin"
)
// NewHealthzController creates a new controller for the healthcheck endpoints
// @Summary Healthcheck controller
// @Description Initializes healthcheck endpoints
// @Tags Health
func NewHealthzController(r *gin.Engine) {
hc := &HealthzController{}
r.GET("/healthz", hc.healthzHandler)
}
type HealthzController struct{}
// healthzHandler godoc
// @Summary Responds to healthchecks
// @Description Responds with a successful status code to healthcheck requests
// @Tags Health
// @Success 204 ""
// @Router /healthz [get]
func (hc *HealthzController) healthzHandler(c *gin.Context) {
c.Status(http.StatusNoContent)
}

View File

@@ -7,9 +7,9 @@ services:
- 3000:80
volumes:
- "./data:/app/backend/data"
# Optional healthcheck
# Optional healthcheck
healthcheck:
test: "curl -f http://localhost/health"
test: "curl -f http://localhost/healthz"
interval: 1m30s
timeout: 5s
retries: 2

View File

@@ -23,33 +23,33 @@ const paraglideHandle: Handle = ({ event, resolve }) => {
const authenticationHandle: Handle = async ({ event, resolve }) => {
const { isSignedIn, isAdmin } = verifyJwt(event.cookies.get(ACCESS_TOKEN_COOKIE_NAME));
const isUnauthenticatedOnlyPath = event.url.pathname.startsWith('/login') || event.url.pathname.startsWith('/lc')
const isPublicPath = ['/authorize', '/device', '/health'].includes(event.url.pathname);
const isAdminPath = event.url.pathname.startsWith('/settings/admin');
const path = event.url.pathname;
const isUnauthenticatedOnlyPath = path == '/login' || path.startsWith('/login/') || path == '/lc' || path.startsWith('/lc/')
const isPublicPath = ['/authorize', '/device', '/health', '/healthz'].includes(path);
const isAdminPath = path == '/settings/admin' || path.startsWith('/settings/admin/');
if (!isUnauthenticatedOnlyPath && !isPublicPath && !isSignedIn) {
return new Response(null, {
status: 302,
status: 303,
headers: { location: '/login' }
});
}
if (isUnauthenticatedOnlyPath && isSignedIn) {
return new Response(null, {
status: 302,
status: 303,
headers: { location: '/settings' }
});
}
if (isAdminPath && !isAdmin) {
return new Response(null, {
status: 302,
status: 303,
headers: { location: '/settings' }
});
}
const response = await resolve(event);
return response;
return resolve(event);
};
export const handle: Handle = sequence(paraglideHandle, authenticationHandle);

View File

@@ -1,20 +1,2 @@
import AppConfigService from '$lib/services/app-config-service';
import type { RequestHandler } from '@sveltejs/kit';
export const GET: RequestHandler = async () => {
const appConfigService = new AppConfigService();
let backendOk = true;
await appConfigService.list().catch(() => (backendOk = false));
return new Response(
JSON.stringify({
status: backendOk ? 'HEALTHY' : 'UNHEALTHY'
}),
{
status: backendOk ? 200 : 500,
headers: {
'content-type': 'application/json'
}
}
);
};
// /health is an alias of /healthz, for backwards-compatibility reasons
export {GET} from '../healthz/+server';

View File

@@ -0,0 +1,18 @@
import type { RequestHandler } from '@sveltejs/kit';
import axios from 'axios';
export const GET: RequestHandler = async () => {
const backendOK = await axios
.get(process!.env!.INTERNAL_BACKEND_URL + '/healthz')
.then(() => true, () => false);
return new Response(
backendOK ? `{"status":"HEALTHY"}` : `{"status":"UNHEALTHY"}`,
{
status: backendOK ? 200 : 500,
headers: {
'content-type': 'application/json'
}
}
);
};