feat: middleware to return error as html

This commit is contained in:
efim 2023-10-09 07:45:29 +00:00
parent ec47c9d610
commit 8842235372
8 changed files with 202 additions and 73 deletions

View File

@ -242,11 +242,12 @@ https://stackoverflow.com/questions/62307431/firefox-sends-secure-cookies-to-loc
see: except on localhost : https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie
** TODO prettying up server responses for "we show html" land
** DONE prettying up server responses for "we show html" land
let's do this also, yes
*** DONE logout should push root url in htmx
*** TODO lets make 404 page and return it
*** TODO lets make 401 page and return it
*** DONE lets make 404 page and return it
*** DONE lets make 401 page and return it
*** DONE and let's make NavInfo init common for reuse
** TODO get icons for the auth providers. surely they are accessible from the pocketbase itself?
http://localhost:8090/_/images/oauth2/apple.svg
yes.

View File

@ -11,6 +11,7 @@ func main() {
app := pocketbase.New()
middleware.AddCookieSessionMiddleware(app)
middleware.AddErrorsMiddleware(app)
pages.AddPageRoutes(app)
if err := app.Start(); err != nil {

54
middleware/httpErrors.go Normal file
View File

@ -0,0 +1,54 @@
package middleware
import (
"bytes"
"html/template"
"log"
"github.com/pocketbase/pocketbase"
"github.com/pocketbase/pocketbase/core"
)
func AddErrorsMiddleware(app *pocketbase.PocketBase) {
app.OnBeforeApiError().Add(func(e *core.ApiErrorEvent) error {
log.Printf("in before api error with %+v with response %v and error %+v", e, e.HttpContext.Response(), e.Error)
// oh, i guess i could do redirect?
return renderErrorPage(e)
})
}
var redirectTemplate = `
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="refresh" content="0; url='/error/{{ . }}'" />
</head>
<body>
<p>Redirecting to error page</p>
</body>
</html>
`
var tmpl = template.Must( template.New("redirect-to-error").Parse(redirectTemplate) )
func renderErrorPage(e *core.ApiErrorEvent) error {
errorMessage := e.Error.Error()
log.Printf("in error to html middleware for %s with status %+v", errorMessage, e)
errorCode := 500
switch errorMessage {
case "Not Found.":
// not authorized
errorCode = 404
case "The request requires admin or record authorization token to be set.":
// not found
errorCode = 401
}
var instantiatedTemplate bytes.Buffer
if err := tmpl.Execute(&instantiatedTemplate, errorCode); err != nil {
// couldn't execute the template
return e.HttpContext.HTML(200, "Error 500")
}
return e.HttpContext.HTML(200, instantiatedTemplate.String())
}

View File

@ -6,6 +6,7 @@ import (
"html/template"
"math/rand"
"net/http"
"strconv"
"github.com/labstack/echo/v5"
"github.com/pocketbase/pocketbase"
@ -21,7 +22,8 @@ var staticFilesFS embed.FS
func AddPageRoutes(app *pocketbase.PocketBase) {
app.OnBeforeServe().Add(getIndexPageRoute(app))
app.OnBeforeServe().Add(somePageRoute)
app.OnBeforeServe().Add(getSomePageRoute(app))
app.OnBeforeServe().Add(getErrorPageRoute(app))
app.OnBeforeServe().Add(func(e *core.ServeEvent) error {
e.Router.StaticFS("/static", staticFilesFS)
@ -41,40 +43,13 @@ func getIndexPageRoute(app *pocketbase.PocketBase) func(*core.ServeEvent) error
return func(e *core.ServeEvent) error {
e.Router.GET("/", func(c echo.Context) error {
// first collect data
info := apis.RequestInfo(c)
admin := info.Admin // nil if not authenticated as admin
record := info.AuthRecord // nil if not authenticated as regular auth record
isGuest := admin == nil && record == nil
username := ""
switch {
case admin != nil:
username = admin.Email
case record != nil:
username = record.Username()
}
oauthProviders := app.Settings().NamedAuthProviderConfigs()
oauthProviderNames := make([]string, 0, len(oauthProviders))
for name, config := range oauthProviders {
if config.Enabled {
oauthProviderNames = append(oauthProviderNames, name)
}
}
navInfoData := initNavInfoData(app, c)
indexPageData := struct {
IsGuest, IsAdmin bool
Username string
EnabledOauthProviders []string
BackendMessage string
NavInfo navInfo
}{
IsAdmin: admin != nil,
NavInfo: navInfo{
IsGuest: isGuest,
Username: username,
EnabledOauthProviders: oauthProviderNames,
},
BackendMessage: "Hello from the backend!",
NavInfo: navInfoData,
}
// then render template with it
@ -101,22 +76,12 @@ func stringWithCharset(length int, charset string) string {
}
return string(b)
}
func somePageRoute(e *core.ServeEvent) error {
func getSomePageRoute(app *pocketbase.PocketBase) func(*core.ServeEvent) error {
return func(e *core.ServeEvent) error {
e.Router.GET("/somepage", func(c echo.Context) error {
// get data
// and since i'm using 'base.gohtml' with Nav, i'll need Nav info
info := apis.RequestInfo(c)
admin := info.Admin // nil if not authenticated as admin
record := info.AuthRecord // nil if not authenticated as regular auth record
username := ""
switch {
case admin != nil:
username = admin.Email
case record != nil:
username = record.Username()
}
navInfoData := initNavInfoData(app, c)
somePageData := struct {
RandomNumber int
@ -125,9 +90,7 @@ func somePageRoute(e *core.ServeEvent) error {
}{
RandomNumber: rand.Int(),
RandomString: stringWithCharset(25, charset),
NavInfo: navInfo{
Username: username,
},
NavInfo: navInfoData,
}
// then render template with it
@ -142,3 +105,83 @@ func somePageRoute(e *core.ServeEvent) error {
}, apis.RequireAdminOrRecordAuth())
return nil
}
}
func getErrorPageRoute(app *pocketbase.PocketBase) func(*core.ServeEvent) error {
return func(e *core.ServeEvent) error {
e.Router.GET("/error/:code", func(c echo.Context) error {
// get data
code := c.PathParam("code")
codeNum, err := strconv.ParseInt(code, 10, 64)
if err != nil {
codeNum = 500
}
errorText := http.StatusText(int(codeNum))
if errorText == "" {
codeNum = 500
errorText = http.StatusText(500)
}
// and since i'm using 'base.gohtml' with Nav, i'll need Nav info
navInfoData := initNavInfoData(app, c)
somePageData := struct {
NavInfo navInfo
ErrorCode int64
ErrorText string
}{
NavInfo: navInfoData,
ErrorCode: codeNum,
ErrorText: errorText,
}
// then render template with it
templateName := "templates/errors/error.gohtml"
switch codeNum {
case 404:
templateName = "templates/errors/404.gohtml"
case 401:
templateName = "templates/errors/401.gohtml"
}
tmpl := template.Must(template.ParseFS(templatesFS, "templates/base.gohtml", templateName))
var instantiatedTemplate bytes.Buffer
if err := tmpl.Execute(&instantiatedTemplate, somePageData); err != nil {
return c.JSON(http.StatusInternalServerError, map[string]string{"message": "error parsing template"})
}
return c.HTML(int(codeNum), instantiatedTemplate.String())
})
return nil
}
}
func initNavInfoData(app *pocketbase.PocketBase, c echo.Context) navInfo {
// first collect data
info := apis.RequestInfo(c)
admin := info.Admin // nil if not authenticated as admin
record := info.AuthRecord // nil if not authenticated as regular auth record
isGuest := admin == nil && record == nil
username := ""
switch {
case admin != nil:
username = admin.Email
case record != nil:
username = record.Username()
}
oauthProviders := app.Settings().NamedAuthProviderConfigs()
oauthProviderNames := make([]string, 0, len(oauthProviders))
for name, config := range oauthProviders {
if config.Enabled {
oauthProviderNames = append(oauthProviderNames, name)
}
}
return navInfo{
IsGuest: isGuest,
Username: username,
EnabledOauthProviders: oauthProviderNames,
}
}

View File

@ -0,0 +1,10 @@
{{ define "title" }}
Not Authorized
{{ end }}
{{ define "main" }}
<main hx-boost="true" class="px-10 pt-10 flex flex-col gap-y-10">
The page you are trying to access requires authorization.
Please log in.
</main>
{{ end }}

View File

@ -0,0 +1,9 @@
{{ define "title" }}
Page Not Found
{{ end }}
{{ define "main" }}
<main hx-boost="true" class="px-10 pt-10 flex flex-col gap-y-10">
Error 404 means the page was not found
</main>
{{ end }}

View File

@ -0,0 +1,10 @@
{{ define "title" }}
Error occurred
{{ end }}
{{ define "main" }}
<main hx-boost="true" class="px-10 pt-10 flex flex-col gap-y-10">
<p> Error {{ .ErrorCode }} occurred! </p>
<p> {{ .ErrorText }} </p>
</main>
{{ end }}

View File

@ -13,6 +13,7 @@
</li>
</ul>
{{ else }}
<p>Rendering this on the backend, passing values from the code: {{ .BackendMessage }}</p>
<p>There will be some content only for authorized users</p>
{{ end }}
</main>