mirror of
https://github.com/sonr-io/sonr.git
synced 2026-08-04 10:21:40 +00:00
feature/driver indexed db (#12)
* fix: update commitizen version * feat: add WASM build tags to db actions * feat: Update all actions to follow `AddAsset` for error handling * feat: remove database dependency in dwn and motr commands * feat: add basic info form to registration view * feat: implement basic browser navigation component * refactor: move database related files to middleware * fix: remove unused test command * fix: update source directory for buf-publish workflow
This commit is contained in:
@@ -0,0 +1,95 @@
|
||||
package mdw
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/labstack/echo/v4"
|
||||
"gopkg.in/macaroon.v2"
|
||||
)
|
||||
|
||||
const (
|
||||
OriginMacroonCaveat MacroonCaveat = "origin"
|
||||
ScopesMacroonCaveat MacroonCaveat = "scopes"
|
||||
SubjectMacroonCaveat MacroonCaveat = "subject"
|
||||
ExpMacroonCaveat MacroonCaveat = "exp"
|
||||
TokenMacroonCaveat MacroonCaveat = "token"
|
||||
)
|
||||
|
||||
type MacroonCaveat string
|
||||
|
||||
func (c MacroonCaveat) Equal(other string) bool {
|
||||
return string(c) == other
|
||||
}
|
||||
|
||||
func (c MacroonCaveat) String() string {
|
||||
return string(c)
|
||||
}
|
||||
|
||||
func (c MacroonCaveat) Verify(value string) error {
|
||||
switch c {
|
||||
case OriginMacroonCaveat:
|
||||
return nil
|
||||
case ScopesMacroonCaveat:
|
||||
return nil
|
||||
case SubjectMacroonCaveat:
|
||||
return nil
|
||||
case ExpMacroonCaveat:
|
||||
// Check if the expiration time is still valid
|
||||
exp, err := time.Parse(time.RFC3339, value)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if time.Now().After(exp) {
|
||||
return fmt.Errorf("expired")
|
||||
}
|
||||
return nil
|
||||
case TokenMacroonCaveat:
|
||||
return nil
|
||||
default:
|
||||
return fmt.Errorf("unknown caveat: %s", c)
|
||||
}
|
||||
}
|
||||
|
||||
var MacroonCaveats = []MacroonCaveat{OriginMacroonCaveat, ScopesMacroonCaveat, SubjectMacroonCaveat, ExpMacroonCaveat, TokenMacroonCaveat}
|
||||
|
||||
func MacaroonMiddleware(secretKeyStr string, location string) echo.MiddlewareFunc {
|
||||
secretKey := []byte(secretKeyStr)
|
||||
return func(next echo.HandlerFunc) echo.HandlerFunc {
|
||||
return func(c echo.Context) error {
|
||||
// Extract the macaroon from the Authorization header
|
||||
auth := c.Request().Header.Get("Authorization")
|
||||
if auth == "" {
|
||||
return c.JSON(http.StatusUnauthorized, map[string]string{"error": "Missing Authorization header"})
|
||||
}
|
||||
|
||||
// Decode the macaroon
|
||||
mac, err := macaroon.Base64Decode([]byte(auth))
|
||||
if err != nil {
|
||||
return c.JSON(http.StatusBadRequest, map[string]string{"error": "Invalid macaroon encoding"})
|
||||
}
|
||||
|
||||
token, err := macaroon.New(secretKey, mac, location, macaroon.LatestVersion)
|
||||
if err != nil {
|
||||
return c.JSON(http.StatusBadRequest, map[string]string{"error": "Invalid macaroon"})
|
||||
}
|
||||
|
||||
// Verify the macaroon
|
||||
err = token.Verify(secretKey, func(caveat string) error {
|
||||
for _, c := range MacroonCaveats {
|
||||
if c.String() == caveat {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
return nil // Return nil if the caveat is valid
|
||||
}, nil)
|
||||
if err != nil {
|
||||
return c.JSON(http.StatusUnauthorized, map[string]string{"error": "Invalid macaroon"})
|
||||
}
|
||||
|
||||
// Macaroon is valid, proceed to the next handler
|
||||
return next(c)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
//go:build js && wasm
|
||||
// +build js,wasm
|
||||
|
||||
package mdw
|
||||
|
||||
import (
|
||||
"github.com/donseba/go-htmx"
|
||||
"github.com/labstack/echo/v4"
|
||||
)
|
||||
|
||||
type Browser struct {
|
||||
echo.Context
|
||||
isMobile bool
|
||||
userAgent string
|
||||
width int
|
||||
olc string
|
||||
ksuid string
|
||||
|
||||
// HTMX Specific
|
||||
htmx *htmx.HTMX
|
||||
|
||||
// WebAPIs
|
||||
credentials CredentialsAPI
|
||||
indexedDB IndexedDBAPI
|
||||
localStorage LocalStorageAPI
|
||||
push PushAPI
|
||||
sessionStorage SessionStorageAPI
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
package mdw
|
||||
|
||||
type AuthClient struct{}
|
||||
@@ -0,0 +1,6 @@
|
||||
//go:build js && wasm
|
||||
// +build js,wasm
|
||||
|
||||
package mdw
|
||||
|
||||
type CredentialsAPI interface{}
|
||||
@@ -0,0 +1,184 @@
|
||||
//go:build js && wasm
|
||||
// +build js,wasm
|
||||
|
||||
package mdw
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"syscall/js"
|
||||
|
||||
"github.com/hack-pad/go-indexeddb/idb"
|
||||
)
|
||||
|
||||
type IndexedDBAPI interface{}
|
||||
|
||||
// Model is an interface that must be implemented by types used with Table
|
||||
type Model interface {
|
||||
Table() string
|
||||
}
|
||||
|
||||
// Table is a generic wrapper around IDB for easier database operations on a specific table
|
||||
type Table[T Model] struct {
|
||||
db *idb.Database
|
||||
dbName string
|
||||
keyPath string
|
||||
}
|
||||
|
||||
// NewTable creates a new Table instance
|
||||
func NewTable[T Model](dbName string, version uint, keyPath string) (*Table[T], error) {
|
||||
ctx := context.Background()
|
||||
|
||||
var model T
|
||||
tableName := model.Table()
|
||||
|
||||
openRequest, err := idb.Global().Open(ctx, dbName, version, func(db *idb.Database, oldVersion, newVersion uint) error {
|
||||
_, err := db.CreateObjectStore(tableName, idb.ObjectStoreOptions{
|
||||
KeyPath: js.ValueOf(keyPath),
|
||||
})
|
||||
return err
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
db, err := openRequest.Await(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &Table[T]{
|
||||
db: db,
|
||||
dbName: dbName,
|
||||
keyPath: keyPath,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Insert adds a new record to the table
|
||||
func (t *Table[T]) Insert(data T) error {
|
||||
tx, err := t.db.Transaction(idb.TransactionReadWrite, data.Table())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer tx.Commit()
|
||||
|
||||
objectStore, err := tx.ObjectStore(data.Table())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
jsonData, err := json.Marshal(data)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = objectStore.Add(js.ValueOf(string(jsonData)))
|
||||
return err
|
||||
}
|
||||
|
||||
// Query retrieves a record from the table based on a key
|
||||
func (t *Table[T]) Query(key interface{}) (T, error) {
|
||||
var result T
|
||||
|
||||
tx, err := t.db.Transaction(idb.TransactionReadOnly, result.Table())
|
||||
if err != nil {
|
||||
return result, err
|
||||
}
|
||||
defer tx.Commit()
|
||||
|
||||
objectStore, err := tx.ObjectStore(result.Table())
|
||||
if err != nil {
|
||||
return result, err
|
||||
}
|
||||
|
||||
request, err := objectStore.Get(js.ValueOf(key))
|
||||
if err != nil {
|
||||
return result, err
|
||||
}
|
||||
|
||||
value, err := request.Await(context.Background())
|
||||
if err != nil {
|
||||
return result, err
|
||||
}
|
||||
|
||||
if value.IsUndefined() || value.IsNull() {
|
||||
return result, errors.New("record not found")
|
||||
}
|
||||
|
||||
err = json.Unmarshal([]byte(value.String()), &result)
|
||||
return result, err
|
||||
}
|
||||
|
||||
// First retrieves the first record from the table
|
||||
func (t *Table[T]) First(data T) error {
|
||||
tx, err := t.db.Transaction(idb.TransactionReadOnly, data.Table())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer tx.Commit()
|
||||
|
||||
objectStore, err := tx.ObjectStore(data.Table())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
request, err := objectStore.Get(js.ValueOf(t.keyPath))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
value, err := request.Await(context.Background())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if value.IsUndefined() || value.IsNull() {
|
||||
return errors.New("record not found")
|
||||
}
|
||||
|
||||
err = json.Unmarshal([]byte(value.String()), &data)
|
||||
return err
|
||||
}
|
||||
|
||||
// Delete removes a record from the table based on a key
|
||||
func (t *Table[T]) Delete(key interface{}) error {
|
||||
var model T
|
||||
tx, err := t.db.Transaction(idb.TransactionReadWrite, model.Table())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer tx.Commit()
|
||||
|
||||
objectStore, err := tx.ObjectStore(model.Table())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = objectStore.Delete(js.ValueOf(key))
|
||||
return err
|
||||
}
|
||||
|
||||
// Save updates a record in the table based on a key
|
||||
func (t *Table[T]) Save(data T) error {
|
||||
tx, err := t.db.Transaction(idb.TransactionReadWrite, data.Table())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer tx.Commit()
|
||||
|
||||
objectStore, err := tx.ObjectStore(data.Table())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
jsonData, err := json.Marshal(data)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = objectStore.Put(js.ValueOf(string(jsonData)))
|
||||
return err
|
||||
}
|
||||
|
||||
// Close closes the database connection
|
||||
func (t *Table[T]) Close() error {
|
||||
return t.db.Close()
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
package mdw
|
||||
|
||||
type RequestHeaders struct {
|
||||
Authorization *string `header:"Authorization"`
|
||||
CacheControl *string `header:"Cache-Control"`
|
||||
DeviceMemory *string `header:"Device-Memory"`
|
||||
Forwarded *string `header:"Forwarded"`
|
||||
From *string `header:"From"`
|
||||
Host *string `header:"Host"`
|
||||
Link *string `header:"Link"`
|
||||
PermissionsPolicy *string `header:"Permissions-Policy"`
|
||||
ProxyAuthorization *string `header:"Proxy-Authorization"`
|
||||
Referer *string `header:"Referer"`
|
||||
UserAgent *string `header:"User-Agent"`
|
||||
ViewportWidth *string `header:"Viewport-Width"`
|
||||
Width *string `header:"Width"`
|
||||
WWWAuthenticate *string `header:"WWW-Authenticate"`
|
||||
|
||||
// HTMX Specific
|
||||
HXBoosted *string `header:"HX-Boosted"`
|
||||
HXCurrentURL *string `header:"HX-Current-URL"`
|
||||
HXHistoryRestoreRequest *string `header:"HX-History-Restore-Request"`
|
||||
HXPrompt *string `header:"HX-Prompt"`
|
||||
HXRequest *string `header:"HX-Request"`
|
||||
HXTarget *string `header:"HX-Target"`
|
||||
HXTriggerName *string `header:"HX-Trigger-Name"`
|
||||
HXTrigger *string `header:"HX-Trigger"`
|
||||
}
|
||||
|
||||
type ResponseHeaders struct {
|
||||
AcceptCH *string `header:"Accept-CH"`
|
||||
AccessControlAllowCredentials *string `header:"Access-Control-Allow-Credentials"`
|
||||
AccessControlAllowHeaders *string `header:"Access-Control-Allow-Headers"`
|
||||
AccessControlAllowMethods *string `header:"Access-Control-Allow-Methods"`
|
||||
AccessControlExposeHeaders *string `header:"Access-Control-Expose-Headers"`
|
||||
AccessControlRequestHeaders *string `header:"Access-Control-Request-Headers"`
|
||||
ContentSecurityPolicy *string `header:"Content-Security-Policy"`
|
||||
CrossOriginEmbedderPolicy *string `header:"Cross-Origin-Embedder-Policy"`
|
||||
PermissionsPolicy *string `header:"Permissions-Policy"`
|
||||
ProxyAuthorization *string `header:"Proxy-Authorization"`
|
||||
WWWAuthenticate *string `header:"WWW-Authenticate"`
|
||||
|
||||
// HTMX Specific
|
||||
HXLocation *string `header:"HX-Location"`
|
||||
HXPushURL *string `header:"HX-Push-Url"`
|
||||
HXRedirect *string `header:"HX-Redirect"`
|
||||
HXRefresh *string `header:"HX-Refresh"`
|
||||
HXReplaceURL *string `header:"HX-Replace-Url"`
|
||||
HXReswap *string `header:"HX-Reswap"`
|
||||
HXRetarget *string `header:"HX-Retarget"`
|
||||
HXReselect *string `header:"HX-Reselect"`
|
||||
HXTrigger *string `header:"HX-Trigger"`
|
||||
HXTriggerAfterSettle *string `header:"HX-Trigger-After-Settle"`
|
||||
HXTriggerAfterSwap *string `header:"HX-Trigger-After-Swap"`
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
//go:build js && wasm
|
||||
// +build js,wasm
|
||||
|
||||
package mdw
|
||||
|
||||
type PushAPI interface{}
|
||||
@@ -0,0 +1,66 @@
|
||||
package mdw
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/donseba/go-htmx"
|
||||
"github.com/labstack/echo/v4"
|
||||
"github.com/segmentio/ksuid"
|
||||
)
|
||||
|
||||
type Session struct {
|
||||
echo.Context
|
||||
htmx *htmx.HTMX
|
||||
}
|
||||
|
||||
// GetSession returns the current Session
|
||||
func GetSession(c echo.Context) *Session {
|
||||
return c.(*Session)
|
||||
}
|
||||
|
||||
// UseSession establishes a Session Cookie.
|
||||
func UseSession(next echo.HandlerFunc) echo.HandlerFunc {
|
||||
return func(c echo.Context) error {
|
||||
sc := initSession(c)
|
||||
headers := new(RequestHeaders)
|
||||
sc.Bind(headers)
|
||||
return next(sc)
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Session) Htmx() *htmx.HTMX {
|
||||
return c.htmx
|
||||
}
|
||||
|
||||
func (c *Session) ID() string {
|
||||
return readCookie(c, "session")
|
||||
}
|
||||
|
||||
func initSession(c echo.Context) *Session {
|
||||
s := &Session{Context: c}
|
||||
if val := readCookie(c, "session"); val == "" {
|
||||
id := ksuid.New().String()
|
||||
writeCookie(c, "session", id)
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
func readCookie(c echo.Context, key string) string {
|
||||
cookie, err := c.Cookie(key)
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
if cookie == nil {
|
||||
return ""
|
||||
}
|
||||
return cookie.Value
|
||||
}
|
||||
|
||||
func writeCookie(c echo.Context, key string, value string) {
|
||||
cookie := new(http.Cookie)
|
||||
cookie.Name = key
|
||||
cookie.Value = value
|
||||
cookie.Expires = time.Now().Add(24 * time.Hour)
|
||||
c.SetCookie(cookie)
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
//go:build js && wasm
|
||||
// +build js,wasm
|
||||
|
||||
package mdw
|
||||
|
||||
type LocalStorageAPI interface{}
|
||||
|
||||
type SessionStorageAPI interface{}
|
||||
Reference in New Issue
Block a user