mirror of
https://github.com/pocket-id/pocket-id.git
synced 2026-02-04 15:39:45 +00:00
feat: add healthz endpoint (#494)
This commit is contained in:
committed by
GitHub
parent
c55fef057c
commit
3c87e4ec14
@@ -49,19 +49,18 @@ func initRouterInternal(db *gorm.DB, svc *services) (utils.Service, error) {
|
|||||||
r.Use(otelgin.Middleware("pocket-id-backend"))
|
r.Use(otelgin.Middleware("pocket-id-backend"))
|
||||||
}
|
}
|
||||||
|
|
||||||
rateLimitMiddleware := middleware.NewRateLimitMiddleware()
|
rateLimitMiddleware := middleware.NewRateLimitMiddleware().Add(rate.Every(time.Second), 60)
|
||||||
|
|
||||||
// Setup global middleware
|
// Setup global middleware
|
||||||
r.Use(middleware.NewCorsMiddleware().Add())
|
r.Use(middleware.NewCorsMiddleware().Add())
|
||||||
r.Use(middleware.NewErrorHandlerMiddleware().Add())
|
r.Use(middleware.NewErrorHandlerMiddleware().Add())
|
||||||
r.Use(rateLimitMiddleware.Add(rate.Every(time.Second), 60))
|
|
||||||
|
|
||||||
// Initialize middleware for specific routes
|
// Initialize middleware for specific routes
|
||||||
authMiddleware := middleware.NewAuthMiddleware(svc.apiKeyService, svc.userService, svc.jwtService)
|
authMiddleware := middleware.NewAuthMiddleware(svc.apiKeyService, svc.userService, svc.jwtService)
|
||||||
fileSizeLimitMiddleware := middleware.NewFileSizeLimitMiddleware()
|
fileSizeLimitMiddleware := middleware.NewFileSizeLimitMiddleware()
|
||||||
|
|
||||||
// Set up API routes
|
// Set up API routes
|
||||||
apiGroup := r.Group("/api")
|
apiGroup := r.Group("/api", rateLimitMiddleware)
|
||||||
controller.NewApiKeyController(apiGroup, authMiddleware, svc.apiKeyService)
|
controller.NewApiKeyController(apiGroup, authMiddleware, svc.apiKeyService)
|
||||||
controller.NewWebauthnController(apiGroup, authMiddleware, middleware.NewRateLimitMiddleware(), svc.webauthnService, svc.appConfigService)
|
controller.NewWebauthnController(apiGroup, authMiddleware, middleware.NewRateLimitMiddleware(), svc.webauthnService, svc.appConfigService)
|
||||||
controller.NewOidcController(apiGroup, authMiddleware, fileSizeLimitMiddleware, svc.oidcService, svc.jwtService)
|
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
|
// Set up base routes
|
||||||
baseGroup := r.Group("/")
|
baseGroup := r.Group("/", rateLimitMiddleware)
|
||||||
controller.NewWellKnownController(baseGroup, svc.jwtService)
|
controller.NewWellKnownController(baseGroup, svc.jwtService)
|
||||||
|
|
||||||
|
// Set up healthcheck routes
|
||||||
|
// These are not rate-limited
|
||||||
|
controller.NewHealthzController(r)
|
||||||
|
|
||||||
// Set up the server
|
// Set up the server
|
||||||
srv := &http.Server{
|
srv := &http.Server{
|
||||||
Addr: net.JoinHostPort(common.EnvConfig.Host, common.EnvConfig.Port),
|
Addr: net.JoinHostPort(common.EnvConfig.Host, common.EnvConfig.Port),
|
||||||
|
|||||||
29
backend/internal/controller/healthz_controller.go
Normal file
29
backend/internal/controller/healthz_controller.go
Normal 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)
|
||||||
|
}
|
||||||
@@ -9,7 +9,7 @@ services:
|
|||||||
- "./data:/app/backend/data"
|
- "./data:/app/backend/data"
|
||||||
# Optional healthcheck
|
# Optional healthcheck
|
||||||
healthcheck:
|
healthcheck:
|
||||||
test: "curl -f http://localhost/health"
|
test: "curl -f http://localhost/healthz"
|
||||||
interval: 1m30s
|
interval: 1m30s
|
||||||
timeout: 5s
|
timeout: 5s
|
||||||
retries: 2
|
retries: 2
|
||||||
|
|||||||
@@ -23,33 +23,33 @@ const paraglideHandle: Handle = ({ event, resolve }) => {
|
|||||||
const authenticationHandle: Handle = async ({ event, resolve }) => {
|
const authenticationHandle: Handle = async ({ event, resolve }) => {
|
||||||
const { isSignedIn, isAdmin } = verifyJwt(event.cookies.get(ACCESS_TOKEN_COOKIE_NAME));
|
const { isSignedIn, isAdmin } = verifyJwt(event.cookies.get(ACCESS_TOKEN_COOKIE_NAME));
|
||||||
|
|
||||||
const isUnauthenticatedOnlyPath = event.url.pathname.startsWith('/login') || event.url.pathname.startsWith('/lc')
|
const path = event.url.pathname;
|
||||||
const isPublicPath = ['/authorize', '/device', '/health'].includes(event.url.pathname);
|
const isUnauthenticatedOnlyPath = path == '/login' || path.startsWith('/login/') || path == '/lc' || path.startsWith('/lc/')
|
||||||
const isAdminPath = event.url.pathname.startsWith('/settings/admin');
|
const isPublicPath = ['/authorize', '/device', '/health', '/healthz'].includes(path);
|
||||||
|
const isAdminPath = path == '/settings/admin' || path.startsWith('/settings/admin/');
|
||||||
|
|
||||||
if (!isUnauthenticatedOnlyPath && !isPublicPath && !isSignedIn) {
|
if (!isUnauthenticatedOnlyPath && !isPublicPath && !isSignedIn) {
|
||||||
return new Response(null, {
|
return new Response(null, {
|
||||||
status: 302,
|
status: 303,
|
||||||
headers: { location: '/login' }
|
headers: { location: '/login' }
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isUnauthenticatedOnlyPath && isSignedIn) {
|
if (isUnauthenticatedOnlyPath && isSignedIn) {
|
||||||
return new Response(null, {
|
return new Response(null, {
|
||||||
status: 302,
|
status: 303,
|
||||||
headers: { location: '/settings' }
|
headers: { location: '/settings' }
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isAdminPath && !isAdmin) {
|
if (isAdminPath && !isAdmin) {
|
||||||
return new Response(null, {
|
return new Response(null, {
|
||||||
status: 302,
|
status: 303,
|
||||||
headers: { location: '/settings' }
|
headers: { location: '/settings' }
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const response = await resolve(event);
|
return resolve(event);
|
||||||
return response;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export const handle: Handle = sequence(paraglideHandle, authenticationHandle);
|
export const handle: Handle = sequence(paraglideHandle, authenticationHandle);
|
||||||
|
|||||||
@@ -1,20 +1,2 @@
|
|||||||
import AppConfigService from '$lib/services/app-config-service';
|
// /health is an alias of /healthz, for backwards-compatibility reasons
|
||||||
import type { RequestHandler } from '@sveltejs/kit';
|
export {GET} from '../healthz/+server';
|
||||||
|
|
||||||
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'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|||||||
18
frontend/src/routes/healthz/+server.ts
Normal file
18
frontend/src/routes/healthz/+server.ts
Normal 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'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
};
|
||||||
Reference in New Issue
Block a user