From d1f0fce47791e5416a0e9ed117f1abc64817f577 Mon Sep 17 00:00:00 2001 From: Norman Date: Wed, 25 Dec 2024 20:03:28 +0100 Subject: [PATCH] feat: auth pattern Signed-off-by: Norman --- examples/gno.land/p/demo/auth/auth.gno | 16 +++++ examples/gno.land/p/demo/auth/gno.mod | 1 + .../gno.land/r/demo/authbanker/authbanker.gno | 62 +++++++++++++++++++ examples/gno.land/r/demo/authbanker/gno.mod | 1 + examples/gno.land/r/demo/authreg/authreg.gno | 26 ++++++++ examples/gno.land/r/demo/authreg/gno.mod | 1 + examples/gno.land/r/demo/subacc/gno.mod | 1 + examples/gno.land/r/demo/subacc/subacc.gno | 47 ++++++++++++++ .../gno.land/r/demo/subacc/subacc_test.gno | 26 ++++++++ 9 files changed, 181 insertions(+) create mode 100644 examples/gno.land/p/demo/auth/auth.gno create mode 100644 examples/gno.land/p/demo/auth/gno.mod create mode 100644 examples/gno.land/r/demo/authbanker/authbanker.gno create mode 100644 examples/gno.land/r/demo/authbanker/gno.mod create mode 100644 examples/gno.land/r/demo/authreg/authreg.gno create mode 100644 examples/gno.land/r/demo/authreg/gno.mod create mode 100644 examples/gno.land/r/demo/subacc/gno.mod create mode 100644 examples/gno.land/r/demo/subacc/subacc.gno create mode 100644 examples/gno.land/r/demo/subacc/subacc_test.gno diff --git a/examples/gno.land/p/demo/auth/auth.gno b/examples/gno.land/p/demo/auth/auth.gno new file mode 100644 index 00000000000..3be76cfb19e --- /dev/null +++ b/examples/gno.land/p/demo/auth/auth.gno @@ -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") +) diff --git a/examples/gno.land/p/demo/auth/gno.mod b/examples/gno.land/p/demo/auth/gno.mod new file mode 100644 index 00000000000..3eada3a47d0 --- /dev/null +++ b/examples/gno.land/p/demo/auth/gno.mod @@ -0,0 +1 @@ +module gno.land/p/demo/auth \ No newline at end of file diff --git a/examples/gno.land/r/demo/authbanker/authbanker.gno b/examples/gno.land/r/demo/authbanker/authbanker.gno new file mode 100644 index 00000000000..942f44d7650 --- /dev/null +++ b/examples/gno.land/r/demo/authbanker/authbanker.gno @@ -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) +} diff --git a/examples/gno.land/r/demo/authbanker/gno.mod b/examples/gno.land/r/demo/authbanker/gno.mod new file mode 100644 index 00000000000..d76938eb808 --- /dev/null +++ b/examples/gno.land/r/demo/authbanker/gno.mod @@ -0,0 +1 @@ +module gno.land/p/demo/authbanker \ No newline at end of file diff --git a/examples/gno.land/r/demo/authreg/authreg.gno b/examples/gno.land/r/demo/authreg/authreg.gno new file mode 100644 index 00000000000..bf7407d885b --- /dev/null +++ b/examples/gno.land/r/demo/authreg/authreg.gno @@ -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)) +} diff --git a/examples/gno.land/r/demo/authreg/gno.mod b/examples/gno.land/r/demo/authreg/gno.mod new file mode 100644 index 00000000000..2818c069540 --- /dev/null +++ b/examples/gno.land/r/demo/authreg/gno.mod @@ -0,0 +1 @@ +module gno.land/r/demo/authreg \ No newline at end of file diff --git a/examples/gno.land/r/demo/subacc/gno.mod b/examples/gno.land/r/demo/subacc/gno.mod new file mode 100644 index 00000000000..5d5183885cc --- /dev/null +++ b/examples/gno.land/r/demo/subacc/gno.mod @@ -0,0 +1 @@ +module gno.land/r/demo/subacc \ No newline at end of file diff --git a/examples/gno.land/r/demo/subacc/subacc.gno b/examples/gno.land/r/demo/subacc/subacc.gno new file mode 100644 index 00000000000..b7a85fb128a --- /dev/null +++ b/examples/gno.land/r/demo/subacc/subacc.gno @@ -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) +} diff --git a/examples/gno.land/r/demo/subacc/subacc_test.gno b/examples/gno.land/r/demo/subacc/subacc_test.gno new file mode 100644 index 00000000000..e0ec474e89c --- /dev/null +++ b/examples/gno.land/r/demo/subacc/subacc_test.gno @@ -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 +}