feature/data persistence (#1180)

- **feat: add documentation and GitHub Actions workflow for publishing
documentation**
- **docs(concepts): add documentation for chain modules**
- **refactor: Simplify session management with SQLite storage and remove
deprecated code**
- **refactor: Simplify database initialization and remove
DatabaseContext**
- **refactor: move connection handling logic to resolver package**
- **feat: implement session management with database persistence**
- **feat: Ensure config directory exists when creating database path**
- **feat: Add SetUserHandle function to set user handle in session**
- **feat: Add public methods to set session fields with database save**
- **refactor: Remove unused session setter functions**
- **feat: Add getter methods for all Session Model properties**
- **feat: enhance Session model with user name details**
- **feat: add Motr support and update UI elements**
- **<no value>**
- **feat: Add unique handle constraint and method to check handle
existence**
- **docs: update site URL to onsonr.dev**
- **fix: correct import statement for database package**
- **test: updated CI to run tests on pull requests and merge groups**
- **docs: remove reference to develop branch in workflow**
- **feat: add WebAuthn support for user registration**
- **fix: correct smart account attenuation preset name**
- **feat: add ComputeIssuerDID and ComputeSonrAddr functions to ucan
package**
- **test: add unit tests for MPC keyset and keyshare**
- **feat: introduce new script to streamline GitHub issue creation**
This commit is contained in:
Prad Nukala
2024-12-06 21:31:20 -05:00
committed by GitHub
parent 94fb4dceac
commit 38447af730
47 changed files with 1992 additions and 725 deletions
+4
View File
@@ -4,6 +4,10 @@ import (
"encoding/base64"
)
type Payment struct {
IsPayment bool `json:"isPayment"`
}
type LargeBlob struct {
Support string `json:"support"`
Write string `json:"write"`
@@ -1,4 +1,4 @@
package clients
package resolver
import (
"net/http"
@@ -1,4 +1,4 @@
package clients
package resolver
import (
bankv1beta1 "cosmossdk.io/api/cosmos/bank/v1beta1"
+65
View File
@@ -0,0 +1,65 @@
package webauth
import (
"github.com/go-webauthn/webauthn/protocol"
"github.com/go-webauthn/webauthn/protocol/webauthncose"
"github.com/labstack/echo/v4"
"github.com/onsonr/sonr/pkg/common"
)
func buildRegisterOptions(user protocol.UserEntity, blob common.LargeBlob, service protocol.RelyingPartyEntity) protocol.PublicKeyCredentialCreationOptions {
return protocol.PublicKeyCredentialCreationOptions{
Timeout: 10000,
Attestation: protocol.PreferDirectAttestation,
AuthenticatorSelection: protocol.AuthenticatorSelection{
AuthenticatorAttachment: "platform",
ResidentKey: protocol.ResidentKeyRequirementPreferred,
UserVerification: "preferred",
},
RelyingParty: service,
User: user,
Extensions: protocol.AuthenticationExtensions{
"largeBlob": blob,
},
Parameters: []protocol.CredentialParameter{
{
Type: "public-key",
Algorithm: webauthncose.AlgES256,
},
{
Type: "public-key",
Algorithm: webauthncose.AlgES256K,
},
{
Type: "public-key",
Algorithm: webauthncose.AlgEdDSA,
},
},
}
}
func buildLargeBlob(userKeyshareJSON string) common.LargeBlob {
return common.LargeBlob{
Support: "required",
Write: userKeyshareJSON,
}
}
func buildUserEntity(userAddress string, userHandle string) protocol.UserEntity {
return protocol.UserEntity{
ID: userAddress,
DisplayName: userHandle,
CredentialEntity: protocol.CredentialEntity{
Name: userAddress,
},
}
}
func buildServiceEntity(c echo.Context) protocol.RelyingPartyEntity {
return protocol.RelyingPartyEntity{
CredentialEntity: protocol.CredentialEntity{
Name: "Sonr.ID",
},
ID: c.Request().Host,
}
}
+18 -12
View File
@@ -25,24 +25,30 @@ func HandleIndex(c echo.Context) error {
// Initial users have no authorization, user handle, or vault address
func isInitial(c echo.Context) bool {
noAuth := !session.HasAuthorization(c)
noUserHandle := !session.HasUserHandle(c)
noVaultAddress := !session.HasVaultAddress(c)
return noUserHandle && noVaultAddress && noAuth
sess, err := session.Get(c)
if err != nil {
return false
}
data := sess.Session()
return data.UserHandle == "" && data.VaultAddress == ""
}
// Expired users have either a user handle or vault address
func isExpired(c echo.Context) bool {
noAuth := !session.HasAuthorization(c)
hasUserHandle := session.HasUserHandle(c)
hasVaultAddress := session.HasVaultAddress(c)
return noAuth && hasUserHandle || noAuth && hasVaultAddress
sess, err := session.Get(c)
if err != nil {
return false
}
data := sess.Session()
return data.UserHandle != "" || data.VaultAddress != ""
}
// Returning users have a valid authorization, and either a user handle or vault address
func isReturning(c echo.Context) bool {
hasAuth := session.HasAuthorization(c)
hasUserHandle := session.HasUserHandle(c)
hasVaultAddress := session.HasVaultAddress(c)
return hasAuth && (hasUserHandle || hasVaultAddress)
sess, err := session.Get(c)
if err != nil {
return false
}
data := sess.Session()
return data.UserHandle != "" && data.VaultAddress != ""
}
+6 -15
View File
@@ -3,7 +3,6 @@ package handlers
import (
"net/http"
"github.com/cosmos/btcutil/bech32"
"github.com/go-webauthn/webauthn/protocol"
"github.com/go-webauthn/webauthn/protocol/webauthncose"
"github.com/labstack/echo/v4"
@@ -22,23 +21,14 @@ func HandleRegisterView(env config.Env) echo.HandlerFunc {
}
func HandleRegisterStart(c echo.Context) error {
firstName := c.FormValue("first_name")
lastName := c.FormValue("last_name")
handle := c.FormValue("handle")
if firstName == "" || lastName == "" || handle == "" {
return response.RedirectLanding(c)
}
ks, err := mpc.NewKeyset()
if err != nil {
return echo.NewHTTPError(http.StatusInternalServerError, err.Error())
}
adr, err := bech32.Encode("idx", ks.Val().GetPublicKey())
if err != nil {
return echo.NewHTTPError(http.StatusInternalServerError, err.Error())
}
req := getLinkCredentialRequest(c, adr, handle, ks.UserJSON())
req := getLinkCredentialRequest(c, ks.Address(), handle, ks.UserJSON())
return response.TemplEcho(c, register.LinkCredentialView(req))
}
@@ -60,14 +50,15 @@ func getLinkCredentialRequest(c echo.Context, addr string, handle string, userKS
RegisterOptions: buildRegisterOptions(buildUserEntity(addr, handle), buildLargeBlob(userKSJSON), buildServiceEntity(c)),
}
}
data := cc.Session()
usr := buildUserEntity(addr, handle)
blob := buildLargeBlob(userKSJSON)
service := buildServiceEntity(c)
return register.LinkCredentialRequest{
Platform: cc.BrowserName(),
Handle: handle,
DeviceModel: cc.BrowserVersion(),
Platform: data.BrowserName,
Handle: data.UserHandle,
DeviceModel: data.BrowserVersion,
Address: addr,
RegisterOptions: buildRegisterOptions(usr, blob, service),
}
-16
View File
@@ -1,16 +0,0 @@
package database
import (
"net/http"
"github.com/labstack/echo/v4"
)
var (
ErrInvalidCredentials = echo.NewHTTPError(http.StatusUnauthorized, "Invalid credentials")
ErrInvalidSubject = echo.NewHTTPError(http.StatusBadRequest, "Invalid subject")
ErrInvalidUser = echo.NewHTTPError(http.StatusBadRequest, "Invalid user")
ErrUserAlreadyExists = echo.NewHTTPError(http.StatusConflict, "User already exists")
ErrUserNotFound = echo.NewHTTPError(http.StatusNotFound, "User not found")
)
@@ -1,57 +0,0 @@
package database
import (
"os"
"path/filepath"
"github.com/labstack/echo/v4"
"github.com/onsonr/sonr/pkg/gateway/config"
"gorm.io/driver/sqlite"
"gorm.io/gorm"
)
type DatabaseContext struct {
echo.Context
db *gorm.DB
}
func Middleware(env config.Env) echo.MiddlewareFunc {
cc := initDB(env.GetSqliteFile())
return func(next echo.HandlerFunc) echo.HandlerFunc {
return func(c echo.Context) error {
cc.Context = c
return next(cc)
}
}
}
func (c *DatabaseContext) HasDB() bool {
return c.db != nil
}
func initDB(path string) *DatabaseContext {
cc := new(DatabaseContext)
db, err := gorm.Open(sqlite.Open(path), &gorm.Config{})
if err != nil {
cc.db = nil
return cc
}
// Migrate the schema
db.AutoMigrate(&Session{})
db.AutoMigrate(&User{})
return &DatabaseContext{
db: db,
}
}
func formatDBPath(path string) string {
home := os.Getenv("HOME")
if home == "" {
home = os.Getenv("USERPROFILE")
}
if home == "" {
home = "."
}
return filepath.Join(home, ".config", "hway", path)
}
+31 -9
View File
@@ -1,19 +1,41 @@
package database
import (
"net/http"
"github.com/labstack/echo/v4"
"gorm.io/gorm"
)
var (
ErrInvalidCredentials = echo.NewHTTPError(http.StatusUnauthorized, "Invalid credentials")
ErrInvalidSubject = echo.NewHTTPError(http.StatusBadRequest, "Invalid subject")
ErrInvalidUser = echo.NewHTTPError(http.StatusBadRequest, "Invalid user")
ErrUserAlreadyExists = echo.NewHTTPError(http.StatusConflict, "User already exists")
ErrUserNotFound = echo.NewHTTPError(http.StatusNotFound, "User not found")
)
type User struct {
gorm.Model
Address string `json:"address"`
Handle string `json:"handle"`
FirstName string `json:"firstName"`
LastInitial string `json:"lastInitial"`
VaultCID string `json:"vaultCID"`
}
type Session struct {
ID string `json:"id"`
gorm.Model
ID string `json:"id" gorm:"primaryKey"`
BrowserName string `json:"browserName"`
BrowserVersion string `json:"browserVersion"`
UserArchitecture string `json:"userArchitecture"`
Platform string `json:"platform"`
PlatformVersion string `json:"platformVersion"`
DeviceModel string `json:"deviceModel"`
}
type User struct {
Address string `json:"address"`
Handle string `json:"handle"`
FirstName string `json:"firstName"`
LastName string `json:"lastName"`
VaultCID string `json:"vaultCID"`
UserHandle string `json:"userHandle"`
FirstName string `json:"firstName"`
LastInitial string `json:"lastInitial"`
VaultAddress string `json:"vaultAddress"`
}
+43
View File
@@ -0,0 +1,43 @@
package database
import (
"os"
"path/filepath"
"github.com/onsonr/sonr/pkg/gateway/config"
"gorm.io/driver/sqlite"
"gorm.io/gorm"
)
// InitDB initializes and returns a configured database connection
func InitDB(env config.Env) (*gorm.DB, error) {
path := formatDBPath(env.GetSqliteFile())
db, err := gorm.Open(sqlite.Open(path), &gorm.Config{})
if err != nil {
return nil, err
}
// Migrate the schema
db.AutoMigrate(&Session{})
db.AutoMigrate(&User{})
return db, nil
}
func formatDBPath(path string) string {
home := os.Getenv("HOME")
if home == "" {
home = os.Getenv("USERPROFILE")
}
if home == "" {
home = "."
}
configDir := filepath.Join(home, ".config", "hway")
if err := os.MkdirAll(configDir, 0755); err != nil {
// If we can't create the directory, fall back to current directory
return path
}
return filepath.Join(configDir, path)
}
+1 -1
View File
@@ -10,7 +10,7 @@ templ InitialView() {
@layout.Container() {
@text.Header("Sonr.ID", "The decentralized identity layer for the web.")
<div class="pt-3 flex flex-col items-center justify-center h-full">
<sl-button hx-target="#container" hx-get="/register" type="button">
<sl-button hx-target="#container" hx-get="/register" hx-push-url="/register" type="button">
<sl-icon slot="prefix" library="sonr" name="sonr"></sl-icon>
Get Started
<sl-icon slot="suffix" library="sonr" name="arrow-right"></sl-icon>
@@ -62,7 +62,7 @@ func InitialView() templ.Component {
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(" <div class=\"pt-3 flex flex-col items-center justify-center h-full\"><sl-button hx-target=\"#container\" hx-get=\"/register\" type=\"button\"><sl-icon slot=\"prefix\" library=\"sonr\" name=\"sonr\"></sl-icon> Get Started <sl-icon slot=\"suffix\" library=\"sonr\" name=\"arrow-right\"></sl-icon></sl-button></div>")
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(" <div class=\"pt-3 flex flex-col items-center justify-center h-full\"><sl-button hx-target=\"#container\" hx-get=\"/register\" hx-push-url=\"/register\" type=\"button\"><sl-icon slot=\"prefix\" library=\"sonr\" name=\"sonr\"></sl-icon> Get Started <sl-icon slot=\"suffix\" library=\"sonr\" name=\"arrow-right\"></sl-icon></sl-button></div>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
+87 -24
View File
@@ -2,26 +2,18 @@ package session
import (
"net/http"
"regexp"
"strings"
"github.com/labstack/echo/v4"
"github.com/onsonr/sonr/pkg/common"
"github.com/onsonr/sonr/pkg/gateway/internal/database"
"github.com/segmentio/ksuid"
"gorm.io/gorm"
)
type contextKey string
// Context keys
const (
DataContextKey contextKey = "http_session_data"
)
type SessionCtx interface {
ID() string
BrowserName() string
BrowserVersion() string
}
// Get returns the session.Context from the echo context.
func Get(c echo.Context) (SessionCtx, error) {
// Get returns the HTTPContext from the echo context
func Get(c echo.Context) (*HTTPContext, error) {
ctx, ok := c.(*HTTPContext)
if !ok {
return nil, echo.NewHTTPError(http.StatusInternalServerError, "Session Context not found")
@@ -29,18 +21,89 @@ func Get(c echo.Context) (SessionCtx, error) {
return ctx, nil
}
// TODO: Returns fixed chain ID for testing.
func GetChainID(c echo.Context) string {
return "sonr-testnet-1"
// HTTPContext is the context for HTTP endpoints.
type HTTPContext struct {
echo.Context
db *gorm.DB
sess *database.Session
}
// SetVaultAddress sets the address of the vault
func SetVaultAddress(c echo.Context, address string) error {
return common.WriteCookie(c, common.SonrAddress, address)
// NewHTTPContext creates a new session context
func NewHTTPContext(c echo.Context, db *gorm.DB) *HTTPContext {
return &HTTPContext{
Context: c,
db: db,
}
}
// SetVaultAuthorization sets the UCAN CID of the vault
func SetVaultAuthorization(c echo.Context, ucanCID string) error {
common.HeaderWrite(c, common.Authorization, formatAuth(ucanCID))
// Session returns the current session
func (s *HTTPContext) Session() *database.Session {
return s.sess
}
// InitSession initializes or loads an existing session
func (s *HTTPContext) InitSession() error {
sessionID := s.getOrCreateSessionID()
// Try to load existing session
var sess database.Session
result := s.db.Where("id = ?", sessionID).First(&sess)
if result.Error != nil {
// Create new session if not found
bn, bv := extractBrowserInfo(s.Context)
sess = database.Session{
ID: sessionID,
BrowserName: bn,
BrowserVersion: bv,
}
if err := s.db.Create(&sess).Error; err != nil {
return err
}
}
s.sess = &sess
return nil
}
func (s *HTTPContext) getOrCreateSessionID() string {
if ok := common.CookieExists(s.Context, common.SessionID); !ok {
sessionID := ksuid.New().String()
common.WriteCookie(s.Context, common.SessionID, sessionID)
return sessionID
}
sessionID, err := common.ReadCookie(s.Context, common.SessionID)
if err != nil {
sessionID = ksuid.New().String()
common.WriteCookie(s.Context, common.SessionID, sessionID)
}
return sessionID
}
func extractBrowserInfo(c echo.Context) (string, string) {
userAgent := common.HeaderRead(c, common.UserAgent)
if userAgent == "" {
return "N/A", "-1"
}
var name, ver string
entries := strings.Split(strings.TrimSpace(userAgent), ",")
for _, entry := range entries {
entry = strings.TrimSpace(entry)
re := regexp.MustCompile(`"([^"]+)";v="([^"]+)"`)
matches := re.FindStringSubmatch(entry)
if len(matches) == 3 {
browserName := matches[1]
version := matches[2]
if browserName != common.BrowserNameUnknown.String() &&
browserName != common.BrowserNameChromium.String() {
name = browserName
ver = version
break
}
}
}
return name, ver
}
+7 -37
View File
@@ -2,48 +2,18 @@ package session
import (
"github.com/labstack/echo/v4"
"github.com/onsonr/sonr/pkg/common"
"github.com/onsonr/sonr/pkg/gateway/config"
"gorm.io/gorm"
)
// Middleware establishes a Session Cookie.
func Middleware(env config.Env) echo.MiddlewareFunc {
// Middleware creates a new session middleware
func Middleware(db *gorm.DB) echo.MiddlewareFunc {
return func(next echo.HandlerFunc) echo.HandlerFunc {
return func(c echo.Context) error {
cc := injectSession(c, common.RoleHway)
cc := NewHTTPContext(c, db)
if err := cc.InitSession(); err != nil {
return err
}
return next(cc)
}
}
}
// injectSession returns the session injectSession from the cookies.
func injectSession(c echo.Context, role common.PeerRole) *HTTPContext {
if c == nil {
return initHTTPContext(nil)
}
common.WriteCookie(c, common.SessionRole, role.String())
// Continue even if there are errors, just ensure we have valid session data
if err := loadOrGenKsuid(c); err != nil {
// Log error but continue
}
return initHTTPContext(c)
}
// HasAuthorization checks if the request has an authorization header
func HasAuthorization(c echo.Context) bool {
return common.HeaderExists(c, common.Authorization)
}
// HasUserHandle checks if the request has a user handle cookie
func HasUserHandle(c echo.Context) bool {
return common.CookieExists(c, common.UserHandle)
}
// HasVaultAddress checks if the request has a vault address cookie
func HasVaultAddress(c echo.Context) bool {
return common.CookieExists(c, common.SonrAddress)
}
+154 -170
View File
@@ -1,184 +1,168 @@
package session
import (
"regexp"
"strings"
"github.com/go-webauthn/webauthn/protocol"
"github.com/go-webauthn/webauthn/protocol/webauthncose"
"github.com/labstack/echo/v4"
"github.com/segmentio/ksuid"
"github.com/onsonr/sonr/pkg/common"
"github.com/onsonr/sonr/pkg/gateway/internal/database"
)
const kWebAuthnTimeout = 6000
// ╭───────────────────────────────────────────────────────╮
// │ DB Setter Functions │
// ╰───────────────────────────────────────────────────────╯
// HTTPContext is the context for HTTP endpoints.
type HTTPContext struct {
echo.Context
role common.PeerRole
id string
chal string
bn string
bv string
// SetUserHandle sets the user handle in the session
func SetUserHandle(c echo.Context, handle string) error {
sess, err := Get(c)
if err != nil {
return err
}
sess.Session().UserHandle = handle
return sess.db.Save(sess.Session()).Error
}
// initHTTPContext loads the headers from the request.
func initHTTPContext(c echo.Context) *HTTPContext {
if c == nil {
return &HTTPContext{}
// SetFirstName sets the first name in the session
func SetFirstName(c echo.Context, name string) error {
sess, err := Get(c)
if err != nil {
return err
}
sess.Session().FirstName = name
return sess.db.Save(sess.Session()).Error
}
// SetLastInitial sets the last initial in the session
func SetLastInitial(c echo.Context, initial string) error {
sess, err := Get(c)
if err != nil {
return err
}
sess.Session().LastInitial = initial
return sess.db.Save(sess.Session()).Error
}
// SetVaultAddress sets the vault address in the session
func SetVaultAddress(c echo.Context, address string) error {
sess, err := Get(c)
if err != nil {
return err
}
sess.Session().VaultAddress = address
return sess.db.Save(sess.Session()).Error
}
// ╭───────────────────────────────────────────────────────╮
// │ DB Getter Functions │
// ╰───────────────────────────────────────────────────────╯
// GetID returns the session ID
func GetID(c echo.Context) (string, error) {
sess, err := Get(c)
if err != nil {
return "", err
}
return sess.Session().ID, nil
}
// GetBrowserName returns the browser name
func GetBrowserName(c echo.Context) (string, error) {
sess, err := Get(c)
if err != nil {
return "", err
}
return sess.Session().BrowserName, nil
}
// GetBrowserVersion returns the browser version
func GetBrowserVersion(c echo.Context) (string, error) {
sess, err := Get(c)
if err != nil {
return "", err
}
return sess.Session().BrowserVersion, nil
}
// GetUserArchitecture returns the user architecture
func GetUserArchitecture(c echo.Context) (string, error) {
sess, err := Get(c)
if err != nil {
return "", err
}
return sess.Session().UserArchitecture, nil
}
// GetPlatform returns the platform
func GetPlatform(c echo.Context) (string, error) {
sess, err := Get(c)
if err != nil {
return "", err
}
return sess.Session().Platform, nil
}
// GetPlatformVersion returns the platform version
func GetPlatformVersion(c echo.Context) (string, error) {
sess, err := Get(c)
if err != nil {
return "", err
}
return sess.Session().PlatformVersion, nil
}
// GetDeviceModel returns the device model
func GetDeviceModel(c echo.Context) (string, error) {
sess, err := Get(c)
if err != nil {
return "", err
}
return sess.Session().DeviceModel, nil
}
// GetUserHandle returns the user handle
func GetUserHandle(c echo.Context) (string, error) {
sess, err := Get(c)
if err != nil {
return "", err
}
return sess.Session().UserHandle, nil
}
// GetFirstName returns the first name
func GetFirstName(c echo.Context) (string, error) {
sess, err := Get(c)
if err != nil {
return "", err
}
return sess.Session().FirstName, nil
}
// GetLastInitial returns the last initial
func GetLastInitial(c echo.Context) (string, error) {
sess, err := Get(c)
if err != nil {
return "", err
}
return sess.Session().LastInitial, nil
}
// GetVaultAddress returns the vault address
func GetVaultAddress(c echo.Context) (string, error) {
sess, err := Get(c)
if err != nil {
return "", err
}
return sess.Session().VaultAddress, nil
}
// HandleExists checks if a handle already exists in any session
func HandleExists(c echo.Context, handle string) (bool, error) {
sess, err := Get(c)
if err != nil {
return false, err
}
id, chal := extractPeerInfo(c)
bn, bv := extractBrowserInfo(c)
cc := &HTTPContext{
Context: c,
role: common.PeerRole(common.ReadCookieUnsafe(c, common.SessionRole)),
id: id,
chal: chal,
bn: bn,
bv: bv,
var count int64
if err := sess.db.Model(&database.Session{}).Where("user_handle = ?", handle).Count(&count).Error; err != nil {
return false, err
}
// Set the session data in both contexts
return cc
}
func (s *HTTPContext) ID() string {
return s.id
}
func (s *HTTPContext) BrowserName() string {
return s.bn
}
func (s *HTTPContext) BrowserVersion() string {
return s.bv
}
// ╭───────────────────────────────────────────────────────────╮
// │ Initialization │
// ╰───────────────────────────────────────────────────────────╯
func loadOrGenKsuid(c echo.Context) error {
var (
sessionID string
err error
)
// Setup genKsuid function
genKsuid := func() string {
return ksuid.New().String()
}
// Attempt to read the session ID from the "session" cookie
if ok := common.CookieExists(c, common.SessionID); !ok {
sessionID = genKsuid()
} else {
sessionID, err = common.ReadCookie(c, common.SessionID)
if err != nil {
sessionID = genKsuid()
}
}
common.WriteCookie(c, common.SessionID, sessionID)
return nil
}
// ╭───────────────────────────────────────────────────────────╮
// │ Extraction │
// ╰───────────────────────────────────────────────────────────╯
func extractPeerInfo(c echo.Context) (string, string) {
var chal protocol.URLEncodedBase64
id, _ := common.ReadCookie(c, common.SessionID)
chalRaw, _ := common.ReadCookieBytes(c, common.SessionChallenge)
chal.UnmarshalJSON(chalRaw)
return id, common.Base64Encode(chal)
}
func extractBrowserInfo(c echo.Context) (string, string) {
secCHUA := common.HeaderRead(c, common.UserAgent)
// If common.is empty, return empty BrowserInfo
if secCHUA == "" {
return "N/A", "-1"
}
// Split the common.into individual browser entries
var (
name string
ver string
)
entries := strings.Split(strings.TrimSpace(secCHUA), ",")
for _, entry := range entries {
// Remove leading/trailing spaces and quotes
entry = strings.TrimSpace(entry)
// Use regex to extract the browser name and version
re := regexp.MustCompile(`"([^"]+)";v="([^"]+)"`)
matches := re.FindStringSubmatch(entry)
if len(matches) == 3 {
browserName := matches[1]
version := matches[2]
// Skip "Not A;Brand"
if !validBrowser(browserName) {
continue
}
// Store the first valid browser info as fallback
name = browserName
ver = version
}
}
return name, ver
}
func validBrowser(name string) bool {
return name != common.BrowserNameUnknown.String() && name != common.BrowserNameChromium.String()
}
// ╭───────────────────────────────────────────────────────────╮
// │ Authentication │
// ╰───────────────────────────────────────────────────────────╯
func buildUserEntity(userID string) protocol.UserEntity {
return protocol.UserEntity{
ID: userID,
}
}
// returns the base options for registering a new user without challenge or user entity.
func baseRegisterOptions() *protocol.PublicKeyCredentialCreationOptions {
return &protocol.PublicKeyCredentialCreationOptions{
Timeout: kWebAuthnTimeout,
Attestation: protocol.PreferDirectAttestation,
AuthenticatorSelection: protocol.AuthenticatorSelection{
AuthenticatorAttachment: "platform",
ResidentKey: protocol.ResidentKeyRequirementPreferred,
UserVerification: "preferred",
},
Parameters: []protocol.CredentialParameter{
{
Type: "public-key",
Algorithm: webauthncose.AlgES256,
},
{
Type: "public-key",
Algorithm: webauthncose.AlgES256K,
},
{
Type: "public-key",
Algorithm: webauthncose.AlgEdDSA,
},
},
}
}
func formatAuth(ucanCID string) string {
return "Bearer " + ucanCID
return count > 0, nil
}
+11 -4
View File
@@ -10,16 +10,23 @@ import (
"github.com/onsonr/sonr/pkg/gateway/internal/session"
)
func RegisterRoutes(e *echo.Echo, env config.Env) {
func RegisterRoutes(e *echo.Echo, env config.Env) error {
// Custom error handler for gateway
e.HTTPErrorHandler = response.RedirectOnError("http://localhost:3000")
// Inject session middleware
e.Use(session.Middleware(env))
e.Use(database.Middleware(env))
// Initialize database
db, err := database.InitDB(env)
if err != nil {
return err
}
// Inject session middleware with database connection
e.Use(session.Middleware(db))
// Register routes
e.GET("/", handlers.HandleIndex)
e.GET("/register", handlers.HandleRegisterView(env))
e.POST("/register/start", handlers.HandleRegisterStart)
e.POST("/register/finish", handlers.HandleRegisterFinish)
return nil
}
-15
View File
@@ -1,15 +0,0 @@
package button
templ Primary(href string, text string) {
<div>
<div class="btn cursor-pointer text-zinc-100 bg-zinc-900 hover:bg-zinc-800 w-full shadow" hx-swap="afterend" hx-get={ href }>
{ text }
</div>
</div>
}
templ Secondary(href string, text string) {
<div>
<div x-on:click="toast('Default Toast Notification', 'default', '', 'top-center')" class="btn cursor-pointer text-zinc-600 bg-white hover:text-zinc-900 w-full shadow">{ text }</div>
</div>
}
-108
View File
@@ -1,108 +0,0 @@
// Code generated by templ - DO NOT EDIT.
// templ: version: v0.2.793
package button
//lint:file-ignore SA4006 This context is only used if a nested component is present.
import "github.com/a-h/templ"
import templruntime "github.com/a-h/templ/runtime"
func Primary(href string, text string) templ.Component {
return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
return templ_7745c5c3_CtxErr
}
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
if !templ_7745c5c3_IsBuffer {
defer func() {
templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
if templ_7745c5c3_Err == nil {
templ_7745c5c3_Err = templ_7745c5c3_BufErr
}
}()
}
ctx = templ.InitializeContext(ctx)
templ_7745c5c3_Var1 := templ.GetChildren(ctx)
if templ_7745c5c3_Var1 == nil {
templ_7745c5c3_Var1 = templ.NopComponent
}
ctx = templ.ClearChildren(ctx)
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<div><div class=\"btn cursor-pointer text-zinc-100 bg-zinc-900 hover:bg-zinc-800 w-full shadow\" hx-swap=\"afterend\" hx-get=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var2 string
templ_7745c5c3_Var2, templ_7745c5c3_Err = templ.JoinStringErrs(href)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `pkg/styles/button/default.templ`, Line: 5, Col: 124}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var2))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var3 string
templ_7745c5c3_Var3, templ_7745c5c3_Err = templ.JoinStringErrs(text)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `pkg/styles/button/default.templ`, Line: 6, Col: 9}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var3))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</div></div>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
return templ_7745c5c3_Err
})
}
func Secondary(href string, text string) templ.Component {
return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
return templ_7745c5c3_CtxErr
}
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
if !templ_7745c5c3_IsBuffer {
defer func() {
templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
if templ_7745c5c3_Err == nil {
templ_7745c5c3_Err = templ_7745c5c3_BufErr
}
}()
}
ctx = templ.InitializeContext(ctx)
templ_7745c5c3_Var4 := templ.GetChildren(ctx)
if templ_7745c5c3_Var4 == nil {
templ_7745c5c3_Var4 = templ.NopComponent
}
ctx = templ.ClearChildren(ctx)
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<div><div x-on:click=\"toast(&#39;Default Toast Notification&#39;, &#39;default&#39;, &#39;&#39;, &#39;top-center&#39;)\" class=\"btn cursor-pointer text-zinc-600 bg-white hover:text-zinc-900 w-full shadow\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var5 string
templ_7745c5c3_Var5, templ_7745c5c3_Err = templ.JoinStringErrs(text)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `pkg/styles/button/default.templ`, Line: 13, Col: 175}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var5))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</div></div>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
return templ_7745c5c3_Err
})
}
var _ = templruntime.GeneratedTemplate
@@ -160,7 +160,7 @@ func Alpine() templ.Component {
var templ_7745c5c3_Var7 string
templ_7745c5c3_Var7, templ_7745c5c3_Err = templ.JoinStringErrs(jsDelivrURL("alpinejs", "3.14.6", "dist/cdn.min.js"))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `pkg/styles/layout/scripts.templ`, Line: 36, Col: 68}
return templ.Error{Err: templ_7745c5c3_Err, FileName: `pkg/styles/layout/imports.templ`, Line: 36, Col: 68}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var7))
if templ_7745c5c3_Err != nil {
@@ -173,7 +173,7 @@ func Alpine() templ.Component {
var templ_7745c5c3_Var8 string
templ_7745c5c3_Var8, templ_7745c5c3_Err = templ.JoinStringErrs(jsDelivrURL("@alpinejs/focus", "3.14.6", "dist/cdn.min.js"))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `pkg/styles/layout/scripts.templ`, Line: 37, Col: 75}
return templ.Error{Err: templ_7745c5c3_Err, FileName: `pkg/styles/layout/imports.templ`, Line: 37, Col: 75}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var8))
if templ_7745c5c3_Err != nil {
@@ -234,7 +234,7 @@ func Dexie() templ.Component {
var templ_7745c5c3_Var11 string
templ_7745c5c3_Var11, templ_7745c5c3_Err = templ.JoinStringErrs(jsDelivrURL("dexie", "4.0.10", "dist/dexie.min.js"))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `pkg/styles/layout/scripts.templ`, Line: 44, Col: 67}
return templ.Error{Err: templ_7745c5c3_Err, FileName: `pkg/styles/layout/imports.templ`, Line: 44, Col: 67}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var11))
if templ_7745c5c3_Err != nil {
@@ -247,7 +247,7 @@ func Dexie() templ.Component {
var templ_7745c5c3_Var12 string
templ_7745c5c3_Var12, templ_7745c5c3_Err = templ.JoinStringErrs(jsDelivrURL("dexie-export-import", "4.1.4", "dist/dexie-export-import.min.js"))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `pkg/styles/layout/scripts.templ`, Line: 45, Col: 94}
return templ.Error{Err: templ_7745c5c3_Err, FileName: `pkg/styles/layout/imports.templ`, Line: 45, Col: 94}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var12))
if templ_7745c5c3_Err != nil {
@@ -308,7 +308,7 @@ func Htmx() templ.Component {
var templ_7745c5c3_Var15 string
templ_7745c5c3_Var15, templ_7745c5c3_Err = templ.JoinStringErrs(jsDelivrURL("htmx.org", "1.9.12", "dist/htmx.min.js"))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `pkg/styles/layout/scripts.templ`, Line: 52, Col: 69}
return templ.Error{Err: templ_7745c5c3_Err, FileName: `pkg/styles/layout/imports.templ`, Line: 52, Col: 69}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var15))
if templ_7745c5c3_Err != nil {
@@ -321,7 +321,7 @@ func Htmx() templ.Component {
var templ_7745c5c3_Var16 string
templ_7745c5c3_Var16, templ_7745c5c3_Err = templ.JoinStringErrs(jsDelivrURL("htmx-ext-include-vals", "2.0.0", "include-vals.min.js"))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `pkg/styles/layout/scripts.templ`, Line: 53, Col: 84}
return templ.Error{Err: templ_7745c5c3_Err, FileName: `pkg/styles/layout/imports.templ`, Line: 53, Col: 84}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var16))
if templ_7745c5c3_Err != nil {
@@ -334,7 +334,7 @@ func Htmx() templ.Component {
var templ_7745c5c3_Var17 string
templ_7745c5c3_Var17, templ_7745c5c3_Err = templ.JoinStringErrs(jsDelivrURL("htmx-ext-path-params", "2.0.0", "path-params.min.js"))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `pkg/styles/layout/scripts.templ`, Line: 54, Col: 82}
return templ.Error{Err: templ_7745c5c3_Err, FileName: `pkg/styles/layout/imports.templ`, Line: 54, Col: 82}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var17))
if templ_7745c5c3_Err != nil {
@@ -347,7 +347,7 @@ func Htmx() templ.Component {
var templ_7745c5c3_Var18 string
templ_7745c5c3_Var18, templ_7745c5c3_Err = templ.JoinStringErrs(jsDelivrURL("htmx-ext-alpine-morph", "2.0.0", "alpine-morph.min.js"))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `pkg/styles/layout/scripts.templ`, Line: 55, Col: 84}
return templ.Error{Err: templ_7745c5c3_Err, FileName: `pkg/styles/layout/imports.templ`, Line: 55, Col: 84}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var18))
if templ_7745c5c3_Err != nil {
@@ -396,7 +396,7 @@ func Nebula(version string) templ.Component {
var templ_7745c5c3_Var20 string
templ_7745c5c3_Var20, templ_7745c5c3_Err = templ.JoinStringErrs(jsDelivrURL("@onsonr/nebula", version, "cdn/themes/light.css"))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `pkg/styles/layout/scripts.templ`, Line: 64, Col: 71}
return templ.Error{Err: templ_7745c5c3_Err, FileName: `pkg/styles/layout/imports.templ`, Line: 64, Col: 71}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var20))
if templ_7745c5c3_Err != nil {
@@ -409,7 +409,7 @@ func Nebula(version string) templ.Component {
var templ_7745c5c3_Var21 string
templ_7745c5c3_Var21, templ_7745c5c3_Err = templ.JoinStringErrs(jsDelivrURL("@onsonr/nebula", version, "cdn/themes/dark.css"))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `pkg/styles/layout/scripts.templ`, Line: 69, Col: 70}
return templ.Error{Err: templ_7745c5c3_Err, FileName: `pkg/styles/layout/imports.templ`, Line: 69, Col: 70}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var21))
if templ_7745c5c3_Err != nil {
@@ -438,7 +438,7 @@ func Nebula(version string) templ.Component {
var templ_7745c5c3_Var23 string
templ_7745c5c3_Var23, templ_7745c5c3_Err = templ.JoinStringErrs(jsDelivrURL("@onsonr/nebula", version, "cdn/shoelace-autoloader.js"))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `pkg/styles/layout/scripts.templ`, Line: 73, Col: 98}
return templ.Error{Err: templ_7745c5c3_Err, FileName: `pkg/styles/layout/imports.templ`, Line: 73, Col: 98}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var23))
if templ_7745c5c3_Err != nil {
+1 -1
View File
@@ -27,7 +27,7 @@ var (
templ Root(title string) {
<!DOCTYPE html>
<html lang="en">
@Head(title, "0.0.7")
@Head(title, "0.0.11")
<body class="flex items-center justify-center h-full lg:p-24 md:16 p-4 no-scrollbar">
<main class="flex-row items-center justify-center mx-auto w-fit max-w-screen-sm gap-y-3">
{ children... }
+1 -1
View File
@@ -57,7 +57,7 @@ func Root(title string) templ.Component {
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = Head(title, "0.0.7").Render(ctx, templ_7745c5c3_Buffer)
templ_7745c5c3_Err = Head(title, "0.0.11").Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
-87
View File
@@ -1,87 +0,0 @@
package view
// Modal is a component that renders a modal with a title and description
templ Modal(title, description string) {
<div
x-data="{ modalOpen: true }"
@keydown.escape="modalOpen=false"
:class="{ 'z-40': modalOpen }"
class="relative w-auto h-auto"
>
<template x-teleport="body">
<div x-show="modalOpen" class="fixed top-0 left-0 z-[99] flex items-center justify-center w-screen h-screen" x-cloak>
<div
x-show="modalOpen"
x-transition:enter="ease-out duration-300"
x-transition:enter-start="opacity-0"
x-transition:enter-end="opacity-100"
x-transition:leave="ease-in duration-300"
x-transition:leave-start="opacity-100"
x-transition:leave-end="opacity-0"
@click="modalOpen=false"
class="absolute inset-0 w-full h-full bg-zinc-900 bg-opacity-90 backdrop-blur-sm"
></div>
<div
x-show="modalOpen"
x-trap.inert.noscroll="modalOpen"
x-transition:enter="ease-out duration-300"
x-transition:enter-start="opacity-0 scale-90"
x-transition:enter-end="opacity-100 scale-100"
x-transition:leave="ease-in duration-200"
x-transition:leave-start="opacity-100 scale-100"
x-transition:leave-end="opacity-0 scale-90"
class="relative w-full py-6 bg-white shadow-md px-7 bg-opacity-90 drop-shadow-md backdrop-blur-sm sm:max-w-lg sm:rounded-lg"
>
<div class="flex items-center justify-between pb-3">
<h3 class="text-lg font-semibold">{ title }</h3>
<button @click="modalOpen=false" class="absolute top-0 right-0 flex items-center justify-center w-8 h-8 mt-5 mr-5 text-zinc-600 rounded-full hover:text-zinc-800 hover:bg-zinc-50">
<svg class="w-5 h-5" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" d="M6 18L18 6M6 6l12 12"></path></svg>
</button>
</div>
<div class="relative w-auto pb-8">
<p>{ description }</p>
</div>
{ children... }
</div>
</div>
</template>
</div>
}
templ FullScreenModal() {
<div
x-data="{ fullscreenModal: true }"
x-init="
$watch('fullscreenModal', function(value){
if(value === true){
document.body.classList.add('overflow-hidden');
}else{
document.body.classList.remove('overflow-hidden');
}
})
"
@keydown.escape="fullscreenModal=false"
>
<template x-teleport="body">
<div
x-show="fullscreenModal"
x-transition:enter="transition ease-out duration-150"
x-transition:enter-start="opacity-0"
x-transition:enter-end="opacity-100"
x-transition:leave="transition ease-in duration-150"
x-transition:leave-start="opacity-100"
x-transition:leave-end="opacity-0"
class="flex fixed inset-0 z-[99] w-screen h-screen bg-white"
>
<button @click="fullscreenModal=false" class="absolute top-0 right-0 z-30 flex items-center justify-center px-3 py-3 mt-5 mr-5 space-x-1 text-xs font-medium uppercase rounded-full text-zinc-500 hover:bg-zinc-200">
<svg class="w-8 h-8" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" d="M6 18L18 6M6 6l12 12"></path></svg>
</button>
<div class="relative flex flex-wrap items-center w-full h-full px-8">
<div class="relative w-full max-w-sm mx-auto lg:mb-0">
{ children... }
</div>
</div>
</div>
</template>
</div>
}
-112
View File
@@ -1,112 +0,0 @@
// Code generated by templ - DO NOT EDIT.
// templ: version: v0.2.793
package view
//lint:file-ignore SA4006 This context is only used if a nested component is present.
import "github.com/a-h/templ"
import templruntime "github.com/a-h/templ/runtime"
// Modal is a component that renders a modal with a title and description
func Modal(title, description string) templ.Component {
return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
return templ_7745c5c3_CtxErr
}
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
if !templ_7745c5c3_IsBuffer {
defer func() {
templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
if templ_7745c5c3_Err == nil {
templ_7745c5c3_Err = templ_7745c5c3_BufErr
}
}()
}
ctx = templ.InitializeContext(ctx)
templ_7745c5c3_Var1 := templ.GetChildren(ctx)
if templ_7745c5c3_Var1 == nil {
templ_7745c5c3_Var1 = templ.NopComponent
}
ctx = templ.ClearChildren(ctx)
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<div x-data=\"{ modalOpen: true }\" @keydown.escape=\"modalOpen=false\" :class=\"{ &#39;z-40&#39;: modalOpen }\" class=\"relative w-auto h-auto\"><template x-teleport=\"body\"><div x-show=\"modalOpen\" class=\"fixed top-0 left-0 z-[99] flex items-center justify-center w-screen h-screen\" x-cloak><div x-show=\"modalOpen\" x-transition:enter=\"ease-out duration-300\" x-transition:enter-start=\"opacity-0\" x-transition:enter-end=\"opacity-100\" x-transition:leave=\"ease-in duration-300\" x-transition:leave-start=\"opacity-100\" x-transition:leave-end=\"opacity-0\" @click=\"modalOpen=false\" class=\"absolute inset-0 w-full h-full bg-zinc-900 bg-opacity-90 backdrop-blur-sm\"></div><div x-show=\"modalOpen\" x-trap.inert.noscroll=\"modalOpen\" x-transition:enter=\"ease-out duration-300\" x-transition:enter-start=\"opacity-0 scale-90\" x-transition:enter-end=\"opacity-100 scale-100\" x-transition:leave=\"ease-in duration-200\" x-transition:leave-start=\"opacity-100 scale-100\" x-transition:leave-end=\"opacity-0 scale-90\" class=\"relative w-full py-6 bg-white shadow-md px-7 bg-opacity-90 drop-shadow-md backdrop-blur-sm sm:max-w-lg sm:rounded-lg\"><div class=\"flex items-center justify-between pb-3\"><h3 class=\"text-lg font-semibold\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var2 string
templ_7745c5c3_Var2, templ_7745c5c3_Err = templ.JoinStringErrs(title)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `pkg/styles/view/modals.templ`, Line: 36, Col: 47}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var2))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</h3><button @click=\"modalOpen=false\" class=\"absolute top-0 right-0 flex items-center justify-center w-8 h-8 mt-5 mr-5 text-zinc-600 rounded-full hover:text-zinc-800 hover:bg-zinc-50\"><svg class=\"w-5 h-5\" xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" viewBox=\"0 0 24 24\" stroke-width=\"1.5\" stroke=\"currentColor\"><path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M6 18L18 6M6 6l12 12\"></path></svg></button></div><div class=\"relative w-auto pb-8\"><p>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var3 string
templ_7745c5c3_Var3, templ_7745c5c3_Err = templ.JoinStringErrs(description)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `pkg/styles/view/modals.templ`, Line: 42, Col: 22}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var3))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</p></div>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templ_7745c5c3_Var1.Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</div></div></template></div>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
return templ_7745c5c3_Err
})
}
func FullScreenModal() templ.Component {
return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
return templ_7745c5c3_CtxErr
}
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
if !templ_7745c5c3_IsBuffer {
defer func() {
templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
if templ_7745c5c3_Err == nil {
templ_7745c5c3_Err = templ_7745c5c3_BufErr
}
}()
}
ctx = templ.InitializeContext(ctx)
templ_7745c5c3_Var4 := templ.GetChildren(ctx)
if templ_7745c5c3_Var4 == nil {
templ_7745c5c3_Var4 = templ.NopComponent
}
ctx = templ.ClearChildren(ctx)
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<div x-data=\"{ fullscreenModal: true }\" x-init=\"\n$watch(&#39;fullscreenModal&#39;, function(value){\nif(value === true){\ndocument.body.classList.add(&#39;overflow-hidden&#39;);\n}else{\ndocument.body.classList.remove(&#39;overflow-hidden&#39;);\n}\n})\n\" @keydown.escape=\"fullscreenModal=false\"><template x-teleport=\"body\"><div x-show=\"fullscreenModal\" x-transition:enter=\"transition ease-out duration-150\" x-transition:enter-start=\"opacity-0\" x-transition:enter-end=\"opacity-100\" x-transition:leave=\"transition ease-in duration-150\" x-transition:leave-start=\"opacity-100\" x-transition:leave-end=\"opacity-0\" class=\"flex fixed inset-0 z-[99] w-screen h-screen bg-white\"><button @click=\"fullscreenModal=false\" class=\"absolute top-0 right-0 z-30 flex items-center justify-center px-3 py-3 mt-5 mr-5 space-x-1 text-xs font-medium uppercase rounded-full text-zinc-500 hover:bg-zinc-200\"><svg class=\"w-8 h-8\" xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" viewBox=\"0 0 24 24\" stroke-width=\"1.5\" stroke=\"currentColor\"><path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M6 18L18 6M6 6l12 12\"></path></svg></button><div class=\"relative flex flex-wrap items-center w-full h-full px-8\"><div class=\"relative w-full max-w-sm mx-auto lg:mb-0\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templ_7745c5c3_Var4.Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</div></div></div></template></div>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
return templ_7745c5c3_Err
})
}
var _ = templruntime.GeneratedTemplate