feat: middleware to return error as html
This commit is contained in:
parent
ec47c9d610
commit
8842235372
|
@ -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
|
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
|
let's do this also, yes
|
||||||
*** DONE logout should push root url in htmx
|
*** DONE logout should push root url in htmx
|
||||||
*** TODO lets make 404 page and return it
|
*** DONE lets make 404 page and return it
|
||||||
*** TODO lets make 401 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?
|
** TODO get icons for the auth providers. surely they are accessible from the pocketbase itself?
|
||||||
http://localhost:8090/_/images/oauth2/apple.svg
|
http://localhost:8090/_/images/oauth2/apple.svg
|
||||||
yes.
|
yes.
|
||||||
|
|
1
main.go
1
main.go
|
@ -11,6 +11,7 @@ func main() {
|
||||||
app := pocketbase.New()
|
app := pocketbase.New()
|
||||||
|
|
||||||
middleware.AddCookieSessionMiddleware(app)
|
middleware.AddCookieSessionMiddleware(app)
|
||||||
|
middleware.AddErrorsMiddleware(app)
|
||||||
pages.AddPageRoutes(app)
|
pages.AddPageRoutes(app)
|
||||||
|
|
||||||
if err := app.Start(); err != nil {
|
if err := app.Start(); err != nil {
|
||||||
|
|
|
@ -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())
|
||||||
|
}
|
|
@ -6,6 +6,7 @@ import (
|
||||||
"html/template"
|
"html/template"
|
||||||
"math/rand"
|
"math/rand"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
"github.com/labstack/echo/v5"
|
"github.com/labstack/echo/v5"
|
||||||
"github.com/pocketbase/pocketbase"
|
"github.com/pocketbase/pocketbase"
|
||||||
|
@ -21,7 +22,8 @@ var staticFilesFS embed.FS
|
||||||
|
|
||||||
func AddPageRoutes(app *pocketbase.PocketBase) {
|
func AddPageRoutes(app *pocketbase.PocketBase) {
|
||||||
app.OnBeforeServe().Add(getIndexPageRoute(app))
|
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 {
|
app.OnBeforeServe().Add(func(e *core.ServeEvent) error {
|
||||||
e.Router.StaticFS("/static", staticFilesFS)
|
e.Router.StaticFS("/static", staticFilesFS)
|
||||||
|
@ -41,40 +43,13 @@ func getIndexPageRoute(app *pocketbase.PocketBase) func(*core.ServeEvent) error
|
||||||
return func(e *core.ServeEvent) error {
|
return func(e *core.ServeEvent) error {
|
||||||
e.Router.GET("/", func(c echo.Context) error {
|
e.Router.GET("/", func(c echo.Context) error {
|
||||||
// first collect data
|
// first collect data
|
||||||
info := apis.RequestInfo(c)
|
navInfoData := initNavInfoData(app, 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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
indexPageData := struct {
|
indexPageData := struct {
|
||||||
IsGuest, IsAdmin bool
|
BackendMessage string
|
||||||
Username string
|
NavInfo navInfo
|
||||||
EnabledOauthProviders []string
|
|
||||||
NavInfo navInfo
|
|
||||||
}{
|
}{
|
||||||
IsAdmin: admin != nil,
|
BackendMessage: "Hello from the backend!",
|
||||||
NavInfo: navInfo{
|
NavInfo: navInfoData,
|
||||||
IsGuest: isGuest,
|
|
||||||
Username: username,
|
|
||||||
EnabledOauthProviders: oauthProviderNames,
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// then render template with it
|
// then render template with it
|
||||||
|
@ -101,44 +76,112 @@ func stringWithCharset(length int, charset string) string {
|
||||||
}
|
}
|
||||||
return string(b)
|
return string(b)
|
||||||
}
|
}
|
||||||
func somePageRoute(e *core.ServeEvent) error {
|
func getSomePageRoute(app *pocketbase.PocketBase) func(*core.ServeEvent) error {
|
||||||
e.Router.GET("/somepage", func(c echo.Context) error {
|
return func(e *core.ServeEvent) error {
|
||||||
// get data
|
e.Router.GET("/somepage", func(c echo.Context) error {
|
||||||
// and since i'm using 'base.gohtml' with Nav, i'll need Nav info
|
// get data
|
||||||
|
// and since i'm using 'base.gohtml' with Nav, i'll need Nav info
|
||||||
|
navInfoData := initNavInfoData(app, c)
|
||||||
|
|
||||||
info := apis.RequestInfo(c)
|
somePageData := struct {
|
||||||
admin := info.Admin // nil if not authenticated as admin
|
RandomNumber int
|
||||||
record := info.AuthRecord // nil if not authenticated as regular auth record
|
RandomString string
|
||||||
|
NavInfo navInfo
|
||||||
|
}{
|
||||||
|
RandomNumber: rand.Int(),
|
||||||
|
RandomString: stringWithCharset(25, charset),
|
||||||
|
NavInfo: navInfoData,
|
||||||
|
}
|
||||||
|
|
||||||
username := ""
|
// then render template with it
|
||||||
switch {
|
templateName := "templates/somepage.gohtml"
|
||||||
case admin != nil:
|
tmpl := template.Must(template.ParseFS(templatesFS, "templates/base.gohtml", templateName))
|
||||||
username = admin.Email
|
var instantiatedTemplate bytes.Buffer
|
||||||
case record != nil:
|
if err := tmpl.Execute(&instantiatedTemplate, somePageData); err != nil {
|
||||||
username = record.Username()
|
return c.JSON(http.StatusInternalServerError, map[string]string{"message": "error parsing template"})
|
||||||
}
|
}
|
||||||
|
|
||||||
somePageData := struct {
|
return c.HTML(http.StatusOK, instantiatedTemplate.String())
|
||||||
RandomNumber int
|
}, apis.RequireAdminOrRecordAuth())
|
||||||
RandomString string
|
return nil
|
||||||
NavInfo navInfo
|
}
|
||||||
}{
|
}
|
||||||
RandomNumber: rand.Int(),
|
|
||||||
RandomString: stringWithCharset(25, charset),
|
func getErrorPageRoute(app *pocketbase.PocketBase) func(*core.ServeEvent) error {
|
||||||
NavInfo: navInfo{
|
return func(e *core.ServeEvent) error {
|
||||||
Username: username,
|
e.Router.GET("/error/:code", func(c echo.Context) error {
|
||||||
},
|
// get data
|
||||||
}
|
code := c.PathParam("code")
|
||||||
|
codeNum, err := strconv.ParseInt(code, 10, 64)
|
||||||
// then render template with it
|
if err != nil {
|
||||||
templateName := "templates/somepage.gohtml"
|
codeNum = 500
|
||||||
tmpl := template.Must(template.ParseFS(templatesFS, "templates/base.gohtml", templateName))
|
}
|
||||||
var instantiatedTemplate bytes.Buffer
|
errorText := http.StatusText(int(codeNum))
|
||||||
if err := tmpl.Execute(&instantiatedTemplate, somePageData); err != nil {
|
if errorText == "" {
|
||||||
return c.JSON(http.StatusInternalServerError, map[string]string{"message": "error parsing template"})
|
codeNum = 500
|
||||||
}
|
errorText = http.StatusText(500)
|
||||||
|
}
|
||||||
return c.HTML(http.StatusOK, instantiatedTemplate.String())
|
|
||||||
}, apis.RequireAdminOrRecordAuth())
|
// and since i'm using 'base.gohtml' with Nav, i'll need Nav info
|
||||||
return nil
|
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,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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 }}
|
|
@ -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 }}
|
|
@ -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 }}
|
|
@ -13,6 +13,7 @@
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
{{ else }}
|
{{ 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>
|
<p>There will be some content only for authorized users</p>
|
||||||
{{ end }}
|
{{ end }}
|
||||||
</main>
|
</main>
|
||||||
|
|
Loading…
Reference in New Issue