From c87abb69567339caa99091ef2d51cd46313aea40 Mon Sep 17 00:00:00 2001 From: efim Date: Fri, 6 Oct 2023 06:49:22 +0000 Subject: [PATCH] initial hardcoded github oauth button --- auth-notes.org | 23 ++++++++ go.mod | 8 ++- main.go | 7 ++- middleware/auth.go | 111 +++++++++++++++++++++++++++++++++++ pages/pageRoutes.go | 96 ++++++++++++++++++++++++++++++ pages/templates/index.gohtml | 61 +++++++++++++++++++ 6 files changed, 300 insertions(+), 6 deletions(-) create mode 100644 middleware/auth.go create mode 100644 pages/pageRoutes.go create mode 100644 pages/templates/index.gohtml diff --git a/auth-notes.org b/auth-notes.org index 3248101..7891654 100644 --- a/auth-notes.org +++ b/auth-notes.org @@ -1,3 +1,26 @@ #+title: Auth Notes * starting the pocketbase as framework https://pocketbase.io/docs/go-overview/ +* plan +** DONE start pocketbase +** DONE add middlewares for cookie session +** TODO add index page, that will have either "current user" or 'login' link +** TODO 'login' link should open dialog with oauth providers +so, i want a window with available oauth providers, +to trigger the js code from example +https://pocketbase.io/docs/authentication/ +( all in one, recommended ) + +let's get configured providers in the go code, +add as slice of strings, and in template create buttons for each of those +with js code from the example +** TODO i guess i would also like to send htmx event for reloading the page +on successful auth? +** TODO add one more page that checks auth +** TODO add tailwind styling +** TODO package static into single binary +** TODO write nix build +** TODO write nixos module +** TODO add docker image from nix +*** TODO add cli for port and host +** TODO add readme and comments diff --git a/go.mod b/go.mod index f45eb19..dcca734 100644 --- a/go.mod +++ b/go.mod @@ -2,7 +2,11 @@ module sunshine.industries/auth-pocketbase-attempt go 1.20 -require github.com/pocketbase/pocketbase v0.18.9 +require ( + github.com/labstack/echo/v5 v5.0.0-20230722203903-ec5b858dab61 + github.com/pocketbase/pocketbase v0.18.9 + github.com/spf13/cast v1.5.1 +) require ( github.com/AlecAivazis/survey/v2 v2.3.7 // indirect @@ -43,14 +47,12 @@ require ( github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/jmespath/go-jmespath v0.4.0 // indirect github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect - github.com/labstack/echo/v5 v5.0.0-20230722203903-ec5b858dab61 // indirect github.com/mattn/go-colorable v0.1.13 // indirect github.com/mattn/go-isatty v0.0.19 // indirect github.com/mattn/go-sqlite3 v1.14.17 // indirect github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d // indirect github.com/pocketbase/dbx v1.10.1 // indirect github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect - github.com/spf13/cast v1.5.1 // indirect github.com/spf13/cobra v1.7.0 // indirect github.com/spf13/pflag v1.0.5 // indirect github.com/valyala/bytebufferpool v1.0.0 // indirect diff --git a/main.go b/main.go index 1f445ea..e6181a1 100644 --- a/main.go +++ b/main.go @@ -2,15 +2,16 @@ package main import ( "log" - // "os" "github.com/pocketbase/pocketbase" - // "github.com/pocketbase/pocketbase/apis" - // "github.com/pocketbase/pocketbase/core" + "sunshine.industries/auth-pocketbase-attempt/middleware" + "sunshine.industries/auth-pocketbase-attempt/pages" ) func main() { app := pocketbase.New() + middleware.AddCookieSessionMiddleware(app) + pages.AddPageRoutes(app) if err := app.Start(); err != nil { log.Fatal(err) diff --git a/middleware/auth.go b/middleware/auth.go new file mode 100644 index 0000000..9e9a32d --- /dev/null +++ b/middleware/auth.go @@ -0,0 +1,111 @@ +package middleware + +import ( + "fmt" + "log" + "net/http" + "github.com/labstack/echo/v5" + "github.com/pocketbase/pocketbase" + "github.com/pocketbase/pocketbase/apis" + "github.com/pocketbase/pocketbase/core" + "github.com/pocketbase/pocketbase/tokens" + "github.com/pocketbase/pocketbase/tools/security" + "github.com/spf13/cast" +) + +const AuthCookieName = "Auth" + +func AddCookieSessionMiddleware(app *pocketbase.PocketBase) { + app.OnBeforeServe().Add(func(e *core.ServeEvent) error { + e.Router.Use(loadAuthContextFromCookie(app)) + + return nil + }) + + // fires for every auth collection + app.OnRecordAuthRequest().Add(func(e *core.RecordAuthEvent) error { + log.Println(e.HttpContext) + log.Println(e.Record) + log.Println(e.Token) + log.Println(e.Meta) + e.HttpContext.SetCookie(&http.Cookie{ + Name: AuthCookieName, + Value: e.Token, + Path: "/", + }) + e.HttpContext.SetCookie(&http.Cookie{ + Name: "username", + Value: e.Record.Username(), + }) + return nil + }) + app.OnAdminAuthRequest().Add(func(e *core.AdminAuthEvent) error { + log.Println(e.HttpContext) + log.Println(e.Admin) + log.Println(e.Token) + e.HttpContext.SetCookie(&http.Cookie{ + Name: AuthCookieName, + Value: e.Token, + Path: "/", + }) + return nil + }) + +} + +func loadAuthContextFromCookie(app core.App) echo.MiddlewareFunc { + return func(next echo.HandlerFunc) echo.HandlerFunc { + return func(c echo.Context) error { + tokenCookie, err := c.Request().Cookie(AuthCookieName) + if err != nil || tokenCookie.Value == "" { + return next(c) // no token cookie + } + + token := tokenCookie.Value + + claims, _ := security.ParseUnverifiedJWT(token) + tokenType := cast.ToString(claims["type"]) + + switch tokenType { + case tokens.TypeAdmin: + admin, err := app.Dao().FindAdminByToken( + token, + app.Settings().AdminAuthToken.Secret, + ) + if err == nil && admin != nil { + // "authenticate" the admin + c.Set(apis.ContextAdminKey, admin) + someData := struct { + username string + email string + } { + admin.Email, + admin.Created.String(), + } + fmt.Printf("triggering the middlewar for cookie %v and err %v\n", someData, err) + } + + case tokens.TypeAuthRecord: + record, err := app.Dao().FindAuthRecordByToken( + token, + app.Settings().RecordAuthToken.Secret, + ) + if err == nil && record != nil { + // "authenticate" the app user + c.Set(apis.ContextAuthRecordKey, record) + someData := struct { + username string + email string + } { + record.Username(), + record.Email(), + } + fmt.Printf("triggering the middlewar for cookie %v and err %v\n", someData, err) + + } + } + + return next(c) + } + } +} diff --git a/pages/pageRoutes.go b/pages/pageRoutes.go new file mode 100644 index 0000000..723ee55 --- /dev/null +++ b/pages/pageRoutes.go @@ -0,0 +1,96 @@ +package pages + +import ( + "bytes" + "embed" + "fmt" + "html/template" + "net/http" + + "github.com/labstack/echo/v5" + "github.com/pocketbase/pocketbase" + "github.com/pocketbase/pocketbase/apis" + "github.com/pocketbase/pocketbase/core" +) + +//go:embed templates +var templatesFS embed.FS + +func AddPageRoutes(app *pocketbase.PocketBase) { + app.OnBeforeServe().Add(getIndexPageRoute(app)) + app.OnBeforeServe().Add(getAuthPageRoute(app)) +} + +// render and return index page +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 + coolMessage := fmt.Sprintf("got admin %v and record %v. is guest: %t", admin, record, isGuest) + fmt.Print(coolMessage) + + 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) + } + } + fmt.Printf(">> enabled providers names %+v\n", oauthProviderNames) + + indexPageData := struct { + IsGuest, IsAdmin bool + Username string + EnabledOauthProviders []string + }{ + IsGuest: isGuest, + IsAdmin: admin != nil, + Username: username, + EnabledOauthProviders: oauthProviderNames, + } + + // then render template with it + templateName := "templates/index.gohtml" + tmpl := template.Must(template.ParseFS(templatesFS, templateName)) + var instantiatedTemplate bytes.Buffer + if err := tmpl.Execute(&instantiatedTemplate, indexPageData); err != nil { + return c.JSON(http.StatusInternalServerError, map[string]string{"message": "error parsing template"}) + } + + return c.HTML(http.StatusOK, instantiatedTemplate.String()) + }) + return nil + } +} + +// render and return login page with configured oauth providers +func getAuthPageRoute(app *pocketbase.PocketBase) func(*core.ServeEvent) error { + return func (e *core.ServeEvent) error { + e.Router.GET("/login", func(c echo.Context) error { + + templateName := "templates/login.gohtml" + tmpl := template.Must(template.ParseFS(templatesFS, templateName)) + var instantiatedTemplate bytes.Buffer + if err := tmpl.Execute(&instantiatedTemplate, nil); err != nil { + return c.JSON(http.StatusInternalServerError, map[string]string{"message": "error parsing template"}) + } + + return c.HTML(http.StatusOK, instantiatedTemplate.String()) + }) + return nil + } +} + diff --git a/pages/templates/index.gohtml b/pages/templates/index.gohtml new file mode 100644 index 0000000..89ef6db --- /dev/null +++ b/pages/templates/index.gohtml @@ -0,0 +1,61 @@ + + + + + + index page + + + + + + + + + +

Welcome to index page

+ +

{{ .Username }}

+ +

Greetings, one and all!

+ +
+ +