initial hardcoded github oauth button

This commit is contained in:
efim 2023-10-06 06:49:22 +00:00
parent 8c255ed812
commit c87abb6956
6 changed files with 300 additions and 6 deletions

View File

@ -1,3 +1,26 @@
#+title: Auth Notes #+title: Auth Notes
* starting the pocketbase as framework * starting the pocketbase as framework
https://pocketbase.io/docs/go-overview/ 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

8
go.mod
View File

@ -2,7 +2,11 @@ module sunshine.industries/auth-pocketbase-attempt
go 1.20 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 ( require (
github.com/AlecAivazis/survey/v2 v2.3.7 // indirect github.com/AlecAivazis/survey/v2 v2.3.7 // indirect
@ -43,14 +47,12 @@ require (
github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/jmespath/go-jmespath v0.4.0 // indirect github.com/jmespath/go-jmespath v0.4.0 // indirect
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // 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-colorable v0.1.13 // indirect
github.com/mattn/go-isatty v0.0.19 // indirect github.com/mattn/go-isatty v0.0.19 // indirect
github.com/mattn/go-sqlite3 v1.14.17 // indirect github.com/mattn/go-sqlite3 v1.14.17 // indirect
github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d // indirect github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d // indirect
github.com/pocketbase/dbx v1.10.1 // indirect github.com/pocketbase/dbx v1.10.1 // indirect
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // 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/cobra v1.7.0 // indirect
github.com/spf13/pflag v1.0.5 // indirect github.com/spf13/pflag v1.0.5 // indirect
github.com/valyala/bytebufferpool v1.0.0 // indirect github.com/valyala/bytebufferpool v1.0.0 // indirect

View File

@ -2,15 +2,16 @@ package main
import ( import (
"log" "log"
// "os"
"github.com/pocketbase/pocketbase" "github.com/pocketbase/pocketbase"
// "github.com/pocketbase/pocketbase/apis" "sunshine.industries/auth-pocketbase-attempt/middleware"
// "github.com/pocketbase/pocketbase/core" "sunshine.industries/auth-pocketbase-attempt/pages"
) )
func main() { func main() {
app := pocketbase.New() app := pocketbase.New()
middleware.AddCookieSessionMiddleware(app)
pages.AddPageRoutes(app)
if err := app.Start(); err != nil { if err := app.Start(); err != nil {
log.Fatal(err) log.Fatal(err)

111
middleware/auth.go Normal file
View File

@ -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)
}
}
}

96
pages/pageRoutes.go Normal file
View File

@ -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
}
}

View File

@ -0,0 +1,61 @@
<!doctype html>
<html class="no-js" lang="">
<head>
<meta charset="utf-8" />
<meta http-equiv="x-ua-compatible" content="ie=edge" />
<title>index page</title>
<meta name="description" content="" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<link rel="apple-touch-icon" href="/apple-touch-icon.png" />
<!-- Place favicon.ico in the root directory -->
<script
defer
src="https://cdn.jsdelivr.net/gh/pocketbase/js-sdk@master/dist/pocketbase.umd.js"
></script>
<script defer type="text/javascript">
async function callOauth(providerName) {
const pb = new PocketBase("http://127.0.0.1:8090");
// This method initializes a one-off realtime subscription and will
// open a popup window with the OAuth2 vendor page to authenticate.
//
// Once the external OAuth2 sign-in/sign-up flow is completed, the popup
// window will be automatically closed and the OAuth2 data sent back
// to the user through the previously established realtime connection.
const authData = await pb
.collection("users")
.authWithOAuth2({ provider: providerName });
// after the above you can also access the auth data from the authStore
console.log(pb.authStore.isValid);
console.log(pb.authStore.token);
console.log(pb.authStore.model.id);
// "logout" the last authenticated model
pb.authStore.clear();
}
</script>
</head>
<body>
<h1>Welcome to index page</h1>
<!--[if lt IE 8]>
<p class="browserupgrade">
You are using an <strong>outdated</strong> browser. Please
<a href="http://browsehappy.com/">upgrade your browser</a> to improve
your experience.
</p<dialog open>
<p>Greetings, one and all!</p>
<form method="dialog">
<button>OK</button>
</form>
</dialog>
>
<![endif]-->
<p>{{ .Username }}</p>
<dialog open>
<p>Greetings, one and all!</p>
<button onClick="callOauth('github')">OK!</button>
</dialog>
</body>
</html>