Compare commits

...

2 Commits

Author SHA1 Message Date
efim 8842235372 feat: middleware to return error as html 2023-10-09 07:45:29 +00:00
efim ec47c9d610 fix: logout should set address to / and open / 2023-10-09 05:42:50 +00:00
10 changed files with 209 additions and 73 deletions

View File

@ -242,7 +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 maybe add middleware so that 401 would be a page, and not json
** DONE prettying up server responses for "we show html" land
let's do this also, yes
*** DONE logout should push root url in htmx
*** 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.
@ -253,3 +258,6 @@ https://pocketbase.io/docs/go-migrations/#enable-go-migrations
if i understood correctly, when i enable migration generation
i would be able to modify locally run instance via admin interface,
go files with migration would be generated, i'll have to import them somewhere in my main module, and then after building/packaging when i run `serve` on production the migrations would run on the production data
** adding google oauth
support article : https://developers.google.com/identity/sign-in/web/sign-in
settings are in : https://console.cloud.google.com/apis/credentials

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 {

View File

@ -101,7 +101,7 @@ func getLogoutRoute(app *pocketbase.PocketBase) func(*core.ServeEvent) error {
HttpOnly: true,
})
c.Response().Header().Add("HX-Trigger", "auth-change-event")
return c.JSON(http.StatusOK, map[string]string{"message": "session cookie removed"})
return c.NoContent(http.StatusOK)
})
return 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
@ -141,4 +104,84 @@ func somePageRoute(e *core.ServeEvent) error {
return c.HTML(http.StatusOK, instantiatedTemplate.String())
}, 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

@ -16,7 +16,7 @@
></script>
<script defer src="/static/static/public/htmx.min.js"></script>
</head>
<body hx-get="/" hx-trigger="auth-change-event">
<body hx-get="/" hx-trigger="auth-change-event" hx-push-url="true">
<!--[if lt IE 8]>
<p class="browserupgrade">
You are using an <strong>outdated</strong> browser. Please

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>