From ff0f6ba724748dbe340187fdd831a4d4f7f0ae5e Mon Sep 17 00:00:00 2001 From: raven Date: Wed, 22 Oct 2025 16:28:22 -0500 Subject: passwords --- server/user/command.go | 37 ++++++++++++++++++++++++++++++++++++ server/user/user.go | 51 ++++++++++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 86 insertions(+), 2 deletions(-) (limited to 'server/user') diff --git a/server/user/command.go b/server/user/command.go index 47a3efb..b4c6d5f 100644 --- a/server/user/command.go +++ b/server/user/command.go @@ -51,6 +51,43 @@ func (u *User) SendRequest(r session.Request) { u.Stream.Event(r.Cmd) r.ReplyOk() + case "auth-update": + if r.From.UserId != u.Id() { + r.Reply(proto.Fail{"forbidden", "", nil}.Cmd()) + return + } + if !u.anonymous && !r.From.PasswordAuthed { + r.Reply(proto.Fail{"password-required", "", nil}.Cmd()) + return + } + if len(r.Cmd.Args) != 1 { + r.ReplyInvalid() + return + } + upd := r.Cmd.Args[0] + switch upd.Kind { + case "password": + var password string + for k, v := range upd.Fields { + switch k { + case "": + password = v + default: + r.ReplyInvalid() + } + } + if password == "" { + r.ReplyInvalid() + return + } + u.SetPassword(password) + r.From.PasswordAuthed = true + r.ReplyOk() + default: + r.ReplyInvalid() + return + } + case "i": r.Reply(proto.NewCmd("i", "", u.InfoFor(r.From.UserId))) diff --git a/server/user/user.go b/server/user/user.go index 311a1b8..d2d5724 100644 --- a/server/user/user.go +++ b/server/user/user.go @@ -4,6 +4,7 @@ import ( "citrons.xyz/talk/server/object" "citrons.xyz/talk/server/session" "citrons.xyz/talk/server/validate" + "citrons.xyz/talk/server/passwords" "citrons.xyz/talk/proto" bolt "go.etcd.io/bbolt" "log" @@ -30,9 +31,16 @@ func Kind(world *object.World) *UserKind { } func (us *UserKind) CreateUser(name string) (*User, *proto.Fail) { - if us.ByName(name) != nil { + existing := us.ByName(name) + if existing != nil { + anon := "no" + if existing.anonymous { + anon = "yes" + } return nil, &proto.Fail { - "name-taken", "", map[string]string {"": name}, + "name-taken", "", map[string]string { + "": name, "anonymous": anon, "id": existing.Id(), + }, } } if !validate.Name(name) { @@ -157,6 +165,8 @@ func (u *User) Delete() { anons.Delete([]byte(u.id)) channels, _ := tx.CreateBucketIfNotExists([]byte("user channels")) channels.DeleteBucket([]byte(u.id)) + auth, _ := tx.CreateBucketIfNotExists([]byte("auth")) + auth.DeleteBucket([]byte(u.id)) return nil }) if err != nil { @@ -180,3 +190,40 @@ func (u *User) InfoFor(uid string) proto.Object { func (u *User) IsAnonymous() bool { return u.anonymous } + +func (u *User) CheckPassword(password string) bool { + var hash []byte + err := u.kind.db.View(func(tx *bolt.Tx) error { + auth := tx.Bucket([]byte("auth")) + if auth == nil { + return nil + } + userData := auth.Bucket([]byte(u.id)) + if userData == nil { + return nil + } + hash = userData.Get([]byte("password hash")) + return nil + }) + if err != nil { + log.Fatal("error reading database: ", err) + } + if hash == nil { + return false + } + return passwords.Check(password, hash) +} + +func (u *User) SetPassword(password string) { + err := u.kind.db.Update(func(tx *bolt.Tx) error { + auth, _ := tx.CreateBucketIfNotExists([]byte("auth")) + userData, _ := auth.CreateBucketIfNotExists([]byte(u.id)) + userData.Put([]byte("password hash"), passwords.Hash(password)) + return nil + }) + if err != nil { + log.Fatal("error updating database: ", err) + } + u.anonymous = false + u.Save() +} -- cgit v1.2.3