From 0ac82c133b78a14239beb9034380c44d95d4bad3 Mon Sep 17 00:00:00 2001 From: Melonai Date: Mon, 26 Jul 2021 21:52:58 +0200 Subject: Portgate pages and templates --- assets/templates/_base.template.html | 11 ++++++ assets/templates/authenticate.template.html | 3 ++ assets/templates/information.template.html | 3 ++ cmd/portgate.go | 5 ++- handlers/handler.go | 56 +++++++++++++++++++---------- handlers/portgate.go | 45 ++++++++++++++++++++--- path.go | 10 ++++++ templates.go | 55 ++++++++++++++++++++++++++++ 8 files changed, 165 insertions(+), 23 deletions(-) create mode 100644 assets/templates/_base.template.html create mode 100644 assets/templates/authenticate.template.html create mode 100644 assets/templates/information.template.html create mode 100644 templates.go diff --git a/assets/templates/_base.template.html b/assets/templates/_base.template.html new file mode 100644 index 0000000..2ed25d1 --- /dev/null +++ b/assets/templates/_base.template.html @@ -0,0 +1,11 @@ + + + + + Portgate + + +

Portgate

+ {{template "content"}} + + \ No newline at end of file diff --git a/assets/templates/authenticate.template.html b/assets/templates/authenticate.template.html new file mode 100644 index 0000000..21f31e4 --- /dev/null +++ b/assets/templates/authenticate.template.html @@ -0,0 +1,3 @@ +{{define "content"}} +

This is the authentication page...

+{{end}} \ No newline at end of file diff --git a/assets/templates/information.template.html b/assets/templates/information.template.html new file mode 100644 index 0000000..e87885c --- /dev/null +++ b/assets/templates/information.template.html @@ -0,0 +1,3 @@ +{{define "content"}} +

This is the information page...

+{{end}} \ No newline at end of file diff --git a/cmd/portgate.go b/cmd/portgate.go index afa756a..2bc0693 100644 --- a/cmd/portgate.go +++ b/cmd/portgate.go @@ -17,8 +17,11 @@ func main() { log.Fatal("Failed to get Portgate config.") } + // Load and Parse all Portgate templates + templates, _ := portgate.LoadAllTemplates() + // Create handler for requests - handler := handlers.NewRequestHandler(&config) + handler := handlers.NewRequestHandler(&config, templates) // Start to listen to the outside world. log.Print("Listening for requests on port 8080.") diff --git a/handlers/handler.go b/handlers/handler.go index 6484c61..56801a3 100644 --- a/handlers/handler.go +++ b/handlers/handler.go @@ -5,6 +5,7 @@ import ( "github.com/valyala/fasthttp" "net/http" "portgate" + "strings" ) // RequestHandler keeps data relevant to the request handlers. @@ -13,12 +14,29 @@ type RequestHandler struct { config *portgate.Config // HTTP Client for requesting resources from the destination host. client fasthttp.Client + // Handler for static Portgate assets. + staticHandler fasthttp.RequestHandler + // Templates for Portgate pages. + templates portgate.Templates } -func NewRequestHandler(config *portgate.Config) RequestHandler { +// NewRequestHandler creates a new RequestHandler instance. +func NewRequestHandler(config *portgate.Config, templates portgate.Templates) RequestHandler { + // Serves static Portgate files when called. + fs := fasthttp.FS{ + Root: "./assets/static/", + PathRewrite: func(ctx *fasthttp.RequestCtx) []byte { + return []byte(strings.TrimPrefix(string(ctx.Path()), "/_portgate/static")) + }, + PathNotFound: nil, + } + staticHandler := fs.NewRequestHandler() + return RequestHandler{ - config: config, - client: fasthttp.Client{}, + config: config, + client: fasthttp.Client{}, + staticHandler: staticHandler, + templates: templates, } } @@ -26,24 +44,26 @@ func NewRequestHandler(config *portgate.Config) RequestHandler { func (h *RequestHandler) HandleRequest(ctx *fasthttp.RequestCtx) { path := portgate.ParsePath(string(ctx.Path())) + if path.IsPortgatePath() { + h.handlePortgateRequest(ctx, path) + return + } + if path.DestinationIdentifier == -1 { - // We were not given a port. + // We were not given a destination. - if path.ResourcePath == "/_portgate" { - h.handlePortgateRequest(ctx) + // Try to grab actual destination from Referer header. + // This can help us if the user followed an absolute link on a proxied page. + refererPath, err := portgate.ParsePathFromReferer(path, string(ctx.Request.Header.Referer())) + if err != nil || refererPath.DestinationIdentifier == -1 { + // The referer path also has no destination + h.handleUnknownRequest(ctx) } else { - // Try to grab actual destination from Referer header. - refererPath, err := portgate.ParsePathFromReferer(path, string(ctx.Request.Header.Referer())) - if err != nil || refererPath.DestinationIdentifier == -1 { - // The referer path also has no destination - h.handleUnknownRequest(ctx) - } else { - // We found the destination from the referer path, so we - // redirect the user to the Portgate URL they should've requested. - - portgateUrl := fmt.Sprintf("/%d%s", refererPath.DestinationIdentifier, refererPath.ResourcePath) - ctx.Redirect(portgateUrl, http.StatusTemporaryRedirect) - } + // We found the destination from the referer path, so we + // redirect the user to the Portgate URL they should've requested. + + portgateUrl := fmt.Sprintf("/%d%s", refererPath.DestinationIdentifier, refererPath.ResourcePath) + ctx.Redirect(portgateUrl, http.StatusTemporaryRedirect) } } else { // We were given a port, so we have to pass the request through to the destination host. diff --git a/handlers/portgate.go b/handlers/portgate.go index 9d4f3ef..1cd9bb8 100644 --- a/handlers/portgate.go +++ b/handlers/portgate.go @@ -1,10 +1,47 @@ package handlers -import "github.com/valyala/fasthttp" +import ( + "github.com/valyala/fasthttp" + "net/http" + "portgate" +) // handlePortgateRequest handles all Portgate specific request for either showing Portgate // specific pages or handling creation of authorization tokens. -func (h *RequestHandler) handlePortgateRequest(ctx *fasthttp.RequestCtx) { - // TODO: Implement authentication, authorization - _, _ = ctx.WriteString("Portgate request.") +func (h *RequestHandler) handlePortgateRequest(ctx *fasthttp.RequestCtx, path portgate.Path) { + if path.IsPortgateStaticPath() { + h.staticHandler(ctx) + } else { + // TODO: Implement authentication, authorization + h.handlePortgateIndexRequest(ctx) + } +} + +// handlePortgateIndexRequest delegates requests directed at /_portgate. +func (h *RequestHandler) handlePortgateIndexRequest(ctx *fasthttp.RequestCtx) { + switch string(ctx.Method()) { + case "GET": + // If we received a GET request, the user wants to see either the login page, + // or an info page if they're already authenticated. + h.handlePortgatePageRequest(ctx) + case "POST": + // If we received a POST request, the user wants to get authorization to Portgate. + h.handleAuthenticateRequest(ctx) + } +} + +// handlePortgatePageRequest renders the Portgate page with either the authentication page or +// a basic information page. +func (h *RequestHandler) handlePortgatePageRequest(ctx *fasthttp.RequestCtx) { + // We render the page template and pass it to the user. + ctx.Response.Header.SetContentType("text/html") + err := h.templates.ExecuteTemplate(ctx, "authenticate.template.html", nil) + if err != nil { + ctx.SetStatusCode(http.StatusInternalServerError) + _, _ = ctx.WriteString("An error occurred.") + } +} + +func (h *RequestHandler) handleAuthenticateRequest(ctx *fasthttp.RequestCtx) { + // TODO } diff --git a/path.go b/path.go index 3aa7816..82a8353 100644 --- a/path.go +++ b/path.go @@ -68,3 +68,13 @@ func ParsePathFromReferer(p Path, r string) (Path, error) { ResourcePath: p.ResourcePath, }, nil } + +// IsPortgatePath returns whether the Path goes to Portgate. +func (p *Path) IsPortgatePath() bool { + return p.DestinationIdentifier == -1 && strings.HasPrefix(p.ResourcePath, "/_portgate") +} + +// IsPortgateStaticPath returns whether the Path goes to the Portgate static files. +func (p *Path) IsPortgateStaticPath() bool { + return p.DestinationIdentifier == -1 && strings.HasPrefix(p.ResourcePath, "/_portgate/static") +} diff --git a/templates.go b/templates.go new file mode 100644 index 0000000..25cb480 --- /dev/null +++ b/templates.go @@ -0,0 +1,55 @@ +package portgate + +import ( + "errors" + "html/template" + "io" + "io/fs" + "path/filepath" + "strings" +) + +// Templates houses all the Portgate page templates and can render them with correct styling. +type Templates struct { + templateMap map[string]*template.Template +} + +// LoadAllTemplates loads all Portgate templates from ./assets/templates/ and bundles them +// into a single Templates instance. +func LoadAllTemplates() (Templates, error) { + templateMap := make(map[string]*template.Template) + + // We walk through every file in the templates folder. + err := filepath.Walk("./assets/templates", func(path string, info fs.FileInfo, err error) error { + // We only care about files which are actual templates and are not of special use (like "_base") + if !strings.HasPrefix(info.Name(), "_") && strings.HasSuffix(path, ".template.html") { + // We bundle the templates together with the base template so that we can render them together later. + t, err := template.ParseFiles("./assets/templates/_base.template.html", path) + if err == nil { + // We keep the parsed template in the templateMap by it's filename. + templateMap[info.Name()] = t + } + } + + return err + }) + + if err != nil { + return Templates{}, err + } + + return Templates{ + templateMap: templateMap, + }, nil +} + +// ExecuteTemplate executes a single template with the given name and data and writes it to the given +// io.Writer, which is usually fasthttp.RequestCtx. +func (templates *Templates) ExecuteTemplate(w io.Writer, name string, data interface{}) error { + t := templates.templateMap[name] + if t == nil { + return errors.New("Unknown template name: " + name) + } + + return t.ExecuteTemplate(w, "_base.template.html", data) +} -- cgit 1.4.1