mirror of
https://github.com/sonr-io/sonr.git
synced 2026-08-02 17:31:39 +00:00
feature/1111 sync chain dwn endpoint (#1143)
- **feat(did): add assertion type to DID spec** - **refactor: update build process to include assets generation** - **refactor: update import paths for to** - **feat: introduce new authentication state management** - **feat: add current account route** - **feat: implement global toasts with custom HTML** - **refactor: remove unused session code** - **feat: add config.json to embedded assets** - **refactor: remove unused dependency on gorilla/sessions** - **refactor: simplify session management and remove unnecessary fields** - **fix: remove unnecessary import for unused protobuf types** - **feat: introduce separate HTTP contexts for Highway and DWN** - **fix(keeper): Handle missing controller during initial sync** - **refactor: extract DWN configuration from DWNContext** - **feat: add view route** - **fix: update configuration file name in embed.go** - **feat: improve vaultindex page loading experience** - **feat(hway): add highway context to echo context** - **chore(deps): bump onsonr/crypto from 1.32.0 to 1.33.0** - **refactor: rename DWNSessionMiddleware to WebNodeSessionMiddleware** - **feat: rename client API to web node API** - **refactor: separate API and view routes** - **refactor: remove unused build targets in Makefile** - **feat: add Devbox integration to container** - **feat: add wasm support for dwn** - **refactor: update module proto import** - **feat: add default first and third party caveats** - **feat: Add target vault allocation mechanism** - **refactor: introduce standardized session cookie handling** - **fix: update service worker installation and ready states** - **feat: add worker handlers** - **feat: Enable SSH access to devcontainer** - **refactor: rename HighwayContext to HwayContext** - **feat: add block expiration calculation to sonr context** - **feat: remove config from cookie and header** - **feat(gen): Remove generated code for IPFS, Motr and Sonr** - **refactor: remove unused createMotrConfig function** - **feat: add project analytics with Repobeats** - **docs: Remove component details from README** - **refactor: rename SetConfig to injectConfig**
This commit is contained in:
@@ -1,95 +0,0 @@
|
||||
package ctx
|
||||
|
||||
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"
|
||||
)
|
||||
|
||||
var MacroonCaveats = []MacroonCaveat{OriginMacroonCaveat, ScopesMacroonCaveat, SubjectMacroonCaveat, ExpMacroonCaveat, TokenMacroonCaveat}
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,44 +0,0 @@
|
||||
//go:build js && wasm
|
||||
|
||||
package ctx
|
||||
|
||||
import (
|
||||
"syscall/js"
|
||||
|
||||
"github.com/labstack/echo/v4"
|
||||
)
|
||||
|
||||
type BroadcastContext struct {
|
||||
echo.Context
|
||||
Channel js.Value
|
||||
}
|
||||
|
||||
func (c *BroadcastContext) BroadcastMessage(message string) {
|
||||
c.Channel.Call("postMessage", message)
|
||||
}
|
||||
|
||||
type JSHandler func(this js.Value, args []js.Value) interface{}
|
||||
|
||||
func UseBroadcastChannel(channelName string, handler JSHandler) echo.MiddlewareFunc {
|
||||
var channel js.Value
|
||||
|
||||
return func(next echo.HandlerFunc) echo.HandlerFunc {
|
||||
return func(c echo.Context) error {
|
||||
if channel.IsUndefined() {
|
||||
channel = js.Global().Get("BroadcastChannel").New(channelName)
|
||||
channel.Call("addEventListener", "message", handler)
|
||||
}
|
||||
|
||||
cc := &BroadcastContext{
|
||||
Context: c,
|
||||
Channel: channel,
|
||||
}
|
||||
return next(cc)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func PostBroadcastMessage(c echo.Context, message string) {
|
||||
cc := c.(*BroadcastContext)
|
||||
cc.BroadcastMessage(message)
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
package ctx
|
||||
|
||||
import (
|
||||
"github.com/labstack/echo/v4"
|
||||
"github.com/segmentio/ksuid"
|
||||
)
|
||||
|
||||
type CookieKey string
|
||||
|
||||
const (
|
||||
CookieKeySessionID CookieKey = "session.id"
|
||||
CookieKeySonrAddr CookieKey = "sonr.addr"
|
||||
CookieKeySonrDID CookieKey = "sonr.did"
|
||||
CookieKeyVaultCID CookieKey = "vault.cid"
|
||||
CookieKeyVaultSchema CookieKey = "vault.schema"
|
||||
)
|
||||
|
||||
func (c CookieKey) String() string {
|
||||
return string(c)
|
||||
}
|
||||
|
||||
func GetSessionID(c echo.Context) string {
|
||||
// Attempt to read the session ID from the "session" cookie
|
||||
sessionID, err := ReadCookie(c, CookieKeySessionID)
|
||||
if err != nil {
|
||||
// Generate a new KSUID if the session cookie is missing or invalid
|
||||
WriteCookie(c, CookieKeySessionID, ksuid.New().String())
|
||||
}
|
||||
return sessionID
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
package ctx
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
|
||||
"github.com/labstack/echo/v4"
|
||||
dwngen "github.com/onsonr/sonr/internal/dwn/gen"
|
||||
)
|
||||
|
||||
type DWNContext struct {
|
||||
echo.Context
|
||||
|
||||
// Defaults
|
||||
id string // Generated ksuid http cookie; Initialized on first request
|
||||
}
|
||||
|
||||
func (s *DWNContext) HasAuthorization() bool {
|
||||
v := ReadHeader(s.Context, HeaderAuthorization)
|
||||
return v != ""
|
||||
}
|
||||
|
||||
func (s *DWNContext) ID() string {
|
||||
return s.id
|
||||
}
|
||||
|
||||
func (s *DWNContext) Address() string {
|
||||
v, err := ReadCookie(s.Context, CookieKeySonrAddr)
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
return v
|
||||
}
|
||||
|
||||
func (s *DWNContext) IPFSGatewayURL() string {
|
||||
return ReadHeader(s.Context, HeaderIPFSGatewayURL)
|
||||
}
|
||||
|
||||
func (s *DWNContext) ChainID() string {
|
||||
return ReadHeader(s.Context, HeaderSonrChainID)
|
||||
}
|
||||
|
||||
func (s *DWNContext) Schema() *dwngen.Schema {
|
||||
v, err := ReadCookie(s.Context, CookieKeyVaultSchema)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
var schema dwngen.Schema
|
||||
err = json.Unmarshal([]byte(v), &schema)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
return &schema
|
||||
}
|
||||
|
||||
func GetDWNContext(c echo.Context) (*DWNContext, error) {
|
||||
ctx, ok := c.(*DWNContext)
|
||||
if !ok {
|
||||
return nil, echo.NewHTTPError(http.StatusInternalServerError, "DWN Context not found")
|
||||
}
|
||||
return ctx, nil
|
||||
}
|
||||
|
||||
// HighwaySessionMiddleware establishes a Session Cookie.
|
||||
func DWNSessionMiddleware(config *dwngen.Config) echo.MiddlewareFunc {
|
||||
return func(next echo.HandlerFunc) echo.HandlerFunc {
|
||||
return func(c echo.Context) error {
|
||||
sessionID := GetSessionID(c)
|
||||
injectConfig(c, config)
|
||||
cc := &DWNContext{
|
||||
Context: c,
|
||||
id: sessionID,
|
||||
}
|
||||
return next(cc)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
package ctx
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/labstack/echo/v4"
|
||||
)
|
||||
|
||||
type HwayContext struct {
|
||||
echo.Context
|
||||
|
||||
// Defaults
|
||||
id string // Generated ksuid http cookie; Initialized on first request
|
||||
}
|
||||
|
||||
func (s *HwayContext) ID() string {
|
||||
return s.id
|
||||
}
|
||||
|
||||
func GetHWAYContext(c echo.Context) (*HwayContext, error) {
|
||||
ctx, ok := c.(*HwayContext)
|
||||
if !ok {
|
||||
return nil, echo.NewHTTPError(http.StatusInternalServerError, "Highway Context not found")
|
||||
}
|
||||
return ctx, nil
|
||||
}
|
||||
|
||||
// HighwaySessionMiddleware establishes a Session Cookie.
|
||||
func HighwaySessionMiddleware(next echo.HandlerFunc) echo.HandlerFunc {
|
||||
return func(c echo.Context) error {
|
||||
sessionID := GetSessionID(c)
|
||||
cc := &HwayContext{
|
||||
Context: c,
|
||||
id: sessionID,
|
||||
}
|
||||
return next(cc)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
package ctx
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
)
|
||||
|
||||
type SonrContext struct {
|
||||
sdk.Context
|
||||
}
|
||||
|
||||
func GetSonrCTX(ctx sdk.Context) *SonrContext {
|
||||
return &SonrContext{ctx}
|
||||
}
|
||||
|
||||
func (s *SonrContext) GetBlockExpiration(duration time.Duration) int64 {
|
||||
blockTime := s.BlockTime()
|
||||
avgBlockTime := float64(blockTime.Sub(blockTime).Seconds())
|
||||
return int64(duration.Seconds() / avgBlockTime)
|
||||
}
|
||||
+28
-59
@@ -1,68 +1,37 @@
|
||||
package ctx
|
||||
|
||||
// ╭───────────────────────────────────────────────────────────╮
|
||||
// │ Request Headers │
|
||||
// ╰───────────────────────────────────────────────────────────╯
|
||||
import (
|
||||
"encoding/json"
|
||||
|
||||
type RequestHeaders struct {
|
||||
CacheControl *string `header:"Cache-Control"`
|
||||
DeviceMemory *string `header:"Device-Memory"`
|
||||
From *string `header:"From"`
|
||||
Host *string `header:"Host"`
|
||||
Referer *string `header:"Referer"`
|
||||
UserAgent *string `header:"User-Agent"`
|
||||
ViewportWidth *string `header:"Viewport-Width"`
|
||||
Width *string `header:"Width"`
|
||||
"github.com/labstack/echo/v4"
|
||||
dwngen "github.com/onsonr/sonr/internal/dwn/gen"
|
||||
)
|
||||
|
||||
// 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 HeaderKey string
|
||||
|
||||
const (
|
||||
HeaderAuthorization HeaderKey = "Authorization"
|
||||
|
||||
HeaderIPFSGatewayURL HeaderKey = "X-IPFS-Gateway"
|
||||
HeaderSonrChainID HeaderKey = "X-Sonr-ChainID"
|
||||
HeaderSonrKeyshare HeaderKey = "X-Sonr-Keyshare"
|
||||
)
|
||||
|
||||
func (h HeaderKey) String() string {
|
||||
return string(h)
|
||||
}
|
||||
|
||||
type ProtectedRequestHeaders struct {
|
||||
Authorization *string `header:"Authorization"`
|
||||
Forwarded *string `header:"Forwarded"`
|
||||
Link *string `header:"Link"`
|
||||
PermissionsPolicy *string `header:"Permissions-Policy"`
|
||||
ProxyAuthorization *string `header:"Proxy-Authorization"`
|
||||
WWWAuthenticate *string `header:"WWW-Authenticate"`
|
||||
}
|
||||
func injectConfig(c echo.Context, config *dwngen.Config) {
|
||||
WriteHeader(c, HeaderIPFSGatewayURL, config.IpfsGatewayUrl)
|
||||
WriteHeader(c, HeaderSonrChainID, config.SonrChainId)
|
||||
WriteHeader(c, HeaderSonrKeyshare, config.MotrKeyshare)
|
||||
WriteCookie(c, CookieKeySonrAddr, config.MotrAddress)
|
||||
|
||||
// ╭───────────────────────────────────────────────────────────╮
|
||||
// │ Response Headers │
|
||||
// ╰───────────────────────────────────────────────────────────╯
|
||||
schemaBz, err := json.Marshal(config.VaultSchema)
|
||||
if err != nil {
|
||||
c.Logger().Error(err)
|
||||
return
|
||||
}
|
||||
|
||||
type ResponseHeaders struct {
|
||||
// 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"`
|
||||
}
|
||||
|
||||
type ProtectedResponseHeaders 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"`
|
||||
WriteCookie(c, CookieKeyVaultSchema, string(schemaBz))
|
||||
}
|
||||
|
||||
@@ -1,25 +0,0 @@
|
||||
package ctx
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
|
||||
"github.com/a-h/templ"
|
||||
"github.com/labstack/echo/v4"
|
||||
)
|
||||
|
||||
func RenderTempl(c echo.Context, cmp templ.Component) error {
|
||||
// Create a buffer to store the rendered HTML
|
||||
buf := &bytes.Buffer{}
|
||||
// Render the component to the buffer
|
||||
err := cmp.Render(c.Request().Context(), buf)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Set the content type
|
||||
c.Response().Header().Set(echo.HeaderContentType, echo.MIMETextHTML)
|
||||
|
||||
// Write the buffered content to the response
|
||||
_, err = c.Response().Write(buf.Bytes())
|
||||
return err
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
package ctx
|
||||
|
||||
// ╭───────────────────────────────────────────────────────────╮
|
||||
// │ Request Headers │
|
||||
// ╰───────────────────────────────────────────────────────────╯
|
||||
|
||||
type RequestHeaders struct {
|
||||
CacheControl *string `header:"Cache-Control"`
|
||||
DeviceMemory *string `header:"Device-Memory"`
|
||||
From *string `header:"From"`
|
||||
Host *string `header:"Host"`
|
||||
Referer *string `header:"Referer"`
|
||||
UserAgent *string `header:"User-Agent"`
|
||||
ViewportWidth *string `header:"Viewport-Width"`
|
||||
Width *string `header:"Width"`
|
||||
|
||||
// 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 ProtectedRequestHeaders struct {
|
||||
Authorization *string `header:"Authorization"`
|
||||
Forwarded *string `header:"Forwarded"`
|
||||
Link *string `header:"Link"`
|
||||
PermissionsPolicy *string `header:"Permissions-Policy"`
|
||||
ProxyAuthorization *string `header:"Proxy-Authorization"`
|
||||
WWWAuthenticate *string `header:"WWW-Authenticate"`
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
package ctx
|
||||
|
||||
import "github.com/go-webauthn/webauthn/protocol"
|
||||
|
||||
type WebBytes = protocol.URLEncodedBase64
|
||||
|
||||
// ╭───────────────────────────────────────────────────────────╮
|
||||
// │ Response Headers │
|
||||
// ╰───────────────────────────────────────────────────────────╯
|
||||
|
||||
type ResponseHeaders struct {
|
||||
// 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"`
|
||||
}
|
||||
|
||||
type ProtectedResponseHeaders 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"`
|
||||
}
|
||||
@@ -1,103 +0,0 @@
|
||||
package ctx
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"time"
|
||||
|
||||
"github.com/gorilla/sessions"
|
||||
"github.com/labstack/echo/v4"
|
||||
"github.com/segmentio/ksuid"
|
||||
)
|
||||
|
||||
var store sessions.Store
|
||||
|
||||
type ctxKeySessionID struct{}
|
||||
|
||||
// SessionMiddleware establishes a Session Cookie.
|
||||
func SessionMiddleware(next echo.HandlerFunc) echo.HandlerFunc {
|
||||
store = sessions.NewCookieStore([]byte("SESSION_KEY"))
|
||||
return func(c echo.Context) error {
|
||||
ctx := c.Request().Context()
|
||||
|
||||
// Attempt to read the session ID from the "session" cookie
|
||||
sessionID, err := readSessionIDFromCookie(c)
|
||||
if err != nil {
|
||||
// Generate a new KSUID if the session cookie is missing or invalid
|
||||
sessionID = ksuid.New().String()
|
||||
// Write the new session ID to the "session" cookie
|
||||
err = writeSessionIDToCookie(c, sessionID)
|
||||
if err != nil {
|
||||
return c.JSON(
|
||||
http.StatusInternalServerError,
|
||||
map[string]string{"error": "Failed to set session cookie"},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// Inject the session ID into the context
|
||||
ctx = context.WithValue(ctx, ctxKeySessionID{}, sessionID)
|
||||
// Update the request with the new context
|
||||
c.SetRequest(c.Request().WithContext(ctx))
|
||||
|
||||
return next(c)
|
||||
}
|
||||
}
|
||||
|
||||
func buildSession(c echo.Context, id string) *Session {
|
||||
return &Session{
|
||||
ID: id,
|
||||
Origin: getOrigin(c.Request().Header.Get("Host")),
|
||||
UserAgent: c.Request().Header.Get("Sec-Ch-Ua"),
|
||||
Platform: c.Request().Header.Get("Sec-Ch-Ua-Platform"),
|
||||
Address: c.Request().Header.Get("X-Sonr-Address"),
|
||||
ChainID: "",
|
||||
}
|
||||
}
|
||||
|
||||
func getOrigin(o string) string {
|
||||
if o == "" {
|
||||
return ""
|
||||
}
|
||||
u, err := url.Parse(o)
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
return u.Hostname()
|
||||
}
|
||||
|
||||
func getSessionID(ctx context.Context) (string, error) {
|
||||
sessionID, ok := ctx.Value(ctxKeySessionID{}).(string)
|
||||
if !ok || sessionID == "" {
|
||||
return "", errors.New("session ID not found in context")
|
||||
}
|
||||
return sessionID, nil
|
||||
}
|
||||
|
||||
func readSessionIDFromCookie(c echo.Context) (string, error) {
|
||||
cookie, err := c.Cookie("session")
|
||||
if err != nil {
|
||||
// Cookie not found or other error
|
||||
return "", err
|
||||
}
|
||||
if cookie == nil || cookie.Value == "" {
|
||||
// Cookie is empty
|
||||
return "", http.ErrNoCookie
|
||||
}
|
||||
return cookie.Value, nil
|
||||
}
|
||||
|
||||
func writeSessionIDToCookie(c echo.Context, sessionID string) error {
|
||||
cookie := &http.Cookie{
|
||||
Name: "session",
|
||||
Value: sessionID,
|
||||
Expires: time.Now().Add(24 * time.Hour),
|
||||
HttpOnly: true,
|
||||
Path: "/",
|
||||
// Add Secure and SameSite attributes as needed
|
||||
}
|
||||
c.SetCookie(cookie)
|
||||
return nil
|
||||
}
|
||||
@@ -1,27 +0,0 @@
|
||||
package ctx
|
||||
|
||||
import "github.com/labstack/echo/v4"
|
||||
|
||||
type AuthState string
|
||||
|
||||
const (
|
||||
Visitor AuthState = "visitor"
|
||||
Authenticated AuthState = "authenticated"
|
||||
Expired AuthState = "expired"
|
||||
|
||||
PendingCredentials AuthState = "pending_credentials"
|
||||
PendingAssertion AuthState = "pending_assertion"
|
||||
)
|
||||
|
||||
func (s AuthState) String() string {
|
||||
return string(s)
|
||||
}
|
||||
|
||||
func GetAuthState(c echo.Context) AuthState {
|
||||
vals := c.Request().Header.Values("Authorization")
|
||||
if len(vals) == 0 {
|
||||
return Visitor
|
||||
}
|
||||
s := AuthState(c.Request().Header.Get("Authorization"))
|
||||
return s
|
||||
}
|
||||
@@ -1,70 +0,0 @@
|
||||
package ctx
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"github.com/go-webauthn/webauthn/protocol"
|
||||
"github.com/labstack/echo/v4"
|
||||
)
|
||||
|
||||
type WebBytes = protocol.URLEncodedBase64
|
||||
|
||||
type Session struct {
|
||||
// Defaults
|
||||
ID string // Generated ksuid http cookie; Initialized on first request
|
||||
Origin string // Webauthn mapping to Relaying Party ID; Initialized on first request
|
||||
UserAgent string
|
||||
Platform string
|
||||
|
||||
// Initialization
|
||||
Address string // Webauthn mapping to User ID; Supplied by DWN frontend
|
||||
ChainID string // Macaroon mapping to location; Supplied by DWN frontend
|
||||
|
||||
Subject string // Webauthn mapping to User Displayable Name; Supplied by DWN frontend
|
||||
|
||||
// Authentication
|
||||
challenge WebBytes // Webauthn mapping to Challenge; Per session based on origin
|
||||
}
|
||||
|
||||
func (s *Session) GetChallenge(subject string) (WebBytes, error) {
|
||||
// Check if challenge is already set and subject matches
|
||||
if s.Subject != "" && s.Subject != subject {
|
||||
return nil, errors.New("challenge already set, and subject does not match")
|
||||
} else if s.Subject == "" {
|
||||
s.Subject = subject
|
||||
} else {
|
||||
return s.challenge, nil
|
||||
}
|
||||
|
||||
if s.challenge == nil {
|
||||
chl, err := protocol.CreateChallenge()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
s.challenge = chl
|
||||
}
|
||||
return s.challenge, nil
|
||||
}
|
||||
|
||||
func (s *Session) ValidateChallenge(challenge WebBytes, subject string) error {
|
||||
if s.challenge == nil {
|
||||
return nil
|
||||
}
|
||||
if s.challenge.String() != challenge.String() {
|
||||
return fmt.Errorf("invalid challenge")
|
||||
}
|
||||
s.Subject = subject
|
||||
return nil
|
||||
}
|
||||
|
||||
func GetSession(c echo.Context) *Session {
|
||||
id, _ := getSessionID(c.Request().Context())
|
||||
return buildSession(c, id)
|
||||
}
|
||||
|
||||
func SetAddress(c echo.Context, address string) *Session {
|
||||
// Write address to X-Sonr-Address header
|
||||
c.Response().Header().Set("X-Sonr-Address", address)
|
||||
return buildSession(c, "")
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
package ctx
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/a-h/templ"
|
||||
"github.com/labstack/echo/v4"
|
||||
)
|
||||
|
||||
// ╭───────────────────────────────────────────────────────────╮
|
||||
// │ Template Rendering │
|
||||
// ╰───────────────────────────────────────────────────────────╯
|
||||
|
||||
func RenderTempl(c echo.Context, cmp templ.Component) error {
|
||||
// Create a buffer to store the rendered HTML
|
||||
buf := &bytes.Buffer{}
|
||||
// Render the component to the buffer
|
||||
err := cmp.Render(c.Request().Context(), buf)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Set the content type
|
||||
c.Response().Header().Set(echo.HeaderContentType, echo.MIMETextHTML)
|
||||
|
||||
// Write the buffered content to the response
|
||||
_, err = c.Response().Write(buf.Bytes())
|
||||
return err
|
||||
}
|
||||
|
||||
// ╭──────────────────────────────────────────────────────────╮
|
||||
// │ Cookie Management │
|
||||
// ╰──────────────────────────────────────────────────────────╯
|
||||
|
||||
func ReadCookie(c echo.Context, key CookieKey) (string, error) {
|
||||
cookie, err := c.Cookie(key.String())
|
||||
if err != nil {
|
||||
// Cookie not found or other error
|
||||
return "", err
|
||||
}
|
||||
if cookie == nil || cookie.Value == "" {
|
||||
// Cookie is empty
|
||||
return "", http.ErrNoCookie
|
||||
}
|
||||
return cookie.Value, nil
|
||||
}
|
||||
|
||||
func WriteCookie(c echo.Context, key CookieKey, value string) error {
|
||||
cookie := &http.Cookie{
|
||||
Name: key.String(),
|
||||
Value: value,
|
||||
Expires: time.Now().Add(24 * time.Hour),
|
||||
HttpOnly: true,
|
||||
Path: "/",
|
||||
// Add Secure and SameSite attributes as needed
|
||||
}
|
||||
c.SetCookie(cookie)
|
||||
return nil
|
||||
}
|
||||
|
||||
// ╭────────────────────────────────────────────────────────╮
|
||||
// │ HTTP Headers │
|
||||
// ╰────────────────────────────────────────────────────────╯
|
||||
|
||||
func WriteHeader(c echo.Context, key HeaderKey, value string) {
|
||||
c.Response().Header().Set(key.String(), value)
|
||||
}
|
||||
|
||||
func ReadHeader(c echo.Context, key HeaderKey) string {
|
||||
return c.Response().Header().Get(key.String())
|
||||
}
|
||||
Reference in New Issue
Block a user