initial hardcoded github oauth button
This commit is contained in:
parent
8c255ed812
commit
c87abb6956
|
@ -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
8
go.mod
|
@ -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
|
||||||
|
|
7
main.go
7
main.go
|
@ -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)
|
||||||
|
|
|
@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -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>
|
Loading…
Reference in New Issue