git.sophuwu.com > authuwu
idk started a new auth library, hope it meets its goals ;?
sophuwu sophie@sophuwu.com
Sun, 13 Jul 2025 00:26:26 +0200
commit

05670058fc1ff89252c6767c0f223dece37a9dd6

9 files changed, 235 insertions(+), 0 deletions(-)

jump to
A .gitignore

@@ -0,0 +1,3 @@

+.idea +build/ +test/
A README.md

@@ -0,0 +1,14 @@

+# authuwu +### sophuwu's authentication standard + +An attempt to create a standardized authentication database and API for my projects. + +## Goals: +- standardized database files that can be used across different projects +- APIs for those projects to use +- a CLI to manage authentication databases with ease +- Golang HTTP middleware library for authentication +- HTTP login sessions, cookies, password reset, and other common features +- support for multiple authentication methods (e.g. password, OAuth, etc.) + +
A authuwu.go

@@ -0,0 +1,1 @@

+package authuwu
A db/db.go

@@ -0,0 +1,25 @@

+package db + +import ( + "github.com/asdine/storm/v3" +) + +var AuthUwu *storm.DB + +func Open(path string) error { + db, err := storm.Open(path) + if err != nil { + return err + } + AuthUwu = db + return nil +} + +func Close() error { + if AuthUwu == nil { + return nil + } + err := AuthUwu.Close() + AuthUwu = nil + return err +}
A go.mod

@@ -0,0 +1,11 @@

+module git.sophuwu.com/authuwu + +go 1.24.4 + +require ( + github.com/asdine/storm/v3 v3.2.1 // indirect + github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc // indirect + github.com/pquerna/otp v1.5.0 // indirect + go.etcd.io/bbolt v1.4.2 // indirect + golang.org/x/sys v0.34.0 // indirect +)
A go.sum

@@ -0,0 +1,36 @@

+github.com/DataDog/zstd v1.4.1/go.mod h1:1jcaCB/ufaK+sKp1NBhlGmpz41jOoPQ35bpF36t7BBo= +github.com/Sereal/Sereal v0.0.0-20190618215532-0b8ac451a863/go.mod h1:D0JMgToj/WdxCgd30Kc1UcA9E+WdZoJqeVOuYW7iTBM= +github.com/asdine/storm/v3 v3.2.1 h1:I5AqhkPK6nBZ/qJXySdI7ot5BlXSZ7qvDY1zAn5ZJac= +github.com/asdine/storm/v3 v3.2.1/go.mod h1:LEpXwGt4pIqrE/XcTvCnZHT5MgZCV6Ub9q7yQzOFWr0= +github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc h1:biVzkmvwrH8WK8raXaxBx6fRVTlJILwEwQGL1I/ByEI= +github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/pquerna/otp v1.5.0 h1:NMMR+WrmaqXU4EzdGJEE1aUUI0AMRzsp96fFFWNPwxs= +github.com/pquerna/otp v1.5.0/go.mod h1:dkJfzwRKNiegxyNb54X/3fLwhCynbMspSyWKnvi1AEg= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/vmihailenco/msgpack v4.0.4+incompatible/go.mod h1:fy3FlTQTDXWkZ7Bh6AcGMlsjHatGryHQYUTf1ShIgkk= +go.etcd.io/bbolt v1.3.4/go.mod h1:G5EMThwa9y8QZGBClrRx5EY+Yw9kAhnjy3bSjsnlVTQ= +go.etcd.io/bbolt v1.4.2 h1:IrUHp260R8c+zYx/Tm8QZr04CX+qWS5PGfPdevhdm1I= +go.etcd.io/bbolt v1.4.2/go.mod h1:Is8rSHO/b4f3XigBC0lL0+4FwAQv3HXEEIgFMuKHceM= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= +golang.org/x/net v0.0.0-20191105084925-a882066a44e0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.34.0 h1:H5Y5sJ2L2JRdyv7ROF1he/lPdvFsd0mJHFw2ThKHxLA= +golang.org/x/sys v0.34.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
A otp/otp.go

@@ -0,0 +1,26 @@

+package otp + +import ( + "github.com/pquerna/otp/totp" +) + +func (u *User) NewOTP() (string, error) { + otp, err := totp.Generate(totp.GenerateOpts{ + Issuer: "authuwu.sophuwu.com", + AccountName: u.Username, + }) + if err != nil { + return "", err + } + u.OTP = otp.Secret() + return otp.URL(), nil +} + +func (u *User) CheckOTP(otp string) bool { + return totp.Validate(otp, u.OTP) +} + +type User struct { + Username string `storm:"id"` + OTP string +}
A standard/standard.go

@@ -0,0 +1,24 @@

+package standard + +import ( + "crypto/sha512" + "encoding/base64" + "hash" +) + +// HashAlgorithm is the algorithm used for hashing passwords. +const HashAlgorithm = "SHA512-384" + +// SaltLength is the length of the salt used in password hashing. +const SaltLength = 24 + +// Encoding is the encoding used for storing passwords. +const Encoding = "base64URL" + +func NewHash() hash.Hash { + return sha512.New384() +} + +func NewEncoder() *base64.Encoding { + return base64.URLEncoding +}
A userpass/userpass.go

@@ -0,0 +1,95 @@

+package userpass + +import ( + "crypto/rand" + "crypto/subtle" + "git.sophuwu.com/authuwu/db" + "git.sophuwu.com/authuwu/standard" +) + +type Password struct { + Hash []byte + Salt []byte +} + +func (p *Password) Encode() string { + e := standard.NewEncoder() + s := e.EncodeToString(p.Hash) + s += ":" + s += e.EncodeToString(p.Salt) + return s +} + +func (p *Password) CheckPassword(password string) bool { + h := standard.NewHash() + h.Write(p.Salt) + h.Write([]byte(password)) + b := h.Sum(nil) + return 1 == subtle.ConstantTimeCompare(b, p.Hash) +} + +func Salt() ([]byte, error) { + salt := make([]byte, standard.SaltLength) + _, err := rand.Read(salt) + return salt, err +} + +func (p *Password) SetPassword(password string) error { + salt, err := Salt() + if err != nil { + return err + } + p.Salt = salt + h := standard.NewHash() + h.Write(salt) + h.Write([]byte(password)) + p.Hash = h.Sum(nil) + return nil +} + +func (u *User) SetPassword(password string) error { + if err := u.Password.SetPassword(password); err != nil { + return err + } + return nil +} + +func (u *User) Authenticate(password string) bool { + return u.Password.CheckPassword(password) +} + +type User struct { + Username string `storm:"id"` + Password Password `storm:"inline"` +} + +func (u *User) String() string { + return u.Username + ":" + u.Password.Encode() +} + +func NewUser(username string, password string) error { + u := &User{Username: username} + err := u.SetPassword(password) + if err != nil { + return err + } + err = db.AuthUwu.Save(u) + return err +} + +func GetUser(username string) (*User, error) { + u := &User{Username: username} + err := db.AuthUwu.One("Username", username, u) + if err != nil { + return nil, err + } + return u, nil +} + +func UserAuth(username string, password string) (bool, error) { + u, err := GetUser(username) + if err != nil { + return false, err + } + return u.Authenticate(password), nil +}