WIP: Add Two Factor Authentication #7

Draft
adoralaura wants to merge 11 commits from feature-2fa into main
30 changed files with 741 additions and 130 deletions
Showing only changes of commit ce88c1a676 - Show all commits

1
.gitignore vendored
View file

@ -26,3 +26,4 @@ __debug_*
fly.toml fly.toml
settings.env settings.env
config.toml

1
.vscode/launch.json vendored
View file

@ -13,5 +13,6 @@
"envFile": "${workspaceFolder}/settings.env" "envFile": "${workspaceFolder}/settings.env"
} }
] ]
} }

View file

@ -1,21 +1,30 @@
package app package app
import ( import (
"flag"
"fmt" "fmt"
"code.lila.network/adoralaura/go-urlsh/internal/app" "code.lila.network/adoralaura/go-urlsh/internal/app"
"code.lila.network/adoralaura/go-urlsh/internal/config"
"code.lila.network/adoralaura/go-urlsh/internal/db" "code.lila.network/adoralaura/go-urlsh/internal/db"
"code.lila.network/adoralaura/go-urlsh/models" "github.com/rs/zerolog"
) )
func Run() error { func Run() error {
models.DB = db.InitializeDB() configPath := flag.String("config", "", "location of the config file to use (default: /etc/go-urlsh/config.toml or $pwd/config.toml)")
flag.Parse()
go app.CleanupLogins() cfg := config.NewConfig(*configPath)
go app.CleanupLoginsCronJob()
err := app.SetupFiber() logger := config.NewLogger(zerolog.Level(cfg.LogLevel))
database := db.InitializeDB(cfg, logger)
go app.CleanupLogins(database, logger)
go app.CleanupLoginsCronJob(database, logger)
err := app.SetupFiber(database, logger, cfg)
if err != nil { if err != nil {
return fmt.Errorf("couldn't start webserver: %v", err.Error()) return fmt.Errorf("couldn't start webserver: %v", err.Error())
} }

BIN
go-urlsh Executable file

Binary file not shown.

26
go.mod
View file

@ -7,30 +7,50 @@ require (
github.com/gofiber/template/html/v2 v2.1.1 github.com/gofiber/template/html/v2 v2.1.1
github.com/jasonlvhit/gocron v0.0.1 github.com/jasonlvhit/gocron v0.0.1
github.com/pquerna/otp v1.4.0 github.com/pquerna/otp v1.4.0
github.com/uptrace/bun v1.2.1 github.com/uptrace/bun v1.1.8
github.com/uptrace/bun/dialect/pgdialect v1.2.1 github.com/uptrace/bun/dialect/pgdialect v1.1.8
github.com/uptrace/bun/driver/pgdriver v1.2.1 github.com/uptrace/bun/driver/pgdriver v1.1.8
) )
require ( require (
github.com/andybalholm/brotli v1.1.0 // indirect github.com/andybalholm/brotli v1.1.0 // indirect
github.com/boombuler/barcode v1.0.1 // indirect github.com/boombuler/barcode v1.0.1 // indirect
github.com/fsnotify/fsnotify v1.7.0 // indirect
github.com/gofiber/template v1.8.3 // indirect github.com/gofiber/template v1.8.3 // indirect
github.com/gofiber/utils v1.1.0 // indirect github.com/gofiber/utils v1.1.0 // indirect
github.com/google/uuid v1.6.0 // indirect github.com/google/uuid v1.6.0 // indirect
github.com/hashicorp/hcl v1.0.0 // indirect
github.com/jinzhu/inflection v1.0.0 // indirect github.com/jinzhu/inflection v1.0.0 // indirect
github.com/klauspost/compress v1.17.8 // indirect github.com/klauspost/compress v1.17.8 // indirect
github.com/magiconair/properties v1.8.7 // indirect
github.com/mattn/go-colorable v0.1.13 // indirect github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect github.com/mattn/go-isatty v0.0.20 // indirect
github.com/mattn/go-runewidth v0.0.15 // indirect github.com/mattn/go-runewidth v0.0.15 // indirect
github.com/mitchellh/mapstructure v1.5.0 // indirect
github.com/pelletier/go-toml/v2 v2.1.0 // indirect
github.com/rivo/uniseg v0.4.7 // indirect github.com/rivo/uniseg v0.4.7 // indirect
github.com/rs/zerolog v1.32.0 // indirect
github.com/sagikazarmark/locafero v0.4.0 // indirect
github.com/sagikazarmark/slog-shim v0.1.0 // indirect
github.com/sourcegraph/conc v0.3.0 // indirect
github.com/spf13/afero v1.11.0 // indirect
github.com/spf13/cast v1.6.0 // indirect
github.com/spf13/pflag v1.0.5 // indirect
github.com/spf13/viper v1.18.2 // indirect
github.com/subosito/gotenv v1.6.0 // indirect
github.com/tmthrgd/go-hex v0.0.0-20190904060850-447a3041c3bc // indirect github.com/tmthrgd/go-hex v0.0.0-20190904060850-447a3041c3bc // indirect
github.com/valyala/bytebufferpool v1.0.0 // indirect github.com/valyala/bytebufferpool v1.0.0 // indirect
github.com/valyala/fasthttp v1.52.0 // indirect github.com/valyala/fasthttp v1.52.0 // indirect
github.com/valyala/tcplisten v1.0.0 // indirect github.com/valyala/tcplisten v1.0.0 // indirect
github.com/vmihailenco/msgpack/v5 v5.4.1 // indirect github.com/vmihailenco/msgpack/v5 v5.4.1 // indirect
github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect
go.uber.org/atomic v1.9.0 // indirect
go.uber.org/multierr v1.9.0 // indirect
golang.org/x/crypto v0.22.0 // indirect golang.org/x/crypto v0.22.0 // indirect
golang.org/x/exp v0.0.0-20230905200255-921286631fa9 // indirect
golang.org/x/sys v0.19.0 // indirect golang.org/x/sys v0.19.0 // indirect
golang.org/x/text v0.14.0 // indirect
gopkg.in/ini.v1 v1.67.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
mellium.im/sasl v0.3.1 // indirect mellium.im/sasl v0.3.1 // indirect
) )

57
go.sum
View file

@ -3,11 +3,16 @@ github.com/andybalholm/brotli v1.1.0/go.mod h1:sms7XGricyQI9K10gOSf56VKKWS4oLer5
github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8= github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8=
github.com/boombuler/barcode v1.0.1 h1:NDBbPmhS+EqABEs5Kg3n/5ZNjy73Pz7SIV+KCeqyXcs= github.com/boombuler/barcode v1.0.1 h1:NDBbPmhS+EqABEs5Kg3n/5ZNjy73Pz7SIV+KCeqyXcs=
github.com/boombuler/barcode v1.0.1/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8= github.com/boombuler/barcode v1.0.1/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8=
github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA=
github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM=
github.com/go-redis/redis v6.15.5+incompatible/go.mod h1:NAIEuMOZ/fxfXJIrKDQDz8wamY7mA7PouImQ2Jvg6kA= github.com/go-redis/redis v6.15.5+incompatible/go.mod h1:NAIEuMOZ/fxfXJIrKDQDz8wamY7mA7PouImQ2Jvg6kA=
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
github.com/gofiber/fiber/v2 v2.52.4 h1:P+T+4iK7VaqUsq2PALYEfBBo6bJZ4q3FP8cZ84EggTM= github.com/gofiber/fiber/v2 v2.52.4 h1:P+T+4iK7VaqUsq2PALYEfBBo6bJZ4q3FP8cZ84EggTM=
github.com/gofiber/fiber/v2 v2.52.4/go.mod h1:KEOE+cXMhXG0zHc9d8+E38hoX+ZN7bhOtgeF2oT6jrQ= github.com/gofiber/fiber/v2 v2.52.4/go.mod h1:KEOE+cXMhXG0zHc9d8+E38hoX+ZN7bhOtgeF2oT6jrQ=
github.com/gofiber/template v1.8.3 h1:hzHdvMwMo/T2kouz2pPCA0zGiLCeMnoGsQZBTSYgZxc= github.com/gofiber/template v1.8.3 h1:hzHdvMwMo/T2kouz2pPCA0zGiLCeMnoGsQZBTSYgZxc=
@ -19,6 +24,8 @@ github.com/gofiber/utils v1.1.0/go.mod h1:poZpsnhBykfnY1Mc0KeEa6mSHrS3dV0+oBWyeQ
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
github.com/jasonlvhit/gocron v0.0.1 h1:qTt5qF3b3srDjeOIR4Le1LfeyvoYzJlYpqvG7tJX5YU= github.com/jasonlvhit/gocron v0.0.1 h1:qTt5qF3b3srDjeOIR4Le1LfeyvoYzJlYpqvG7tJX5YU=
github.com/jasonlvhit/gocron v0.0.1/go.mod h1:k9a3TV8VcU73XZxfVHCHWMWF9SOqgoku0/QlY2yvlA4= github.com/jasonlvhit/gocron v0.0.1/go.mod h1:k9a3TV8VcU73XZxfVHCHWMWF9SOqgoku0/QlY2yvlA4=
@ -26,34 +33,72 @@ github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD
github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc= github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
github.com/klauspost/compress v1.17.8 h1:YcnTYrq7MikUT7k0Yb5eceMmALQPYBW/Xltxn0NAMnU= github.com/klauspost/compress v1.17.8 h1:YcnTYrq7MikUT7k0Yb5eceMmALQPYBW/Xltxn0NAMnU=
github.com/klauspost/compress v1.17.8/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw= github.com/klauspost/compress v1.17.8/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw=
github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY=
github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0=
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U= github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U=
github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=
github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/ginkgo v1.10.1/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.10.1/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/gomega v1.7.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= github.com/onsi/gomega v1.7.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
github.com/pelletier/go-toml/v2 v2.1.0 h1:FnwAJ4oYMvbT/34k9zzHuZNrhlz48GB3/s6at6/MHO4=
github.com/pelletier/go-toml/v2 v2.1.0/go.mod h1:tJU2Z3ZkXwnxa4DPO899bsyIoywizdUvyaeZurnPPDc=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
github.com/pquerna/otp v1.4.0 h1:wZvl1TIVxKRThZIBiwOOHOGP/1+nZyWBil9Y2XNEDzg= github.com/pquerna/otp v1.4.0 h1:wZvl1TIVxKRThZIBiwOOHOGP/1+nZyWBil9Y2XNEDzg=
github.com/pquerna/otp v1.4.0/go.mod h1:dkJfzwRKNiegxyNb54X/3fLwhCynbMspSyWKnvi1AEg= github.com/pquerna/otp v1.4.0/go.mod h1:dkJfzwRKNiegxyNb54X/3fLwhCynbMspSyWKnvi1AEg=
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
github.com/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg=
github.com/rs/zerolog v1.32.0 h1:keLypqrlIjaFsbmJOBdB/qvyF8KEtCWHwobLp5l/mQ0=
github.com/rs/zerolog v1.32.0/go.mod h1:/7mN4D5sKwJLZQ2b/znpjC3/GQWY/xaDXUM0kKWRHss=
github.com/sagikazarmark/locafero v0.4.0 h1:HApY1R9zGo4DBgr7dqsTH/JJxLTTsOt7u6keLGt6kNQ=
github.com/sagikazarmark/locafero v0.4.0/go.mod h1:Pe1W6UlPYUk/+wc/6KFhbORCfqzgYEpgQ3O5fPuL3H4=
github.com/sagikazarmark/slog-shim v0.1.0 h1:diDBnUNK9N/354PgrxMywXnAwEr1QZcOr6gto+ugjYE=
github.com/sagikazarmark/slog-shim v0.1.0/go.mod h1:SrcSrq8aKtyuqEI1uvTDTK1arOWRIczQRv+GVI1AkeQ=
github.com/sourcegraph/conc v0.3.0 h1:OQTbbt6P72L20UqAkXXuLOj79LfEanQ+YQFNpLA9ySo=
github.com/sourcegraph/conc v0.3.0/go.mod h1:Sdozi7LEKbFPqYX2/J+iBAM6HpqSLTASQIKqDmF7Mt0=
github.com/spf13/afero v1.11.0 h1:WJQKhtpdm3v2IzqG8VMqrr6Rf3UYpEF239Jy9wNepM8=
github.com/spf13/afero v1.11.0/go.mod h1:GH9Y3pIexgf1MTIWtNGyogA5MwRIDXGUr+hbWNoBjkY=
github.com/spf13/cast v1.6.0 h1:GEiTHELF+vaR5dhz3VqZfFSzZjYbgeKDpBxQVS4GYJ0=
github.com/spf13/cast v1.6.0/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo=
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/spf13/viper v1.18.2 h1:LUXCnvUvSM6FXAsj6nnfc8Q2tp1dIgUfY9Kc8GsSOiQ=
github.com/spf13/viper v1.18.2/go.mod h1:EKmWIqdnk5lOcmR72yw6hS+8OPYcwD0jteitLMVB+yk=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8=
github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU=
github.com/tmthrgd/go-hex v0.0.0-20190904060850-447a3041c3bc h1:9lRDQMhESg+zvGYmW5DyG0UqvY96Bu5QYsTLvCHdrgo= github.com/tmthrgd/go-hex v0.0.0-20190904060850-447a3041c3bc h1:9lRDQMhESg+zvGYmW5DyG0UqvY96Bu5QYsTLvCHdrgo=
github.com/tmthrgd/go-hex v0.0.0-20190904060850-447a3041c3bc/go.mod h1:bciPuU6GHm1iF1pBvUfxfsH0Wmnc2VbpgvbI9ZWuIRs= github.com/tmthrgd/go-hex v0.0.0-20190904060850-447a3041c3bc/go.mod h1:bciPuU6GHm1iF1pBvUfxfsH0Wmnc2VbpgvbI9ZWuIRs=
github.com/uptrace/bun v1.1.8 h1:slxuaP4LYWFbPRUmTtQhfJN+6eX/6ar2HDKYTcI50SA=
github.com/uptrace/bun v1.1.8/go.mod h1:iT89ESdV3uMupD9ixt6Khidht+BK0STabK/LeZE+B84=
github.com/uptrace/bun v1.2.1 h1:2ENAcfeCfaY5+2e7z5pXrzFKy3vS8VXvkCag6N2Yzfk= github.com/uptrace/bun v1.2.1 h1:2ENAcfeCfaY5+2e7z5pXrzFKy3vS8VXvkCag6N2Yzfk=
github.com/uptrace/bun v1.2.1/go.mod h1:cNg+pWBUMmJ8rHnETgf65CEvn3aIKErrwOD6IA8e+Ec= github.com/uptrace/bun v1.2.1/go.mod h1:cNg+pWBUMmJ8rHnETgf65CEvn3aIKErrwOD6IA8e+Ec=
github.com/uptrace/bun/dialect/pgdialect v1.1.8 h1:wayJhjYDPGv8tgOBLolbBtSFQ0TihFoo8E1T129UdA8=
github.com/uptrace/bun/dialect/pgdialect v1.1.8/go.mod h1:nNbU8PHTjTUM+CRtGmqyBb9zcuRAB8I680/qoFSmBUk=
github.com/uptrace/bun/dialect/pgdialect v1.2.1 h1:ceP99r03u+s8ylaDE/RzgcajwGiC76Jz3nS2ZgyPQ4M= github.com/uptrace/bun/dialect/pgdialect v1.2.1 h1:ceP99r03u+s8ylaDE/RzgcajwGiC76Jz3nS2ZgyPQ4M=
github.com/uptrace/bun/dialect/pgdialect v1.2.1/go.mod h1:mv6B12cisvSc6bwKm9q9wcrr26awkZK8QXM+nso9n2U= github.com/uptrace/bun/dialect/pgdialect v1.2.1/go.mod h1:mv6B12cisvSc6bwKm9q9wcrr26awkZK8QXM+nso9n2U=
github.com/uptrace/bun/driver/pgdriver v1.1.8 h1:gyL22axRQfjJS2Umq0erzJnp0bLOdUE8/USKZHPQB8o=
github.com/uptrace/bun/driver/pgdriver v1.1.8/go.mod h1:4tHK0h7a/UoldBoe9J3GU4tEYjr3mkd62U3Kq3PVk3E=
github.com/uptrace/bun/driver/pgdriver v1.2.1 h1:Cp6c1tKzbTIyL8o0cGT6cOhTsmQZdsUNhgcV51dsmLU= github.com/uptrace/bun/driver/pgdriver v1.2.1 h1:Cp6c1tKzbTIyL8o0cGT6cOhTsmQZdsUNhgcV51dsmLU=
github.com/uptrace/bun/driver/pgdriver v1.2.1/go.mod h1:jEd3WGx74hWLat3/IkesOoWNjrFNUDADK3nkyOFOOJM= github.com/uptrace/bun/driver/pgdriver v1.2.1/go.mod h1:jEd3WGx74hWLat3/IkesOoWNjrFNUDADK3nkyOFOOJM=
github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
@ -66,9 +111,15 @@ github.com/vmihailenco/msgpack/v5 v5.4.1 h1:cQriyiUvjTwOHg8QZaPihLWeRAAVoCpE00IU
github.com/vmihailenco/msgpack/v5 v5.4.1/go.mod h1:GaZTsDaehaPpQVyxrf5mtQlH+pc21PIudVV/E3rRQok= github.com/vmihailenco/msgpack/v5 v5.4.1/go.mod h1:GaZTsDaehaPpQVyxrf5mtQlH+pc21PIudVV/E3rRQok=
github.com/vmihailenco/tagparser/v2 v2.0.0 h1:y09buUbR+b5aycVFQs/g70pqKVZNBmxwAhO7/IwNM9g= github.com/vmihailenco/tagparser/v2 v2.0.0 h1:y09buUbR+b5aycVFQs/g70pqKVZNBmxwAhO7/IwNM9g=
github.com/vmihailenco/tagparser/v2 v2.0.0/go.mod h1:Wri+At7QHww0WTrCBeu4J6bNtoV6mEfg5OIWRZA9qds= github.com/vmihailenco/tagparser/v2 v2.0.0/go.mod h1:Wri+At7QHww0WTrCBeu4J6bNtoV6mEfg5OIWRZA9qds=
go.uber.org/atomic v1.9.0 h1:ECmE8Bn/WFTYwEW/bpKD3M8VtR/zQVbavAoalC1PYyE=
go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
go.uber.org/multierr v1.9.0 h1:7fIwc/ZtS0q++VgcfqFDxSBZVv/Xo49/SYnDFupUwlI=
go.uber.org/multierr v1.9.0/go.mod h1:X2jQV1h+kxSjClGpnseKVIxpmcjrj7MNnI0bnlfKTVQ=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.22.0 h1:g1v0xeRhjcugydODzvb3mEM9SQ0HGp9s/nh3COQ/C30= golang.org/x/crypto v0.22.0 h1:g1v0xeRhjcugydODzvb3mEM9SQ0HGp9s/nh3COQ/C30=
golang.org/x/crypto v0.22.0/go.mod h1:vr6Su+7cTlO45qkww3VDJlzDn0ctJvRgYbC2NvXHt+M= golang.org/x/crypto v0.22.0/go.mod h1:vr6Su+7cTlO45qkww3VDJlzDn0ctJvRgYbC2NvXHt+M=
golang.org/x/exp v0.0.0-20230905200255-921286631fa9 h1:GoHiUyI/Tp2nVkLI2mCxVkOjsbSXD66ic0XW0js0R9g=
golang.org/x/exp v0.0.0-20230905200255-921286631fa9/go.mod h1:S2oDrQGGwySpoQPVqRShND87VCbxmc6bL1Yd2oYrm6k=
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
@ -78,14 +129,20 @@ golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5h
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.19.0 h1:q5f1RH2jigJ1MoAWp2KTp3gm5zAGFUTarQZ5U386+4o= golang.org/x/sys v0.19.0 h1:q5f1RH2jigJ1MoAWp2KTp3gm5zAGFUTarQZ5U386+4o=
golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA=
gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
mellium.im/sasl v0.3.1 h1:wE0LW6g7U83vhvxjC1IY8DnXM+EU095yeo8XClvCdfo= mellium.im/sasl v0.3.1 h1:wE0LW6g7U83vhvxjC1IY8DnXM+EU095yeo8XClvCdfo=

View file

@ -2,14 +2,14 @@ package api
import "github.com/gofiber/fiber/v2" import "github.com/gofiber/fiber/v2"
func HandleApiKeysGetAll(c *fiber.Ctx) error { func (am APIRouteManager) HandleApiKeysGetAll(c *fiber.Ctx) error {
return nil return fiber.NewError(501, "Not Implemented")
} }
func HandleApiKeysPost(c *fiber.Ctx) error { func (am APIRouteManager) HandleApiKeysPost(c *fiber.Ctx) error {
return nil return fiber.NewError(501, "Not Implemented")
} }
func HandleApiKeysDelete(c *fiber.Ctx) error { func (am APIRouteManager) HandleApiKeysDelete(c *fiber.Ctx) error {
return nil return fiber.NewError(501, "Not Implemented")
} }

View file

@ -15,14 +15,14 @@ import (
"github.com/gofiber/fiber/v2" "github.com/gofiber/fiber/v2"
) )
func HandleLinkGetAll(c *fiber.Ctx) error { func (am APIRouteManager) HandleLinkGetAll(c *fiber.Ctx) error {
if !db.IsCookieValid(c.Cookies(constants.LoginCookieName, "")) && !db.IsApiKeyValid(c.GetRespHeader("x-api-key", "")) { if !db.IsCookieValid(c.Cookies(constants.LoginCookieName, "")) && !db.IsApiKeyValid(c.GetRespHeader("x-api-key", "")) {
return fiber.NewError(fiber.StatusUnauthorized, "401 Unauthorized") return fiber.NewError(fiber.StatusUnauthorized, "401 Unauthorized")
} }
var links []models.Link var links []models.Link
err := models.DB.NewSelect().Model(&links).Scan(context.Background()) err := am.db.NewSelect().Model(&links).Scan(context.Background())
if err != nil { if err != nil {
return fmt.Errorf("error querying links: %v", err.Error()) return fmt.Errorf("error querying links: %v", err.Error())
} }
@ -37,11 +37,11 @@ func HandleLinkGetAll(c *fiber.Ctx) error {
return nil return nil
} }
func HandleLinkGet(c *fiber.Ctx) error { func (am APIRouteManager) HandleLinkGet(c *fiber.Ctx) error {
return fiber.NewError(fiber.StatusNotImplemented, "501 Not Implemented") return fiber.NewError(fiber.StatusNotImplemented, "501 Not Implemented")
} }
func HandleLinkPost(c *fiber.Ctx) error { func (am APIRouteManager) HandleLinkPost(c *fiber.Ctx) error {
if !db.IsCookieValid(c.Cookies(constants.LoginCookieName, "")) && !db.IsApiKeyValid(c.GetRespHeader("x-api-key", "")) { if !db.IsCookieValid(c.Cookies(constants.LoginCookieName, "")) && !db.IsApiKeyValid(c.GetRespHeader("x-api-key", "")) {
return fiber.NewError(fiber.StatusUnauthorized, "401 Unauthorized") return fiber.NewError(fiber.StatusUnauthorized, "401 Unauthorized")
} }
@ -63,7 +63,7 @@ func HandleLinkPost(c *fiber.Ctx) error {
newlink.Created = now newlink.Created = now
newlink.Modified = now newlink.Modified = now
_, err = models.DB.NewInsert().Model(&newlink).Exec(context.Background()) _, err = am.db.NewInsert().Model(&newlink).Exec(context.Background())
if err != nil { if err != nil {
return fiber.NewError(fiber.StatusInternalServerError, "500 Internal Server Error: "+err.Error()) return fiber.NewError(fiber.StatusInternalServerError, "500 Internal Server Error: "+err.Error())
} }
@ -79,7 +79,7 @@ func HandleLinkPost(c *fiber.Ctx) error {
return nil return nil
} }
func HandleLinkPut(c *fiber.Ctx) error { func (am APIRouteManager) HandleLinkPut(c *fiber.Ctx) error {
if !db.IsCookieValid(c.Cookies(constants.LoginCookieName, "")) && !db.IsApiKeyValid(c.GetRespHeader("x-api-key", "")) { if !db.IsCookieValid(c.Cookies(constants.LoginCookieName, "")) && !db.IsApiKeyValid(c.GetRespHeader("x-api-key", "")) {
return fiber.NewError(fiber.StatusUnauthorized, "401 Unauthorized") return fiber.NewError(fiber.StatusUnauthorized, "401 Unauthorized")
} }
@ -92,7 +92,7 @@ func HandleLinkPut(c *fiber.Ctx) error {
return fiber.NewError(fiber.StatusBadRequest, "400 Bad Request") return fiber.NewError(fiber.StatusBadRequest, "400 Bad Request")
} }
err := models.DB.NewSelect().Model(&dblink).Where("id = ?", id).Scan(context.Background()) err := am.db.NewSelect().Model(&dblink).Where("id = ?", id).Scan(context.Background())
if err != nil { if err != nil {
if err == sql.ErrNoRows { if err == sql.ErrNoRows {
@ -113,7 +113,7 @@ func HandleLinkPut(c *fiber.Ctx) error {
dblink.URL = editlink.URL dblink.URL = editlink.URL
dblink.Modified = time.Now() dblink.Modified = time.Now()
_, err = models.DB.NewUpdate().Model(&dblink).WherePK().Exec(context.Background()) _, err = am.db.NewUpdate().Model(&dblink).WherePK().Exec(context.Background())
if err != nil { if err != nil {
return fiber.NewError(fiber.StatusInternalServerError, "500 Internal Server Error: "+err.Error()) return fiber.NewError(fiber.StatusInternalServerError, "500 Internal Server Error: "+err.Error())
} }
@ -124,14 +124,14 @@ func HandleLinkPut(c *fiber.Ctx) error {
return nil return nil
} }
func HandleLinkDelete(c *fiber.Ctx) error { func (am APIRouteManager) HandleLinkDelete(c *fiber.Ctx) error {
id := c.Params("id") id := c.Params("id")
if id == "" { if id == "" {
return fiber.NewError(fiber.StatusBadRequest, "400 Bad Request") return fiber.NewError(fiber.StatusBadRequest, "400 Bad Request")
} }
numrows, err := models.DB.NewSelect().Model((*models.Link)(nil)).Where("id = ?", id).Count(context.Background()) numrows, err := am.db.NewSelect().Model((*models.Link)(nil)).Where("id = ?", id).Count(context.Background())
if err != nil { if err != nil {
log.Println(err.Error()) log.Println(err.Error())
return fiber.NewError(fiber.StatusInternalServerError, "500 Internal Server Error") return fiber.NewError(fiber.StatusInternalServerError, "500 Internal Server Error")
@ -141,7 +141,7 @@ func HandleLinkDelete(c *fiber.Ctx) error {
return fiber.NewError(fiber.StatusNotFound, "404 Not Found") return fiber.NewError(fiber.StatusNotFound, "404 Not Found")
} }
_, err = models.DB.NewDelete().Model((*models.Link)(nil)).Where("id = ?", id).Exec(context.Background()) _, err = am.db.NewDelete().Model((*models.Link)(nil)).Where("id = ?", id).Exec(context.Background())
if err != nil { if err != nil {
log.Println(err.Error()) log.Println(err.Error())
return fiber.NewError(fiber.StatusInternalServerError, "500 Internal Server Error") return fiber.NewError(fiber.StatusInternalServerError, "500 Internal Server Error")

26
internal/api/model.go Normal file
View file

@ -0,0 +1,26 @@
package api
import (
"code.lila.network/adoralaura/go-urlsh/internal/config"
"github.com/rs/zerolog"
"github.com/uptrace/bun"
)
// APIRouteManager is a model that provides db, logger and config access
// to API routes
type APIRouteManager struct {
db *bun.DB
logger *zerolog.Logger
config *config.Config
}
// NewAPIRouteManager creates a new Instance of an APIRouteManager
// and initializes it with the given DB, Logger and Config
func NewAPIRouteManager(db *bun.DB, logger *zerolog.Logger, config *config.Config) *APIRouteManager {
manager := new(APIRouteManager)
manager.db = db
manager.logger = logger
manager.config = config
return manager
}

View file

@ -16,7 +16,7 @@ import (
"github.com/gofiber/fiber/v2" "github.com/gofiber/fiber/v2"
) )
func HandleUserPost(c *fiber.Ctx) error { func (am APIRouteManager) HandleUserPost(c *fiber.Ctx) error {
var newuser models.LoginRequest var newuser models.LoginRequest
err := json.Unmarshal(c.Body(), &newuser) err := json.Unmarshal(c.Body(), &newuser)
@ -25,7 +25,7 @@ func HandleUserPost(c *fiber.Ctx) error {
return fiber.NewError(fiber.StatusBadRequest, "400 Bad Request") return fiber.NewError(fiber.StatusBadRequest, "400 Bad Request")
} }
usercount, err := models.DB.NewSelect().Model((*models.User)(nil)).Count(context.Background()) usercount, err := am.db.NewSelect().Model((*models.User)(nil)).Count(context.Background())
if err != nil { if err != nil {
log.Printf("[POST /api/v1/users] Error querying database for users: %v\n", err.Error()) log.Printf("[POST /api/v1/users] Error querying database for users: %v\n", err.Error())
return fiber.NewError(fiber.StatusInternalServerError, "500 Internal Server Error") return fiber.NewError(fiber.StatusInternalServerError, "500 Internal Server Error")
@ -46,7 +46,7 @@ func HandleUserPost(c *fiber.Ctx) error {
user.PasswordHash = hash user.PasswordHash = hash
user.Created = created user.Created = created
_, err = models.DB.NewInsert().Model(user).Exec(context.Background()) _, err = am.db.NewInsert().Model(user).Exec(context.Background())
if err != nil { if err != nil {
log.Printf("[POST /api/v1/users] Error adding user %v to database : %v\n", newuser.Username, err.Error()) log.Printf("[POST /api/v1/users] Error adding user %v to database : %v\n", newuser.Username, err.Error())
return fiber.NewError(fiber.StatusInternalServerError, "500 Internal Server Error") return fiber.NewError(fiber.StatusInternalServerError, "500 Internal Server Error")
@ -63,7 +63,7 @@ func HandleUserPost(c *fiber.Ctx) error {
} }
} }
func HandleUserMe(c *fiber.Ctx) error { func (am APIRouteManager) HandleUserMe(c *fiber.Ctx) error {
if !db.IsCookieValid(c.Cookies(constants.LoginCookieName, "")) && !db.IsApiKeyValid(c.GetRespHeader("x-api-key", "")) { if !db.IsCookieValid(c.Cookies(constants.LoginCookieName, "")) && !db.IsApiKeyValid(c.GetRespHeader("x-api-key", "")) {
return fiber.NewError(fiber.StatusUnauthorized, "401 Unauthorized") return fiber.NewError(fiber.StatusUnauthorized, "401 Unauthorized")
} }
@ -83,7 +83,7 @@ func HandleUserMe(c *fiber.Ctx) error {
switch authmethod { switch authmethod {
case "cookie": case "cookie":
var session models.Session var session models.Session
err := models.DB.NewSelect().Model(&session).Where("cookie = ?", cookie).Scan(context.Background()) err := am.db.NewSelect().Model(&session).Where("cookie = ?", cookie).Scan(context.Background())
if err != nil { if err != nil {
if err == sql.ErrNoRows { if err == sql.ErrNoRows {
log.Printf("[GET /api/v1/users/me] Session %v not found\n", cookie) log.Printf("[GET /api/v1/users/me] Session %v not found\n", cookie)
@ -93,7 +93,7 @@ func HandleUserMe(c *fiber.Ctx) error {
return fiber.NewError(fiber.StatusInternalServerError, "500 Internal Server Error") return fiber.NewError(fiber.StatusInternalServerError, "500 Internal Server Error")
} }
err = models.DB.NewSelect().Model(&user).Where("username = ?", session.UserName).Scan(context.Background()) err = am.db.NewSelect().Model(&user).Where("username = ?", session.UserName).Scan(context.Background())
if err != nil { if err != nil {
log.Printf("[GET /api/v1/users/me] Error querying user %v from database: %v\n", session.UserName, err) log.Printf("[GET /api/v1/users/me] Error querying user %v from database: %v\n", session.UserName, err)
return fiber.NewError(fiber.StatusInternalServerError, "500 Internal Server Error") return fiber.NewError(fiber.StatusInternalServerError, "500 Internal Server Error")
@ -101,7 +101,7 @@ func HandleUserMe(c *fiber.Ctx) error {
case "apikey": case "apikey":
var key models.ApiKey var key models.ApiKey
err := models.DB.NewSelect().Model(&key).Where("key = ?", apikey).Scan(context.Background()) err := am.db.NewSelect().Model(&key).Where("key = ?", apikey).Scan(context.Background())
if err != nil { if err != nil {
if err == sql.ErrNoRows { if err == sql.ErrNoRows {
log.Printf("[GET /api/v1/users/me] ApiKey %v not found\n", apikey) log.Printf("[GET /api/v1/users/me] ApiKey %v not found\n", apikey)
@ -111,7 +111,7 @@ func HandleUserMe(c *fiber.Ctx) error {
return fiber.NewError(fiber.StatusInternalServerError, "500 Internal Server Error") return fiber.NewError(fiber.StatusInternalServerError, "500 Internal Server Error")
} }
err = models.DB.NewSelect().Model(&user).Where("username = ?", key.UserName).Scan(context.Background()) err = am.db.NewSelect().Model(&user).Where("username = ?", key.UserName).Scan(context.Background())
if err != nil { if err != nil {
log.Printf("[GET /api/v1/users/me] Error querying user %v from database: %v\n", key.UserName, err) log.Printf("[GET /api/v1/users/me] Error querying user %v from database: %v\n", key.UserName, err)
return fiber.NewError(fiber.StatusInternalServerError, "500 Internal Server Error") return fiber.NewError(fiber.StatusInternalServerError, "500 Internal Server Error")

View file

@ -1,71 +1,47 @@
package app package app
import ( import (
"os" "strconv"
"code.lila.network/adoralaura/go-urlsh/internal/config"
"code.lila.network/adoralaura/go-urlsh/internal/misc" "code.lila.network/adoralaura/go-urlsh/internal/misc"
"code.lila.network/adoralaura/go-urlsh/internal/web"
"github.com/gofiber/fiber/v2" "github.com/gofiber/fiber/v2"
"github.com/gofiber/fiber/v2/middleware/compress" "github.com/gofiber/fiber/v2/middleware/compress"
"github.com/gofiber/fiber/v2/middleware/cors" "github.com/gofiber/fiber/v2/middleware/cors"
"github.com/gofiber/fiber/v2/middleware/etag" "github.com/gofiber/fiber/v2/middleware/etag"
"github.com/gofiber/fiber/v2/middleware/logger"
"github.com/gofiber/fiber/v2/middleware/recover" "github.com/gofiber/fiber/v2/middleware/recover"
"github.com/gofiber/template/html/v2" "github.com/gofiber/template/html/v2"
"github.com/rs/zerolog"
"github.com/uptrace/bun"
) )
func SetupFiber() error { func SetupFiber(db *bun.DB, logger *zerolog.Logger, config *config.Config) error {
var prod = false var prod = false
var port = "3000"
var ip = ""
var proxyheader = "X-Forwarded-For"
var template_engine = html.New("./views", ".tmpl") var template_engine = html.New("./views", ".tmpl")
if os.Getenv("PROD") == "true" {
prod = true
}
if os.Getenv("PORT") != "" {
port = os.Getenv("PORT")
}
if os.Getenv("LISTEN") != "" {
ip = os.Getenv("LISTEN")
}
if os.Getenv("PROXYHEADER") != "" {
proxyheader = os.Getenv("PROXYHEADER")
}
fiberapp := fiber.New(fiber.Config{ fiberapp := fiber.New(fiber.Config{
AppName: "go-urlsh", AppName: "go-urlsh",
ProxyHeader: proxyheader, ProxyHeader: config.ProxyHeader,
Prefork: prod, Prefork: prod,
ErrorHandler: misc.HandleError, ErrorHandler: misc.HandleError,
Views: template_engine, Views: template_engine,
CompressedFileSuffix: ".gz", CompressedFileSuffix: ".gz",
}) })
fiberapp.Use(logger.New(logger.Config{Format: "[${ip}]:${port} ${status} ${method} ${path}\n"}))
fiberapp.Use(etag.New(etag.Config{Weak: false})) fiberapp.Use(etag.New(etag.Config{Weak: false}))
fiberapp.Use(compress.New()) fiberapp.Use(compress.New())
fiberapp.Use(recover.New()) fiberapp.Use(recover.New())
addWebRoutes(fiberapp) addWebRoutes(fiberapp)
fiberapp.Static("/admin/", "./web")
fiberapp.Get("/", web.HandleIndexGet)
fiberapp.Get("/:id", web.HandleIndexGet)
v1 := fiberapp.Group("/api/v1") v1 := fiberapp.Group("/api/v1")
v1.Use(cors.New(cors.Config{AllowOrigins: "*"})) v1.Use(cors.New(cors.Config{AllowOrigins: "*"}))
addApiRoutes(v1) addApiRoutes(v1, db, logger, config)
listenerr := fiberapp.Listen(ip + ":" + port) listenerr := fiberapp.Listen(config.HostIP.String() + ":" + strconv.Itoa(config.HostPort))
if listenerr != nil { if listenerr != nil {
return listenerr return listenerr
} }

View file

@ -2,11 +2,16 @@ package app
import ( import (
"code.lila.network/adoralaura/go-urlsh/internal/api" "code.lila.network/adoralaura/go-urlsh/internal/api"
"code.lila.network/adoralaura/go-urlsh/internal/config"
"code.lila.network/adoralaura/go-urlsh/internal/web" "code.lila.network/adoralaura/go-urlsh/internal/web"
"github.com/gofiber/fiber/v2" "github.com/gofiber/fiber/v2"
"github.com/rs/zerolog"
"github.com/uptrace/bun"
) )
func addWebRoutes(f *fiber.App) { func addWebRoutes(
f *fiber.App) {
f.Get("/admin/", web.HandleAdminLinkIndexGet) f.Get("/admin/", web.HandleAdminLinkIndexGet)
f.Get("/admin/account/", web.HandleAdminAccountGet) f.Get("/admin/account/", web.HandleAdminAccountGet)
@ -23,18 +28,30 @@ func addWebRoutes(f *fiber.App) {
f.Get("/admin/", web.HandleAdminLinkIndexGet) f.Get("/admin/", web.HandleAdminLinkIndexGet)
f.Get("/admin/links/new", web.HandleAdminLinkNewGet) f.Get("/admin/links/new", web.HandleAdminLinkNewGet)
f.Get("/admin/links/edit/:id", web.HandleAdminLinkEditGet) f.Get("/admin/links/edit/:id", web.HandleAdminLinkEditGet)
f.Static("/admin/", "./web")
f.Get("/", web.HandleIndexGet)
f.Get("/:id", web.HandleIndexGet)
} }
func addApiRoutes(r fiber.Router) { func addApiRoutes(
r.Get("/links", api.HandleLinkGetAll) r fiber.Router,
r.Get("/links/:id", api.HandleLinkGet) db *bun.DB,
r.Put("/links/:id", api.HandleLinkPut) logger *zerolog.Logger,
r.Post("/links", api.HandleLinkPost) config *config.Config) {
r.Delete("/links/:id", api.HandleLinkDelete) am := api.NewAPIRouteManager(db, logger, config)
r.Get("/apikeys", api.HandleApiKeysGetAll) r.Get("/links", am.HandleLinkGetAll)
r.Post("/apikeys", api.HandleApiKeysPost) r.Get("/links/:id", am.HandleLinkGet)
r.Delete("/apikeys/:id<guid>", api.HandleApiKeysPost) r.Put("/links/:id", am.HandleLinkPut)
r.Post("/links", am.HandleLinkPost)
r.Delete("/links/:id", am.HandleLinkDelete)
r.Post("/users", api.HandleUserPost) r.Get("/apikeys", am.HandleApiKeysGetAll)
r.Post("/apikeys", am.HandleApiKeysPost)
r.Delete("/apikeys/:id<guid>", am.HandleApiKeysPost)
r.Post("/users", am.HandleUserPost)
} }

View file

@ -4,32 +4,40 @@ import (
"context" "context"
"log" "log"
"code.lila.network/adoralaura/go-urlsh/models"
"github.com/jasonlvhit/gocron" "github.com/jasonlvhit/gocron"
"github.com/rs/zerolog"
"github.com/uptrace/bun"
) )
func CleanupLoginsCronJob() { func CleanupLoginsCronJob(db *bun.DB, logger *zerolog.Logger) {
err := gocron.Every(5).Minutes().Do(CleanupLogins) err := gocron.Every(5).Minutes().Do(CleanupLogins, db, logger)
if err != nil { if err != nil {
return return
} }
<-gocron.Start() <-gocron.Start()
} }
func CleanupLogins() { func CleanupLogins(db *bun.DB, logger *zerolog.Logger) {
_, err := models.DB.NewDelete().Table("logins").Where("expires < NOW()").Exec(context.Background()) res, err := db.NewDelete().Table("logins").Where("expires < NOW()").Exec(context.Background())
if err != nil { if err != nil {
log.Printf("[CleanupLogins] Error deleting logins: %v\n", err) log.Printf("[CleanupLogins] Error deleting logins: %v\n", err)
} }
_, err = models.DB.NewDelete().Table("logintransactions").Where("expires < NOW()").Exec(context.Background()) numdeleted, _ := res.RowsAffected()
logger.Debug().Int64("logins", numdeleted).Msg("cleaned up logins from DB")
res, err = db.NewDelete().Table("logintransactions").Where("expires < NOW()").Exec(context.Background())
if err != nil { if err != nil {
log.Printf("[CleanupLogins] Error deleting login transactions: %v\n", err) log.Printf("[CleanupLogins] Error deleting login transactions: %v\n", err)
} }
numdeleted, _ = res.RowsAffected()
logger.Debug().Int64("transactions", numdeleted).Msg("cleaned up transactions from DB")
res, err = db.NewDelete().Table("multifactor").Where("expires_at < NOW()").Where("active = false").Exec(context.Background())
if err != nil {
log.Printf("[CleanupLogins] Error deleting 2fa transactions: %v\n", err)
}
_, err = models.DB.NewDelete().Table("multifactor").Where("expires_at < NOW()").Where("active = false").Exec(context.Background()) numdeleted, _ = res.RowsAffected()
if err != nil { logger.Debug().Int64("mfatransactions", numdeleted).Msg("cleaned up 2fa transactions from DB")
log.Printf("[CleanupLogins] Error deleting login transactions: %v\n", err)
}
//log.Printf("Cleaned up logins")
} }

144
internal/config/config.go Normal file
View file

@ -0,0 +1,144 @@
package config
import (
"net/netip"
"slices"
"strconv"
"github.com/rs/zerolog"
"github.com/spf13/viper"
)
type Config struct {
DatabaseDSN string
// panic = 5,
// fatal = 4,
// error = 3,
// warn = 2,
// info = 1,
// debug = 0,
// trace = -1
LogLevel int
HostIP netip.Addr
HostPort int
HostURL string
ProxyHeader string
AccessLog bool
}
func NewConfig(configPath string) *Config {
logger := NewLogger(zerolog.InfoLevel)
loadConfigFile(configPath, logger)
cfg := new(Config)
exit := false
// handle Database DSN
if viper.GetString("database.url") != "" {
cfg.DatabaseDSN = viper.GetString("database.url")
} else {
dsn := "postgres://"
if viper.GetString("database.username") == "" {
exit = true
logger.Error().Msg("If database.url is not set, database.username must not be empty!")
} else {
dsn = dsn + viper.GetString("database.username")
}
if viper.GetString("database.password") == "" {
exit = true
logger.Error().Msg("If database.url is not set, database.password must not be empty!")
} else {
dsn = dsn + ":" + viper.GetString("database.password")
}
if viper.GetString("database.address") == "" {
exit = true
logger.Error().Msg("If database.url is not set, database.address must not be empty!")
} else {
dsn = dsn + "@" + viper.GetString("database.address")
}
if viper.GetString("database.port") != "" {
dsn = dsn + ":" + strconv.Itoa(viper.GetInt("database.port"))
}
if viper.GetString("database.database") == "" {
exit = true
logger.Error().Msg("If database.url is not set, database.database must not be empty!")
} else {
dsn = dsn + "/" + viper.GetString("database.database")
}
if slices.Contains([]string{"disable", "require", "verify-full", ""}, viper.GetString("database.ssl-mode")) {
if viper.GetString("database.ssl-mode") == "" {
cfg.DatabaseDSN = dsn + "?sslmode=disable"
} else {
cfg.DatabaseDSN = dsn + "?sslmode=" + viper.GetString("database.ssl-mode")
}
} else {
logger.Error().Msg(`If database.url is not set, database.ssl-mode must be either "disable", "require", "verify-full" or empty!`)
}
}
// Handle Logging
switch logLevel := viper.GetString("logging.level"); logLevel {
case "panic":
cfg.LogLevel = 5
case "fatal":
cfg.LogLevel = 4
case "error":
cfg.LogLevel = 3
case "warn":
cfg.LogLevel = 2
case "info":
cfg.LogLevel = 1
case "debug":
cfg.LogLevel = 0
case "trace":
cfg.LogLevel = -1
default:
exit = true
logger.Error().Str("value", logLevel).Msg(`Value of logging.level is invalid!`)
}
cfg.HostURL = viper.GetString("application.base-url")
if viper.GetString("application.listen") != "" {
ip, err := netip.ParseAddr(viper.GetString("application.listen"))
if err != nil {
exit = true
logger.Error().Str("value", viper.GetString("application.listen")).Msg(`Value of application.listen is not a valid IP address!`)
} else {
cfg.HostIP = ip
}
} else {
cfg.HostIP = netip.MustParseAddr("127.0.0.1")
}
if viper.GetInt("application.port") != 0 {
// if port is a valid port number
if viper.GetInt("application.port") <= 65535 && viper.GetInt("application.port") > 0 {
cfg.HostPort = viper.GetInt("application.port")
} else {
logger.Error().Str("value", viper.GetString("application.port")).Msg(`Value of application.port is not a valid port number!`)
}
} else {
cfg.HostPort = 3000
}
if viper.GetString("application.proxy-header") == "" {
cfg.ProxyHeader = "X-Forwarded-For"
} else {
cfg.ProxyHeader = viper.GetString("application.proxy-header")
}
if exit {
logger.Fatal().Msg("There were errors reading the config file. See errors above!")
}
return cfg
}

30
internal/config/file.go Normal file
View file

@ -0,0 +1,30 @@
package config
import (
"os"
"github.com/rs/zerolog"
"github.com/spf13/viper"
)
// loadConfigFile reads the config file from given string OR
// from either $WorkDir/config.* or from /etc/go-urlsh/config.*
// Returns map[string]any with the specified config files.
// Panics if it can't read any config file.
func loadConfigFile(filepath string, logger *zerolog.Logger) {
if filepath == "" {
path, err := os.Executable()
if err != nil {
logger.Fatal().Err(err).Msg("error getting current working dir")
}
viper.SetConfigName("config")
viper.AddConfigPath("/etc/go-urlsh/")
viper.AddConfigPath(path)
} else {
viper.SetConfigFile(filepath)
}
err := viper.ReadInConfig()
if err != nil {
logger.Fatal().Err(err).Msg("failed to open config file")
}
}

View file

@ -0,0 +1,60 @@
package config
import (
"os"
"testing"
"github.com/rs/zerolog"
"github.com/spf13/viper"
)
// writeTestConfigFile creates a test config file at given filename location
func writeTestConfigFile(t *testing.T, filename string) {
testdata := []byte("[application]\nlisten = \"127.0.0.1\"\n[database]\nport = 54321\ntestbool = false")
err := os.WriteFile(filename, testdata, 0644)
if err != nil {
t.Fatalf("Can't write test file %q: %v", filename, err)
}
}
// removeTestConfigFile removes given config file
func removeTestConfigFile(t *testing.T, filename string) {
err := os.Remove(filename)
if err != nil {
t.Fatalf("Can't remove test file %q: %v", filename, err)
}
}
// TestLoadConfigFile calls config.loadConfigFile
// to check if a valid Configuration can be loaded.
func TestLoadConfigFile(t *testing.T) {
logger := NewLogger(zerolog.InfoLevel)
dir, err := os.Getwd()
if err != nil {
t.Fatalf("Can't get current working directory")
}
testFile := dir + "/" + "config_unittest.toml"
writeTestConfigFile(t, testFile)
defer removeTestConfigFile(t, testFile)
loadConfigFile(testFile, logger)
if viper.GetString("application.listen") != "127.0.0.1" {
t.Fatalf(`viper.GetString("application.listen") = %q, want "127.0.0.1"`, viper.GetString("application.listen"))
}
if viper.GetInt("database.port") != 54321 {
t.Fatalf(`viper.GetInt("database.port") = %q, want 54321`, viper.GetInt("database.port"))
}
if viper.GetBool("database.testbool") != false {
t.Fatalf(`viper.GetBool("database.testbool") = true, want false`)
}
if viper.GetString("invalid.config.value") != "" {
t.Fatalf(`viper.GetString("invalid.config.value") = %q, want ""`, viper.GetString("invalid.config.value"))
}
}

17
internal/config/logger.go Normal file
View file

@ -0,0 +1,17 @@
package config
import (
"os"
"time"
"github.com/rs/zerolog"
)
// NewLogger creates and initializes a new *zerolog.Logger with the given
// log level.
func NewLogger(level zerolog.Level) *zerolog.Logger {
output := zerolog.ConsoleWriter{Out: os.Stdout, NoColor: false, TimeFormat: time.RFC3339}
logger := zerolog.New(output).With().Timestamp().Logger()
return &logger
}

View file

@ -0,0 +1,26 @@
package db
//
//import (
// "database/sql"
// "regexp"
// "testing"
//)
//
//var db sql.DB
//
//func init() {
//
//}
// TestHelloName calls greetings.Hello with a name, checking
// for a valid return value.
//func TestCookieValidation(t *testing.T) {
// name := "Gladys"
// want := regexp.MustCompile(`\b` + name + `\b`)
// msg, err := Hello("Gladys")
// if !want.MatchString(msg) || err != nil {
// t.Fatalf(`Hello("Gladys") = %q, %v, want match for %#q, nil`, msg, err, want)
// }
//}

11
internal/db/errors.go Normal file
View file

@ -0,0 +1,11 @@
package db
import "fmt"
type DatabaseInsertError struct {
message string
}
func (e *DatabaseInsertError) Error() string {
return fmt.Sprintf("%d - %s", e.arg, e.message)
}

5
internal/db/global.go Normal file
View file

@ -0,0 +1,5 @@
package db
import "github.com/uptrace/bun"
var database *bun.DB

8
internal/db/init_test.go Normal file
View file

@ -0,0 +1,8 @@
// myfile_test.go
package db
import (
"github.com/uptrace/bun"
)
var TestingDB *bun.DB

View file

@ -3,58 +3,63 @@ package db
import ( import (
"context" "context"
"database/sql" "database/sql"
"os"
"log"
"code.lila.network/adoralaura/go-urlsh/internal/config"
"code.lila.network/adoralaura/go-urlsh/models" "code.lila.network/adoralaura/go-urlsh/models"
"github.com/rs/zerolog"
"github.com/uptrace/bun" "github.com/uptrace/bun"
"github.com/uptrace/bun/dialect/pgdialect" "github.com/uptrace/bun/dialect/pgdialect"
"github.com/uptrace/bun/driver/pgdriver" "github.com/uptrace/bun/driver/pgdriver"
) )
func InitializeDB() *bun.DB { func dbError(err error, logger *zerolog.Logger) {
sqldb := sql.OpenDB(pgdriver.NewConnector(pgdriver.WithDSN(os.Getenv("DATABASE_URL")))) logger.Fatal().Str("error", err.Error()).Msg("[DB] couldn't create database")
}
func InitializeDB(cfg *config.Config, logger *zerolog.Logger) *bun.DB {
sqldb := sql.OpenDB(pgdriver.NewConnector(pgdriver.WithDSN(cfg.DatabaseDSN)))
db := bun.NewDB(sqldb, pgdialect.New()) db := bun.NewDB(sqldb, pgdialect.New())
models.DB = db
_, err := db.NewCreateTable().IfNotExists().Model((*models.Link)(nil)).Exec(context.Background()) _, err := db.NewCreateTable().IfNotExists().Model((*models.Link)(nil)).Exec(context.Background())
if err != nil { if err != nil {
log.Panicf("[DB] couldn't create database: [%w]", err) dbError(err, logger)
} }
_, err = db.NewCreateTable().IfNotExists().Model((*models.User)(nil)).Exec(context.Background()) _, err = db.NewCreateTable().IfNotExists().Model((*models.User)(nil)).Exec(context.Background())
if err != nil { if err != nil {
log.Panicf("[DB] couldn't create database: [%w]", err) dbError(err, logger)
} }
_, err = db.NewCreateTable().IfNotExists().Model((*models.Session)(nil)).Exec(context.Background()) _, err = db.NewCreateTable().IfNotExists().Model((*models.Session)(nil)).Exec(context.Background())
if err != nil { if err != nil {
log.Panicf("[DB] couldn't create database: [%w]", err) dbError(err, logger)
} }
_, err = db.NewCreateTable().IfNotExists().Model((*models.ApiKey)(nil)).Exec(context.Background()) _, err = db.NewCreateTable().IfNotExists().Model((*models.ApiKey)(nil)).Exec(context.Background())
if err != nil { if err != nil {
log.Panicf("[DB] couldn't create database: [%w]", err) dbError(err, logger)
} }
_, err = db.NewCreateTable().IfNotExists().Model((*models.MFALoginTransaction)(nil)).Exec(context.Background()) _, err = db.NewCreateTable().IfNotExists().Model((*models.MFALoginTransaction)(nil)).Exec(context.Background())
if err != nil { if err != nil {
log.Panicf("[DB] couldn't create database: [%w]", err) dbError(err, logger)
} }
_, err = db.NewCreateTable().IfNotExists().Model((*models.MFAConfig)(nil)).Exec(context.Background()) _, err = db.NewCreateTable().IfNotExists().Model((*models.MFAConfig)(nil)).Exec(context.Background())
if err != nil { if err != nil {
log.Panicf("[DB] couldn't create database: [%w]", err) dbError(err, logger)
} }
_, err = db.NewCreateTable().IfNotExists().Model((*models.MFAScratchCode)(nil)).Exec(context.Background()) _, err = db.NewCreateTable().IfNotExists().Model((*models.MFAScratchCode)(nil)).Exec(context.Background())
if err != nil { if err != nil {
log.Panicf("[DB] couldn't create database: [%w]", err) dbError(err, logger)
} }
err = doMigrations(db) err = doMigrations(db, logger)
if err != nil { if err != nil {
log.Panicf("[DB] Error during Migrations: [%w]", err) dbError(err, logger)
} }
return db return db

View file

@ -0,0 +1,14 @@
package db
import (
"context"
"github.com/uptrace/bun"
)
func InsertIntoDB(db *bun.DB, thing any) error {
_, err := db.NewInsert().Model(&thing).Exec(context.Background())
if err != nil {
t.Fatalf("Couldn't insert test mfa config into DB: %q\n", err)
}
}

View file

@ -2,14 +2,14 @@ package db
import ( import (
"context" "context"
"log"
"code.lila.network/adoralaura/go-urlsh/migrations" "code.lila.network/adoralaura/go-urlsh/migrations"
"github.com/rs/zerolog"
"github.com/uptrace/bun" "github.com/uptrace/bun"
"github.com/uptrace/bun/migrate" "github.com/uptrace/bun/migrate"
) )
func doMigrations(db *bun.DB) error { func doMigrations(db *bun.DB, logger *zerolog.Logger) error {
ctx := context.Background() ctx := context.Background()
migrator := migrate.NewMigrator(db, migrations.Migrations) migrator := migrate.NewMigrator(db, migrations.Migrations)
@ -20,9 +20,9 @@ func doMigrations(db *bun.DB) error {
return err return err
} }
if group.IsZero() { if group.IsZero() {
log.Printf("[DB] No new migrations to run (database is up to date)\n") logger.Info().Msg("No new database migrations to run")
return nil return nil
} }
log.Printf("[DB] Migrated to %s\n", group) logger.Info().Msgf("Database got migrated to %s", group)
return nil return nil
} }

View file

@ -2,10 +2,13 @@ package db
import ( import (
"context" "context"
"errors"
"fmt" "fmt"
"log" "log"
"code.lila.network/adoralaura/go-urlsh/internal/misc"
"code.lila.network/adoralaura/go-urlsh/models" "code.lila.network/adoralaura/go-urlsh/models"
"github.com/uptrace/bun"
) )
// UserHasMFA checks the DB if given models.User has MFA enabled. // UserHasMFA checks the DB if given models.User has MFA enabled.
@ -23,11 +26,11 @@ func UserHasMFA(user models.User) (bool, error) {
return false, nil return false, nil
} }
// ScratchCodeUnique checks the database if the generated scratch code // scratchCodeUnique checks the database if the generated scratch code
// is unique (not in the database yet) // is unique (not in the database yet)
func ScratchCodeIsUnique(scratchcode string) bool { func scratchCodeIsUnique(db *bun.DB, scratchcode string) bool {
var dbitem models.MFAScratchCode var dbitem models.MFAScratchCode
numrows, err := models.DB.NewSelect().Model(&dbitem).Where("code = ?", scratchcode).Count(context.Background()) numrows, err := db.NewSelect().Model(&dbitem).Where("code = ?", scratchcode).Count(context.Background())
if err != nil { if err != nil {
return false return false
} }
@ -64,3 +67,48 @@ func RemoveMFAFromDB(user models.User) error {
return nil return nil
} }
// UserHasFailedMFAAttempt checks the DB for failed MFA Setup attempts
// e.g. inactive MFA config
func UserHasFailedMFAAttempt(user models.User) (bool, error) {
//var mfaconfig models.MFAConfig
//numrows, err := models.DB.NewSelect().Model(&mfaconfig).
// Where("username = ?", user.UserName).Where("active = ?", false).
// Count(context.Background())
//if err != nil {
// return false, err
//}
return true, nil
}
// GenerateNewScratchcodes generates new unique scratch codes, inserts them into the database
// and returns the scratch codes as []models.MFAScratchCode, and nil if successful. Returns empty
// []models.MFAScratchCode and an error if something failed
func GenerateNewScratchcodes(db *bun.DB, user models.User) ([]models.MFAScratchCode, error) {
scratchcodes := []models.MFAScratchCode{}
scratchcodeFailed := 0
// generate four unique(!) scratch codes for the user
for len(scratchcodes) != 4 {
if scratchcodeFailed > 15 {
return []models.MFAScratchCode{}, errors.New("failed to generate new scratch codes: maximum tries exceeded")
}
code := misc.RandomString(8)
if scratchCodeIsUnique(db, code) {
scratchcodes = append(scratchcodes, models.MFAScratchCode{
IsUsed: false, Code: code, UserName: user.UserName})
} else {
scratchcodeFailed++
}
}
_, err := db.NewInsert().Model(&scratchcodes).Exec(context.Background())
if err != nil {
return []models.MFAScratchCode{}, fmt.Errorf("error inserting new scratch codes to DB: %w", err)
}
return scratchcodes, nil
}

View file

@ -0,0 +1,109 @@
package db
import (
"context"
"os"
"testing"
"time"
"code.lila.network/adoralaura/go-urlsh/internal/config"
"code.lila.network/adoralaura/go-urlsh/models"
"github.com/rs/zerolog"
"github.com/uptrace/bun"
)
// createTestingUser creates the necessary testing users for testing MFA.
func createTestingUser(t *testing.T, db *bun.DB, user *models.User) {
mfaconfig := models.MFAConfig{
UserName: user.UserName,
TOTPSecret: "abcd",
ExpiresAt: time.Now().Add(5 * time.Minute),
Active: false,
}
_, err := db.NewInsert().Model(user).Exec(context.Background())
if err != nil {
t.Fatalf("Couldn't insert test user into DB: %q\n", err)
}
_, err = db.NewInsert().Model(&mfaconfig).Exec(context.Background())
if err != nil {
t.Fatalf("Couldn't insert test mfa config into DB: %q\n", err)
}
}
// removeTestingUser is used as deferred cleanup in multifactor tests.
// It removes models.User and models.MFAConfig with given username from DB.
func removeTestingUser(t *testing.T, db *bun.DB, username string) {
_, err := db.NewInsert().Model((*models.User)(nil)).
Where("username = ?", username).Exec(context.Background())
if err != nil {
t.Fatalf("Couldn't remove test user %q from DB: %q\n", username, err)
}
_, err = db.NewInsert().Model((*models.MFAConfig)(nil)).
Where("username = ?", username).Exec(context.Background())
if err != nil {
t.Fatalf("Couldn't remove test mfa config for %q from DB: %q\n", username, err)
}
}
func TestUserHasFailedMFAAttemptTrue(t *testing.T) {
cfgfile := os.Getenv("GOURLSH_TEST_CONFIGFILE")
logger := config.NewLogger(zerolog.InfoLevel)
cfg := config.NewConfig(cfgfile)
// setup test environment
testdb := InitializeDB(cfg, logger)
testUserName := "test_mfafailedtrue"
mfauser := models.User{
UserName: testUserName,
Created: time.Now(),
}
createTestingUser(t, testdb, &mfauser)
defer removeTestingUser(t, testdb, testUserName)
answer, err := UserHasFailedMFAAttempt(mfauser)
if err != nil {
t.Fatalf("Error running UserHasFailedMFAAttempt in tests: %q\n", err)
}
if answer == false {
t.Fatalf("db.UserHasFailedMFAAttempt() = false, want true")
}
}
func TestUserHasFailedMFAAttemptFalse(t *testing.T) {
cfgfile := os.Getenv("GOURLSH_TEST_CONFIGFILE")
cfg := config.NewConfig(cfgfile)
logger := config.NewLogger(zerolog.InfoLevel)
// setup test environment
testdb := InitializeDB(cfg, logger)
testUserName := "test_mfafailed"
mfauser := models.User{
UserName: testUserName,
Created: time.Now(),
}
createTestingUser(t, testdb, &mfauser)
defer removeTestingUser(t, testdb, testUserName)
answer, err := UserHasFailedMFAAttempt(mfauser)
if err != nil {
t.Fatalf("Error running UserHasFailedMFAAttempt in tests: %q\n", err)
}
if answer == true {
t.Fatalf("db.UserHasFailedMFAAttempt() = true, want false")
}
}
func TestGenerateNewScratchcodes(t *testing.T) {
}

22
internal/web/model.go Normal file
View file

@ -0,0 +1,22 @@
package web
import (
"code.lila.network/adoralaura/go-urlsh/internal/config"
"github.com/rs/zerolog"
"github.com/uptrace/bun"
)
type WebRouteManager struct {
db *bun.DB
logger *zerolog.Logger
config *config.Config
}
func NewWebRouteManager(db *bun.DB, logger *zerolog.Logger, config *config.Config) *WebRouteManager {
manager := new(WebRouteManager)
manager.db = db
manager.logger = logger
manager.config = config
return manager
}

View file

@ -5,7 +5,6 @@ import (
"context" "context"
"encoding/base64" "encoding/base64"
"encoding/json" "encoding/json"
"fmt"
"image/png" "image/png"
"log" "log"
"strconv" "strconv"
@ -37,27 +36,6 @@ func HandleAdminAccountMFASetupGet(c *fiber.Ctx) error {
fiber.NewError(fiber.StatusInternalServerError, "500 Internal Server Error") fiber.NewError(fiber.StatusInternalServerError, "500 Internal Server Error")
} }
scratchcodes := []models.MFAScratchCode{}
scratchcodeFailed := 0
// generate four unique(!) scratch codes for the user
for len(scratchcodes) != 4 {
if scratchcodeFailed > 15 {
//TODO: structurized error logging
fmt.Println("[HandleAdminAccountMFASetupPost] Failed to generate unique scratch code 15 times! Aborting")
return misc.New500Error()
}
code := misc.RandomString(8)
if db.ScratchCodeIsUnique(code) {
scratchcodes = append(scratchcodes, models.MFAScratchCode{
IsUsed: false, Code: code, UserName: user.UserName})
} else {
scratchcodeFailed++
}
}
mfaconfig.UserName = user.UserName mfaconfig.UserName = user.UserName
key, err := totp.Generate(totp.GenerateOpts{ key, err := totp.Generate(totp.GenerateOpts{
@ -84,6 +62,7 @@ func HandleAdminAccountMFASetupGet(c *fiber.Ctx) error {
mfaobject.Image = base64img mfaobject.Image = base64img
// todo: use function
_, err = models.DB.NewInsert().Model(&scratchcodes).Exec(context.Background()) _, err = models.DB.NewInsert().Model(&scratchcodes).Exec(context.Background())
if err != nil { if err != nil {
log.Printf("[HandleAdminAccountMFASetupGet] Error inserting scratch codes to DB: %q\n", err) log.Printf("[HandleAdminAccountMFASetupGet] Error inserting scratch codes to DB: %q\n", err)

17
main_test.go Normal file
View file

@ -0,0 +1,17 @@
package main
import (
"os"
"testing"
"code.lila.network/adoralaura/go-urlsh/internal/db"
"github.com/uptrace/bun"
)
var TestingDB *bun.DB
func TestMain(m *testing.M) {
TestingDB = db.InitializeDB()
os.Exit(m.Run())
}

View file

@ -4,4 +4,5 @@ import (
"github.com/uptrace/bun" "github.com/uptrace/bun"
) )
// Deprecated: use db.database in db Package instead
var DB *bun.DB var DB *bun.DB