Refactor OIDC middleware handler logic.

This commit is contained in:
Kailash Nadh 2024-04-02 12:23:25 +05:30
parent e406b2516a
commit 8ca95f6827
3 changed files with 178 additions and 180 deletions

View file

@ -2,14 +2,12 @@ package main
import ( import (
"bytes" "bytes"
"crypto/subtle"
"net/http" "net/http"
"net/url" "net/url"
"path" "path"
"regexp" "regexp"
"strings"
"github.com/knadh/listmonk/internal/oidc" "github.com/knadh/listmonk/internal/auth"
"github.com/knadh/paginator" "github.com/knadh/paginator"
"github.com/labstack/echo/v4" "github.com/labstack/echo/v4"
"github.com/labstack/echo/v4/middleware" "github.com/labstack/echo/v4/middleware"
@ -23,6 +21,9 @@ const (
sortDesc = "desc" sortDesc = "desc"
basicAuthd = "basicauthd" basicAuthd = "basicauthd"
// URIs.
uriAdmin = "/admin"
) )
type okResp struct { type okResp struct {
@ -52,52 +53,7 @@ var (
// registerHandlers registers HTTP handlers. // registerHandlers registers HTTP handlers.
func initHTTPHandlers(e *echo.Echo, app *App) { func initHTTPHandlers(e *echo.Echo, app *App) {
// Group of private handlers with BasicAuth. // Default error handler.
var g *echo.Group
if app.constants.Security.OIDC.Enabled {
cbURL, err := url.JoinPath(app.constants.RootURL, "/auth/oidc")
if err != nil {
lo.Fatalf("error preparing OIDC callback URL: %v", err)
}
oiCfg := oidc.Config{
ProviderURL: app.constants.Security.OIDC.Provider,
ClientID: app.constants.Security.OIDC.ClientID,
ClientSecret: app.constants.Security.OIDC.ClientSecret,
RedirectURL: cbURL,
}
// If admin username and password are set in the config file,
// enable BasicAuth for /api/* handlers.
if len(app.constants.AdminUsername) > 0 && len(app.constants.AdminPassword) > 0 {
baCfg := middleware.BasicAuthConfig{
// If the Authorization header isn't BasicAuth (is OIDC), skip validation.
Skipper: func(c echo.Context) bool {
auth := c.Request().Header.Get(echo.HeaderAuthorization)
l := len("basic")
if len(auth) > l+1 && strings.EqualFold(auth[:l], "basic") {
return false
}
return true
},
Validator: basicAuth,
}
// Skip OIDC check if the request is already BasicAuth'd.
oiCfg.Skipper = func(c echo.Context) bool {
return c.Get(basicAuthd) != nil && c.Get(basicAuthd).(bool)
}
g = e.Group("", middleware.BasicAuthWithConfig(baCfg), oidc.OIDCAuth(oiCfg))
} else {
g = e.Group("", oidc.OIDCAuth(oiCfg))
}
} else if len(app.constants.AdminUsername) > 0 && len(app.constants.AdminPassword) > 0 {
g = e.Group("", middleware.BasicAuth(basicAuth))
} else {
g = e.Group("")
}
e.HTTPErrorHandler = func(err error, c echo.Context) { e.HTTPErrorHandler = func(err error, c echo.Context) {
// Generic, non-echo error. Log it. // Generic, non-echo error. Log it.
if _, ok := err.(*echo.HTTPError); !ok { if _, ok := err.(*echo.HTTPError); !ok {
@ -106,171 +62,230 @@ func initHTTPHandlers(e *echo.Echo, app *App) {
e.DefaultHTTPErrorHandler(err, c) e.DefaultHTTPErrorHandler(err, c)
} }
// Admin JS app views. var (
// /admin/static/* file server is registered in initHTTPServer(). // Authenticated /api/* handlers.
e.GET("/", func(c echo.Context) error { api = e.Group("", app.auth.Middleware, func(next echo.HandlerFunc) echo.HandlerFunc {
return c.Render(http.StatusOK, "home", publicTpl{Title: "listmonk"}) return func(c echo.Context) error {
}) u := c.Get(auth.UserKey)
g.GET(path.Join(adminRoot, ""), handleAdminPage) // On no-auth, respond with a JSON error.
g.GET(path.Join(adminRoot, "/custom.css"), serveCustomAppearance("admin.custom_css")) if err, ok := u.(*echo.HTTPError); ok {
g.GET(path.Join(adminRoot, "/custom.js"), serveCustomAppearance("admin.custom_js")) return err
g.GET(path.Join(adminRoot, "/*"), handleAdminPage) }
// OIDC oAuth callback. The execution is handled by the middleware. return next(c)
g.GET("/auth/oidc", func(c echo.Context) error { }
return nil })
})
// Authenticated non /api handlers.
a = e.Group("", app.auth.Middleware, func(next echo.HandlerFunc) echo.HandlerFunc {
return func(c echo.Context) error {
u := c.Get(auth.UserKey)
// On no-auth, redirect to login page
if _, ok := u.(*echo.HTTPError); ok {
u, _ := url.Parse(app.constants.LoginURL)
q := url.Values{}
q.Set("next", c.Request().RequestURI)
u.RawQuery = q.Encode()
return c.Redirect(http.StatusTemporaryRedirect, u.String())
}
return next(c)
}
})
// Public unauthenticated endpoints.
p = e.Group("")
)
// Authenticated endpoints.
a.GET(path.Join(uriAdmin, ""), handleAdminPage)
a.GET(path.Join(uriAdmin, "/custom.css"), serveCustomAppearance("admin.custom_css"))
a.GET(path.Join(uriAdmin, "/custom.js"), serveCustomAppearance("admin.custom_js"))
a.GET(path.Join(uriAdmin, "/*"), handleAdminPage)
pm := app.auth.Perm
// API endpoints. // API endpoints.
g.GET("/api/health", handleHealthCheck) api.GET("/api/health", handleHealthCheck)
g.GET("/api/config", handleGetServerConfig) api.GET("/api/config", handleGetServerConfig)
g.GET("/api/lang/:lang", handleGetI18nLang) api.GET("/api/lang/:lang", handleGetI18nLang)
g.GET("/api/dashboard/charts", handleGetDashboardCharts) api.GET("/api/dashboard/charts", handleGetDashboardCharts)
g.GET("/api/dashboard/counts", handleGetDashboardCounts) api.GET("/api/dashboard/counts", handleGetDashboardCounts)
g.GET("/api/settings", handleGetSettings) api.GET("/api/settings", pm(handleGetSettings, "settings:get"))
g.PUT("/api/settings", handleUpdateSettings) api.PUT("/api/settings", pm(handleUpdateSettings, "settings:manage"))
g.POST("/api/settings/smtp/test", handleTestSMTPSettings) api.POST("/api/settings/smtp/test", pm(handleTestSMTPSettings, "settings:manage"))
g.POST("/api/admin/reload", handleReloadApp) api.POST("/api/admin/reload", pm(handleReloadApp, "settings:manage"))
g.GET("/api/logs", handleGetLogs) api.GET("/api/logs", pm(handleGetLogs, "settings:get"))
g.GET("/api/about", handleGetAboutInfo) api.GET("/api/events", pm(handleEventStream, "settings:get"))
api.GET("/api/about", handleGetAboutInfo)
g.GET("/api/subscribers/:id", handleGetSubscriber) api.GET("/api/subscribers", pm(handleQuerySubscribers, "subscribers:get_all", "subscribers:get"))
g.GET("/api/subscribers/:id/export", handleExportSubscriberData) api.GET("/api/subscribers/:id", pm(handleGetSubscriber, "subscribers:get_all", "subscribers:get"))
g.GET("/api/subscribers/:id/bounces", handleGetSubscriberBounces) api.GET("/api/subscribers/:id/export", pm(handleExportSubscriberData, "subscribers:get_all", "subscribers:get"))
g.DELETE("/api/subscribers/:id/bounces", handleDeleteSubscriberBounces) api.GET("/api/subscribers/:id/bounces", pm(handleGetSubscriberBounces, "bounces:get"))
g.POST("/api/subscribers", handleCreateSubscriber) api.DELETE("/api/subscribers/:id/bounces", pm(handleDeleteSubscriberBounces, "bounces:manage"))
g.PUT("/api/subscribers/:id", handleUpdateSubscriber) api.POST("/api/subscribers", pm(handleCreateSubscriber, "subscribers:manage"))
g.POST("/api/subscribers/:id/optin", handleSubscriberSendOptin) api.PUT("/api/subscribers/:id", pm(handleUpdateSubscriber, "subscribers:manage"))
g.PUT("/api/subscribers/blocklist", handleBlocklistSubscribers) api.POST("/api/subscribers/:id/optin", pm(handleSubscriberSendOptin, "subscribers:manage"))
g.PUT("/api/subscribers/:id/blocklist", handleBlocklistSubscribers) api.PUT("/api/subscribers/blocklist", pm(handleBlocklistSubscribers, "subscribers:manage"))
g.PUT("/api/subscribers/lists/:id", handleManageSubscriberLists) api.PUT("/api/subscribers/:id/blocklist", pm(handleBlocklistSubscribers, "subscribers:manage"))
g.PUT("/api/subscribers/lists", handleManageSubscriberLists) api.PUT("/api/subscribers/lists/:id", pm(handleManageSubscriberLists, "subscribers:manage"))
g.DELETE("/api/subscribers/:id", handleDeleteSubscribers) api.PUT("/api/subscribers/lists", pm(handleManageSubscriberLists, "subscribers:manage"))
g.DELETE("/api/subscribers", handleDeleteSubscribers) api.DELETE("/api/subscribers/:id", pm(handleDeleteSubscribers, "subscribers:manage"))
api.DELETE("/api/subscribers", pm(handleDeleteSubscribers, "subscribers:manage"))
g.GET("/api/bounces", handleGetBounces) api.GET("/api/bounces", pm(handleGetBounces, "bounces:get"))
g.GET("/api/bounces/:id", handleGetBounces) api.GET("/api/bounces/:id", pm(handleGetBounces, "bounces:get"))
g.DELETE("/api/bounces", handleDeleteBounces) api.DELETE("/api/bounces", pm(handleDeleteBounces, "bounces:manage"))
g.DELETE("/api/bounces/:id", handleDeleteBounces) api.DELETE("/api/bounces/:id", pm(handleDeleteBounces, "bounces:manage"))
// Subscriber operations based on arbitrary SQL queries. // Subscriber operations based on arbitrary SQL queries.
// These aren't very REST-like. // These aren't very REST-like.
g.POST("/api/subscribers/query/delete", handleDeleteSubscribersByQuery) api.POST("/api/subscribers/query/delete", pm(handleDeleteSubscribersByQuery, "subscribers:manage"))
g.PUT("/api/subscribers/query/blocklist", handleBlocklistSubscribersByQuery) api.PUT("/api/subscribers/query/blocklist", pm(handleBlocklistSubscribersByQuery, "subscribers:manage"))
g.PUT("/api/subscribers/query/lists", handleManageSubscriberListsByQuery) api.PUT("/api/subscribers/query/lists", pm(handleManageSubscriberListsByQuery, "subscribers:manage"))
g.GET("/api/subscribers", handleQuerySubscribers) api.GET("/api/subscribers/export",
g.GET("/api/subscribers/export", pm(middleware.GzipWithConfig(middleware.GzipConfig{Level: 9})(handleExportSubscribers), "subscribers:get_all", "subscribers:get"))
middleware.GzipWithConfig(middleware.GzipConfig{Level: 9})(handleExportSubscribers))
g.GET("/api/import/subscribers", handleGetImportSubscribers) api.GET("/api/import/subscribers", pm(handleGetImportSubscribers, "subscribers:import"))
g.GET("/api/import/subscribers/logs", handleGetImportSubscriberStats) api.GET("/api/import/subscribers/logs", pm(handleGetImportSubscriberStats, "subscribers:import"))
g.POST("/api/import/subscribers", handleImportSubscribers) api.POST("/api/import/subscribers", pm(handleImportSubscribers, "subscribers:import"))
g.DELETE("/api/import/subscribers", handleStopImportSubscribers) api.DELETE("/api/import/subscribers", pm(handleStopImportSubscribers, "subscribers:import"))
g.GET("/api/lists", handleGetLists) // Individual list permissions are applied directly within handleGetLists.
g.GET("/api/lists/:id", handleGetLists) api.GET("/api/lists", handleGetLists)
g.POST("/api/lists", handleCreateList) api.GET("/api/lists/:id", listPerm(handleGetLists))
g.PUT("/api/lists/:id", handleUpdateList) api.POST("/api/lists", pm(handleCreateList, "lists:manage_all"))
g.DELETE("/api/lists/:id", handleDeleteLists) api.PUT("/api/lists/:id", listPerm(handleUpdateList))
api.DELETE("/api/lists/:id", listPerm(handleDeleteLists))
g.GET("/api/campaigns", handleGetCampaigns) api.GET("/api/campaigns", pm(handleGetCampaigns, "campaigns:get"))
g.GET("/api/campaigns/running/stats", handleGetRunningCampaignStats) api.GET("/api/campaigns/running/stats", pm(handleGetRunningCampaignStats, "campaigns:get"))
g.GET("/api/campaigns/:id", handleGetCampaign) api.GET("/api/campaigns/:id", pm(handleGetCampaign, "campaigns:get"))
g.GET("/api/campaigns/analytics/:type", handleGetCampaignViewAnalytics) api.GET("/api/campaigns/analytics/:type", pm(handleGetCampaignViewAnalytics, "campaigns:get_analytics"))
g.GET("/api/campaigns/:id/preview", handlePreviewCampaign) api.GET("/api/campaigns/:id/preview", pm(handlePreviewCampaign, "campaigns:get"))
g.POST("/api/campaigns/:id/preview", handlePreviewCampaign) api.POST("/api/campaigns/:id/preview", pm(handlePreviewCampaign, "campaigns:get"))
g.POST("/api/campaigns/:id/content", handleCampaignContent) api.POST("/api/campaigns/:id/content", pm(handleCampaignContent, "campaigns:manage"))
g.POST("/api/campaigns/:id/text", handlePreviewCampaign) api.POST("/api/campaigns/:id/text", pm(handlePreviewCampaign, "campaigns:manage"))
g.POST("/api/campaigns/:id/test", handleTestCampaign) api.POST("/api/campaigns/:id/test", pm(handleTestCampaign, "campaigns:manage"))
g.POST("/api/campaigns", handleCreateCampaign) api.POST("/api/campaigns", pm(handleCreateCampaign, "campaigns:manage"))
g.PUT("/api/campaigns/:id", handleUpdateCampaign) api.PUT("/api/campaigns/:id", pm(handleUpdateCampaign, "campaigns:manage"))
g.PUT("/api/campaigns/:id/status", handleUpdateCampaignStatus) api.PUT("/api/campaigns/:id/status", pm(handleUpdateCampaignStatus, "campaigns:manage"))
g.PUT("/api/campaigns/:id/archive", handleUpdateCampaignArchive) api.PUT("/api/campaigns/:id/archive", pm(handleUpdateCampaignArchive, "campaigns:manage"))
g.DELETE("/api/campaigns/:id", handleDeleteCampaign) api.DELETE("/api/campaigns/:id", pm(handleDeleteCampaign, "campaigns:manage"))
g.GET("/api/media", handleGetMedia) api.GET("/api/media", pm(handleGetMedia, "media:get"))
g.GET("/api/media/:id", handleGetMedia) api.GET("/api/media/:id", pm(handleGetMedia, "media:get"))
g.POST("/api/media", handleUploadMedia) api.POST("/api/media", pm(handleUploadMedia, "media:manage"))
g.DELETE("/api/media/:id", handleDeleteMedia) api.DELETE("/api/media/:id", pm(handleDeleteMedia, "media:manage"))
g.GET("/api/templates", handleGetTemplates) api.GET("/api/templates", pm(handleGetTemplates, "templates:get"))
g.GET("/api/templates/:id", handleGetTemplates) api.GET("/api/templates/:id", pm(handleGetTemplates, "templates:get"))
g.GET("/api/templates/:id/preview", handlePreviewTemplate) api.GET("/api/templates/:id/preview", pm(handlePreviewTemplate, "templates:get"))
g.POST("/api/templates/preview", handlePreviewTemplate) api.POST("/api/templates/preview", pm(handlePreviewTemplate, "templates:get"))
g.POST("/api/templates", handleCreateTemplate) api.POST("/api/templates", pm(handleCreateTemplate, "templates:manage"))
g.PUT("/api/templates/:id", handleUpdateTemplate) api.PUT("/api/templates/:id", pm(handleUpdateTemplate, "templates:manage"))
g.PUT("/api/templates/:id/default", handleTemplateSetDefault) api.PUT("/api/templates/:id/default", pm(handleTemplateSetDefault, "templates:manage"))
g.DELETE("/api/templates/:id", handleDeleteTemplate) api.DELETE("/api/templates/:id", pm(handleDeleteTemplate, "templates:manage"))
g.DELETE("/api/maintenance/subscribers/:type", handleGCSubscribers) api.DELETE("/api/maintenance/subscribers/:type", pm(handleGCSubscribers, "settings:maintain"))
g.DELETE("/api/maintenance/analytics/:type", handleGCCampaignAnalytics) api.DELETE("/api/maintenance/analytics/:type", pm(handleGCCampaignAnalytics, "settings:maintain"))
g.DELETE("/api/maintenance/subscriptions/unconfirmed", handleGCSubscriptions) api.DELETE("/api/maintenance/subscriptions/unconfirmed", pm(handleGCSubscriptions, "settings:maintain"))
g.POST("/api/tx", handleSendTxMessage) api.POST("/api/tx", pm(handleSendTxMessage, "tx:send"))
g.GET("/api/events", handleEventStream) api.GET("/api/profile", handleGetUserProfile)
api.PUT("/api/profile", handleUpdateUserProfile)
api.GET("/api/users", pm(handleGetUsers, "users:get"))
api.GET("/api/users/:id", pm(handleGetUsers, "users:get"))
api.POST("/api/users", pm(handleCreateUser, "users:manage"))
api.PUT("/api/users/:id", pm(handleUpdateUser, "users:manage"))
api.DELETE("/api/users", pm(handleDeleteUsers, "users:manage"))
api.DELETE("/api/users/:id", pm(handleDeleteUsers, "users:manage"))
api.POST("/api/logout", handleLogout)
api.GET("/api/roles", pm(handleGetRoles, "roles:get"))
api.POST("/api/roles", pm(handleCreateRole, "roles:manage"))
api.PUT("/api/roles/:id", pm(handleUpdateRole, "roles:manage"))
api.DELETE("/api/roles/:id", pm(handleDeleteRole, "roles:manage"))
if app.constants.BounceWebhooksEnabled { if app.constants.BounceWebhooksEnabled {
// Private authenticated bounce endpoint. // Private authenticated bounce endpoint.
g.POST("/webhooks/bounce", handleBounceWebhook) api.POST("/webhooks/bounce", pm(handleBounceWebhook, "webhooks:post_bounce"))
// Public bounce endpoints for webservices like SES. // Public bounce endpoints for webservices like SES.
e.POST("/webhooks/service/:service", handleBounceWebhook) p.POST("/webhooks/service/:service", handleBounceWebhook)
} }
// =================================================================
// Public API endpoints. // Public API endpoints.
e.GET("/api/public/lists", handleGetPublicLists)
e.POST("/api/public/subscription", handlePublicSubscription)
// Landing page.
p.GET("/", func(c echo.Context) error {
return c.Render(http.StatusOK, "home", publicTpl{Title: "listmonk"})
})
// Public admin endpoints (login page, OIDC endpoints).
p.GET(path.Join(uriAdmin, "/login"), handleLoginPage)
p.POST(path.Join(uriAdmin, "/login"), handleLoginPage)
if app.constants.Security.OIDC.Enabled {
p.POST("/auth/oidc", handleOIDCLogin)
p.GET("/auth/oidc", handleOIDCFinish)
}
// Public APIs.
p.GET("/api/public/lists", handleGetPublicLists)
p.POST("/api/public/subscription", handlePublicSubscription)
if app.constants.EnablePublicArchive { if app.constants.EnablePublicArchive {
e.GET("/api/public/archive", handleGetCampaignArchives) p.GET("/api/public/archive", handleGetCampaignArchives)
} }
// /public/static/* file server is registered in initHTTPServer(). // /public/static/* file server is registered in initHTTPServer().
// Public subscriber facing views. // Public subscriber facing views.
e.GET("/subscription/form", handleSubscriptionFormPage) p.GET("/subscription/form", handleSubscriptionFormPage)
e.POST("/subscription/form", handleSubscriptionForm) p.POST("/subscription/form", handleSubscriptionForm)
e.GET("/subscription/:campUUID/:subUUID", noIndex(validateUUID(subscriberExists(handleSubscriptionPage), p.GET("/subscription/:campUUID/:subUUID", noIndex(validateUUID(subscriberExists(handleSubscriptionPage),
"campUUID", "subUUID"))) "campUUID", "subUUID")))
e.POST("/subscription/:campUUID/:subUUID", validateUUID(subscriberExists(handleSubscriptionPrefs), p.POST("/subscription/:campUUID/:subUUID", validateUUID(subscriberExists(handleSubscriptionPrefs),
"campUUID", "subUUID")) "campUUID", "subUUID"))
e.GET("/subscription/optin/:subUUID", noIndex(validateUUID(subscriberExists(handleOptinPage), "subUUID"))) p.GET("/subscription/optin/:subUUID", noIndex(validateUUID(subscriberExists(handleOptinPage), "subUUID")))
e.POST("/subscription/optin/:subUUID", validateUUID(subscriberExists(handleOptinPage), "subUUID")) p.POST("/subscription/optin/:subUUID", validateUUID(subscriberExists(handleOptinPage), "subUUID"))
e.POST("/subscription/export/:subUUID", validateUUID(subscriberExists(handleSelfExportSubscriberData), p.POST("/subscription/export/:subUUID", validateUUID(subscriberExists(handleSelfExportSubscriberData),
"subUUID")) "subUUID"))
e.POST("/subscription/wipe/:subUUID", validateUUID(subscriberExists(handleWipeSubscriberData), p.POST("/subscription/wipe/:subUUID", validateUUID(subscriberExists(handleWipeSubscriberData),
"subUUID")) "subUUID"))
e.GET("/link/:linkUUID/:campUUID/:subUUID", noIndex(validateUUID(handleLinkRedirect, p.GET("/link/:linkUUID/:campUUID/:subUUID", noIndex(validateUUID(handleLinkRedirect,
"linkUUID", "campUUID", "subUUID"))) "linkUUID", "campUUID", "subUUID")))
e.GET("/campaign/:campUUID/:subUUID", noIndex(validateUUID(handleViewCampaignMessage, p.GET("/campaign/:campUUID/:subUUID", noIndex(validateUUID(handleViewCampaignMessage,
"campUUID", "subUUID"))) "campUUID", "subUUID")))
e.GET("/campaign/:campUUID/:subUUID/px.png", noIndex(validateUUID(handleRegisterCampaignView, p.GET("/campaign/:campUUID/:subUUID/px.png", noIndex(validateUUID(handleRegisterCampaignView,
"campUUID", "subUUID"))) "campUUID", "subUUID")))
if app.constants.EnablePublicArchive { if app.constants.EnablePublicArchive {
e.GET("/archive", handleCampaignArchivesPage) p.GET("/archive", handleCampaignArchivesPage)
e.GET("/archive.xml", handleGetCampaignArchivesFeed) p.GET("/archive.xml", handleGetCampaignArchivesFeed)
e.GET("/archive/:id", handleCampaignArchivePage) p.GET("/archive/:id", handleCampaignArchivePage)
e.GET("/archive/latest", handleCampaignArchivePageLatest) p.GET("/archive/latest", handleCampaignArchivePageLatest)
} }
e.GET("/public/custom.css", serveCustomAppearance("public.custom_css")) p.GET("/public/custom.css", serveCustomAppearance("public.custom_css"))
e.GET("/public/custom.js", serveCustomAppearance("public.custom_js")) p.GET("/public/custom.js", serveCustomAppearance("public.custom_js"))
// Public health API endpoint. // Public health API endpoint.
e.GET("/health", handleHealthCheck) p.GET("/health", handleHealthCheck)
// 404 pages. // 404 pages.
e.RouteNotFound("/*", func(c echo.Context) error { p.RouteNotFound("/*", func(c echo.Context) error {
return c.Render(http.StatusNotFound, tplMessage, return c.Render(http.StatusNotFound, tplMessage,
makeMsgTpl("404 - "+app.i18n.T("public.notFoundTitle"), "", "")) makeMsgTpl("404 - "+app.i18n.T("public.notFoundTitle"), "", ""))
}) })
e.RouteNotFound("/api/*", func(c echo.Context) error { p.RouteNotFound("/api/*", func(c echo.Context) error {
return echo.NewHTTPError(http.StatusNotFound, "404 unknown endpoint") return echo.NewHTTPError(http.StatusNotFound, "404 unknown endpoint")
}) })
e.RouteNotFound("/admin/*", func(c echo.Context) error { p.RouteNotFound("/admin/*", func(c echo.Context) error {
return echo.NewHTTPError(http.StatusNotFound, "404 page not found") return echo.NewHTTPError(http.StatusNotFound, "404 page not found")
}) })
} }
@ -279,7 +294,7 @@ func initHTTPHandlers(e *echo.Echo, app *App) {
func handleAdminPage(c echo.Context) error { func handleAdminPage(c echo.Context) error {
app := c.Get("app").(*App) app := c.Get("app").(*App)
b, err := app.fs.Read(path.Join(adminRoot, "/index.html")) b, err := app.fs.Read(path.Join(uriAdmin, "/index.html"))
if err != nil { if err != nil {
return echo.NewHTTPError(http.StatusInternalServerError, err.Error()) return echo.NewHTTPError(http.StatusInternalServerError, err.Error())
} }
@ -327,19 +342,6 @@ func serveCustomAppearance(name string) echo.HandlerFunc {
} }
} }
// basicAuth middleware does an HTTP BasicAuth authentication for admin handlers.
func basicAuth(username, password string, c echo.Context) (bool, error) {
app := c.Get("app").(*App)
if subtle.ConstantTimeCompare([]byte(username), app.constants.AdminUsername) == 1 &&
subtle.ConstantTimeCompare([]byte(password), app.constants.AdminPassword) == 1 {
// Mark the request as BasicAuth'd so that the OIDC check (if enabled) is skipped.
c.Set(basicAuthd, true)
return true, nil
}
return false, nil
}
// validateUUID middleware validates the UUID string format for a given set of params. // validateUUID middleware validates the UUID string format for a given set of params.
func validateUUID(next echo.HandlerFunc, params ...string) echo.HandlerFunc { func validateUUID(next echo.HandlerFunc, params ...string) echo.HandlerFunc {
return func(c echo.Context) error { return func(c echo.Context) error {

View file

@ -49,9 +49,6 @@ import (
const ( const (
queryFilePath = "queries.sql" queryFilePath = "queries.sql"
// Root URI of the admin frontend.
adminRoot = "/admin"
) )
// constants contains static, constant config values required by the app. // constants contains static, constant config values required by the app.

View file

@ -46,7 +46,6 @@
</div> </div>
</div> </div>
</div> </div>
</div>
</template> </template>
<script> <script>