Search Apps Documentation Source Content File Folder Download Copy Actions Download

profile.gno

4.11 Kb · 170 lines
  1package profile
  2
  3import (
  4	"chain"
  5
  6	"gno.land/p/nt/avl/v0"
  7	"gno.land/p/nt/mux/v0"
  8	"gno.land/p/nt/ufmt/v0"
  9)
 10
 11var (
 12	fields = avl.NewTree()
 13	router = mux.NewRouter()
 14)
 15
 16// Standard fields
 17const (
 18	DisplayName        = "DisplayName"
 19	Homepage           = "Homepage"
 20	Bio                = "Bio"
 21	Age                = "Age"
 22	Location           = "Location"
 23	Avatar             = "Avatar"
 24	GravatarEmail      = "GravatarEmail"
 25	AvailableForHiring = "AvailableForHiring"
 26	InvalidField       = "InvalidField"
 27)
 28
 29// Events
 30const (
 31	ProfileFieldCreated = "ProfileFieldCreated"
 32	ProfileFieldUpdated = "ProfileFieldUpdated"
 33)
 34
 35// Field types used when emitting event
 36const FieldType = "FieldType"
 37
 38const (
 39	BoolField   = "BoolField"
 40	StringField = "StringField"
 41	IntField    = "IntField"
 42)
 43
 44func init() {
 45	router.HandleFunc("", homeHandler)
 46	router.HandleFunc("u/{addr}", profileHandler)
 47	router.HandleFunc("f/{addr}/{field}", fieldHandler)
 48}
 49
 50// List of supported string fields
 51var stringFields = map[string]bool{
 52	DisplayName:   true,
 53	Homepage:      true,
 54	Bio:           true,
 55	Location:      true,
 56	Avatar:        true,
 57	GravatarEmail: true,
 58}
 59
 60// List of support int fields
 61var intFields = map[string]bool{
 62	Age: true,
 63}
 64
 65// List of support bool fields
 66var boolFields = map[string]bool{
 67	AvailableForHiring: true,
 68}
 69
 70// Setters
 71//
 72// Security properties (AAA-1 B5 — guard the vendored setters):
 73//
 74//  1. Address safety (caller-keyed): every key is `PreviousRealm().Address() + ":" + field`,
 75//     i.e. the immediate caller's own address — the address is NEVER a parameter.
 76//     So a caller (user OR another realm) can only ever write its OWN profile;
 77//     cross-address forgery is structurally impossible. This is why the upstream
 78//     demo is safe to vendor: a DAO calling these writes the DAO's profile, not a
 79//     member's, and no realm can impersonate another address.
 80//
 81//  2. Field allowlist (now ENFORCED): the stringFields/intFields/boolFields maps
 82//     existed but were never checked, so any caller could write arbitrary field
 83//     names into the shared tree (state-bloat / unexpected-field injection on its
 84//     own profile). Each setter now rejects unknown fields. basedao only ever sets
 85//     DisplayName/Bio/Avatar (all allowlisted), so this does not break the DAO path.
 86
 87func SetStringField(cur realm, field, value string) bool {
 88	if !stringFields[field] {
 89		panic("unknown string profile field: " + field)
 90	}
 91	addr := cur.Previous().Address()
 92	key := addr.String() + ":" + field
 93	updated := fields.Set(key, value)
 94
 95	event := ProfileFieldCreated
 96	if updated {
 97		event = ProfileFieldUpdated
 98	}
 99
100	chain.Emit(event, FieldType, StringField, field, value)
101
102	return updated
103}
104
105func SetIntField(cur realm, field string, value int) bool {
106	if !intFields[field] {
107		panic("unknown int profile field: " + field)
108	}
109	addr := cur.Previous().Address()
110	key := addr.String() + ":" + field
111	updated := fields.Set(key, value)
112
113	event := ProfileFieldCreated
114	if updated {
115		event = ProfileFieldUpdated
116	}
117
118	// ufmt.Sprintf("%d", …): string(int) would coerce the value to a single rune
119	// (e.g. 65 → "A"), corrupting the event payload.
120	chain.Emit(event, FieldType, IntField, field, ufmt.Sprintf("%d", value))
121
122	return updated
123}
124
125func SetBoolField(cur realm, field string, value bool) bool {
126	if !boolFields[field] {
127		panic("unknown bool profile field: " + field)
128	}
129	addr := cur.Previous().Address()
130	key := addr.String() + ":" + field
131	updated := fields.Set(key, value)
132
133	event := ProfileFieldCreated
134	if updated {
135		event = ProfileFieldUpdated
136	}
137
138	chain.Emit(event, FieldType, BoolField, field, ufmt.Sprintf("%t", value))
139
140	return updated
141}
142
143// Getters
144
145func GetStringField(addr address, field, def string) string {
146	key := addr.String() + ":" + field
147	if value, ok := fields.Get(key); ok {
148		return value.(string)
149	}
150
151	return def
152}
153
154func GetBoolField(addr address, field string, def bool) bool {
155	key := addr.String() + ":" + field
156	if value, ok := fields.Get(key); ok {
157		return value.(bool)
158	}
159
160	return def
161}
162
163func GetIntField(addr address, field string, def int) int {
164	key := addr.String() + ":" + field
165	if value, ok := fields.Get(key); ok {
166		return value.(int)
167	}
168
169	return def
170}