From e03d17ffbebebba0c5f0468addf8349c2336c437 Mon Sep 17 00:00:00 2001 From: AKP Date: Fri, 1 Apr 2022 19:49:58 +0100 Subject: First endpoint Signed-off-by: AKP --- .gitignore | 27 ++++++++++++++ Makefile | 12 +++++++ go.mod | 24 +++++++++++++ go.sum | 74 ++++++++++++++++++++++++++++++++++++++ walrss/internal/core/sessions.go | 40 +++++++++++++++++++++ walrss/internal/core/userError.go | 52 +++++++++++++++++++++++++++ walrss/internal/core/users.go | 61 +++++++++++++++++++++++++++++++ walrss/internal/core/util.go | 15 ++++++++ walrss/internal/core/validation.go | 21 +++++++++++ walrss/internal/db/db.go | 39 ++++++++++++++++++++ walrss/internal/http/auth.go | 29 +++++++++++++++ walrss/internal/http/http.go | 49 +++++++++++++++++++++++++ walrss/internal/state/state.go | 54 ++++++++++++++++++++++++++++ walrss/internal/urls/urls.go | 9 +++++ walrss/main.go | 55 ++++++++++++++++++++++++++++ 15 files changed, 561 insertions(+) create mode 100644 .gitignore create mode 100644 Makefile create mode 100644 go.mod create mode 100644 go.sum create mode 100644 walrss/internal/core/sessions.go create mode 100644 walrss/internal/core/userError.go create mode 100644 walrss/internal/core/users.go create mode 100644 walrss/internal/core/util.go create mode 100644 walrss/internal/core/validation.go create mode 100644 walrss/internal/db/db.go create mode 100644 walrss/internal/http/auth.go create mode 100644 walrss/internal/http/http.go create mode 100644 walrss/internal/state/state.go create mode 100644 walrss/internal/urls/urls.go create mode 100644 walrss/main.go diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..60759b8 --- /dev/null +++ b/.gitignore @@ -0,0 +1,27 @@ + +### Go ### +# Binaries for programs and plugins +*.exe +*.exe~ +*.dll +*.so +*.dylib + +# Test binary, built with `go test -c` +*.test + +# Output of the go coverage tool, specifically when used with LiteIDE +*.out + +# Dependency directories (remove the comment below to include it) +# vendor/ + +### Go Patch ### +/vendor/ +/Godeps/ + +### ----------------- + + +run/ +bin/ diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..60bc03c --- /dev/null +++ b/Makefile @@ -0,0 +1,12 @@ +.PHONY: prebuild fmt + +build: + mkdir -p bin + go build -o bin/walrss github.com/codemicro/walrss/walrss + +run: build + mkdir -p run + cd run && ../bin/walrss + +fmt: + go fmt github.com/codemicro/walrss/... diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..b358424 --- /dev/null +++ b/go.mod @@ -0,0 +1,24 @@ +module github.com/codemicro/walrss + +go 1.18 + +require ( + github.com/andybalholm/brotli v1.0.4 // indirect + github.com/bwmarrin/go-alone v0.0.0-20190806015146-742bb55d1631 // indirect + github.com/gofiber/fiber/v2 v2.31.0 // indirect + github.com/google/uuid v1.3.0 // indirect + github.com/kkyr/fig v0.3.0 // indirect + github.com/klauspost/compress v1.15.0 // indirect + github.com/lithammer/shortuuid/v4 v4.0.0 // indirect + github.com/mitchellh/mapstructure v1.4.1 // indirect + github.com/pelletier/go-toml v1.9.3 // indirect + github.com/rs/zerolog v1.26.1 // indirect + github.com/timshannon/bolthold v0.0.0-20210913165410-232392fc8a6a // indirect + github.com/valyala/bytebufferpool v1.0.0 // indirect + github.com/valyala/fasthttp v1.34.0 // indirect + github.com/valyala/tcplisten v1.0.0 // indirect + go.etcd.io/bbolt v1.3.6 // indirect + golang.org/x/crypto v0.0.0-20220214200702-86341886e292 // indirect + golang.org/x/sys v0.0.0-20220227234510-4e6760a101f9 // indirect + gopkg.in/yaml.v2 v2.4.0 // indirect +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..c9bfcf0 --- /dev/null +++ b/go.sum @@ -0,0 +1,74 @@ +github.com/andybalholm/brotli v1.0.4 h1:V7DdXeJtZscaqfNuAdSRuRFzuiKlHSC/Zh3zl9qY3JY= +github.com/andybalholm/brotli v1.0.4/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig= +github.com/bwmarrin/go-alone v0.0.0-20190806015146-742bb55d1631 h1:Xb5rra6jJt5Z1JsZhIMby+IP5T8aU+Uc2RC9RzSxs9g= +github.com/bwmarrin/go-alone v0.0.0-20190806015146-742bb55d1631/go.mod h1:P86Dksd9km5HGX5UMIocXvX87sEp2xUARle3by+9JZ4= +github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= +github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= +github.com/gofiber/fiber/v2 v2.31.0 h1:M2rWPQbD5fDVAjcoOLjKRXTIlHesI5Eq7I5FEQPt4Ow= +github.com/gofiber/fiber/v2 v2.31.0/go.mod h1:1Ega6O199a3Y7yDGuM9FyXDPYQfv+7/y48wl6WCwUF4= +github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= +github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/kkyr/fig v0.3.0 h1:5bd1amYKp/gsK2bGEUJYzcCrQPKOZp6HZD9K21v9Guo= +github.com/kkyr/fig v0.3.0/go.mod h1:fEnrLjwg/iwSr8ksJF4DxrDmCUir5CaVMLORGYMcz30= +github.com/klauspost/compress v1.15.0 h1:xqfchp4whNFxn5A4XFyyYtitiWI8Hy5EW59jEwcyL6U= +github.com/klauspost/compress v1.15.0/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk= +github.com/lithammer/shortuuid/v4 v4.0.0 h1:QRbbVkfgNippHOS8PXDkti4NaWeyYfcBTHtw7k08o4c= +github.com/lithammer/shortuuid/v4 v4.0.0/go.mod h1:Zs8puNcrvf2rV9rTH51ZLLcj7ZXqQI3lv67aw4KiB1Y= +github.com/mitchellh/mapstructure v1.4.1 h1:CpVNEelQCZBooIPDn+AR3NpivK/TIKU8bDxdASFVQag= +github.com/mitchellh/mapstructure v1.4.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= +github.com/pelletier/go-toml v1.9.3 h1:zeC5b1GviRUyKYd6OJPvBU/mcVDVoL1OhT17FCt5dSQ= +github.com/pelletier/go-toml v1.9.3/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/rs/xid v1.3.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg= +github.com/rs/zerolog v1.26.1 h1:/ihwxqH+4z8UxyI70wM1z9yCvkWcfz/a3mj48k/Zngc= +github.com/rs/zerolog v1.26.1/go.mod h1:/wSSJWX7lVrsOwlbyTRSOJvqRlc+WjWlfes+CiJ+tmc= +github.com/timshannon/bolthold v0.0.0-20210913165410-232392fc8a6a h1:oIi7H/bwFUYKYhzKbHc+3MvHRWqhQwXVB4LweLMiVy0= +github.com/timshannon/bolthold v0.0.0-20210913165410-232392fc8a6a/go.mod h1:iSvujNDmpZ6eQX+bg/0X3lF7LEmZ8N77g2a/J/+Zt2U= +github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= +github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= +github.com/valyala/fasthttp v1.34.0 h1:d3AAQJ2DRcxJYHm7OXNXtXt2as1vMDfxeIcFvhmGGm4= +github.com/valyala/fasthttp v1.34.0/go.mod h1:epZA5N+7pY6ZaEKRmstzOuYJx9HI8DI1oaCGZpdH4h0= +github.com/valyala/tcplisten v1.0.0 h1:rBHj/Xf+E1tRGZyWIWwJDiRY0zc1Js+CV5DqwacVSA8= +github.com/valyala/tcplisten v1.0.0/go.mod h1:T0xQ8SeCZGxckz9qRXTfG43PvQ/mcWh7FwZEA7Ioqkc= +github.com/yuin/goldmark v1.4.0/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= +go.etcd.io/bbolt v1.3.6 h1:/ecaJf0sk1l4l6V4awd65v2C3ILy7MSj+s/x1ADCIMU= +go.etcd.io/bbolt v1.3.6/go.mod h1:qXsaaIqmgQH0T+OPdb99Bf+PKfBBQVAdyD6TY9G8XM4= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20211215165025-cf75a172585e/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8= +golang.org/x/crypto v0.0.0-20220214200702-86341886e292 h1:f+lwQ+GtmgoY+A2YaQxlSOnDjXcQ7ZRLWOHbC6HtRqE= +golang.org/x/crypto v0.0.0-20220214200702-86341886e292/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= +golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200923182605-d9f96fdee20d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220227234510-4e6760a101f9 h1:nhht2DYV/Sn3qOayu8lM+cU1ii9sTLUeBQwQQfUHtrs= +golang.org/x/sys v0.0.0-20220227234510-4e6760a101f9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.1.7/go.mod h1:LGqMHiF4EqQNHR1JncWGqT5BVaXmza+X+BDGol+dOxo= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= +gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= diff --git a/walrss/internal/core/sessions.go b/walrss/internal/core/sessions.go new file mode 100644 index 0000000..8e1ab73 --- /dev/null +++ b/walrss/internal/core/sessions.go @@ -0,0 +1,40 @@ +package core + +import ( + "crypto/rand" + "encoding/hex" + goalone "github.com/bwmarrin/go-alone" + "time" +) + +var ( + sessionSigner *goalone.Sword + sessionSalt = []byte("session") +) + +func init() { + sessionSecret := make([]byte, 50) + if _, err := rand.Read(sessionSecret); err != nil { + panic(err) + } + sessionSigner = goalone.New(sessionSecret, goalone.Timestamp) +} + +func GenerateSessionToken(userID string) string { + combined := combineStringAndSalt(userID, sessionSalt) + return hex.EncodeToString(sessionSigner.Sign(combined)) +} + +func ValidateSessionToken(input string) (string, time.Time, error) { + signed, err := hex.DecodeString(input) + if err != nil { + return "", time.Time{}, err + } + + if _, err := sessionSigner.Unsign(signed); err != nil { + return "", time.Time{}, AsUserError(400, err) + } + + parsed := sessionSigner.Parse(signed) + return string(parsed.Payload), parsed.Timestamp, nil +} diff --git a/walrss/internal/core/userError.go b/walrss/internal/core/userError.go new file mode 100644 index 0000000..4938466 --- /dev/null +++ b/walrss/internal/core/userError.go @@ -0,0 +1,52 @@ +package core + +import "fmt" + +var ErrNotFound = NewUserErrorWithStatus(404, "item not found") + +type UserError struct { + Original error + Status int +} + +func (ue *UserError) Error() string { + return ue.Original.Error() +} + +func (ue *UserError) Unwrap() error { + return ue.Original +} + +func AsUserError(status int, err error) error { + return &UserError{ + Original: err, + Status: status, + } +} + +func NewUserError(format string, args ...any) error { + return NewUserErrorWithStatus(400, format, args...) +} + +func NewUserErrorWithStatus(status int, format string, args ...any) error { + return &UserError{ + Original: fmt.Errorf(format, args...), + Status: status, + } +} + +func IsUserError(err error) bool { + if err == nil { + return false + } + _, ok := err.(*UserError) + return ok +} + +func GetUserErrorStatus(err error) int { + ue, ok := err.(*UserError) + if !ok { + return 0 + } + return ue.Status +} diff --git a/walrss/internal/core/users.go b/walrss/internal/core/users.go new file mode 100644 index 0000000..f909a1d --- /dev/null +++ b/walrss/internal/core/users.go @@ -0,0 +1,61 @@ +package core + +import ( + "errors" + "github.com/codemicro/walrss/walrss/internal/db" + "github.com/codemicro/walrss/walrss/internal/state" + "github.com/lithammer/shortuuid/v4" + bh "github.com/timshannon/bolthold" + "golang.org/x/crypto/bcrypt" +) + +func RegisterUser(st *state.State, email, password string) (*db.User, error) { + if err := validateEmailAddress(email); err != nil { + return nil, err + } + + if err := validatePassword(password); err != nil { + return nil, err + } + + u := &db.User{ + ID: shortuuid.New(), + Email: email, + Salt: generateRandomData(30), + } + + hash, err := bcrypt.GenerateFromPassword(combineStringAndSalt(password, u.Salt), bcrypt.DefaultCost) + if err != nil { + return nil, err + } + + u.Password = hash + + if err := st.Data.Insert(bh.Key, u); err != nil { + if errors.Is(err, bh.ErrUniqueExists) { + return nil, NewUserError("email address in use") + } + return nil, err + } + + return u, nil +} + +func AreUserCredentialsCorrect(st *state.State, email, password string) (bool, error) { + user := new(db.User) + if err := st.Data.FindOne(user, bh.Where("Email").Eq(email)); err != nil { + if errors.Is(err, bh.ErrNotFound) { + return false, ErrNotFound + } + return false, err + } + + if err := bcrypt.CompareHashAndPassword(user.Password, combineStringAndSalt(password, user.Salt)); err != nil { + if errors.Is(err, bcrypt.ErrMismatchedHashAndPassword) { + return false, nil + } + return false, err + } + + return true, nil +} diff --git a/walrss/internal/core/util.go b/walrss/internal/core/util.go new file mode 100644 index 0000000..f92d365 --- /dev/null +++ b/walrss/internal/core/util.go @@ -0,0 +1,15 @@ +package core + +import ( + "crypto/rand" +) + +func generateRandomData(n int) []byte { + bytes := make([]byte, n) + _, _ = rand.Read(bytes) + return bytes +} + +func combineStringAndSalt(password string, salt []byte) []byte { + return append([]byte(password), salt...) +} diff --git a/walrss/internal/core/validation.go b/walrss/internal/core/validation.go new file mode 100644 index 0000000..501c023 --- /dev/null +++ b/walrss/internal/core/validation.go @@ -0,0 +1,21 @@ +package core + +import ( + "regexp" +) + +var emailRegexp = regexp.MustCompile("^[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$") + +func validateEmailAddress(email string) error { + if !emailRegexp.MatchString(email) { + return NewUserError("invalid email address") + } + return nil +} + +func validatePassword(password string) error { + if len(password) <= 3 { + return NewUserError("password must be at least three characters long") + } + return nil +} diff --git a/walrss/internal/db/db.go b/walrss/internal/db/db.go new file mode 100644 index 0000000..efb3f46 --- /dev/null +++ b/walrss/internal/db/db.go @@ -0,0 +1,39 @@ +package db + +import ( + bh "github.com/timshannon/bolthold" +) + +func New(filename string) (*bh.Store, error) { + store, err := bh.Open(filename, 0644, nil) + if err != nil { + return nil, err + } + return store, nil +} + +type User struct { + ID string `boldholdKey:""` + Email string `boltholdUnique:"UniqueEmail" boltholdIndex:"Email"` + Password []byte + Salt []byte + + Schedule struct { + Day SendDay `boltholdIndex:"Day"` + Hour int `boltholdIndex:"Hour"` + } +} + +type SendDay uint32 + +const ( + SendDayNever = iota + SendDaily + SendOnMonday + SendOnTuesday + SendOnWednesday + SendOnThursday + SendOnFriday + SendOnSaturday + SendOnSunday +) diff --git a/walrss/internal/http/auth.go b/walrss/internal/http/auth.go new file mode 100644 index 0000000..7be4078 --- /dev/null +++ b/walrss/internal/http/auth.go @@ -0,0 +1,29 @@ +package http + +import ( + "github.com/codemicro/walrss/walrss/internal/core" + "github.com/gofiber/fiber/v2" + "time" +) + +func (s *Server) authRegister(ctx *fiber.Ctx) error { + user, err := core.RegisterUser(s.state, + ctx.FormValue("email"), + ctx.FormValue("password"), + ) + if err != nil { + return err + } + + token := core.GenerateSessionToken(user.ID) + + ctx.Cookie(&fiber.Cookie{ + Name: sessionCookieKey, + Value: token, + Expires: time.Now().UTC().Add(sessionDuration), + Secure: !s.state.Config.Debug, + HTTPOnly: true, + }) + + return ctx.SendString("ok!") +} diff --git a/walrss/internal/http/http.go b/walrss/internal/http/http.go new file mode 100644 index 0000000..5af260e --- /dev/null +++ b/walrss/internal/http/http.go @@ -0,0 +1,49 @@ +package http + +import ( + "github.com/codemicro/walrss/walrss/internal/core" + "github.com/codemicro/walrss/walrss/internal/state" + "github.com/codemicro/walrss/walrss/internal/urls" + "github.com/gofiber/fiber/v2" + "time" +) + +const ( + sessionCookieKey = "walrss-session" + sessionDuration = (time.Hour * 24) * 7 // 7 days +) + +type Server struct { + state *state.State + app *fiber.App +} + +func New(st *state.State) (*Server, error) { + app := fiber.New(fiber.Config{ + DisableStartupMessage: !st.Config.Debug, + AppName: "Walrss", + }) + // TODO: Add error handler with UserError support + + s := &Server{ + state: st, + app: app, + } + + s.registerHandlers() + + return s, nil +} + +func (s *Server) registerHandlers() { + s.app.Post(urls.AuthRegister, s.authRegister) +} + +func (s *Server) Run() error { + return s.app.Listen(s.state.Config.GetHTTPAddress()) +} + +func UserErrorToResponse(ctx *fiber.Ctx, ue core.UserError) error { + ctx.Status(ue.Status) + return ctx.SendString(ue.Error()) +} diff --git a/walrss/internal/state/state.go b/walrss/internal/state/state.go new file mode 100644 index 0000000..54badd7 --- /dev/null +++ b/walrss/internal/state/state.go @@ -0,0 +1,54 @@ +package state + +import ( + "errors" + "fmt" + "github.com/kkyr/fig" + bh "github.com/timshannon/bolthold" + "io/ioutil" + "os" +) + +type State struct { + Config *Config + Data *bh.Store +} + +func New() *State { + return &State{} +} + +type Config struct { + Server struct { + Host string `fig:"host" default:"127.0.0.1"` + Port int `fig:"port" default:"8080"` + } + DataDirectory string `fig:"dataDir" default:"./"` + Debug bool `fig:"debug"` +} + +const configFilename = "config.yaml" + +func LoadConfig() (*Config, error) { + // If the file doesn't exist, Fig will throw a hissy fit, so we should create a blank one if it doesn't exist + if _, err := os.Stat(configFilename); err != nil { + if errors.Is(err, os.ErrNotExist) { + // If the file doesn't have contents, Fig will throw an EOF, despite `touch config.yaml` working fine. idk lol + if err := ioutil.WriteFile(configFilename, []byte("{}"), 0777); err != nil { + return nil, err + } + } else { + return nil, err + } + } + + cfg := new(Config) + if err := fig.Load(cfg); err != nil { + return nil, err + } + return cfg, nil +} + +func (cfg *Config) GetHTTPAddress() string { + return fmt.Sprintf("%s:%d", cfg.Server.Host, cfg.Server.Port) +} diff --git a/walrss/internal/urls/urls.go b/walrss/internal/urls/urls.go new file mode 100644 index 0000000..1d38f96 --- /dev/null +++ b/walrss/internal/urls/urls.go @@ -0,0 +1,9 @@ +package urls + +const ( + Index = "/" + + Auth = "/auth" + AuthSignIn = Auth + "/signin" + AuthRegister = Auth + "/register" +) \ No newline at end of file diff --git a/walrss/main.go b/walrss/main.go new file mode 100644 index 0000000..9cb951d --- /dev/null +++ b/walrss/main.go @@ -0,0 +1,55 @@ +package main + +import ( + "github.com/codemicro/walrss/walrss/internal/db" + "github.com/codemicro/walrss/walrss/internal/http" + "github.com/codemicro/walrss/walrss/internal/state" + "github.com/rs/zerolog/log" + "os" +) + +const dbFilename = "walrss.db" +const walrssDirectoryEnv = "WALRSS_DIR" + +func run() error { + if err := switchToDataDirectory(); err != nil { + return err + } + + st := state.New() + if config, err := state.LoadConfig(); err != nil { + return err + } else { + st.Config = config + } + + if err := os.Chdir(st.Config.DataDirectory); err != nil { + return err + } + + store, err := db.New(dbFilename) + if err != nil { + return err + } + st.Data = store + + server, err := http.New(st) + if err != nil { + return err + } + + return server.Run() +} + +func main() { + if err := run(); err != nil { + log.Fatal().Err(err).Msg("could not start") + } +} + +func switchToDataDirectory() error { + if dir := os.Getenv(walrssDirectoryEnv); dir != "" { + return os.Chdir(dir) + } + return nil +} -- cgit v1.2.3-70-g09d2