/* * Amsterdam Web Communities System * Copyright (c) 2025-2026 Erbosoft Metaverse Design Solutions, All Rights Reserved * * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at https://mozilla.org/MPL/2.0/. * * SPDX-License-Identifier: MPL-2.0 */ // Package main contains the high-level Amsterdam logic. package main import ( "errors" "fmt" "net/http" "time" "git.erbosoft.com/amy/amsterdam/config" "git.erbosoft.com/amy/amsterdam/ui" "github.com/labstack/echo/v4" "github.com/labstack/echo/v4/middleware" log "github.com/sirupsen/logrus" "golang.org/x/time/rate" ) // EBUTTON is the standard error for an unknown button. var EBUTTON error = errors.New("invalid or unknown button pressed") // EINVAL is the standard error for an invalid parameter. var EINVAL error = errors.New("invalid parameter to operation") // ELOGIN is the standard error for not being logged in var ELOGIN error = errors.New("you are not logged in") // ENOPERM is the standard "not permitted" error message. var ENOPERM *echo.HTTPError = echo.NewHTTPError(http.StatusForbidden, "you are not permitted to perform this operation") // ENOACCESS is the standard "no access" error message. var ENOACCESS *echo.HTTPError = echo.NewHTTPError(http.StatusForbidden, "you are not permitted to access this page") // EPARAM is an error for no parameters being specified. var EPARAM error = errors.New("no parameters specified") /* NotImplPage is used for all TODO links, to show that something hasn't yet been implemented. * Parameters: * ctxt - The AmContext for the request. * Returns: * Command string dictating what to be rendered. * Data as a parameter for the command string. */ func NotImplPage(ctxt ui.AmContext) (string, any) { ctxt.SetLeftMenu("top") ctxt.SetFrameTitle("Function Not Implemented") ctxt.VarMap().Set("path", ctxt.URLPath()) return "framed", "notimpl.jet" } /* AmNotFoundHandler handles all paths that are "not found" in the application. * Parameters: * ctxt - The AmContext for the request. * Returns: * Command string dictating what to be rendered. * Data as a parameter for the command string. */ func AmNotFoundHandler(ctxt ui.AmContext) (string, any) { log.Infof("-> AmNotFoundHandler on path %s", ctxt.URLPath()) return "error", fmt.Errorf("Path not found: %s", ctxt.URLPath()) } /* AmErrorHandler handles all the mundane HTTP errors generated by the Echo engine. * Parameters: * err - The error to be handled. * c - The Echo context error is being handled on. */ func AmErrorHandler(err error, c echo.Context) { log.Infof("-> AmErrorHandler on path %s", c.Request().URL.Path) if c.Response().Committed { return } cerr := ui.AmWithTempContext(c, func(ctxt ui.AmContext) (string, any) { return "error", err }) if cerr != nil { log.Errorf("Error rendering error (%v): %v", err, cerr) } } // rateLimitErrorHandler is called if there's an error getting the identifier for a connection (unlikely). func rateLimitErrorHandler(c echo.Context, err error) error { return ui.AmWithTempContext(c, func(ctxt ui.AmContext) (string, any) { return "error", err }) } // rateLimitDenyHandler is called if the rate limit is exceeded by a connection. func rateLimitDenyHandler(c echo.Context, identifier string, err error) error { return ui.AmWithTempContext(c, func(ctxt ui.AmContext) (string, any) { ctxt.VarMap().Set("identifier", identifier) return "ratelimit", err }) } // AmSetupRateLimiter sets up the rate-limiting middleware. func AmSetupRateLimiter() echo.MiddlewareFunc { rcfg := middleware.RateLimiterMemoryStoreConfig{ Rate: rate.Limit(config.GlobalConfig.Site.RateLimit.Rate), Burst: config.GlobalConfig.Site.RateLimit.Burst, ExpiresIn: time.Duration(config.GlobalConfig.Site.RateLimit.ExpireMinutes) * time.Minute, } cfg := middleware.RateLimiterConfig{ Store: middleware.NewRateLimiterMemoryStoreWithConfig(rcfg), ErrorHandler: rateLimitErrorHandler, DenyHandler: rateLimitDenyHandler, } return middleware.RateLimiterWithConfig(cfg) }