diff --git a/README.md b/README.md index 8650001..5c4012e 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,20 @@ -# MapyWeb - -basic web panel/user system +# CokePlate +boilerplate golang project + +## features + +* logging + - json + - pretty printing + +* configuration + - toml + - write defaults if doesn't exist + +* database + - dynamic (configurable) bitcask database initialization and access + +* misc + - base64'd ascii/ansi banner defined in config + \ No newline at end of file diff --git a/cmd/main.go b/cmd/main.go new file mode 100644 index 0000000..e0a5a97 --- /dev/null +++ b/cmd/main.go @@ -0,0 +1,23 @@ +package main + +import ( + "github.com/rs/zerolog" + + "mapyweb/config" + "mapyweb/db" + "mapyweb/extra" +) + +var log zerolog.Logger + +func init() { + config.Init() + extra.Banner() + log = config.StartLogger() + log.Info().Msg("Starting databases...") + db.StartDatabases() +} + +func main() { + +} diff --git a/config/config.go b/config/config.go new file mode 100644 index 0000000..eb089e0 --- /dev/null +++ b/config/config.go @@ -0,0 +1,300 @@ +package config + +import ( + "bytes" + "fmt" + "io/ioutil" + "os" + "runtime" + + "github.com/rs/zerolog" + "github.com/spf13/viper" +) + +const ( + // Version roughly represents the applications current version. + Version = "0.2" + // Title is the name of the application used throughout the configuration process. + Title = "mapyweb" +) + +var ( + // BannerOnly when toggled causes the appllication to only print the banner and version then exit. + BannerOnly = false + // GenConfig when toggled causes the application to write its default config to the cwd and then exit. + GenConfig = false + // NoColor when true will disable the banner and any colored console output. + NoColor bool +) + +// "http" +var ( + // HTTPBindAddr is defined via our toml configuration file. It is the address that the http server listens on. + HTTPBindAddr string + // HTTPBindPort is defined via our toml configuration file. It is the port that the http server listens on. + HTTPBindPort string + // UseUnixSocket when toggled disables the TCP listener and listens on the given UnixSocketPath. + UseUnixSocket bool + // UnixSocketPath is the path of the unix socket used when UseUnixSocket is toggled. + UnixSocketPath = "" + // HTTPUseSSL determins whether or not we serve HTTPs + HTTPUseSSL bool + // SSLCertFile is our fullchain SSL Certificate + SSLCertFile string + // SSLKeyFile is our SSL Certificate private key + SSLKeyFile string +) + +// "db" +var ( + UseEmbeddedPostgres bool + SQLHost string + SQLPort string + SQLUsername string + SQLPassword string + SQLDatabase string + DataPath string +) + +var ( + // Filename returns the current location of our toml config file. + Filename string +) + +var ( + f *os.File + err error + noColorForce = false + customconfig = false + home string + configLocations []string +) + +var ( + // Debug is our global debug toggle + Debug bool + + prefConfigLocation string + snek *viper.Viper +) + +func init() { + if home, err = os.UserHomeDir(); err != nil { + panic(err) + } + prefConfigLocation = home + "/.config/" + Title + snek = viper.New() +} + +func writeConfig() { + if runtime.GOOS != "windows" { + if _, err := os.Stat(prefConfigLocation); os.IsNotExist(err) { + if err = os.Mkdir(prefConfigLocation, 0755); err != nil { + println("error writing new config: " + err.Error()) + } + } + newconfig := prefConfigLocation + "/" + "config.toml" + if err = snek.SafeWriteConfigAs(newconfig); err != nil { + fmt.Println(err.Error()) + os.Exit(1) + } + Filename = newconfig + return + } + + newconfig := Title + snek.SetConfigName(newconfig) + if err = snek.MergeInConfig(); err != nil { + if err = snek.SafeWriteConfigAs(newconfig + ".toml"); err != nil { + fmt.Println(err.Error()) + os.Exit(1) + } + } + + Filename = newconfig +} + +// Init will initialize our toml configuration engine and define our default configuration values which can be written to a new configuration file if desired +func Init() { + snek.SetConfigType("toml") + snek.SetConfigName("config") + + argParse() + + if customconfig { + associate() + return + } + + acquireClue() + + setDefaults() + + for _, loc := range configLocations { + snek.AddConfigPath(loc) + } + + if err = snek.MergeInConfig(); err != nil { + writeConfig() + } + + if len(Filename) < 1 { + Filename = snek.ConfigFileUsed() + } + + associate() +} + +func setDefaults() { + var ( + // Add new configuration categories here + configSections = []string{"logger", "http", "db"} + + deflogdir = home + "/.config/" + Title + "/logs/" + defNoColor = false + ) + + if runtime.GOOS == "windows" { + deflogdir = "logs/" + defNoColor = true + } + + Opt := make(map[string]map[string]interface{}) + + Opt["logger"] = map[string]interface{}{ + "debug": true, + "directory": deflogdir, + "nocolor": defNoColor, + "use_date_filename": true, + } + Opt["http"] = map[string]interface{}{ + "use_unix_socket": false, + "unix_socket_path": "/var/run/cokeplate", + "bind_addr": "127.0.0.1", + "bind_port": "8080", + } + Opt["db"] = map[string]interface{}{ + "embedded_pgsql": true, + "sql_host": "localhost", + "sql_port": "5432", + "sql_user": "postgres", + "sql_pass": "postgres", + "data_path": "./data", + } + + for _, def := range configSections { + snek.SetDefault(def, Opt[def]) + } + + if GenConfig { + if err = snek.SafeWriteConfigAs("./config.toml"); err != nil { + fmt.Println(err.Error()) + os.Exit(1) + } + os.Exit(0) + } + +} + +func acquireClue() { + configLocations = append(configLocations, "./") + + if runtime.GOOS != "windows" { + configLocations = append(configLocations, prefConfigLocation) + configLocations = append(configLocations, "/etc/"+Title+"/") + configLocations = append(configLocations, "../") + configLocations = append(configLocations, "../../") + } +} + +func loadCustomConfig(path string) { + if f, err = os.Open(path); err != nil { + println("Error opening specified config file: " + path) + panic("config file open fatal error: " + err.Error()) + } + buf, err := ioutil.ReadAll(f) + err2 := snek.ReadConfig(bytes.NewBuffer(buf)) + switch { + case err != nil: + fmt.Println("config file read fatal error: ", err.Error()) + case err2 != nil: + fmt.Println("config file read fatal error: ", err2.Error()) + default: + break + } + customconfig = true +} + +func printUsage() { + println("\n" + Title + " v" + Version + " Usage\n") + println("-c - Specify config file") + println("--nocolor - disable color and banner ") + println("--banner - show banner + version and exit") + println("--genconfig - write default config to 'default.toml' then exit") + os.Exit(0) +} + +// TODO: should probably just make a proper CLI with flags or something +func argParse() { + for i, arg := range os.Args { + switch arg { + case "-h": + printUsage() + case "--genconfig": + GenConfig = true + case "--nocolor": + noColorForce = true + case "--banner": + BannerOnly = true + case "--config": + fallthrough + case "-c": + if len(os.Args) <= i-1 { + panic("syntax error! expected file after -c") + } + loadCustomConfig(os.Args[i+1]) + default: + continue + } + } +} + +func bl(key string) bool { + return snek.GetBool(key) +} +func st(key string) string { + return snek.GetString(key) +} +func sl(key string) []string { + return snek.GetStringSlice(key) +} +func it(key string) int { + return snek.GetInt(key) +} + +func associate() { + HTTPBindAddr = st("http.bind_addr") + HTTPBindPort = st("http.bind_port") + UseUnixSocket = bl("http.use_unix_socket") + + SQLPassword = st("db.sql_password") + SQLUsername = st("db.sql_username") + SQLDatabase = st("db.sql_database") + SQLHost = st("db.sql_host") + SQLPort = st("db.sql_port") + DataPath = st("db.data_path") + + Debug = bl("logger.debug") + logDir = st("logger.directory") + NoColor = bl("logger.nocolor") + + if noColorForce { + NoColor = true + } + // if UseUnixSocket { + // UnixSocketPath = snek.GetString("http.unix_socket_path") + // } + if Debug { + zerolog.SetGlobalLevel(zerolog.DebugLevel) + } +} diff --git a/config/logger.go b/config/logger.go new file mode 100644 index 0000000..7f3581e --- /dev/null +++ b/config/logger.go @@ -0,0 +1,50 @@ +package config + +import ( + "os" + "strings" + "time" + + "github.com/rs/zerolog" +) + +var ( + CurrentLogFile string + logFile *os.File + logDir string + logger zerolog.Logger +) + +// StartLogger instantiates an instance of our zerolog loggger so we can hook it in our main package. +// While this does return a logger, it should not be used for additional retrievals of the logger. Use GetLogger() +func StartLogger() zerolog.Logger { + logDir = snek.GetString("logger.directory") + if err := os.MkdirAll(logDir, 0755); err != nil { + println("cannot create log directory: " + logDir + "(" + err.Error() + ")") + os.Exit(1) + } + + tnow := Title + + if snek.GetBool("logger.use_date_filename") { + tnow = strings.Replace(time.Now().Format(time.RFC822), " ", "_", -1) + tnow = strings.Replace(tnow, ":", "-", -1) + } + + CurrentLogFile = logDir + tnow + ".log" + + if logFile, err = os.OpenFile(CurrentLogFile, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0666); err != nil { + println("cannot create log file: " + err.Error()) + os.Exit(1) + } + + multi := zerolog.MultiLevelWriter(zerolog.ConsoleWriter{NoColor: NoColor, Out: os.Stdout}, logFile) + logger = zerolog.New(multi).With().Timestamp().Logger() + return logger +} + +// GetLogger retrieves our global logger object +func GetLogger() zerolog.Logger { + // future logic here + return logger +} diff --git a/db.go b/db.go deleted file mode 100644 index 334865b..0000000 --- a/db.go +++ /dev/null @@ -1,59 +0,0 @@ -package main - -import ( - "database/sql" - "fmt" - - jsoniter "github.com/json-iterator/go" - "git.tcp.direct/tcp.direct/bitcask-mirror" -) - -/* -create table users ( - id int not null auto_increment, - banExpireDate datetime(3), - banReason varchar(255), - offensemanager int, - votepoints int default 0, - donationpoints int default 0, - maplePoints int default 0, - nxPrepaid int default 0, - name varchar(255), - password varchar(255), - pic varchar(255), - mac varchar(255), - accounttype int default 0, - age int default 0, - vipgrade int default 0, - nblockreason int default 0, - gender tinyint default 0, - msg2 tinyint default 0, - purchaseexp tinyint default 0, - pblockreason tinyint default 3, - chatunblockdate bigint default 0, - hascensorednxloginid boolean default 0, - gradecode tinyint default 0, - censorednxloginid varchar(255), - characterslots int default 4, - creationdate datetime(3), - primary key (id), - foreign key (offensemanager) references offense_managers(id) -); - */ - - -// Credentials is the json structure for user data -type Credentials struct { - Password string `json:"password"` - Username string `json:"username"` - Email string `json:"email"` -} - - -func initDB() { - -} - -func rowExists(query string, args ...interface{}) bool { - -} diff --git a/pgsql/pg.go b/db/auth.go similarity index 72% rename from pgsql/pg.go rename to db/auth.go index 789488c..5dedeb9 100644 --- a/pgsql/pg.go +++ b/db/auth.go @@ -1,8 +1,7 @@ -package main +package db import ( "errors" - "os" "time" pgsql "github.com/fergusstrange/embedded-postgres" @@ -37,6 +36,24 @@ func getUser(username string) (u *User, err error) { return u, nil } +// UserExists queries our database for a user using the given username and returns true if it exists, false if it does not. +func UserExists(username string) bool { + u := new(User) + if err := db.Get(u, "SELECT id FROM accounts WHERE username=$1", username); err != nil { + return false + } + return true +} + +// EmailTaken queries our database for a user using the given email address and returns true if the email is taken, false if it is not. +func EmailTaken(email string) bool { + u := new(User) + if err := db.Get(u, "SELECT id FROM accounts WHERE email=$1", email); err == nil { + return true + } + return false +} + // AttemptWebLogin checks the given credentials for validity and returns a User and an error. func AttemptWebLogin(username, password, ipaddr, useragent string) (*User, error) { var ( @@ -44,21 +61,22 @@ func AttemptWebLogin(username, password, ipaddr, useragent string) (*User, error err error good = false ) + defer func() { + // TODO: make bitcask max value size larger because this 000 is gonna get huge // to log invalid users if u == nil { u = &User{ID: 000} } - if err := authlog.NewAttempt(int(u.ID), ipaddr, useragent, Web, good); err != nil { + if err := authlog.newAttempt(int(u.ID), ipaddr, useragent, Web, good); err != nil { println(err.Error()) log.Error().Err(err).Msg("failed_login") } }() + if u, err = getUser(username); err != nil { return u, err } - println("provided: ", password) - println("hashed: ", u.Password) if !CheckPasswordHash(password, u.Password) { return nil, errors.New("invalid password") } @@ -91,37 +109,5 @@ func RegisterNewUser(username, password, ipaddr string, gender int, admin bool) } _, err = db.NamedExec(`INSERT INTO accounts (username, password, creation, last_login, last_ip, ban, admin, gender) VALUES (:username, :password, :creation, :last_login, :last_ip, 0, :admin, :gender);`, u) - return err } - -func main() { - var err error - postgres = pgsql.NewDatabase(pgsql.DefaultConfig(). - DataPath("./data/pgsql"). - BinariesPath("./postgresql"). - Logger(os.Stdout)) - if err := postgres.Start(); err != nil { - log.Error().Err(err).Msg("postgres_fail") - return - } - - defer postgres.Stop() - - if authlog, err = GetUserAuths("./data/authlog"); err != nil { - log.Error().Caller().Err(err).Msg("!!! GetUserAuths failed !!!") - } - - db, err = sqlx.Connect("postgres", - "host=localhost port=5432 user=postgres password=postgres dbname=postgres sslmode=disable") - - if err != nil { - log.Error().Caller().Err(err).Msg("postgres_fail") - return - } - - Banner() - - argParse() - -} diff --git a/pgsql/authlog.go b/db/authlog.go similarity index 67% rename from pgsql/authlog.go rename to db/authlog.go index 11fa18b..888a456 100644 --- a/pgsql/authlog.go +++ b/db/authlog.go @@ -1,4 +1,4 @@ -package main +package db import ( "encoding/binary" @@ -8,10 +8,10 @@ import ( "git.tcp.direct/tcp.direct/bitcask-mirror" ) -// AuthLog is for implementing an authentication log +// AuthLog is for implementing an authentication log. type AuthLog interface { - NewAttempt(id int, ip, useragent string, source AuthSource, good bool) error - GetLog(id int) (map[time.Time]AuthAttempt, error) + newAttempt(id int, ip, useragent string, source AuthSource, good bool) error + getLog(id int) (map[time.Time]AuthAttempt, error) } type AuthSource uint32 @@ -21,7 +21,7 @@ const ( Client // 1 ) -// AuthAttempt represents a login attempt, used for our authlog +// AuthAttempt represents a login attempt, used for our AuthLog. type AuthAttempt struct { Time time.Time IP string @@ -30,18 +30,18 @@ type AuthAttempt struct { Good bool } -// SecurityLog is an implementation of AuthLog -type SecurityLog struct { +// secLog is an implementation of AuthLog +type secLog struct { DB *bitcask.Bitcask } -// GetUserAuths opens a bitcask database at path or starts a new one, then returns a SecurityLog and an error. -func GetUserAuths(path string) (*SecurityLog, error) { +// GetUserAuths opens a bitcask database at path or starts a new one, then returns a secLog and an error. +func GetUserAuths(path string) (*secLog, error) { casket, err := bitcask.Open(path) if err != nil { return nil, err } - return &SecurityLog{DB: casket}, nil + return &secLog{DB: casket}, nil } func idToUbytes(id int) []byte { @@ -51,7 +51,7 @@ func idToUbytes(id int) []byte { return buf } -func (s *SecurityLog) GetLog(id int) (map[time.Time]AuthAttempt, error) { +func (s *secLog) getLog(id int) (map[time.Time]AuthAttempt, error) { uidb := idToUbytes(id) var logMap = make(map[time.Time]AuthAttempt) if !s.DB.Has(uidb) { @@ -68,10 +68,10 @@ func (s *SecurityLog) GetLog(id int) (map[time.Time]AuthAttempt, error) { return logMap, nil } -func (s *SecurityLog) NewAttempt(id int, ip, useragent string, source AuthSource, good bool) error { +func (s *secLog) newAttempt(id int, ip, useragent string, source AuthSource, good bool) error { uidb := idToUbytes(id) - current, err := s.GetLog(id) + current, err := s.getLog(id) if err != nil { return err } @@ -79,8 +79,8 @@ func (s *SecurityLog) NewAttempt(id int, ip, useragent string, source AuthSource current[time.Now()] = AuthAttempt{ Time: time.Now(), IP: ip, - UserAgent: useragent, Source: source, + UserAgent: useragent, Good: good, } diff --git a/pgsql/passwd.go b/db/passwd.go similarity index 97% rename from pgsql/passwd.go rename to db/passwd.go index 2b09b63..6d337b4 100644 --- a/pgsql/passwd.go +++ b/db/passwd.go @@ -1,4 +1,4 @@ -package main +package db import "golang.org/x/crypto/bcrypt" diff --git a/db/setup.go b/db/setup.go new file mode 100644 index 0000000..a19e276 --- /dev/null +++ b/db/setup.go @@ -0,0 +1,117 @@ +package db + +import ( + "fmt" + "os" + + pgsql "github.com/fergusstrange/embedded-postgres" + "github.com/jmoiron/sqlx" + "github.com/rs/zerolog/log" + + "mapyweb/config" +) + +var accountsSchema = `create table accounts +( + id serial + constraint accounts_pkey + primary key, + username varchar(13) not null, + password varchar not null, + creation date not null, + last_login date not null, + last_ip varchar not null, + ban integer not null, + admin integer not null, + gender smallint default 0 not null +);` + +var authlog *secLog + +func install(db *sqlx.DB) { + if _, err := db.Exec(accountsSchema); err != nil { + panic(err) + } + println("accounts table installed!") +} + +func loginTest() { + var kayos *User + var err error + println("logging in as kayos...") + if kayos, err = AttemptWebLogin( + "kayos", + "yeet", + "192.168.69.5", + "yeetBrowser 420"); err != nil { + println("FAIL: " + err.Error()) + return + } + println("logged in as kayos!") + println(kayos.Password) +} + +func registerTest() { + println("registering kayos...") + if err := RegisterNewUser("kayos", "yeet", "127.0.0.1", 0, true); err != nil { + println("REG_TEST_FAIL: " + err.Error()) + return + } + println("kayos registered!") + return +} + +func argParse() { + for _, arg := range os.Args { + if arg == "--install" { + println("installing...") + install(db) + } + if arg == "--regtest" { + registerTest() + } + if arg == "--logintest" { + loginTest() + } + } +} + +// StartDatabases initializes our embeded postgresql database and our embeded bitcask database. +func StartDatabases() { + var err error + + // embedded postgres - + if config.UseEmbeddedPostgres { + postgres = pgsql.NewDatabase(pgsql.DefaultConfig(). + DataPath("./data/pgsql"). + BinariesPath("./postgresql"). + Logger(os.Stdout)) + if err := postgres.Start(); err != nil { + log.Error().Err(err).Msg("postgres_fail") + return + } + defer postgres.Stop() + + db, err = sqlx.Connect("postgres", + "host=localhost port=5432 user=postgres password=postgres dbname=postgres sslmode=disable") + } else { + connstr := fmt.Sprintf("host=%s port=%s user=%s password=%s dbname=%s sslmode=disable", config.SQLHost, config.SQLPort, config.SQLUsername, config.SQLPassword) + db, err = sqlx.Connect("postgres", connstr) + } + + + if err != nil { + log.Error().Caller().Err(err).Msg("postgres_fail") + return + } + + // ------------------- + + // embedded bitcask for authlog + if authlog, err = GetUserAuths("./data/authlog"); err != nil { + log.Error().Caller().Err(err).Msg("!!! GetUserAuths failed !!!") + } + // ------------------- + + argParse() +} diff --git a/pgsql/banner.go b/extra/banner.go similarity index 98% rename from pgsql/banner.go rename to extra/banner.go index 69950b9..e1f0c2f 100644 --- a/pgsql/banner.go +++ b/extra/banner.go @@ -1,4 +1,4 @@ -package main +package extra import ( "bytes" diff --git a/go.mod b/go.mod index 0c001ab..9d95fa5 100644 --- a/go.mod +++ b/go.mod @@ -9,12 +9,13 @@ require ( github.com/gorilla/csrf v1.7.1 github.com/gorilla/mux v1.8.0 github.com/jmoiron/sqlx v1.3.4 - github.com/json-iterator/go v1.1.12 github.com/lib/pq v1.10.2 github.com/matcornic/hermes/v2 v2.1.0 github.com/rs/zerolog v1.25.0 github.com/satori/go.uuid v1.2.0 + github.com/savsgio/atreugo/v11 v11.8.3 github.com/spf13/viper v1.8.1 + github.com/valyala/fasthttp v1.30.0 golang.org/x/crypto v0.0.0-20210817164053-32db794688a5 ) @@ -23,28 +24,27 @@ require ( github.com/Masterminds/sprig v2.16.0+incompatible // indirect github.com/PuerkitoBio/goquery v1.5.0 // indirect github.com/abcum/lcp v0.0.0-20201209214815-7a3f3840be81 // indirect - github.com/andybalholm/brotli v1.0.0 // indirect + github.com/andybalholm/brotli v1.0.2 // indirect github.com/andybalholm/cascadia v1.0.0 // indirect github.com/aokoli/goutils v1.0.1 // indirect github.com/dsnet/compress v0.0.1 // indirect + github.com/fasthttp/router v1.4.3 // indirect github.com/fsnotify/fsnotify v1.4.9 // indirect github.com/gofrs/flock v0.8.0 // indirect - github.com/golang/snappy v0.0.1 // indirect - github.com/google/uuid v1.1.2 // indirect + github.com/golang/snappy v0.0.3 // indirect + github.com/google/uuid v1.3.0 // indirect github.com/gorilla/css v1.0.0 // indirect github.com/gorilla/securecookie v1.1.1 // indirect github.com/hashicorp/hcl v1.0.0 // indirect github.com/huandu/xstrings v1.2.0 // indirect github.com/imdario/mergo v0.3.6 // indirect github.com/jaytaylor/html2text v0.0.0-20180606194806-57d518f124b0 // indirect - github.com/klauspost/compress v1.10.10 // indirect + github.com/klauspost/compress v1.13.4 // indirect github.com/klauspost/pgzip v1.2.4 // indirect github.com/magiconair/properties v1.8.5 // indirect github.com/mattn/go-runewidth v0.0.3 // indirect github.com/mholt/archiver/v3 v3.5.0 // indirect github.com/mitchellh/mapstructure v1.4.1 // indirect - github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 // indirect - github.com/modern-go/reflect2 v1.0.2 // indirect github.com/nwaples/rardecode v1.1.0 // indirect github.com/olekukonko/tablewriter v0.0.1 // indirect github.com/pelletier/go-toml v1.9.3 // indirect @@ -52,6 +52,7 @@ require ( github.com/pkg/errors v0.9.1 // indirect github.com/plar/go-adaptive-radix-tree v1.0.4 // indirect github.com/russross/blackfriday/v2 v2.0.1 // indirect + github.com/savsgio/gotils v0.0.0-20210907153846-c06938798b52 // indirect github.com/shurcooL/sanitized_anchor_name v1.0.0 // indirect github.com/sirupsen/logrus v1.8.1 // indirect github.com/spf13/afero v1.6.0 // indirect @@ -61,13 +62,15 @@ require ( github.com/ssor/bom v0.0.0-20170718123548-6386211fdfcf // indirect github.com/subosito/gotenv v1.2.0 // indirect github.com/ulikunitz/xz v0.5.7 // indirect + github.com/valyala/bytebufferpool v1.0.0 // indirect + github.com/valyala/tcplisten v1.0.0 // indirect github.com/vanng822/css v0.0.0-20190504095207-a21e860bcd04 // indirect github.com/vanng822/go-premailer v0.0.0-20191214114701-be27abe028fe // indirect github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8 // indirect golang.org/x/exp v0.0.0-20200228211341-fcea875c7e85 // indirect - golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4 // indirect + golang.org/x/net v0.0.0-20210510120150-4163338589ed // indirect golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1 // indirect - golang.org/x/text v0.3.5 // indirect + golang.org/x/text v0.3.6 // indirect gopkg.in/ini.v1 v1.62.0 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect ) diff --git a/go.sum b/go.sum index df08940..063639f 100644 --- a/go.sum +++ b/go.sum @@ -52,8 +52,9 @@ github.com/abcum/lcp v0.0.0-20201209214815-7a3f3840be81 h1:uHogIJ9bXH75ZYrXnVShH github.com/abcum/lcp v0.0.0-20201209214815-7a3f3840be81/go.mod h1:6ZvnjTZX1LNo1oLpfaJK8h+MXqHxcBFBIwkgsv+xlv0= github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= -github.com/andybalholm/brotli v1.0.0 h1:7UCwP93aiSfvWpapti8g88vVVGp2qqtGyePsSuDafo4= github.com/andybalholm/brotli v1.0.0/go.mod h1:loMXtMfwqflxFJPmdbJO0a3KNoPuLBgiu3qAvBg8x/Y= +github.com/andybalholm/brotli v1.0.2 h1:JKnhI/XQ75uFBTiuzXpzFrUriDPiZjlOSzh6wXogP0E= +github.com/andybalholm/brotli v1.0.2/go.mod h1:loMXtMfwqflxFJPmdbJO0a3KNoPuLBgiu3qAvBg8x/Y= github.com/andybalholm/cascadia v1.0.0 h1:hOCXnnZ5A+3eVDX8pvgl4kofXv2ELss0bKcqRySc45o= github.com/andybalholm/cascadia v1.0.0/go.mod h1:GsXiBklL0woXo1j/WYWtSYYC4ouU9PqHO0sqidkEA4Y= github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= @@ -63,6 +64,8 @@ github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hC github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY= github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= +github.com/atreugo/mock v0.0.0-20200601091009-13c275b330b0 h1:IVqe9WnancrkICl5HqEfGjrnkQ4+VsU5fodcuFVoG/A= +github.com/atreugo/mock v0.0.0-20200601091009-13c275b330b0/go.mod h1:HTHAc8RoZXMVTr6wZQN7Jjm3mYMnbfkqqKdnQgSoe9o= github.com/badoux/checkmail v1.2.1 h1:TzwYx5pnsV6anJweMx2auXdekBwGr/yt1GgalIx9nBQ= github.com/badoux/checkmail v1.2.1/go.mod h1:XroCOBU5zzZJcLvgwU15I+2xXyCdTWXyR9MGfRhBYy0= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= @@ -101,6 +104,8 @@ github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5y github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= +github.com/fasthttp/router v1.4.3 h1:spS+LUnRryQ/+hbmYzs/xWGJlQCkeQI3hxGZdlVYhLU= +github.com/fasthttp/router v1.4.3/go.mod h1:9ytWCfZ5LcCcbD3S7pEXyBX9vZnOZmN918WiiaYUzr8= github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= github.com/fergusstrange/embedded-postgres v1.10.0 h1:YnwF6xAQYmKLAXXrrRx4rHDLih47YJwVPvg8jeKfdNg= github.com/fergusstrange/embedded-postgres v1.10.0/go.mod h1:a008U8/Rws5FtIOTGYDYa7beVWsT3qVKyqExqYYjL+c= @@ -154,8 +159,9 @@ github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= github.com/golang/protobuf v1.5.1/go.mod h1:DopwsBzvsk0Fs44TXzsVbJyPhcCPeIwnvohx4u74HPM= github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= -github.com/golang/snappy v0.0.1 h1:Qgr9rKW7uDUkrbSmQeiDsGa8SjGyCOGtuasMWwvp2P4= github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/golang/snappy v0.0.3 h1:fHPg5GQYlCeLIPB9BZqMVR5nR9A+IM5zcgeTdjMYmLA= +github.com/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= @@ -186,8 +192,9 @@ github.com/google/pprof v0.0.0-20210122040257-d980be63207e/go.mod h1:kpwsk12EmLe github.com/google/pprof v0.0.0-20210226084205-cbba55b83ad5/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/google/uuid v1.1.2 h1:EVhdT+1Kseyi1/pUmXKaFxYsDNy9RQYkMWRH68J/W7Y= github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +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/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= @@ -240,8 +247,6 @@ github.com/jmoiron/sqlx v1.3.4 h1:wv+0IJZfL5z0uZoUjlpKgHkgaFSYD+r9CfrXjEXsO7w= github.com/jmoiron/sqlx v1.3.4/go.mod h1:2BljVx/86SuTyjE+aPYlHCTNvZrnJXghYGpNiXLBMCQ= github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= -github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= -github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo= @@ -251,8 +256,9 @@ github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvW github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/klauspost/compress v1.4.1/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A= -github.com/klauspost/compress v1.10.10 h1:a/y8CglcM7gLGYmlbP/stPE5sR3hbhFRUjCBfd/0B3I= github.com/klauspost/compress v1.10.10/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs= +github.com/klauspost/compress v1.13.4 h1:0zhec2I8zGnjWcKyLl6i3gPqKANCCn5e9xmviEEeX6s= +github.com/klauspost/compress v1.13.4/go.mod h1:8dP1Hq4DHOhN9w426knH3Rhby4rFm6D8eO+e+Dq5Gzg= github.com/klauspost/cpuid v1.2.0/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek= github.com/klauspost/pgzip v1.2.4 h1:TQ7CNpYKovDOmqzRHKxJh0BeaBI7UdQZYc6p7pMQh1A= github.com/klauspost/pgzip v1.2.4/go.mod h1:Ch1tH69qFZu15pkjo5kYi6mth2Zzwzt50oCQKQE9RUs= @@ -293,12 +299,9 @@ github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:F github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= 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/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 h1:ZqeYNhU3OHLH3mGKHDcjJRFFRrJa6eAM5H+CtDdOsPc= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= -github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= -github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/nwaples/rardecode v1.1.0 h1:vSxaY8vQhOcVr4mm5e8XllHWTiM4JF507A0Katqw7MQ= github.com/nwaples/rardecode v1.1.0/go.mod h1:5DzqNKiOdpKKBH87u8VlvAnPZMXcGRhxWkRpHbbfGS0= @@ -342,6 +345,10 @@ github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQD github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= github.com/satori/go.uuid v1.2.0 h1:0uYX9dsZ2yD7q2RtLRtPSdGDWzjeM3TbMJP9utgA0ww= github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= +github.com/savsgio/atreugo/v11 v11.8.3 h1:5w9H7sBwJ4/MOYms3zZ3NRDPIyp2TBkhZ0GrerNGizA= +github.com/savsgio/atreugo/v11 v11.8.3/go.mod h1:d/+jyq8I/QyaWJvdGjvvvqVoG1gnqqjAg/hhSvnZloA= +github.com/savsgio/gotils v0.0.0-20210907153846-c06938798b52 h1:FODZE/jDkENIpW3JiMA9sXBQfNklTfClUNhR9k37dPY= +github.com/savsgio/gotils v0.0.0-20210907153846-c06938798b52/go.mod h1:oejLrk1Y/5zOF+c/aHtXqn3TFlzzbAgPWg8zBiAHDas= github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= github.com/shurcooL/sanitized_anchor_name v1.0.0 h1:PdmoCO6wvbs+7yrJyMORt4/BmY5IYyJwS/kOiWx8mHo= github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= @@ -394,6 +401,12 @@ github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGr github.com/ulikunitz/xz v0.5.6/go.mod h1:2bypXElzHzzJZwzH67Y6wb67pO62Rzfn7BSiF4ABRW8= github.com/ulikunitz/xz v0.5.7 h1:YvTNdFzX6+W5m9msiYg/zpkSURPPtOlzbqYjrFn7Yt4= github.com/ulikunitz/xz v0.5.7/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14= +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.30.0 h1:nBNzWrgZUUHohyLPU/jTvXdhrcaf2m5k3bWk+3Q049g= +github.com/valyala/fasthttp v1.30.0/go.mod h1:2rsYD01CKFrjjsvFxx75KlEUNpWNBY9JWD3K/7o2Cus= +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/vanng822/css v0.0.0-20190504095207-a21e860bcd04 h1:L0rPdfzq43+NV8rfIx2kA4iSSLRj2jN5ijYHoeXRwvQ= github.com/vanng822/css v0.0.0-20190504095207-a21e860bcd04/go.mod h1:tcnB1voG49QhCrwq1W0w5hhGasvOg+VQp9i9H1rCM1w= github.com/vanng822/go-premailer v0.0.0-20191214114701-be27abe028fe h1:9YnI5plmy+ad6BM+JCLJb2ZV7/TNiE5l7SNKfumYKgc= @@ -433,6 +446,7 @@ golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8U golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8= golang.org/x/crypto v0.0.0-20210817164053-32db794688a5 h1:HWj/xjIHfjYU5nVXpTM0s39J9CbLn7Cc5a7IC5rwsMQ= golang.org/x/crypto v0.0.0-20210817164053-32db794688a5/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= @@ -511,8 +525,9 @@ golang.org/x/net v0.0.0-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLdyRGr576XBO4/greRjx4P4O3yc= -golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4 h1:4nGaVu0QrbjT/AK2PRLuQfQuh6DJve+pELhqTdAj3x0= golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= +golang.org/x/net v0.0.0-20210510120150-4163338589ed h1:p9UgmWI9wKpfYmgaV/IZKGdXc5qEK45tDwwwDyjS26I= +golang.org/x/net v0.0.0-20210510120150-4163338589ed/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -581,7 +596,9 @@ golang.org/x/sys v0.0.0-20210315160823-c6e025ad8005/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210514084401-e8d321eab015/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1 h1:SrN+KX8Art/Sf4HNj6Zcz06G7VEz+7w9tdXTPOZ7+l4= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= @@ -591,8 +608,9 @@ golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3 golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.3.5 h1:i6eZZ+zk0SOf0xgBpEpPD18qWcJda6q1sxt3S0kzyUQ= golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.6 h1:aRYxNxv6iGQlyVaZmk6ZgYEDa+Jg18DxebPSrd6bg1M= +golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= diff --git a/main.go b/main.go index c373265..de21412 100644 --- a/main.go +++ b/main.go @@ -1,50 +1,77 @@ package main import ( - "database/sql" - "encoding/json" "fmt" "html/template" - "log" + "net" "net/http" "regexp" + "strconv" "strings" "time" + "github.com/valyala/fasthttp" + + "github.com/rs/zerolog/log" + "github.com/badoux/checkmail" "github.com/gorilla/csrf" "github.com/gorilla/mux" _ "github.com/lib/pq" "github.com/satori/go.uuid" - "golang.org/x/crypto/bcrypt" + + "mapyweb/db" + "mapyweb/extra" ) func init() { - Splash() - initDB() + extra.Banner() + db.StartDatabases() } func main() { HTTPServ() } +type authError string + +const ( + usernameExists authError = "username is taken" + emailExists authError = "email is taken" + rateLimited authError = "you are being ratelimited, slow down" + badEmail authError = "not a valid email address" + internalErr authError = "internal server error" + invalidRequest authError = "invalid request" + badCreds authError = "invalid credentials" +) + func HTTPServ() { r := mux.NewRouter() - r.HandleFunc("/", IndexShow) - r.HandleFunc("/register", RegForm) - r.HandleFunc("/login", LoginForm) + r.HandleFunc("/", showIndex) + r.HandleFunc("/register", registrationForm) + r.HandleFunc("/login", loginForm) r.HandleFunc("/EmailTest", EmailTest) - //without rate limiting - //r.HandleFunc("/login/submit", Login) - //r.HandleFunc("/register/submit", Register) - fmt.Println("Web server starting on port 42069") - log.Fatal(http.ListenAndServe(":42069", csrf.Protect([]byte("7e3e2a60a55a223589f0bf218f23251619182602ae19fd829803d18645379f66"), csrf.Secure(false))(r))) + log.Fatal().Err(http.ListenAndServe(":42069", csrf.Protect([]byte("7e3e2a60a55a223589f0bf218f23251619182602ae19fd829803d18645379f66"), csrf.Secure(false))(r))). + Msg("http failure") } -func IndexShow(w http.ResponseWriter, r *http.Request) { +func logAndReturnError(w http.ResponseWriter, e authError) { + log.Error().Str("error", string(e)).Msg("auth_fail") + if _, err := fmt.Fprint(w, e); err != nil { + log.Debug().Err(err).Msg("failed to write to ResponseWriter") + } +} + +func fprinter(w http.ResponseWriter, s string, a ...interface{}) { + if _, err := fmt.Fprint(w, s, a); err != nil { + log.Debug().Err(err).Msg("failed to write to ResponseWriter") + } +} + +func showIndex(w http.ResponseWriter, r *http.Request) { c, err := r.Cookie("session_token") if err != nil { if err == http.ErrNoCookie { @@ -70,139 +97,122 @@ func IndexShow(w http.ResponseWriter, r *http.Request) { http.Redirect(w, r, "/login", http.StatusSeeOther) return } + // Finally, return the welcome message to the user - w.Write([]byte(fmt.Sprintf("Welcome %s!", response))) + fprinter(w, "Welcome %s!", response) } -func LoginForm(w http.ResponseWriter, r *http.Request) { +func loginForm(w http.ResponseWriter, r *http.Request) { ip := strings.Split(r.RemoteAddr, ":")[0] fmt.Println(ip) - t, _ := template.ParseFiles("login.tmpl") - t.ExecuteTemplate(w, "login.tmpl", map[string]interface{}{ + t, err := template.ParseFiles("login.tmpl") + if err != nil { + panic(err) + } + if err := t.ExecuteTemplate(w, "login.tmpl", map[string]interface{}{ csrf.TemplateTag: csrf.TemplateField(r), - }) + }); err != nil { + panic(err) + } } -func RegForm(w http.ResponseWriter, r *http.Request) { +func registrationForm(w http.ResponseWriter, r *http.Request) { ip := strings.Split(r.RemoteAddr, ":")[0] fmt.Println(ip) - t, _ := template.ParseFiles("register.tmpl") - t.ExecuteTemplate(w, "register.tmpl", map[string]interface{}{ + t, err := template.ParseFiles("register.tmpl") + if err != nil { + panic(err) + } + if err := t.ExecuteTemplate(w, "register.tmpl", map[string]interface{}{ csrf.TemplateTag: csrf.TemplateField(r), - }) + }); err != nil { + panic(err) + } } -func Register(w http.ResponseWriter, r *http.Request) { - creds := &Credentials{} - err := json.NewDecoder(r.Body).Decode(creds) - ip := strings.Split(r.RemoteAddr, ":")[0] - if ip == "[" { - ip = "127.0.0.1" +func register(w http.ResponseWriter, r *http.Request) { + var ip string + var err error + ip, _, err = net.SplitHostPort(r.RemoteAddr) + if err != nil { + panic(err) } - fmt.Println(ip + "/register/submit") + log.Debug().Str("ip", ip).Msg("register") - UsernameInput := r.PostFormValue("username") - PasswordInput := r.PostFormValue("password") - EmailInput := r.PostFormValue("email") - GenderInput := r.PostFormValue("gender") + usernameInput := r.PostFormValue("username") + passwordInput := r.PostFormValue("password") + emailInput := r.PostFormValue("email") + genderInput := r.PostFormValue("gender") - //debug outputs - //fmt.Println(UsernameInput) - //fmt.Println(PasswordInput) - //fmt.Println(EmailInput) - //fmt.Println(GenderInput) + // debug outputs + // fmt.Println(usernameInput) + // fmt.Println(passwordInput) + // fmt.Println(emailInput) + // fmt.Println(genderInput) + var gender int // Verify Gender value is either 0(female) or 1(male) // if not just silently send them a 400 because wtf - if GenderInput != "0" && GenderInput != "1" { - fmt.Println("Gender Input BAD") - w.WriteHeader(http.StatusBadRequest) - return + if gender, err = strconv.Atoi(genderInput); err != nil || gender > 1 { + logAndReturnError(w, invalidRequest) } - //Usernames must only be letters, numbers, dashes, and underscores + // Usernames must only be letters, numbers, dashes, and underscores var rxUsername = regexp.MustCompile("([\\w\\-]+)") - //Usernames must be under 16 characters - if len(UsernameInput) > 16 || !rxUsername.MatchString(UsernameInput) { + + // Usernames must be under 16 characters + if len(usernameInput) > 16 || !rxUsername.MatchString(usernameInput) { fmt.Println("ERROR: Username must only be alphanumeric and under 16 characters. \"-\" and \"_\" are also accepted.") fmt.Println(w, "ERROR: Username must only be alphanumeric and under 16 characters. \"-\" and \"_\" are also accepted.") return } - //Check if username is taken - if rowExists("Select id from maplestory.accounts where username=$1", UsernameInput) { - fmt.Println("ERROR: Username exists.") - fmt.Fprintf(w, "ERROR: Username exists.") + // Check if username is taken + if db.UserExists(usernameInput) { + logAndReturnError(w, usernameExists) return } - //Check if email is taken - if rowExists("Select id from maplestory.accounts where email=$1", EmailInput) { - + // Check if email is taken + if db.EmailTaken(emailInput) { + logAndReturnError(w, emailExists) return } - //Validates email addresses - err = checkmail.ValidateFormat(EmailInput) + // Validates email addresses + err = checkmail.ValidateFormat(emailInput) if err != nil { - fmt.Println("ERROR: that is not a valid email address!") - fmt.Fprintf(w, "ERROR: that is not a valid email address!") - //return - panic(err) - } - - fmt.Println("Passed checks, hashing password with bcrypt...") - hashedPassword, err := bcrypt.GenerateFromPassword([]byte(PasswordInput), 16) - - if _, err = db.Query("INSERT INTO maplestory.accounts (username,password,email,creation,last_login,last_ip,ban,admin,gender) values ($1, $2, $3, $4, $5, $6, $7, $8, $9)", - UsernameInput, string(hashedPassword), EmailInput, time.Now(), time.Now(), ip, 0, 0, GenderInput); err != nil { - w.WriteHeader(http.StatusInternalServerError) - panic(err) + logAndReturnError(w, badEmail) return - } else { - fmt.Println("Success!") - http.Redirect(w, r, "/?regsuccess=true", 301) } + + err = db.RegisterNewUser(usernameInput, passwordInput, r.RemoteAddr, genderInput, false) + if err != nil { + logAndReturnError(w, internalErr) + return + } + + fmt.Println("Success!") + http.Redirect(w, r, "/?regsuccess=true", 301) } -func Login(w http.ResponseWriter, r *http.Request) { - UsernameInput := r.PostFormValue("username") - PasswordInput := r.PostFormValue("password") +func newLogin(w http.ResponseWriter, r *http.Request) { + var u *db.User + var err error - // Get the existing entry present in the database for the given username - result := db.QueryRow("SELECT password FROM maplestory.accounts WHERE username=$1", UsernameInput) + usernameInput := r.PostFormValue("username") + passwordInput := r.PostFormValue("password") - // We create another instance of `Credentials` to store the credentials we get from the database - storedCreds := &Credentials{} - // Store the obtained password in `storedCreds` - err := result.Scan(&storedCreds.Password) - if err != nil { - // If an entry with the username does not exist, send an "Unauthorized"(401) status - if err == sql.ErrNoRows { - fmt.Println("Login failed!") - fmt.Fprintf(w, "Login failed!") - return - } - // If the error is of any other type, send a 500 status - w.WriteHeader(http.StatusInternalServerError) - return + if u, err = db.AttemptWebLogin(usernameInput, passwordInput, r.RemoteAddr, r.UserAgent()); err != nil { + logAndReturnError(w, badCreds) } - // Compare the stored hashed password, with the hashed version of the password that was received - if err = bcrypt.CompareHashAndPassword([]byte(storedCreds.Password), []byte(PasswordInput)); err != nil { - // If the two passwords don't match, return a 401 status - w.WriteHeader(http.StatusUnauthorized) - } - - // If we reach this point, that means the users password was correct, and that they are authorized - // The default 200 status is sent - fmt.Println("Authentication successful, setting session...") - //sets a 2 hour long session and cookie + // sets a 2 hour long session and cookie sessionToken := uuid.NewV4().String() - _, err = cache.Do("SETEX", sessionToken, "7200", UsernameInput) + _, err = cache.Do("SETEX", sessionToken, "7200", usernameInput) if err != nil { w.WriteHeader(http.StatusInternalServerError) fmt.Println("Session management failed!") @@ -214,6 +224,6 @@ func Login(w http.ResponseWriter, r *http.Request) { return } - fmt.Println("Login successful!") - fmt.Fprintf(w, "Login successful!") + fmt.Println("newLogin successful!") + fprinter(w, "newLogin successful!") } diff --git a/pgsql/setup.go b/pgsql/setup.go deleted file mode 100644 index 424b061..0000000 --- a/pgsql/setup.go +++ /dev/null @@ -1,65 +0,0 @@ -package main - -import ( - "os" - - "github.com/jmoiron/sqlx" -) - -var accountsSchema = `create table accounts -( - id serial - constraint accounts_pkey - primary key, - username varchar(13) not null, - password varchar not null, - creation date not null, - last_login date not null, - last_ip varchar not null, - ban integer not null, - admin integer not null, - gender smallint default 0 not null -);` - -var authlog *SecurityLog - -func install(db *sqlx.DB) { - if _, err := db.Exec(accountsSchema); err != nil { - panic(err) - } - println("accounts table installed!") -} - -func argParse() { - var kayos *User - var err error - for _, arg := range os.Args { - if arg == "--install" { - println("installing...") - install(db) - return - } - if arg == "--regkayos" { - println("registering kayos...") - if err := RegisterNewUser("kayos", "yeet", "127.0.0.1", 0, true); err != nil { - println("REG_TEST_FAIL: " + err.Error()) - return - } - println("kayos registered!") - return - } - if arg == "--loginkayos" { - println("logging in as kayos...") - if kayos, err = AttemptWebLogin( - "kayos", - "yeet", - "192.168.69.5", - "yeetBrowser 420"); err != nil { - println("FAIL: " + err.Error()) - return - } - println("logged in as kayos!") - println(kayos.Password) - } - } -} diff --git a/tests/registration.json b/tests/registration.json deleted file mode 100644 index 100a52b..0000000 --- a/tests/registration.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "username": "paul", - "password": "lolbad", - "email": "email@definitely.real", - "gender": 1 -} diff --git a/utils.go b/utils.go deleted file mode 100644 index 4dc7be2..0000000 --- a/utils.go +++ /dev/null @@ -1,44 +0,0 @@ -package main - -import ( - "bytes" - "compress/gzip" - "encoding/base64" - "io/ioutil" -) - -const banner = "H4sIAAAAAAACA81WzYrDIBC+5xV6KXmAINhQy75ErtJbyDXs+992NVU/dWZMumxooRCcic73M2O6y/Nx/7qp1Rjz+zi6x/ny1H7t6n4hYYlxl6lW93dRrWKqz/5bBIqBGjASqzi+oQJQKUOtHbG7QRYoDk4gAaBWpXwTa4cVog5qkwQcOJa21WHJqNFnOQUhB0KiXDE4Z8H0whtOyt6XyApkWGSDOPvhN5lSVl7PVjbp0CFWIIqaTmu8QVDI9BvuHAsctxZJ2OYqyjkjB4le4komeJTfQFGwiqJCmwWTNiFLEumaKuuB9H2uDem9b6pdw6DR9yxHC4GxduTMgC17a6p7y1KbiYUVKnD0eRH1SNhVv9Sb6sUMZCnaUAPC+WX21d0yuDyspHlUbZvAoC23JRg1DOjqLEyUD94NNhMJ/QX3lV/rs7XCMQeoF2UWW75UPmRN9Q7WHJhnglZoVhFsEhMmWnk9EnNYxx5PZ9Pi4Onl15aixtzWm5Y3DQJ/PcaLrSfHctn4mYXJvUlOgVWu+GLM2jYIOKrn75T8i+HItw2hAv1BQcDkhtrbxbCRs0CW9++829MfBxoMKCsKbmx57X9Adj8mnnxLAQ4AAA==" - -func qDeflate(data []byte) []byte { - var ( - gz *gzip.Reader - out []byte - err error - ) - r := bytes.NewReader(data) - if gz, err = gzip.NewReader(r); err != nil { - panic(err) - } - if out, err = ioutil.ReadAll(gz); err != nil { - return nil - } - return out -} - -func b64d(data string) []byte { - var ( - ret []byte - err error - ) - if ret, err = base64.StdEncoding.DecodeString(data); err != nil { - panic(err) - } - return ret -} - -// Splash displays banner -func Splash() { - print(qDeflate(b64d(banner))) - println("------------- v0.2 - kayos - ra - queed squad -------------") - println("") -} diff --git a/web/router.go b/web/router.go new file mode 100644 index 0000000..ac6c6f7 --- /dev/null +++ b/web/router.go @@ -0,0 +1,40 @@ +package web + +import ( + "fmt" + + "github.com/rs/zerolog/log" + "github.com/savsgio/atreugo/v11" + + config "mapyweb/config" +) + +func main() { + webconf := atreugo.Config{ + Addr: fmt.Sprintf("%s:%s", config.HTTPBindAddr, config.HTTPBindPort), + TLSEnable: true, + CertKey: config.SSLKey, + CertFile: config.SSLCert, + Name: "MapyWeb", + Debug: config.Debug, + Compress: true, + + + } + + server := atreugo.New(webconf) + server.GET("/", func(ctx *atreugo.RequestCtx) error { + return ctx.TextResponse("Hello World") + }) + + server.GET("/echo/{path:*}", func(ctx *atreugo.RequestCtx) error { + return ctx.TextResponse("Echo message: " + ctx.UserValue("path").(string)) + }) + + v1 := server.NewGroupPath("/v1") + v1.GET("/", func(ctx *atreugo.RequestCtx) error { + return ctx.TextResponse("Hello V1 Group") + }) + + log.Error().Str("caller", "http").Err(server.ListenAndServe()).Msg("failed to listen") +}