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
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
47 changed files with 1992 additions and 725 deletions

189
.github/scopes.json vendored Normal file
View File

@ -0,0 +1,189 @@
[
{
"name": "cosmos/sdk",
"path": "app",
"docs": [
"https://docs.cosmos.network/v0.50/build/building-modules/module-manager",
"https://docs.cosmos.network/v0.50/build/building-modules/messages-and-queries",
"https://docs.cosmos.network/v0.50/build/building-modules/msg-services",
"https://docs.cosmos.network/v0.50/build/building-modules/query-services",
"https://docs.cosmos.network/v0.50/build/building-modules/depinject"
]
},
{
"name": "cosmos/ibc",
"path": "app",
"docs": [
"https://ibc.cosmos.network/v8/apps/interchain-accounts/overview/",
"https://ibc.cosmos.network/v8/apps/transfer/overview/",
"https://docs.osmosis.zone/osmosis-core/asset-info/",
"https://docs.osmosis.zone/osmosis-core/modules/tokenfactory",
"https://docs.noble.xyz/cctp/mint",
"https://docs.noble.xyz/cctp/mint_forward",
"https://docs.evmos.org/protocol/modules/erc20",
"https://docs.nomic.io/nbtc"
]
},
{
"name": "crypto/mpc",
"path": "crypto/mpc",
"docs": [
"https://csrc.nist.gov/CSRC/media/Events/NTCW19/papers/paper-DKLS.pdf"
]
},
{
"name": "crypto/ucan",
"path": "crypto/ucan",
"docs": [
"https://raw.githubusercontent.com/ucan-wg/spec/refs/heads/main/README.md"
]
},
{
"name": "crypto/zkp",
"path": "crypto/accumulator",
"docs": [
"https://eprint.iacr.org/2021/1672.pdf"
]
},
{
"name": "gateway/handlers",
"path": "pkg/gateway/handlers",
"docs": [
"https://echo.labstack.com/docs/cookbook/sse",
"https://echo.labstack.com/docs/cookbook/websocket",
"https://echo.labstack.com/docs/cookbook/subdomain"
]
},
{
"name": "gateway/database",
"path": "pkg/gateway/internal/database",
"docs": [
"https://docs.tigerbeetle.com/coding/data-modeling",
"https://docs.tigerbeetle.com/coding/two-phase-transfers",
"https://docs.tigerbeetle.com/coding/reliable-transaction-submission",
"https://docs.tigerbeetle.com/coding/recipes/currency-exchange",
"https://docs.tigerbeetle.com/coding/recipes/balance-conditional-transfers",
"https://docs.tigerbeetle.com/reference/account",
"https://docs.tigerbeetle.com/reference/transfer",
"https://docs.substreams.dev/documentation/consume/packages",
"https://docs.substreams.dev/documentation/consume/sql/deployable-services/local-service",
"https://docs.substreams.dev/tutorials/cosmos/injective/foundational"
]
},
{
"name": "vault/handlers",
"path": "pkg/vault/handlers",
"docs": [
"https://echo.labstack.com/docs/cookbook/jwt",
"https://echo.labstack.com/docs/middleware/secure",
"https://developer.mozilla.org/en-US/docs/Web/API/Service_Worker_API"
]
},
{
"name": "vault/database",
"path": "pkg/vault/internal/database",
"docs": [
"https://dexie.org/docs/ExportImport/dexie-export-import",
"https://dexie.org/docs/API-Reference#quick-reference",
"https://templ.guide/syntax-and-usage/script-templates"
]
},
{
"name": "pkl/ipfs",
"path": "pkl/ipfs.net",
"docs": [
"https://github.com/ipfs/kubo/blob/master/docs/config.md",
"https://pkl-lang.org/main/current/language-reference/index.html"
]
},
{
"name": "pkl/matrix",
"path": "pkl/matrix.net",
"docs": [
"https://element-hq.github.io/synapse/latest/usage/configuration/config_documentation.html",
"https://pkl-lang.org/main/current/language-reference/index.html"
]
},
{
"name": "pkl/chain",
"path": "pkl/sonr.chain",
"docs": [
"https://tutorials.cosmos.network/tutorials/9-path-to-prod/5-network.html",
"https://tutorials.cosmos.network/tutorials/9-path-to-prod/4-genesis.html",
"https://pkl-lang.org/main/current/language-reference/index.html",
"https://docs.cosmos.network/v0.50/user/run-node/run-testnet"
]
},
{
"name": "pkl/hway",
"path": "pkl/sonr.hway",
"docs": [
"https://pkl-lang.org/main/current/language-reference/index.html"
]
},
{
"name": "pkl/motr",
"path": "pkl/sonr.motr",
"docs": [
"https://pkl-lang.org/main/current/language-reference/index.html",
"https://web.dev/learn/pwa/service-workers/",
"https://developer.mozilla.org/en-US/docs/Web/API/Service_Worker_API"
]
},
{
"name": "x/did",
"path": "proto/did",
"docs": [
"https://docs.cosmos.network/v0.50/build/packages/orm",
"https://docs.cosmos.network/v0.50/build/building-modules/protobuf-annotations",
"https://docs.cosmos.network/v0.50/build/modules/auth",
"https://docs.cosmos.network/v0.50/build/packages/collections",
"https://docs.cosmos.network/v0.50/build/modules/bank"
]
},
{
"name": "x/dwn",
"path": "proto/dwn",
"docs": [
"https://docs.cosmos.network/v0.50/build/building-modules/protobuf-annotations",
"https://docs.cosmos.network/v0.50/build/packages/orm",
"https://docs.cosmos.network/v0.50/build/modules/authz",
"https://docs.cosmos.network/v0.50/build/packages/collections",
"https://docs.cosmos.network/v0.50/build/modules/gov",
"https://docs.cosmos.network/v0.50/build/modules/staking"
]
},
{
"name": "x/svc",
"path": "proto/svc",
"docs": [
"https://docs.cosmos.network/v0.50/build/packages/collections",
"https://docs.cosmos.network/v0.50/build/building-modules/protobuf-annotations",
"https://docs.cosmos.network/v0.50/build/packages/orm",
"https://docs.cosmos.network/v0.50/build/modules/group",
"https://docs.cosmos.network/v0.50/build/modules/nft"
]
},
{
"name": "repo/ci-cd",
"path": "deploy",
"docs": [
"https://docs.github.com/en/actions/writing-workflows/workflow-syntax-for-github-actions",
"https://docs.cosmos.network/v0.50/build/tooling/cosmovisor",
"https://f1bonacc1.github.io/process-compose/configuration/",
"https://docs.nomic.io/network/ibc-relayer",
"https://www.jetify.com/docs/devbox",
"https://taskfile.dev/reference/cli",
"https://taskfile.dev/reference/schema",
"https://taskfile.dev/reference/templating/"
]
},
{
"name": "repo/docs",
"path": "docs",
"docs": [
"https://squidfunk.github.io/mkdocs-material/reference/",
"https://github.com/mkdocs/catalog/blob/main/README.md"
]
}
]

63
.github/scripts/new_issue.sh vendored Executable file
View File

@ -0,0 +1,63 @@
#!/bin/bash
set -e
ROOT_DIR=$(git rev-parse --show-toplevel)
# Extract scope name and path using jq, and pass it to fzf for selection
SCOPE=$(cat "$ROOT_DIR/.github/scopes.json" | jq -r '.[] | "\(.name)"' | fzf --prompt "Select scope:")
DOCS=$(cat "$ROOT_DIR/.github/scopes.json" | jq -r ".[] | select(.name == \"$SCOPE\") | .docs[]")
# Write Title
TITLE=$(gum input --placeholder "Issue Title...")
# Write Goal
GOAL=$(mods --role "determine-issue-goal" "$SCOPE $TITLE")
# Input Requirements
REQUIREMENTS=()
while true; do
if [ ${#REQUIREMENTS[@]} -ge 2 ]; then
if ! gum confirm "Do you want to add another requirement?"; then
break
fi
fi
REQUIREMENT=$(gum input --placeholder "Add a requirement...")
if [ -n "$REQUIREMENT" ]; then
REQUIREMENTS+=("$REQUIREMENT")
else
echo "Requirement cannot be empty. Please enter a valid requirement."
fi
done
create_body() {
echo "### Goal(s):"
echo "$GOAL"
echo "### Requirements:"
for i in "${!REQUIREMENTS[@]}"; do
echo "$(($i + 1)). ${REQUIREMENTS[$i]}"
done
echo "### Resources:"
for doc in "${DOCS[@]}"; do
echo "- $doc"
done
}
ISSUE_BODY=$(create_body)
# Function to collect output
preview_output() {
echo "# ($SCOPE) $TITLE"
echo "$ISSUE_BODY"
}
# Display the formatted output
preview_output | gum format
# Confirm to create a GitHub issue
if gum confirm "Do you want to create a new GitHub issue with this information?"; then
# Create a new GitHub issue using the gh CLI
gh issue create --repo onsonr/sonr --title "($SCOPE) $TITLE" --body "$ISSUE_BODY"
else
exit 1
fi

28
.github/workflows/make-docs.yml vendored Normal file
View File

@ -0,0 +1,28 @@
name: Publish Docs via GitHub Pages
on:
push:
branches:
- master
permissions:
contents: write
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Configure Git Credentials
run: |
git config user.name github-actions[bot]
git config user.email 41898282+github-actions[bot]@users.noreply.github.com
- uses: actions/setup-python@v5
with:
python-version: 3.x
- run: echo "cache_id=$(date --utc '+%V')" >> $GITHUB_ENV
- uses: actions/cache@v4
with:
key: mkdocs-material-${{ env.cache_id }}
path: .cache
restore-keys: |
mkdocs-material-
- run: pip install mkdocs-material
- run: cd docs && mkdocs gh-deploy --force

View File

@ -1,9 +1,8 @@
name: Run Tests name: Run Tests
on: on:
push: pull_request:
branches: merge_group:
- feature/*
jobs: jobs:
test-unit: test-unit:

View File

@ -16,6 +16,7 @@ type (
) )
type Keyset interface { type Keyset interface {
Address() string
Val() *ValKeyshare Val() *ValKeyshare
ValJSON() string ValJSON() string
User() *UserKeyshare User() *UserKeyshare
@ -25,6 +26,11 @@ type Keyset interface {
type keyset struct { type keyset struct {
val *ValKeyshare val *ValKeyshare
user *UserKeyshare user *UserKeyshare
addr string
}
func (k keyset) Address() string {
return k.addr
} }
func (k keyset) Val() *ValKeyshare { func (k keyset) Val() *ValKeyshare {

View File

@ -4,23 +4,9 @@ import (
"crypto/ecdsa" "crypto/ecdsa"
"github.com/onsonr/sonr/crypto/core/protocol" "github.com/onsonr/sonr/crypto/core/protocol"
"github.com/onsonr/sonr/crypto/tecdsa/dklsv1/dkg"
) )
// Keyshare represents the common interface for both validator and user keyshares
type Keyshare interface {
GetPayloads() map[string][]byte
GetMetadata() map[string]string
GetPublicKey() []byte
GetProtocol() string
GetRole() int32
GetVersion() uint32
ECDSAPublicKey() (*ecdsa.PublicKey, error)
ExtractMessage() *protocol.Message
RefreshFunc() (RefreshFunc, error)
SignFunc(msg []byte) (SignFunc, error)
Marshal() (string, error)
}
// BaseKeyshare contains common fields and methods for both validator and user keyshares // BaseKeyshare contains common fields and methods for both validator and user keyshares
type BaseKeyshare struct { type BaseKeyshare struct {
Message *protocol.Message `json:"message"` Message *protocol.Message `json:"message"`
@ -29,6 +15,24 @@ type BaseKeyshare struct {
CompressedPubKey []byte `json:"compressed_public_key"` CompressedPubKey []byte `json:"compressed_public_key"`
} }
func initFromAlice(aliceOut *dkg.AliceOutput, originalMsg *protocol.Message) BaseKeyshare {
return BaseKeyshare{
Message: originalMsg,
Role: 1,
UncompressedPubKey: aliceOut.PublicKey.ToAffineUncompressed(),
CompressedPubKey: aliceOut.PublicKey.ToAffineCompressed(),
}
}
func initFromBob(bobOut *dkg.BobOutput, originalMsg *protocol.Message) BaseKeyshare {
return BaseKeyshare{
Message: originalMsg,
Role: 2,
UncompressedPubKey: bobOut.PublicKey.ToAffineUncompressed(),
CompressedPubKey: bobOut.PublicKey.ToAffineCompressed(),
}
}
func (b *BaseKeyshare) GetPayloads() map[string][]byte { func (b *BaseKeyshare) GetPayloads() map[string][]byte {
return b.Message.Payloads return b.Message.Payloads
} }

View File

@ -35,7 +35,11 @@ func NewKeyset() (Keyset, error) {
if err != nil { if err != nil {
return nil, err return nil, err
} }
return keyset{val: valShare, user: userShare}, nil addr, err := computeSonrAddr(valShare.CompressedPublicKey())
if err != nil {
return nil, err
}
return keyset{val: valShare, user: userShare, addr: addr}, nil
} }
// ExecuteSigning runs the MPC signing protocol // ExecuteSigning runs the MPC signing protocol
@ -73,7 +77,11 @@ func ExecuteRefresh(refreshFuncVal RefreshFunc, refreshFuncUser RefreshFunc) (Ke
if err != nil { if err != nil {
return nil, err return nil, err
} }
return keyset{val: valShare, user: userShare}, nil addr, err := computeSonrAddr(valShare.CompressedPublicKey())
if err != nil {
return nil, err
}
return keyset{val: valShare, user: userShare, addr: addr}, nil
} }
// SerializeSecp256k1Signature serializes an ECDSA signature into a byte slice // SerializeSecp256k1Signature serializes an ECDSA signature into a byte slice

View File

@ -3,6 +3,7 @@ package mpc
import ( import (
"errors" "errors"
"github.com/cosmos/cosmos-sdk/types/bech32"
"github.com/onsonr/sonr/crypto/core/curves" "github.com/onsonr/sonr/crypto/core/curves"
"github.com/onsonr/sonr/crypto/core/protocol" "github.com/onsonr/sonr/crypto/core/protocol"
"github.com/onsonr/sonr/crypto/tecdsa/dklsv1" "github.com/onsonr/sonr/crypto/tecdsa/dklsv1"
@ -47,7 +48,16 @@ type ValKeyshare struct {
encoded string encoded string
} }
func computeSonrAddr(pk []byte) (string, error) {
sonrAddr, err := bech32.ConvertAndEncode("idx", pk)
if err != nil {
return "", err
}
return sonrAddr, nil
}
func NewValKeyshare(msg *protocol.Message) (*ValKeyshare, error) { func NewValKeyshare(msg *protocol.Message) (*ValKeyshare, error) {
vks := new(ValKeyshare)
encoded, err := protocol.EncodeMessage(msg) encoded, err := protocol.EncodeMessage(msg)
if err != nil { if err != nil {
return nil, err return nil, err
@ -56,15 +66,10 @@ func NewValKeyshare(msg *protocol.Message) (*ValKeyshare, error) {
if err != nil { if err != nil {
return nil, err return nil, err
} }
return &ValKeyshare{
BaseKeyshare: BaseKeyshare{ vks.BaseKeyshare = initFromAlice(valShare, msg)
Message: msg, vks.encoded = encoded
Role: 1, return vks, nil
UncompressedPubKey: valShare.PublicKey.ToAffineUncompressed(),
CompressedPubKey: valShare.PublicKey.ToAffineCompressed(),
},
encoded: encoded,
}, nil
} }
func (v *ValKeyshare) RefreshFunc() (RefreshFunc, error) { func (v *ValKeyshare) RefreshFunc() (RefreshFunc, error) {
@ -83,12 +88,12 @@ func (v *ValKeyshare) String() string {
// PublicKey returns the uncompressed public key (65 bytes) // PublicKey returns the uncompressed public key (65 bytes)
func (v *ValKeyshare) PublicKey() []byte { func (v *ValKeyshare) PublicKey() []byte {
return v.BaseKeyshare.UncompressedPubKey return v.UncompressedPubKey
} }
// CompressedPublicKey returns the compressed public key (33 bytes) // CompressedPublicKey returns the compressed public key (33 bytes)
func (v *ValKeyshare) CompressedPublicKey() []byte { func (v *ValKeyshare) CompressedPublicKey() []byte {
return v.BaseKeyshare.CompressedPubKey return v.CompressedPubKey
} }
type UserKeyshare struct { type UserKeyshare struct {
@ -97,6 +102,7 @@ type UserKeyshare struct {
} }
func NewUserKeyshare(msg *protocol.Message) (*UserKeyshare, error) { func NewUserKeyshare(msg *protocol.Message) (*UserKeyshare, error) {
uks := new(UserKeyshare)
encoded, err := protocol.EncodeMessage(msg) encoded, err := protocol.EncodeMessage(msg)
if err != nil { if err != nil {
return nil, err return nil, err
@ -105,15 +111,10 @@ func NewUserKeyshare(msg *protocol.Message) (*UserKeyshare, error) {
if err != nil { if err != nil {
return nil, err return nil, err
} }
return &UserKeyshare{
BaseKeyshare: BaseKeyshare{ uks.BaseKeyshare = initFromBob(out, msg)
Message: msg, uks.encoded = encoded
Role: 2, return uks, nil
UncompressedPubKey: out.PublicKey.ToAffineUncompressed(),
CompressedPubKey: out.PublicKey.ToAffineCompressed(),
},
encoded: encoded,
}, nil
} }
func (u *UserKeyshare) RefreshFunc() (RefreshFunc, error) { func (u *UserKeyshare) RefreshFunc() (RefreshFunc, error) {
@ -132,12 +133,12 @@ func (u *UserKeyshare) String() string {
// PublicKey returns the uncompressed public key (65 bytes) // PublicKey returns the uncompressed public key (65 bytes)
func (u *UserKeyshare) PublicKey() []byte { func (u *UserKeyshare) PublicKey() []byte {
return u.BaseKeyshare.UncompressedPubKey return u.UncompressedPubKey
} }
// CompressedPublicKey returns the compressed public key (33 bytes) // CompressedPublicKey returns the compressed public key (33 bytes)
func (u *UserKeyshare) CompressedPublicKey() []byte { func (u *UserKeyshare) CompressedPublicKey() []byte {
return u.BaseKeyshare.CompressedPubKey return u.CompressedPubKey
} }
func encodeMessage(m *protocol.Message) (string, error) { func encodeMessage(m *protocol.Message) (string, error) {

View File

@ -97,6 +97,7 @@ func (k ucanKeyshare) newToken(audienceDID string, prf []Proof, att Attenuations
}, nil }, nil
} }
// ComputeIssuerDID computes the issuer DID from a public key
func ComputeIssuerDID(pk []byte) (string, string, error) { func ComputeIssuerDID(pk []byte) (string, string, error) {
addr, err := ComputeSonrAddr(pk) addr, err := ComputeSonrAddr(pk)
if err != nil { if err != nil {
@ -105,6 +106,7 @@ func ComputeIssuerDID(pk []byte) (string, string, error) {
return fmt.Sprintf("did:sonr:%s", addr), addr, nil return fmt.Sprintf("did:sonr:%s", addr), addr, nil
} }
// ComputeSonrAddr computes the Sonr address from a public key
func ComputeSonrAddr(pk []byte) (string, error) { func ComputeSonrAddr(pk []byte) (string, error) {
sonrAddr, err := bech32.ConvertAndEncode("idx", pk) sonrAddr, err := bech32.ConvertAndEncode("idx", pk)
if err != nil { if err != nil {

View File

@ -0,0 +1,492 @@
## v0.5.19 (2024-12-06)
### Feat
- add support for parent field and resources list in Capability message
- add fast reflection methods for Capability and Resource
- add gum package and update devbox configuration
- add new button components and layout improvements
### Fix
- adjust fullscreen modal close button margin
- update devbox lockfile
- resolve rendering issue in login modal
### Refactor
- rename accaddr package to address
- Update Credential table to match WebAuthn Credential Descriptor
- Deployment setup
- migrate build system from Taskfile to Makefile
- rename Assertion to Account and update related code
- remove unused TUI components
- Move IPFS interaction functions to common package
- remove dependency on DWN.pkl
- remove unused dependencies and simplify module imports
- Rename x/vault -> x/dwn and x/service -> x/svc
- move resolver formatter to services package
- remove web documentation
- update devbox configuration and scripts
- rename layout component to root
- refactor authentication pages into their own modules
- update templ version to v0.2.778 and remove unused air config
- move signer implementation to mpc package
## v0.5.18 (2024-11-06)
## v0.5.17 (2024-11-05)
### Feat
- add remote client constructor
- add avatar image components
- add SVG CDN Illustrations to marketing architecture
- **marketing**: refactor marketing page components
- Refactor intro video component to use a proper script template
- Move Alpine.js script initialization to separate component
- Add intro video modal component
- add homepage architecture section
- add Hero section component with stats and buttons
- **css**: add new utility classes for group hover
- implement authentication register finish endpoint
- add controller creation step to allocate
- Update service module README based on protobuf files
- Update x/macaroon/README.md with details from protobuf files
- update Vault README with details from proto files
### Fix
- update file paths in error messages
- update intro video modal script
- include assets generation in wasm build
### Refactor
- update marketing section architecture
- change verification table id
- **proto**: remove macaroon proto
- rename ValidateBasic to Validate
- rename session cookie key
- remove unused sync-initial endpoint
- remove formatter.go from service module
## v0.5.16 (2024-10-21)
## v0.5.15 (2024-10-21)
## v0.5.14 (2024-10-21)
### Refactor
- remove StakingKeeper dependency from GlobalFeeDecorator
## v0.5.13 (2024-10-21)
### Feat
- add custom secp256k1 pubkey
### Refactor
- update gRPC client to use new request types
- use RawPublicKey instead of PublicKey in macaroon issuer
- improve error handling in DID module
## v0.5.12 (2024-10-18)
### Feat
- add User-Agent and Platform to session
- introduce AuthState enum for authentication state
### Fix
- **version**: revert version bump to 0.5.11
- **version**: update version to 0.5.12
### Refactor
- remove dependency on proto change detection
- update asset publishing configuration
## v0.5.11 (2024-10-10)
### Feat
- nebula assets served from CDN
- use CDN for nebula frontend assets
- add static hero section content to homepage
- add wrangler scripts for development, build, and deployment
- remove build configuration
- move gateway web code to dedicated directory
- add PubKey fast reflection
- **macaroon**: add transaction allowlist/denylist caveats
- add PR labeler
- **devbox**: remove hway start command
- add GitHub Actions workflow for running tests
- add workflow for deploying Hway to Cloudflare Workers
- Publish configs to R2
- integrate nebula UI with worker-assets-gen
- extract reusable layout components
- Implement service worker for IPFS vault
- implement CDN support for assets
- add payment method support
- add support for public key management
- add ModalForm component
- add LoginStart and RegisterStart routes
- implement authentication views
- add json tags to config structs
- implement templ forms for consent privacy, credential assert, credential register, and profile details
- **vault**: introduce assembly of the initial vault
- add client logos to homepage
- add tailwind utility classes
- implement new profile card component
### Fix
- Correct source directory for asset publishing
- install dependencies before nebula build
- update Schema service to use new API endpoint
- fix broken logo image path
### Refactor
- remove unnecessary branch configuration from scheduled release workflow
- update dwn configuration generation import path
- use nebula/routes instead of nebula/global
- move index template to routes package
- remove cdn package and move assets to global styles
- move nebula assets to hway build directory
- remove docker build and deployment
- rename internal/session package to internal/ctx
- remove unused fields from
- rename PR_TEMPLATE to PULL_REQUEST_TEMPLATE
- remove devbox.json init hook
- rename sonrd dockerfile to Dockerfile
- remove unused dependency
- rename 'global/cdn' to 'assets'
- move CDN assets to separate folder
- move Pkl module definitions to dedicated package
- move CDN assets to js/ folder
- remove unused component templates
- move ui components to global
- move view handlers to router package
## v0.5.10 (2024-10-07)
### Feat
- **blocks**: remove button component
## v0.5.9 (2024-10-06)
### Feat
- add Motr support
- update UIUX PKL to utilize optional fields
### Fix
- Update source directory for asset publishing
## v0.5.8 (2024-10-04)
### Refactor
- Remove unused logs configuration
## v0.5.7 (2024-10-04)
### Feat
- **devbox**: use process-compose for testnet services
- remove motr.mjs dependency
- add markdown rendering to issue templates
- update issue templates for better clarity
- add issue templates for tracking and task issues
- add issue templates for bug report and tracking
- introduce docker-compose based setup
### Refactor
- update issue template headings
- rename bug-report issue template to bug
## v0.5.6 (2024-10-03)
### Feat
- add hway and sonr processes to dev environment
## v0.5.5 (2024-10-03)
### Feat
- add rudimentary DidController table
- update home section with new features
- introduce Home model and refactor views
- **nebula**: create Home model for home page
### Refactor
- reorganize pkl files for better separation of concerns
- rename msg_server_test.go to rpc_test.go
## v0.5.4 (2024-10-02)
## v0.5.3 (2024-10-02)
### Fix
- remove unnecessary telegram message template
## v0.5.2 (2024-10-02)
### Feat
- **service**: integrate group module (#1104)
### Refactor
- revert version bump to 0.5.1
## v0.5.1 (2024-10-02)
### Refactor
- move Motr API to state package
## v0.5.0 (2024-10-02)
### Feat
- allow multiple macaroons with the same id
## v0.4.5 (2024-10-02)
### Fix
- use correct secret for docker login
## v0.4.4 (2024-10-02)
## v0.4.3 (2024-10-02)
### Feat
- **release**: add docker images for sonrd and motr
- update homepage with new visual design
- add DID to vault genesis schema
- add video component
- add video component
- add hx-get attribute to primary button in hero section
### Fix
- **layout**: add missing favicon
- **hero**: Use hx-swap for primary button to prevent flicker
### Refactor
- use single GITHUB_TOKEN for release workflow
- update workflow variables
## v0.4.2 (2024-10-01)
### Refactor
- use single GITHUB_TOKEN for release workflow
## v0.4.1 (2024-10-01)
### Feat
- Implement session management
- allow manual release triggers
- add Input and RegistrationForm models
- add new utility classes
- add login and registration pages
- add tailwindcss utilities
- add support for ARM64 architecture
- add DWN resolver field
- add stats section to homepage
- implement hero section using Pkl
- add PKL schema for message formats
- add Homebrew tap for sonr
- update release workflow to use latest tag
### Fix
- **version**: update version number to 0.4.0
- update release workflow to use latest tag
- **versioning**: revert version to 0.9.0
- **cta**: Fix typo in CTA title
- change bento section title to reflect security focus
- adjust hero image dimensions
- **Input**: Change type from to
- update hero image height in config.pkl
### Refactor
- move home page sections to home package
- rename motrd to motr
- update hero image dimensions
- move nebula configuration to static file
- rename buf-publish.yml to publish-assets.yml
- remove unused field from
## v0.4.0 (2024-09-30)
### Feat
- **dwn**: add wasm build for dwn
- add macaroon and oracle genesis states
- add scheduled binary release workflow
- introduce process-compose for process management
- add counter animation to hero section
- add registration page
### Fix
- Enable scheduled release workflow
### Refactor
- remove old changelog entries
- remove unnecessary checkout in scheduled-release workflow
- rename build ID to sonr
- remove unnecessary release existence check
- move dwn wasm build to pkg directory
## v0.3.1 (2024-09-29)
### Refactor
- move nebula/pages to pkg/nebula/pages
## v0.3.0 (2024-09-29)
### Feat
- add buf.lock for proto definitions
### Fix
- remove unused linting rules
- update proto breaking check target to master branch
### Refactor
- remove unused lock files and configurations
## v0.2.0 (2024-09-29)
### Feat
- disable goreleaser workflow
- update workflows to include master branch
- remove global style declaration
- **oracle**: add oracle module
- optimize IPFS configuration for better performance
- add local IPFS bootstrap script and refactor devbox config
- add AllocateVault HTTP endpoint
- add WebAuthn credential management functionality
- remove unused coins interface
- remove global integrity proof from genesis state
- add vault module
- enable buf.build publishing on master and develop branches
- add Gitflow workflow for syncing branches
- add automated production release workflow
- **ui**: implement profile page
- add automated production release workflow
- **did**: remove unused proto files
- add enums.pulsar.go file for PermissionScope enum (#4)
- add initial DID implementation
- remove builder interface
- add basic UI for block explorer
- add Usage: pkl [OPTIONS] COMMAND [ARGS]...
- use SQLite embedded driver
- add DID method for each coin
- Expand KeyType enum and update KeyInfo message in genesis.proto
- Add whitelisted key types to genesis params
- Add DID grants protobuf definition
- Add fields to KeyInfo struct to distinguish CBOR and standard blockchain key types
- Add new message types for AssetInfo, ChainInfo, Endpoint, ExplorerInfo, FeeInfo, and KeyInfo
- run sonr-node container in testnet network and make network external
- Add docker-compose.yaml file to start a Sonr testnet node
- configure Sonr testnet environment
- Update Dockerfile to start and run a testnet
- add Equal methods for AssetInfo and ChainInfo types
- Add ProveWitness and SyncVault RPCs
- Add MsgRegisterService to handle service registration
- Add MsgRegisterService to handle service registration
- add enums.pulsar.go file for PermissionScope enum
### Fix
- ensure go version is up-to-date
- use GITHUB_TOKEN for version bump workflow
- update account table interface to use address, chain and network
- **ci**: update docker vm release workflow with new token
- use mnemonic phrases for test account keys
- reduce motr proxy shutdown timeout
- **nebula**: use bunx for tailwindcss build
- **proto**: update protobuf message index numbers
- **ante**: reduce POA rate floor and ceiling
- Update proc_list_width in mprocs.yaml
- Add service to database when registering
- pin added did documents to local ipfs node
- remove extra spaces in typeUrl
- **release**: remove unnecessary quotes in tag pattern
- remove unused imports and simplify KeyInfo message
- bind node ports to localhost
- Update docker-compose network name to dokploy-network
- Update network name to dokploy
- remove unused port mapping
- Update docker-compose.yaml to use correct volume path
- update docker-compose volume name
- Update docker-compose.yaml to use shell directly for sonrd command
- replace "sh" with "/bin/sh" in docker-compose.yaml command
- Update runner image dependencies for debian-11
- **deps**: update golang image to 1.21
- **chains**: update nomic chain build target
- Remove unused `Meta` message from `genesis.proto`
- Add ProveWitness and SyncVault RPCs
### Refactor
- adjust source directory for config files (#1102)
- Use actions/checkout@v4
- remove unused master branch from CI workflow
- rename github token secret
- remove unnecessary x-cloak styles
- optimize oracle genesis proto
- remove unused code related to whitelisted assets
- update buf publish source directory
- adjust devbox configuration to reflect nebula changes
- rename msg_server.go to rpc.go
- remove devbox integration
- move dwn package to app/config
- move configuration files to app directory
- extract root command creation to separate file
- move ipfs setup to function
- remove unnecessary proxy config
- rename script to
- move DWN proxy server logic to separate file
- use htmx instead of dwn for vault client
- remove unused environment variables
- simplify verification method structure
- use staking keeper in DID keeper
- remove unused dependencies
- remove unused image building workflow
- add field to
- Update KeyKind Enum to have proper naming conventions
- Update `DIDNamespace` to have proper naming convention
- expose ports directly in docker-compose
- remove unused port mappings
- streamline script execution
- use CMD instead of ENTRYPOINT in Dockerfile
- **deps**: Upgrade Debian base image to 11
- Simplify the types and properties to keep a consistent structure for the blockchain
- remove PERMISSION_SCOPE_IDENTIFIERS_ENS enum value

View File

@ -0,0 +1,104 @@
## `x/did` - Auth & AuthZ
> The DID module is responsible for managing the creation and management of DIDs.
> Controllers represent on-chain accounts backed by a MPC keypair. Controllers
> provide methods for Wallet Account Abstraction (WAA) and are responsible for
> managing the creation and management of DIDs for an individual user.
### Features
- DID Controllers leverage the Cosmos SDK's `x/accounts` std interface for WAA.
- DIDs are represented by a `x/did` controller and are required to state the
controller's public key, and which map to the controller's capabilities.
- General Sign/Verify methods are provides from the QueryServer for HTTP requests.
- The Execute method is used to broadcast transactions across the network. (TODO)
- Biscuits are used to authenticate and authorize requests between services. (TODO)
### References
- [State](https://github.com/onsonr/sonr/tree/develop/x/did#state)
- [State Transitions](https://github.com/onsonr/sonr/tree/develop/x/did#state-transitions)
- [Messages](https://github.com/onsonr/sonr/tree/develop/x/did#messages)
- [Queries](https://github.com/onsonr/sonr/tree/develop/x/did#query)
- [Params](https://github.com/onsonr/sonr/tree/develop/x/did#params)
- [Client](https://github.com/onsonr/sonr/tree/develop/x/did#client)
- [Future Improvements](https://github.com/onsonr/sonr/tree/develop/x/did#future-improvements)
- [Tests](https://github.com/onsonr/sonr/tree/develop/x/did#tests)
- [Appendix](https://github.com/onsonr/sonr/tree/develop/x/did#appendix)
---
## `x/macaroon`
> The macaroon module is responsible for issuing and verifying macaroons. Macaroons
> are used to authenticate and authorize requests between services.
> Macaroons are requested by NFT Records from [`x/service`](2--Modules-Overview.md#x-service) and granted by controllers from [`x/did`](2--Modules-Overview.md#x/did)
### Features
- On Controller creation, a macaroon is created with an admin scope and a default expiry of _315,569,520 blocks (or ~10 years)_.
- On Service registration, a macaroon is created with a service scope and a default expiry of _31,556,952 blocks (or ~1 year)_.
- Macaroons contain the scope of access for a service and the expiry of the permissions in `blockHeight`.
### References
- [State](https://github.com/onsonr/sonr/tree/develop/x/macaroon#state)
- [State Transitions](https://github.com/onsonr/sonr/tree/develop/x/macaroon#state-transitions)
- [Messages](https://github.com/onsonr/sonr/tree/develop/x/macaroon#messages)
- [Queries](https://github.com/onsonr/sonr/tree/develop/x/macaroon#query)
- [Params](https://github.com/onsonr/sonr/tree/develop/x/macaroon#params)
- [Client](https://github.com/onsonr/sonr/tree/develop/x/macaroon#client)
- [Future Improvements](https://github.com/onsonr/sonr/tree/develop/x/macaroon#future-improvements)
- [Tests](https://github.com/onsonr/sonr/tree/develop/x/macaroon#tests)
- [Appendix](https://github.com/onsonr/sonr/tree/develop/x/macaroon#appendix)
---
## `x/service`
> The service module is responsible for managing decentralized services. Services
> on the Sonr network are essentially on-chain MultiSig wallets that are
> represented by a NFT. Service admins are represented by
> a [`x/did`](2--Modules-Overview.md#x-did) controller and are required to state
> the service's scope of access, and which map to the services' capabilities.
### Features
- Needs a Valid Domain with .htaccess file to be whitelisted.
### References
- [State](https://github.com/onsonr/sonr/tree/develop/x/service#state)
- [State Transitions](https://github.com/onsonr/sonr/tree/develop/x/service#state-transitions)
- [Messages](https://github.com/onsonr/sonr/tree/develop/x/service#messages)
- [Queries](https://github.com/onsonr/sonr/tree/develop/x/service#query)
- [Params](https://github.com/onsonr/sonr/tree/develop/x/service#params)
- [Client](https://github.com/onsonr/sonr/tree/develop/x/service#client)
- [Future Improvements](https://github.com/onsonr/sonr/tree/develop/x/service#future-improvements)
- [Tests](https://github.com/onsonr/sonr/tree/develop/x/service#tests)
- [Appendix](https://github.com/onsonr/sonr/tree/develop/x/service#appendix)
---
## `x/vault`
> The vault module is responsible for managing the storage and acccess-control of
> Decentralized Web Nodes (DWNs) from IPFS. Vaults contain user-facing keys and
> are represented by a [`x/did`](2--Modules-Overview.md#x-did) controller.
### Features
- Vaults can be created by anyone, but efforts are made to restrict 1 per user.
- Vaults are stored in IPFS and when claimed, the bech32 Sonr Address is pinned to IPFS.
### References
- [State](https://github.com/onsonr/sonr/tree/develop/x/vault#state)
- [State Transitions](https://github.com/onsonr/sonr/tree/develop/x/vault#state-transitions)
- [Messages](https://github.com/onsonr/sonr/tree/develop/x/vault#messages)
- [Queries](https://github.com/onsonr/sonr/tree/develop/x/vault#query)
- [Params](https://github.com/onsonr/sonr/tree/develop/x/vault#params)
- [Client](https://github.com/onsonr/sonr/tree/develop/x/vault#client)
- [Future Improvements](https://github.com/onsonr/sonr/tree/develop/x/vault#future-improvements)
- [Tests](https://github.com/onsonr/sonr/tree/develop/x/vault#tests)
- [Appendix](https://github.com/onsonr/sonr/tree/develop/x/vault#appendix)

View File

@ -0,0 +1,170 @@
# Consumer Chain Launch Process
This guide is intended for consumer chain teams that are looking to be onboarded on to the Interchain Security testnet.
## Interchain Security Testnet Overview
- The Interchain Security (ICS) testnet is to be used to launch and test consumer chains. We recommend consumer chains to launch on the testnet before launching on the mainnet.
- All information about the ICS testnet is available in this [repository](https://github.com/cosmos/testnets/tree/master/interchain-security).
- The testnet coordinators (Hypha) have majority voting power in the ICS testnet. This means we need to work with you to bring your chain live and also to successfully pass any governance proposals you make.
## Chain Onboarding Process
For teams looking to join the ICS testnet, the onboarding process can be broken down in four phases:
- Testing and Integration
- Planning with Testnet Coordinators
- Proposal Submission
- Chain Launch
### Local Testing and Integration
During this phase, your team will run integration tests with the following elements of an Interchain Security testnet:
- Gaia provider chain
- Visit the provider chain [page](./provider/) for details on which Gaia version is currently being used.
- Relayers
- You will be responsible for running the relayer that relays the first set of Validator Set Change packets between provider and consumer chain. You should be proficient in setting up and running either [Hermes](https://github.com/informalsystems/hermes) or [rly](https://github.com/cosmos/relayer).
By the end of this phase, you are able to launch a consumer chain within a local testnet or CI workflow that resembles the testnet (or mainnet) environment.
### Planning with Testnet Coordinators
Once you have a binary release ready, you can begin planning the launch with the testnet coordinators (Hypha).
The goals of this phase are to update this repository with all the information validators need to join the network and to produce a `consumer-addition` proposal to be submitted in the provider chain.
We expect you to run the minimum infrastructure required to make your consumer chain usable by testnet participants. This means running:
1. **Seed/persistent nodes**
2. **Relayer** it must be launched before the chain times out, preferably right after blocks start being produced.
- **IMPORTANT**: Make sure you have funds to pay gas fees for the relayer. You will likely need to set up an adequately funded genesis account for this purpose.
Additionally, you may want to run:
- a faucet such as this simple [REST faucet](https://github.com/hyphacoop/cosmos-rest-faucet) (it may need a separate funded account in the genesis file as well)
- a block explorer such as [ping.pub](https://github.com/ping-pub/explorer)
## ✍️ Submitting a PR for a new chain
Each consumer chain gets its own directory. You can use the [`slasher`](./stopped/slasher/) chain as reference. Feel free to clone the slasher directory, modify it for your consumer chain, and make a PR with the relevant information.
Hypha will be reviewing the PR to ensure it meets the following criteria:
#### README includes:
- [ ] Consumer chain repo and release or tag name.
- [ ] Build instructions for chain binary.
- [ ] Checksum of genesis file without CCV.
- [ ] Checksum of reference binary.
- [ ] Instructions on to join
- [ ] Installation steps
- Endpoints
- [ ] Seeds OR persistent peers
- [ ] State sync nodes (if any)
See the `slasher` chain [page](./stopped/slasher) for reference.
#### `chain_id` must be identical in the following places:
- [ ] `README`
- [ ] genesis file
- [ ] consumer addition proposal
- [ ] bash script
We recommend choosing a `chain_id` with the suffix `-1`, even if it's a subsequent test of the same chain, e.g. `testchain-second-rehearsal-1`.
#### Binary checksum validation
- [ ] `shasum -a 256 <binary>` matches the checksum in the proposal
- [ ] `shasum -a 256 <binary>` matches `README`
#### Bash script
- [ ] version built in script must match `README`
- [ ] seeds or persistent peers must match `README`
#### Genesis file
- [ ] Genesis time must match spawn time in the `consumer-addition` proposal
- [ ] Accounts and balances: Properly funded accounts (e.g., gas fees for relayer, faucet, etc.)
- [ ] Bank balance denom matches denom in `README`
- [ ] Slashing parameters: Set `signed_blocks_window` and `min_signed_per_window` adequately to ensure validators have at least 12 hours to join the chain after launch without getting jailed
- [ ] `shasum -a 256 <genesis file without CCV>` matches the checksum in the proposal
- [ ] `shasum -a 256 <genesis file without CCV>` matches the checksum in the `README`
- [ ] The genesis file is correctly formed: `<consumer binary or gaiad> validate-genesis /path/to/genesis-without-ccv.json` returns without error
See the `slasher` chain [genesis](./stopped/slasher/slasher-genesis-without-ccv.json) for reference.
#### `consumer-addition` proposal
- [ ] Spawn time must match genesis time
- [ ] Spawn time must be later than voting period
- [ ] `revision_height: 1`
- [ ] `revision_number: 1` (only if the `chain_id` ends in `-1`)
- [ ] `transfer_timeout_period: 1800000000000`. This value should be smaller than `blocks_per_distribution_transmission * block_time`.
- [ ] `ccv_timeout_period: 2419200000000000`. This value must be larger than the unbonding period, the default is 28 days.
- [ ] `unbonding_period: 1728000000000000` (given current provider params)
See the `slasher` chain consumer-addition [proposal](./stopped/slasher/proposal-slasher.json) and [Interchain Security time-based parameters](https://github.com/cosmos/interchain-security/blob/main/docs/params.md#time-based-parameters) for reference.
#### Node configurations
- [ ] `minimum_gas_prices`
- [ ] Check with Hypha about any other chain-specific params
---
### On-chain Proposal Submission
When you make your proposal, please let us know well in advance. The current voting period is five minutes, which means well need to vote right after you submit your proposal. We recommend submitting the proposal together with us on a call.
The following will take place during the proposal submission phase:
- Your team will submit the `consumer-addition` proposal with a command that looks like this:
```
gaiad tx gov submit-legacy-proposal consumer-addition proposal.json --from <account name> --chain-id provider --gas auto --fees 500uatom -b block -y
```
- Testnet coordinators will vote on it shortly afterwards to make sure it passes.
- You will open a pull request to add the new consumer chain entry to this repo and update the [schedule page](SCHEDULE.md) with the launch date.
- You will announce the upcoming launch, including the spawn time, in the Interchain Security `announcements` channel of the Cosmos Network Discord Server. If you need permissions for posting, please reach out to us.
### Chain Launch
After the spawn time is reached, the Cross-Chain Validation (CCV) state will be available on the provider chain and the new IBC client will be created. At this point, you will be able to:
- Collect the Cross-Chain Validation (CCV) state from the provider chain.
```
gaiad q provider consumer-genesis <chain-id> -o json > ccv-state.json
```
- Update the genesis file with the CCV state.
```
jq -s '.[0].app_state.ccvconsumer = .[1] | .[0]' <consumer genesis without CCV state> ccv-state.json > <consumer genesis file with CCV state>
```
- Publish the genesis file with CCV state to the testnets repo.
- Post the link to the genesis file and the SHA256 hash to the Interchain Security `interchain-security-testnet` channel of the Cosmos Network Discord Server.
- Ensure the required peers are online for people to connect to.
The consumer chain will start producing blocks as soon as 66.67% of the provider chain's voting power comes online. You will be able to start the relayer afterwards:
- Query the IBC client ID of the provider chain.
```
gaiad q provider list-consumer-chains
```
- Create the required IBC connections and channels for the CCV channel to be established. Using Hermes:
```
hermes create connection --a-chain <consumer chain ID> --a-client 07-tendermint-0 --b-client <provider chain client ID>
hermes create channel --a-chain <consumer chain ID> --a-port consumer --b-port provider --order ordered --a-connection connection-0 --channel-version 1
```
- Start the relayer
- The trusting period fraction is set to `0.25` on the provider chain, so you should use a trusting period of 5 days in your relayer configuration.
Finally, the testnet coordinators will:
- Trigger a validator set update in the provider chain to establish the CCV channel and verify the validator set has been updated in the consumer chain.
- Announce the chain is interchain secured.
- Update the testnets repo with the IBC information.
## Talk to us
If you're a consumer chain looking to launch, please get in touch with Hypha. You can reach Lexa Michaelides at `lexa@hypha.coop` or on Telegram.

View File

@ -0,0 +1,104 @@
## `x/did` - Auth & AuthZ
> The DID module is responsible for managing the creation and management of DIDs.
> Controllers represent on-chain accounts backed by a MPC keypair. Controllers
> provide methods for Wallet Account Abstraction (WAA) and are responsible for
> managing the creation and management of DIDs for an individual user.
### Features
- DID Controllers leverage the Cosmos SDK's `x/accounts` std interface for WAA.
- DIDs are represented by a `x/did` controller and are required to state the
controller's public key, and which map to the controller's capabilities.
- General Sign/Verify methods are provides from the QueryServer for HTTP requests.
- The Execute method is used to broadcast transactions across the network. (TODO)
- Biscuits are used to authenticate and authorize requests between services. (TODO)
### References
- [State](https://github.com/onsonr/sonr/tree/develop/x/did#state)
- [State Transitions](https://github.com/onsonr/sonr/tree/develop/x/did#state-transitions)
- [Messages](https://github.com/onsonr/sonr/tree/develop/x/did#messages)
- [Queries](https://github.com/onsonr/sonr/tree/develop/x/did#query)
- [Params](https://github.com/onsonr/sonr/tree/develop/x/did#params)
- [Client](https://github.com/onsonr/sonr/tree/develop/x/did#client)
- [Future Improvements](https://github.com/onsonr/sonr/tree/develop/x/did#future-improvements)
- [Tests](https://github.com/onsonr/sonr/tree/develop/x/did#tests)
- [Appendix](https://github.com/onsonr/sonr/tree/develop/x/did#appendix)
---
## `x/macaroon`
> The macaroon module is responsible for issuing and verifying macaroons. Macaroons
> are used to authenticate and authorize requests between services.
> Macaroons are requested by NFT Records from [`x/service`](2--Modules-Overview.md#x-service) and granted by controllers from [`x/did`](2--Modules-Overview.md#x/did)
### Features
- On Controller creation, a macaroon is created with an admin scope and a default expiry of _315,569,520 blocks (or ~10 years)_.
- On Service registration, a macaroon is created with a service scope and a default expiry of _31,556,952 blocks (or ~1 year)_.
- Macaroons contain the scope of access for a service and the expiry of the permissions in `blockHeight`.
### References
- [State](https://github.com/onsonr/sonr/tree/develop/x/macaroon#state)
- [State Transitions](https://github.com/onsonr/sonr/tree/develop/x/macaroon#state-transitions)
- [Messages](https://github.com/onsonr/sonr/tree/develop/x/macaroon#messages)
- [Queries](https://github.com/onsonr/sonr/tree/develop/x/macaroon#query)
- [Params](https://github.com/onsonr/sonr/tree/develop/x/macaroon#params)
- [Client](https://github.com/onsonr/sonr/tree/develop/x/macaroon#client)
- [Future Improvements](https://github.com/onsonr/sonr/tree/develop/x/macaroon#future-improvements)
- [Tests](https://github.com/onsonr/sonr/tree/develop/x/macaroon#tests)
- [Appendix](https://github.com/onsonr/sonr/tree/develop/x/macaroon#appendix)
---
## `x/service`
> The service module is responsible for managing decentralized services. Services
> on the Sonr network are essentially on-chain MultiSig wallets that are
> represented by a NFT. Service admins are represented by
> a [`x/did`](2--Modules-Overview.md#x-did) controller and are required to state
> the service's scope of access, and which map to the services' capabilities.
### Features
- Needs a Valid Domain with .htaccess file to be whitelisted.
### References
- [State](https://github.com/onsonr/sonr/tree/develop/x/service#state)
- [State Transitions](https://github.com/onsonr/sonr/tree/develop/x/service#state-transitions)
- [Messages](https://github.com/onsonr/sonr/tree/develop/x/service#messages)
- [Queries](https://github.com/onsonr/sonr/tree/develop/x/service#query)
- [Params](https://github.com/onsonr/sonr/tree/develop/x/service#params)
- [Client](https://github.com/onsonr/sonr/tree/develop/x/service#client)
- [Future Improvements](https://github.com/onsonr/sonr/tree/develop/x/service#future-improvements)
- [Tests](https://github.com/onsonr/sonr/tree/develop/x/service#tests)
- [Appendix](https://github.com/onsonr/sonr/tree/develop/x/service#appendix)
---
## `x/vault`
> The vault module is responsible for managing the storage and acccess-control of
> Decentralized Web Nodes (DWNs) from IPFS. Vaults contain user-facing keys and
> are represented by a [`x/did`](2--Modules-Overview.md#x-did) controller.
### Features
- Vaults can be created by anyone, but efforts are made to restrict 1 per user.
- Vaults are stored in IPFS and when claimed, the bech32 Sonr Address is pinned to IPFS.
### References
- [State](https://github.com/onsonr/sonr/tree/develop/x/vault#state)
- [State Transitions](https://github.com/onsonr/sonr/tree/develop/x/vault#state-transitions)
- [Messages](https://github.com/onsonr/sonr/tree/develop/x/vault#messages)
- [Queries](https://github.com/onsonr/sonr/tree/develop/x/vault#query)
- [Params](https://github.com/onsonr/sonr/tree/develop/x/vault#params)
- [Client](https://github.com/onsonr/sonr/tree/develop/x/vault#client)
- [Future Improvements](https://github.com/onsonr/sonr/tree/develop/x/vault#future-improvements)
- [Tests](https://github.com/onsonr/sonr/tree/develop/x/vault#tests)
- [Appendix](https://github.com/onsonr/sonr/tree/develop/x/vault#appendix)

View File

@ -0,0 +1,11 @@
> In order to maintain a tight-knit experience, we designed Sonr to operate completely
> in the point-of-view of the user. This led to us building a Component Library which
> creates consistent UX across the entire ecosystem.
# Overview
The Sonr blockchain is a Delegated Proof of Stake (DPoS) blockchain built with the Cosmos-sdk.
# Nebula Package
> The total supply of `$SNR` is fixed at 1 billion.

View File

@ -0,0 +1,40 @@
# Interchain Accounts
:::note Synopsis
Learn about what the Interchain Accounts module is
:::
## What is the Interchain Accounts module?
Interchain Accounts is the Cosmos SDK implementation of the ICS-27 protocol, which enables cross-chain account management built upon IBC.
- How does an interchain account differ from a regular account?
Regular accounts use a private key to sign transactions. Interchain Accounts are instead controlled programmatically by counterparty chains via IBC packets.
## Concepts
`Host Chain`: The chain where the interchain account is registered. The host chain listens for IBC packets from a controller chain which should contain instructions (e.g. Cosmos SDK messages) for which the interchain account will execute.
`Controller Chain`: The chain registering and controlling an account on a host chain. The controller chain sends IBC packets to the host chain to control the account.
`Interchain Account`: An account on a host chain created using the ICS-27 protocol. An interchain account has all the capabilities of a normal account. However, rather than signing transactions with a private key, a controller chain will send IBC packets to the host chain which signals what transactions the interchain account should execute.
`Authentication Module`: A custom application module on the controller chain that uses the Interchain Accounts module to build custom logic for the creation & management of interchain accounts. It can be either an IBC application module using the [legacy API](10-legacy/03-keeper-api.md), or a regular Cosmos SDK application module sending messages to the controller submodule's `MsgServer` (this is the recommended approach from ibc-go v6 if access to packet callbacks is not needed). Please note that the legacy API will eventually be removed and IBC applications will not be able to use them in later releases.
## SDK security model
SDK modules on a chain are assumed to be trustworthy. For example, there are no checks to prevent an untrustworthy module from accessing the bank keeper.
The implementation of ICS-27 in ibc-go uses this assumption in its security considerations.
The implementation assumes other IBC application modules will not bind to ports within the ICS-27 namespace.
## Channel Closure
The provided interchain account host and controller implementations do not support `ChanCloseInit`. However, they do support `ChanCloseConfirm`.
This means that the host and controller modules cannot close channels, but they will confirm channel closures initiated by other implementations of ICS-27.
In the event of a channel closing (due to a packet timeout in an ordered channel, for example), the interchain account associated with that channel can become accessible again if a new channel is created with a (JSON-formatted) version string that encodes the exact same `Metadata` information of the previous channel. The channel can be reopened using either [`MsgRegisterInterchainAccount`](./05-messages.md#msgregisterinterchainaccount) or `MsgChannelOpenInit`. If `MsgRegisterInterchainAccount` is used, then it is possible to leave the `version` field of the message empty, since it will be filled in by the controller submodule. If `MsgChannelOpenInit` is used, then the `version` field must be provided with the correct JSON-encoded `Metadata` string. See section [Understanding Active Channels](./09-active-channels.md#understanding-active-channels) for more information.
When reopening a channel with the default controller submodule, the ordering of the channel cannot be changed. In order to change the ordering of the channel, the channel has to go through a [channel upgrade handshake](../../01-ibc/06-channel-upgrades.md) or reopen the channel with a custom controller implementation.

View File

@ -0,0 +1,10 @@
> With increasingly sensitive information being stored in centralized databases, we
> believe that a decentralized anonymity mechanism is the only way to protect user data.
> Sonr is at its core a peer-to-peer identity system, which means that users can choose
> to share their identity with others in a way that is private and secure.
# Decentralized Identifiers
# Cross-chain Interoperability
# W3C Web APIs

View File

@ -0,0 +1,11 @@
> The `$SNR` token is the native platform token of the Sonr network. It is used by services to
> pay for Authentication and Authorization services. The system is designed for developers to
> be similar to centralized authentication providers like Google, Facebook, Okta, etc.
# Usage
The Sonr blockchain is a Delegated Proof of Stake (DPoS) blockchain built with the Cosmos-sdk.
# Supply
> The total supply of `$SNR` is fixed at 1 billion.

View File

@ -0,0 +1,21 @@
> Sonr is a decentralized platform that allows users to create and manage their own decentralized identity.
# Blockchain: Sonr
Sonr stores Decentralized Identifiers (DIDs) on its Cosmos-sdk based blockchain. The blockchain's role is to act as the
persistent pointer store for locations of User owned data.
# User Key Vault: Motr
The Motr node is a service-worker which functions as a personal encrypted key-enclave for users stored on IPFS. They can be allocated and persisted on the
Sonr blockchain for Smart Wallet functionality.
# Network Gateway: Hway
The Hway protocol is a network proxy which routes network requests to the appropriate service endpoint. This is used for seamless communication between
Blockchain Nodes, Decentralized Applications, and User Nodes.
# Design System: Nebula
Built with Golang-Templ, TailwindCSS, HTMX, and Service Workers - Nebula is a component library which allows for
consistent UX across the entire ecosystem.

View File

@ -0,0 +1,13 @@
> The `$SNR` token is the native platform token of the Sonr network. It is used by services to
> pay for Authentication and Authorization services. The system is designed for developers to
> be similar to centralized authentication providers like Google, Facebook, Okta, etc.
# Usage
The Sonr blockchain is a Delegated Proof of Stake (DPoS) blockchain built with the Cosmos-sdk.
# Supply
> The total supply of `$SNR` is fixed at 1 billion.
![image](https://github.com/user-attachments/assets/8b9d6e6b-f3e5-464a-9032-6d8fe257a748)

11
docs/docs/design/404.md Normal file
View File

@ -0,0 +1,11 @@
<div style="text-align: center;">
# Page Not Found
![A UFO takes one of the little worker monsters](/assets/images/undraw-taken.svg)
The page you were looking for couldn't be found.
Press [[/]] to search, or [head back to the homepage](/).
</div>

118
docs/docs/design/index.md Normal file
View File

@ -0,0 +1,118 @@
<div class="splash">
<div class="splash-start">
<img class="splash-logo" src="/assets/images/wordmark.svg" alt="Shoelace">
# <sl-visually-hidden>Shoelace:</sl-visually-hidden> A forward-thinking library of web components.
- Works with all frameworks 🧩
- Works with CDNs 🚛
- Fully customizable with CSS 🎨
- Includes a dark theme 🌛
- Built with accessibility in mind ♿️
- First-class [React support](/frameworks/react) ⚛️
- Built-in localization 💬
- Open source 😸
- [More awesome than ever](https://blog.fontawesome.com/shoelace-joins-font-awesome/) ![Awesome emoji](/assets/images/awesome.svg)
</div>
<div class="splash-end">
<img class="splash-image" src="/assets/images/undraw-content-team.svg" alt="Cartoon of people assembling components while standing on a giant laptop.">
</div>
</div>
<div class="badges">
[![jsDelivr](https://data.jsdelivr.com/v1/package/npm/@shoelace-style/shoelace/badge)](https://www.jsdelivr.com/package/npm/@shoelace-style/shoelace)
[![npm](https://img.shields.io/npm/dw/@shoelace-style/shoelace?label=npm&style=flat-square)](https://www.npmjs.com/package/@shoelace-style/shoelace)
[![License](https://img.shields.io/badge/license-MIT-232323.svg?style=flat-square)](https://github.com/shoelace-style/shoelace/blob/next/LICENSE.md)<br>
[![Discord](https://img.shields.io/badge/Discord-Join%20the%20chat-5965f2.svg?style=flat-square&logo=discord&logoColor=white)](https://discord.gg/mg8f26C)
[![Twitter](https://img.shields.io/badge/Twitter-Follow-00acee.svg?style=flat-square&logo=twitter&logoColor=white)](https://twitter.com/shoelace_style)
[![Sponsor](https://img.shields.io/badge/GitHub-Code-232323.svg?style=flat-square&logo=github&logoColor=white)](https://github.com/shoelace-style/shoelace)
</div>
## Quick Start
Add the following code to your page.
<!-- prettier-ignore -->
```html
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@shoelace-style/shoelace@%VERSION%/%CDNDIR%/themes/light.css" />
<script type="module" src="https://cdn.jsdelivr.net/npm/@shoelace-style/shoelace@%VERSION%/%CDNDIR%/shoelace-autoloader.js"></script>
```
Now you have access to all of Shoelace's components! Try adding a button:
```html:preview:expanded:no-codepen
<sl-button>Click me</sl-button>
```
:::tip
This will activate Shoelace's experimental autoloader, which registers components on the fly as you use them. To learn more about it, or for other ways to install Shoelace, refer to the [installation instructions](getting-started/installation).
:::
## New to Web Components?
**TL;DR**  we finally have a way to create [our own HTML elements](https://html.spec.whatwg.org/multipage/custom-elements.html) and use them in any framework we want!
Thanks to the popularity of frameworks such as Angular, Vue, and React, component-driven development has become a part of our every day lives. Components help us encapsulate styles and behaviors into reusable building blocks. They make a lot of sense in terms of design, development, and testing.
Unfortunately, _framework-specific_ components fail us in a number of ways:
- You can only use them in the framework they're designed for 🔒
- Their lifespan is limited to that of the framework's ⏳
- New frameworks/versions can lead to breaking changes, requiring substantial effort to update components 😭
Web components solve these problems. They're [supported by all modern browsers](https://caniuse.com/#feat=custom-elementsv1), they're framework-agnostic, and they're [part of the standard](https://developer.mozilla.org/en-US/docs/Web/Web_Components), so we know they'll be supported for many years to come.
This is the technology that Shoelace is built on.
## What Problem Does This Solve?
Shoelace provides a collection of professionally designed, highly customizable UI components built on a framework agnostic technology. Why spend hundreds of hours (or more) building a design system from scratch? Why make a component library that only works with one framework?
With Shoelace, you can:
- Start building things faster (no need to roll your own buttons)
- Build multiple apps with different frameworks that all share the same UI components
- Fully customize components to match your existing designs
- Incrementally adopt components as needed (no need to ditch your framework)
- Upgrade or switch frameworks without rebuilding foundational components
If your organization is looking to build a design system, [Shoelace will save you thousands of dollars](https://medium.com/eightshapes-llc/and-you-thought-buttons-were-easy-26eb5b5c1871). All the foundational components you need are right here, ready to be customized for your brand. And since it's built on web standards, browsers will continue to support it for many years to come.
Whether you use Shoelace as a starting point for your organization's design system or for a fun personal project, there's no limit to what you can do with it.
## Browser Support
Shoelace is tested in the latest two versions of the following browsers.
<img src="/assets/images/chrome.png" alt="Chrome" width="64" height="64">
<img src="/assets/images/edge.png" alt="Edge" width="64" height="64">
<img src="/assets/images/firefox.png" alt="Firefox" width="64" height="64">
<img src="/assets/images/opera.png" alt="Opera" width="64" height="64">
<img src="/assets/images/safari.png" alt="Safari" width="64" height="64">
Critical bug fixes in earlier versions will be addressed based on their severity and impact.
If you need to support IE11 or pre-Chromium Edge, this library isn't for you. Although web components can (to some degree) be polyfilled for legacy browsers, supporting them is outside the scope of this project. If you're using Shoelace in such a browser, you're gonna have a bad time. ⛷
## License
Shoelace was created in New Hampshire by [Cory LaViska](https://twitter.com/claviska). It's available under the terms of the [MIT license](https://github.com/shoelace-style/shoelace/blob/next/LICENSE.md).
## Attribution
Special thanks to the following projects and individuals that help make Shoelace possible.
- Components are built with [Lit](https://lit.dev/)
- Component metadata is generated by the [Custom Elements Manifest Analyzer](https://github.com/open-wc/custom-elements-manifest)
- Documentation is powered by [11ty](https://www.11ty.dev/)
- CDN services are provided by [jsDelivr](https://www.jsdelivr.com/)
- Color primitives are inspired by [Tailwind](https://tailwindcss.com/)
- Icons are courtesy of [Bootstrap Icons](https://icons.getbootstrap.com/)
- The homepage illustration is courtesy of [unDraw](https://undraw.co/)
- Positioning of dropdowns, tooltips, et al is handled by [Floating UI](https://floating-ui.com/)
- Animations are courtesy of [animate.css](https://animate.style/)
- Search is powered by [Lunr](https://lunrjs.com/)
- The Shoelace logo was designed with a single shoelace by [Adam K Olson](https://twitter.com/adamkolson)

47
docs/docs/index.md Normal file
View File

@ -0,0 +1,47 @@
# Introduction
Sonr is a decentralized identity network built on the Cosmos-sdk. It has early origins as a peer-to-peer file sharing network, but has since evolved into a platform for decentralized authentication and authorization. The early lessons taught from our file sharing roots are used as our theology for building the Sonr Blockchain.
1. [Cosmos-SDK](./concepts/Cosmos-SDK.md)
2. [Chain-Modules](./concepts/Chain-Modules.md)
3. [System-Architecture](./concepts/System-Architecture.md)
4. [Token-Economy](./concepts/Token-Economy.md)
5. [Service-Management](./concepts/Service-Management.md)
6. [Design-System](./concepts/Design-System.md)
7. [Self-Custody](./concepts/Self-Custody.md)
8. [Consumer Launch](./concepts/Consumer-Launch.md)
## Principles
1. Bitcoin is digital gold
2. Blockchains are programmable databases with functional operations
3. Staking is essentially a savings account
4. The Sonr Network conducts all operations in the $SNR token
5. Service Delegation subsidizes user wallet operations.
6. Cryptocurrency has the potential to break the software innovation ceiling
## The Problem
Centralized identity has led to internet monopolies abusing your trust and privacy.
## The Solution
A peer-to-peer system for decentralized personal identity with Authentication and Authorization capabilities.
## What is Sonr?
A privacy preserving, identity system managed by user controlled decentralized vaults which have the flexibility of
software wallets with the security of hardware wallets.
## The End Goal
A Data sharing economy where human-specific information has intrinsic value. Services are incentivized to act in
good faith in order to obtain quality user data.
## How do we do it?
Provide Internet Citizens with a robust easy to use WebVault which features a crypto wallet, passkey authenticator, and encrypted messages. The WebVault serves as a wrapper over every sensitive intent-based user interaction. The Smart blockchain is responsible for keeping a record of where WebVaults are located, when authorization activity occurs, and which services are allowed over what permissions.
## The User Incentive
Data is the byproduct of currency exchange in the Information age. Meaning services pay other services for user data or profits in order to enrich their database with complete user personas.

55
docs/mkdocs.yml Normal file
View File

@ -0,0 +1,55 @@
site_name: Sonr Docs
site_description: Sonr is a decentralized identity network built on the Cosmos-sdk. It has early origins as a peer-to-peer file sharing network, but has since evolved into a platform for decentralized authentication and authorization. The early lessons taught from our file sharing roots are used as our theology for building the Sonr Blockchain.
site_url: https://onsonr.dev
theme:
name: material
features:
- announce.dismiss
- content.action.edit
- content.action.view
- content.code.annotate
- content.code.copy
- content.code.select
# - content.footnote.tooltips
# - content.tabs.link
- content.tooltips
- header.autohide
# - navigation.expand
- navigation.footer
- navigation.indexes
- navigation.instant
- navigation.instant.prefetch
# - navigation.instant.progress
# - navigation.prune
- navigation.sections
- navigation.tabs
- navigation.tabs.sticky
- navigation.top
- navigation.tracking
- search.highlight
- search.share
- search.suggest
- toc.follow
- toc.integrate
palette:
- media: "(prefers-color-scheme)"
toggle:
icon: material/link
name: Switch to light mode
- media: "(prefers-color-scheme: light)"
scheme: default
primary: cyan
accent: cyan
toggle:
icon: material/toggle-switch
name: Switch to dark mode
- media: "(prefers-color-scheme: dark)"
scheme: slate
primary: black
accent: cyan
toggle:
icon: material/toggle-switch-off
name: Switch to system preference
font:
text: Roboto
code: Roboto Mono

View File

@ -4,6 +4,10 @@ import (
"encoding/base64" "encoding/base64"
) )
type Payment struct {
IsPayment bool `json:"isPayment"`
}
type LargeBlob struct { type LargeBlob struct {
Support string `json:"support"` Support string `json:"support"`
Write string `json:"write"` Write string `json:"write"`

View File

@ -1,4 +1,4 @@
package clients package resolver
import ( import (
"net/http" "net/http"

View File

@ -1,4 +1,4 @@
package clients package resolver
import ( import (
bankv1beta1 "cosmossdk.io/api/cosmos/bank/v1beta1" bankv1beta1 "cosmossdk.io/api/cosmos/bank/v1beta1"

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

View File

@ -25,24 +25,30 @@ func HandleIndex(c echo.Context) error {
// Initial users have no authorization, user handle, or vault address // Initial users have no authorization, user handle, or vault address
func isInitial(c echo.Context) bool { func isInitial(c echo.Context) bool {
noAuth := !session.HasAuthorization(c) sess, err := session.Get(c)
noUserHandle := !session.HasUserHandle(c) if err != nil {
noVaultAddress := !session.HasVaultAddress(c) return false
return noUserHandle && noVaultAddress && noAuth }
data := sess.Session()
return data.UserHandle == "" && data.VaultAddress == ""
} }
// Expired users have either a user handle or vault address // Expired users have either a user handle or vault address
func isExpired(c echo.Context) bool { func isExpired(c echo.Context) bool {
noAuth := !session.HasAuthorization(c) sess, err := session.Get(c)
hasUserHandle := session.HasUserHandle(c) if err != nil {
hasVaultAddress := session.HasVaultAddress(c) return false
return noAuth && hasUserHandle || noAuth && hasVaultAddress }
data := sess.Session()
return data.UserHandle != "" || data.VaultAddress != ""
} }
// Returning users have a valid authorization, and either a user handle or vault address // Returning users have a valid authorization, and either a user handle or vault address
func isReturning(c echo.Context) bool { func isReturning(c echo.Context) bool {
hasAuth := session.HasAuthorization(c) sess, err := session.Get(c)
hasUserHandle := session.HasUserHandle(c) if err != nil {
hasVaultAddress := session.HasVaultAddress(c) return false
return hasAuth && (hasUserHandle || hasVaultAddress) }
data := sess.Session()
return data.UserHandle != "" && data.VaultAddress != ""
} }

View File

@ -3,7 +3,6 @@ package handlers
import ( import (
"net/http" "net/http"
"github.com/cosmos/btcutil/bech32"
"github.com/go-webauthn/webauthn/protocol" "github.com/go-webauthn/webauthn/protocol"
"github.com/go-webauthn/webauthn/protocol/webauthncose" "github.com/go-webauthn/webauthn/protocol/webauthncose"
"github.com/labstack/echo/v4" "github.com/labstack/echo/v4"
@ -22,23 +21,14 @@ func HandleRegisterView(env config.Env) echo.HandlerFunc {
} }
func HandleRegisterStart(c echo.Context) error { func HandleRegisterStart(c echo.Context) error {
firstName := c.FormValue("first_name")
lastName := c.FormValue("last_name")
handle := c.FormValue("handle") handle := c.FormValue("handle")
if firstName == "" || lastName == "" || handle == "" {
return response.RedirectLanding(c)
}
ks, err := mpc.NewKeyset() ks, err := mpc.NewKeyset()
if err != nil { if err != nil {
return echo.NewHTTPError(http.StatusInternalServerError, err.Error()) return echo.NewHTTPError(http.StatusInternalServerError, err.Error())
} }
adr, err := bech32.Encode("idx", ks.Val().GetPublicKey())
if err != nil { req := getLinkCredentialRequest(c, ks.Address(), handle, ks.UserJSON())
return echo.NewHTTPError(http.StatusInternalServerError, err.Error())
}
req := getLinkCredentialRequest(c, adr, handle, ks.UserJSON())
return response.TemplEcho(c, register.LinkCredentialView(req)) 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)), RegisterOptions: buildRegisterOptions(buildUserEntity(addr, handle), buildLargeBlob(userKSJSON), buildServiceEntity(c)),
} }
} }
data := cc.Session()
usr := buildUserEntity(addr, handle) usr := buildUserEntity(addr, handle)
blob := buildLargeBlob(userKSJSON) blob := buildLargeBlob(userKSJSON)
service := buildServiceEntity(c) service := buildServiceEntity(c)
return register.LinkCredentialRequest{ return register.LinkCredentialRequest{
Platform: cc.BrowserName(), Platform: data.BrowserName,
Handle: handle, Handle: data.UserHandle,
DeviceModel: cc.BrowserVersion(), DeviceModel: data.BrowserVersion,
Address: addr, Address: addr,
RegisterOptions: buildRegisterOptions(usr, blob, service), RegisterOptions: buildRegisterOptions(usr, blob, service),
} }

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")
)

View File

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

View File

@ -1,19 +1,41 @@
package database 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 { type Session struct {
ID string `json:"id"` gorm.Model
ID string `json:"id" gorm:"primaryKey"`
BrowserName string `json:"browserName"` BrowserName string `json:"browserName"`
BrowserVersion string `json:"browserVersion"` BrowserVersion string `json:"browserVersion"`
UserArchitecture string `json:"userArchitecture"` UserArchitecture string `json:"userArchitecture"`
Platform string `json:"platform"` Platform string `json:"platform"`
PlatformVersion string `json:"platformVersion"` PlatformVersion string `json:"platformVersion"`
DeviceModel string `json:"deviceModel"` DeviceModel string `json:"deviceModel"`
} UserHandle string `json:"userHandle"`
type User struct {
Address string `json:"address"`
Handle string `json:"handle"`
FirstName string `json:"firstName"` FirstName string `json:"firstName"`
LastName string `json:"lastName"` LastInitial string `json:"lastInitial"`
VaultCID string `json:"vaultCID"` VaultAddress string `json:"vaultAddress"`
} }

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

View File

@ -10,7 +10,7 @@ templ InitialView() {
@layout.Container() { @layout.Container() {
@text.Header("Sonr.ID", "The decentralized identity layer for the web.") @text.Header("Sonr.ID", "The decentralized identity layer for the web.")
<div class="pt-3 flex flex-col items-center justify-center h-full"> <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> <sl-icon slot="prefix" library="sonr" name="sonr"></sl-icon>
Get Started Get Started
<sl-icon slot="suffix" library="sonr" name="arrow-right"></sl-icon> <sl-icon slot="suffix" library="sonr" name="arrow-right"></sl-icon>

View File

@ -62,7 +62,7 @@ func InitialView() templ.Component {
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err 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 { if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err return templ_7745c5c3_Err
} }

View File

@ -2,26 +2,18 @@ package session
import ( import (
"net/http" "net/http"
"regexp"
"strings"
"github.com/labstack/echo/v4" "github.com/labstack/echo/v4"
"github.com/onsonr/sonr/pkg/common" "github.com/onsonr/sonr/pkg/common"
"github.com/onsonr/sonr/pkg/gateway/internal/database"
"github.com/segmentio/ksuid"
"gorm.io/gorm"
) )
type contextKey string // Get returns the HTTPContext from the echo context
func Get(c echo.Context) (*HTTPContext, error) {
// 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) {
ctx, ok := c.(*HTTPContext) ctx, ok := c.(*HTTPContext)
if !ok { if !ok {
return nil, echo.NewHTTPError(http.StatusInternalServerError, "Session Context not found") return nil, echo.NewHTTPError(http.StatusInternalServerError, "Session Context not found")
@ -29,18 +21,89 @@ func Get(c echo.Context) (SessionCtx, error) {
return ctx, nil return ctx, nil
} }
// TODO: Returns fixed chain ID for testing. // HTTPContext is the context for HTTP endpoints.
func GetChainID(c echo.Context) string { type HTTPContext struct {
return "sonr-testnet-1" echo.Context
db *gorm.DB
sess *database.Session
} }
// SetVaultAddress sets the address of the vault // NewHTTPContext creates a new session context
func SetVaultAddress(c echo.Context, address string) error { func NewHTTPContext(c echo.Context, db *gorm.DB) *HTTPContext {
return common.WriteCookie(c, common.SonrAddress, address) return &HTTPContext{
Context: c,
db: db,
}
} }
// SetVaultAuthorization sets the UCAN CID of the vault // Session returns the current session
func SetVaultAuthorization(c echo.Context, ucanCID string) error { func (s *HTTPContext) Session() *database.Session {
common.HeaderWrite(c, common.Authorization, formatAuth(ucanCID)) 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 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
}

View File

@ -2,48 +2,18 @@ package session
import ( import (
"github.com/labstack/echo/v4" "github.com/labstack/echo/v4"
"gorm.io/gorm"
"github.com/onsonr/sonr/pkg/common"
"github.com/onsonr/sonr/pkg/gateway/config"
) )
// Middleware establishes a Session Cookie. // Middleware creates a new session middleware
func Middleware(env config.Env) echo.MiddlewareFunc { func Middleware(db *gorm.DB) echo.MiddlewareFunc {
return func(next echo.HandlerFunc) echo.HandlerFunc { return func(next echo.HandlerFunc) echo.HandlerFunc {
return func(c echo.Context) error { 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) 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)
}

View File

@ -1,184 +1,168 @@
package session package session
import ( import (
"regexp"
"strings"
"github.com/go-webauthn/webauthn/protocol"
"github.com/go-webauthn/webauthn/protocol/webauthncose"
"github.com/labstack/echo/v4" "github.com/labstack/echo/v4"
"github.com/segmentio/ksuid" "github.com/onsonr/sonr/pkg/gateway/internal/database"
"github.com/onsonr/sonr/pkg/common"
) )
const kWebAuthnTimeout = 6000 // ╭───────────────────────────────────────────────────────╮
// │ DB Setter Functions │
// ╰───────────────────────────────────────────────────────╯
// HTTPContext is the context for HTTP endpoints. // SetUserHandle sets the user handle in the session
type HTTPContext struct { func SetUserHandle(c echo.Context, handle string) error {
echo.Context sess, err := Get(c)
role common.PeerRole
id string
chal string
bn string
bv string
}
// initHTTPContext loads the headers from the request.
func initHTTPContext(c echo.Context) *HTTPContext {
if c == nil {
return &HTTPContext{}
}
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,
}
// 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 { if err != nil {
sessionID = genKsuid() return err
} }
} sess.Session().UserHandle = handle
common.WriteCookie(c, common.SessionID, sessionID) return sess.db.Save(sess.Session()).Error
return nil
} }
// ╭───────────────────────────────────────────────────────────╮ // SetFirstName sets the first name in the session
// │ Extraction │ func SetFirstName(c echo.Context, name string) error {
// ╰───────────────────────────────────────────────────────────╯ sess, err := Get(c)
if err != nil {
func extractPeerInfo(c echo.Context) (string, string) { return err
var chal protocol.URLEncodedBase64 }
id, _ := common.ReadCookie(c, common.SessionID) sess.Session().FirstName = name
chalRaw, _ := common.ReadCookieBytes(c, common.SessionChallenge) return sess.db.Save(sess.Session()).Error
chal.UnmarshalJSON(chalRaw)
return id, common.Base64Encode(chal)
} }
func extractBrowserInfo(c echo.Context) (string, string) { // SetLastInitial sets the last initial in the session
secCHUA := common.HeaderRead(c, common.UserAgent) func SetLastInitial(c echo.Context, initial string) error {
sess, err := Get(c)
// If common.is empty, return empty BrowserInfo if err != nil {
if secCHUA == "" { return err
return "N/A", "-1"
} }
sess.Session().LastInitial = initial
// Split the common.into individual browser entries return sess.db.Save(sess.Session()).Error
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 { // SetVaultAddress sets the vault address in the session
return name != common.BrowserNameUnknown.String() && name != common.BrowserNameChromium.String() func SetVaultAddress(c echo.Context, address string) error {
} sess, err := Get(c)
if err != nil {
// ╭───────────────────────────────────────────────────────────╮ return err
// │ Authentication │
// ╰───────────────────────────────────────────────────────────╯
func buildUserEntity(userID string) protocol.UserEntity {
return protocol.UserEntity{
ID: userID,
} }
sess.Session().VaultAddress = address
return sess.db.Save(sess.Session()).Error
} }
// returns the base options for registering a new user without challenge or user entity. // ╭───────────────────────────────────────────────────────╮
func baseRegisterOptions() *protocol.PublicKeyCredentialCreationOptions { // │ DB Getter Functions │
return &protocol.PublicKeyCredentialCreationOptions{ // ╰───────────────────────────────────────────────────────╯
Timeout: kWebAuthnTimeout,
Attestation: protocol.PreferDirectAttestation, // GetID returns the session ID
AuthenticatorSelection: protocol.AuthenticatorSelection{ func GetID(c echo.Context) (string, error) {
AuthenticatorAttachment: "platform", sess, err := Get(c)
ResidentKey: protocol.ResidentKeyRequirementPreferred, if err != nil {
UserVerification: "preferred", return "", err
},
Parameters: []protocol.CredentialParameter{
{
Type: "public-key",
Algorithm: webauthncose.AlgES256,
},
{
Type: "public-key",
Algorithm: webauthncose.AlgES256K,
},
{
Type: "public-key",
Algorithm: webauthncose.AlgEdDSA,
},
},
} }
return sess.Session().ID, nil
} }
func formatAuth(ucanCID string) string { // GetBrowserName returns the browser name
return "Bearer " + ucanCID 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
}
var count int64
if err := sess.db.Model(&database.Session{}).Where("user_handle = ?", handle).Count(&count).Error; err != nil {
return false, err
}
return count > 0, nil
} }

View File

@ -10,16 +10,23 @@ import (
"github.com/onsonr/sonr/pkg/gateway/internal/session" "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 // Custom error handler for gateway
e.HTTPErrorHandler = response.RedirectOnError("http://localhost:3000") e.HTTPErrorHandler = response.RedirectOnError("http://localhost:3000")
// Inject session middleware // Initialize database
e.Use(session.Middleware(env)) db, err := database.InitDB(env)
e.Use(database.Middleware(env)) if err != nil {
return err
}
// Inject session middleware with database connection
e.Use(session.Middleware(db))
// Register routes // Register routes
e.GET("/", handlers.HandleIndex) e.GET("/", handlers.HandleIndex)
e.GET("/register", handlers.HandleRegisterView(env)) e.GET("/register", handlers.HandleRegisterView(env))
e.POST("/register/start", handlers.HandleRegisterStart) e.POST("/register/start", handlers.HandleRegisterStart)
e.POST("/register/finish", handlers.HandleRegisterFinish) e.POST("/register/finish", handlers.HandleRegisterFinish)
return nil
} }

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

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

View File

@ -160,7 +160,7 @@ func Alpine() templ.Component {
var templ_7745c5c3_Var7 string var templ_7745c5c3_Var7 string
templ_7745c5c3_Var7, templ_7745c5c3_Err = templ.JoinStringErrs(jsDelivrURL("alpinejs", "3.14.6", "dist/cdn.min.js")) templ_7745c5c3_Var7, templ_7745c5c3_Err = templ.JoinStringErrs(jsDelivrURL("alpinejs", "3.14.6", "dist/cdn.min.js"))
if templ_7745c5c3_Err != nil { 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)) _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var7))
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
@ -173,7 +173,7 @@ func Alpine() templ.Component {
var templ_7745c5c3_Var8 string var templ_7745c5c3_Var8 string
templ_7745c5c3_Var8, templ_7745c5c3_Err = templ.JoinStringErrs(jsDelivrURL("@alpinejs/focus", "3.14.6", "dist/cdn.min.js")) templ_7745c5c3_Var8, templ_7745c5c3_Err = templ.JoinStringErrs(jsDelivrURL("@alpinejs/focus", "3.14.6", "dist/cdn.min.js"))
if templ_7745c5c3_Err != nil { 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)) _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var8))
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
@ -234,7 +234,7 @@ func Dexie() templ.Component {
var templ_7745c5c3_Var11 string var templ_7745c5c3_Var11 string
templ_7745c5c3_Var11, templ_7745c5c3_Err = templ.JoinStringErrs(jsDelivrURL("dexie", "4.0.10", "dist/dexie.min.js")) templ_7745c5c3_Var11, templ_7745c5c3_Err = templ.JoinStringErrs(jsDelivrURL("dexie", "4.0.10", "dist/dexie.min.js"))
if templ_7745c5c3_Err != nil { 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)) _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var11))
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
@ -247,7 +247,7 @@ func Dexie() templ.Component {
var templ_7745c5c3_Var12 string 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")) 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 { 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)) _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var12))
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
@ -308,7 +308,7 @@ func Htmx() templ.Component {
var templ_7745c5c3_Var15 string var templ_7745c5c3_Var15 string
templ_7745c5c3_Var15, templ_7745c5c3_Err = templ.JoinStringErrs(jsDelivrURL("htmx.org", "1.9.12", "dist/htmx.min.js")) templ_7745c5c3_Var15, templ_7745c5c3_Err = templ.JoinStringErrs(jsDelivrURL("htmx.org", "1.9.12", "dist/htmx.min.js"))
if templ_7745c5c3_Err != nil { 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)) _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var15))
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
@ -321,7 +321,7 @@ func Htmx() templ.Component {
var templ_7745c5c3_Var16 string 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")) 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 { 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)) _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var16))
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
@ -334,7 +334,7 @@ func Htmx() templ.Component {
var templ_7745c5c3_Var17 string 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")) 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 { 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)) _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var17))
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
@ -347,7 +347,7 @@ func Htmx() templ.Component {
var templ_7745c5c3_Var18 string 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")) 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 { 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)) _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var18))
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
@ -396,7 +396,7 @@ func Nebula(version string) templ.Component {
var templ_7745c5c3_Var20 string var templ_7745c5c3_Var20 string
templ_7745c5c3_Var20, templ_7745c5c3_Err = templ.JoinStringErrs(jsDelivrURL("@onsonr/nebula", version, "cdn/themes/light.css")) templ_7745c5c3_Var20, templ_7745c5c3_Err = templ.JoinStringErrs(jsDelivrURL("@onsonr/nebula", version, "cdn/themes/light.css"))
if templ_7745c5c3_Err != nil { 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)) _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var20))
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
@ -409,7 +409,7 @@ func Nebula(version string) templ.Component {
var templ_7745c5c3_Var21 string var templ_7745c5c3_Var21 string
templ_7745c5c3_Var21, templ_7745c5c3_Err = templ.JoinStringErrs(jsDelivrURL("@onsonr/nebula", version, "cdn/themes/dark.css")) templ_7745c5c3_Var21, templ_7745c5c3_Err = templ.JoinStringErrs(jsDelivrURL("@onsonr/nebula", version, "cdn/themes/dark.css"))
if templ_7745c5c3_Err != nil { 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)) _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var21))
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
@ -438,7 +438,7 @@ func Nebula(version string) templ.Component {
var templ_7745c5c3_Var23 string var templ_7745c5c3_Var23 string
templ_7745c5c3_Var23, templ_7745c5c3_Err = templ.JoinStringErrs(jsDelivrURL("@onsonr/nebula", version, "cdn/shoelace-autoloader.js")) templ_7745c5c3_Var23, templ_7745c5c3_Err = templ.JoinStringErrs(jsDelivrURL("@onsonr/nebula", version, "cdn/shoelace-autoloader.js"))
if templ_7745c5c3_Err != nil { 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)) _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var23))
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {

View File

@ -27,7 +27,7 @@ var (
templ Root(title string) { templ Root(title string) {
<!DOCTYPE html> <!DOCTYPE html>
<html lang="en"> <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"> <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"> <main class="flex-row items-center justify-center mx-auto w-fit max-w-screen-sm gap-y-3">
{ children... } { children... }

View File

@ -57,7 +57,7 @@ func Root(title string) templ.Component {
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err 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 { if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err return templ_7745c5c3_Err
} }

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

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