Skip to content

Commit

Permalink
feat: auth pattern
Browse files Browse the repository at this point in the history
Signed-off-by: Norman <[email protected]>
  • Loading branch information
Norman authored and Norman committed Dec 25, 2024
1 parent ea32315 commit d1f0fce
Show file tree
Hide file tree
Showing 9 changed files with 181 additions and 0 deletions.
16 changes: 16 additions & 0 deletions examples/gno.land/p/demo/auth/auth.gno
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package auth

import (
"errors"
"std"
)

type Token interface {
Source() std.Realm
}

type AuthenticateFn = func(auth Token) string

var (
ErrInvalidToken = errors.New("invalid token")
)
1 change: 1 addition & 0 deletions examples/gno.land/p/demo/auth/gno.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
module gno.land/p/demo/auth
62 changes: 62 additions & 0 deletions examples/gno.land/r/demo/authbanker/authbanker.gno
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
package authbanker

import (
"std"
"strings"

"gno.land/p/demo/auth"
"gno.land/p/demo/ufmt"
"gno.land/r/demo/authreg"
)

// this example is there mostly to demonstrate auth usage, it is quite limited
// only EOAs can fund accounts

const denom = "ugnot"

var vaults = make(map[string]int64)

func GetCoins(account string) int64 {
return vaults[account]
}

func SendCoins(atok auth.Token, to string, amount int64) {
account := authreg.Authenticate(atok)

if amount < 1 {
panic(ufmt.Errorf("sent amount of %q must be >= 0", denom))
}
if vaultAmount := vaults[account]; amount > vaultAmount {
panic(ufmt.Errorf("not enough %q in account, wanted %d, got %d", denom, amount, vaultAmount))
}

vaults[account] -= amount

if strings.HasPrefix(to, "/") {
vaults[to] += amount
} else {
realmBanker := std.GetBanker(std.BankerTypeRealmSend)
from := std.CurrentRealm().Addr()
coins := std.Coins{std.NewCoin(denom, amount)}
realmBanker.SendCoins(from, std.Address(to), coins)
}
}

func FundVault(to string) {
if !strings.HasPrefix(to, "/") {
panic("invalid destination")
}
// XXX: maybe `authreg.Validate(to)`
std.AssertOriginCall()
sentCoins := std.GetOrigSend()
for _, coin := range sentCoins {
if coin.Denom != denom {
panic(ufmt.Errorf("only %q supported", denom))
}
vaults[to] += coin.Amount
}
}

func TotalCoin(denom string) int64 {
return std.GetBanker(std.BankerTypeRealmSend).TotalCoin(denom)
}
1 change: 1 addition & 0 deletions examples/gno.land/r/demo/authbanker/gno.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
module gno.land/p/demo/authbanker
26 changes: 26 additions & 0 deletions examples/gno.land/r/demo/authreg/authreg.gno
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package authreg

import (
"errors"
"path"
"std"

"gno.land/p/demo/auth"
)

var fns = make(map[string]auth.AuthenticateFn)

// XXX: we could add a slug there
func Register(authenticate auth.AuthenticateFn) {
caller := std.PrevRealm().Addr()
fns[caller.String()] = authenticate
}

func Authenticate(autok auth.Token) string {
provider := autok.Source().Addr().String()
authFn, ok := fns[provider]
if !ok {
panic(errors.New("unknown auth provider"))
}
return path.Join("/", provider, authFn(autok))
}
1 change: 1 addition & 0 deletions examples/gno.land/r/demo/authreg/gno.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
module gno.land/r/demo/authreg
1 change: 1 addition & 0 deletions examples/gno.land/r/demo/subacc/gno.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
module gno.land/r/demo/subacc
47 changes: 47 additions & 0 deletions examples/gno.land/r/demo/subacc/subacc.gno
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
package subacc

import (
"path"
"std"

"gno.land/p/demo/auth"
"gno.land/r/demo/authreg"
)

var source std.Realm

func init() {
source = std.CurrentRealm()
authreg.Register(authenticate)
}

func AuthToken(slug string) auth.Token {
caller := std.PrevRealm().Addr()
return &token{accountKey: accountKey(caller, slug)}
}

type token struct {
accountKey string
}

func (a *token) Source() std.Realm {
return source
}

var _ auth.Token = (*token)(nil)

func authenticate(autho auth.Token) string {
// this check should ensure we created this object
cauth, ok := autho.(*token)
if !ok {
panic(auth.ErrInvalidToken)
}

return cauth.accountKey
}

var _ auth.AuthenticateFn = authenticate

func accountKey(creator std.Address, slug string) string {
return path.Join("/", creator.String(), slug)
}
26 changes: 26 additions & 0 deletions examples/gno.land/r/demo/subacc/subacc_test.gno
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package subacc_test

import (
"path"
"std"
"testing"

"gno.land/p/demo/testutils"
"gno.land/r/demo/authbanker"
)

var (
alice = testutils.TestAddress("alice")
)

func TestSubacc(t *testing.T) {
accountId := path.Join("/", std.DerivePkgAddr("gno.land/r/demo/subacc").String(), alice.String(), "foo")

std.TestSetRealm(std.NewUserRealm(alice))
std.TestSetOrigSend(std.Coins{{"ugnot", 42}}, nil)
authbanker.FundVault(accountId)

println(authbanker.GetCoins(accountId))

// XXX: ./examples/gno.land/r/demo/subacc: test pkg: panic: unexpected unreal object
}

0 comments on commit d1f0fce

Please sign in to comment.