Compare commits

..

2 Commits

Author SHA1 Message Date
6a7584d628 Merge branch 'main' into pongo-templating 2023-10-26 13:28:20 +02:00
e92a56c393 Initial 2023-10-26 13:25:10 +02:00
17 changed files with 75 additions and 118 deletions

View File

@ -16,11 +16,6 @@
- [Password Hashing (bcrypt)](https://gowebexamples.com/password-hashing/) - [Password Hashing (bcrypt)](https://gowebexamples.com/password-hashing/)
## Tools
- [Bombardier benchmarking](https://github.com/codesenberg/bombardier)
## Bombardier benchmark ## Bombardier benchmark
Pandora - Jet templating engine Pandora - Jet templating engine
@ -61,5 +56,3 @@ Statistics Avg Stdev Max
others - 0 others - 0
Throughput: 19.91MB/s Throughput: 19.91MB/s
``` ```

View File

@ -1,4 +1,4 @@
package cfg package common
import ( import (
"fmt" "fmt"

View File

@ -1,9 +1,7 @@
package db package common
import ( import (
"fmt" "fmt"
"iris-test/app/lib/cfg"
"iris-test/app/lib/logging"
"iris-test/app/repository" "iris-test/app/repository"
"strconv" "strconv"
"strings" "strings"
@ -22,18 +20,18 @@ var DBConn *gorm.DB
func InitDB() *gorm.DB { func InitDB() *gorm.DB {
var connectionString = strings.Join([]string{ var connectionString = strings.Join([]string{
"postgres://", "postgres://",
cfg.Config.Database.Username, ":", Config.Database.Username, ":",
cfg.Config.Database.Password, "@", Config.Database.Password, "@",
cfg.Config.Database.Host, ":", Config.Database.Host, ":",
cfg.Config.Database.Port, "/", Config.Database.Port, "/",
cfg.Config.Database.Name, Config.Database.Name,
"?sslmode=disable", "?sslmode=disable",
"&TimeZone=UTC", "&TimeZone=UTC",
"&connect_timeout=", strconv.Itoa(DB_CONNECTION_TIMEOUT), "&connect_timeout=", strconv.Itoa(DB_CONNECTION_TIMEOUT),
}, "") }, "")
var logLevel = gormLogger.Silent var logLevel = gormLogger.Silent
if cfg.Config.Application.DebugSQL { if Config.Application.DebugSQL {
logLevel = gormLogger.Info logLevel = gormLogger.Info
} }
@ -43,7 +41,7 @@ func InitDB() *gorm.DB {
}) })
if err != nil { if err != nil {
msg := fmt.Sprintf("Error connecting to database: %s. Terminating!", err) msg := fmt.Sprintf("Error connecting to database: %s. Terminating!", err)
logging.Error(msg) Log.Error(msg)
panic(msg) panic(msg)
} }

View File

@ -1,10 +1,9 @@
package logging package common
import ( import (
"encoding/json" "encoding/json"
"fmt" "fmt"
"io" "io"
"iris-test/app/lib/cfg"
"os" "os"
"github.com/sirupsen/logrus" "github.com/sirupsen/logrus"
@ -29,9 +28,9 @@ func Warn(message string) {
} }
func InitLogging() { func InitLogging() {
logLevel, err := logrus.ParseLevel(cfg.Config.Application.LogLevel) logLevel, err := logrus.ParseLevel(Config.Application.LogLevel)
if err != nil { if err != nil {
panic(fmt.Sprintf("Invalid configured logLevel: %s\n", cfg.Config.Application.LogLevel)) panic(fmt.Sprintf("Invalid configured logLevel: %s\n", Config.Application.LogLevel))
} }
Log.SetLevel(logLevel) Log.SetLevel(logLevel)
@ -43,14 +42,14 @@ func InitLogging() {
DisableQuote: true, DisableQuote: true,
}) })
LogFile := cfg.Config.Application.LogFile LogFile := Config.Application.LogFile
file, err := os.OpenFile( file, err := os.OpenFile(
LogFile, LogFile,
os.O_CREATE|os.O_WRONLY|os.O_APPEND, os.O_CREATE|os.O_WRONLY|os.O_APPEND,
0655, 0655,
) )
if err != nil { if err != nil {
msg := fmt.Sprintf("Failed to log to file %s: %s", cfg.Config.Application.LogFile, err) msg := fmt.Sprintf("Failed to log to file %s: %s", Config.Application.LogFile, err)
Log.Warning(msg) Log.Warning(msg)
panic(msg) panic(msg)
} }
@ -58,7 +57,7 @@ func InitLogging() {
mw := io.MultiWriter(os.Stdout, file) mw := io.MultiWriter(os.Stdout, file)
Log.SetOutput(mw) Log.SetOutput(mw)
configJson, err := json.Marshal(cfg.Config) configJson, err := json.Marshal(Config)
if err == nil { if err == nil {
Info(fmt.Sprintf("Using config: %s", configJson)) Info(fmt.Sprintf("Using config: %s", configJson))
} }

View File

@ -1 +0,0 @@
package auth

View File

@ -1,41 +0,0 @@
package auth
import (
"regexp"
"golang.org/x/crypto/bcrypt"
)
// about bcrypt cost: https://docs.laminas.dev/laminas-crypt/password/#bcrypt
// bcrypt cost benchmarks: https://github.com/nsmithuk/bcrypt-cost-go
const BCRYPT_COST = 10
const MIN_PASSWORD_LENGTH = 10
func IsPasswordGoodEnough(password string) bool {
var re *regexp.Regexp
passwordBytes := []byte(password)
if len(password) < MIN_PASSWORD_LENGTH {
return false
}
re, _ = regexp.Compile("[a-z]")
if re.Find(passwordBytes) == nil {
return false
}
re, _ = regexp.Compile("[A-Z]")
if re.Find(passwordBytes) == nil {
return false
}
re, _ = regexp.Compile("[0-9]")
//lint:ignore S1008 allow early exit instead optimization
if re.Find(passwordBytes) == nil {
return false
}
return true
}
func HashPassword(password string, secretKey string) (string, error) {
bytes, err := bcrypt.GenerateFromPassword([]byte(password+secretKey), BCRYPT_COST)
return string(bytes), err
}

View File

@ -2,9 +2,7 @@ package main
import ( import (
"fmt" "fmt"
"iris-test/app/lib/cfg" "iris-test/app/common"
"iris-test/app/lib/db"
"iris-test/app/lib/logging"
"iris-test/app/views" "iris-test/app/views"
"os" "os"
"time" "time"
@ -18,17 +16,17 @@ import (
var redisDB *redis.Database var redisDB *redis.Database
func createSessionEngine() *sessions.Sessions { func createSessionEngine() *sessions.Sessions {
redisAddr := fmt.Sprintf("%s:%d", cfg.Config.Redis.Host, cfg.Config.Redis.Port) redisAddr := fmt.Sprintf("%s:%d", common.Config.Redis.Host, common.Config.Redis.Port)
redisDB = redis.New(redis.Config{ redisDB = redis.New(redis.Config{
Network: "tcp", Network: "tcp",
Addr: redisAddr, Addr: redisAddr,
Timeout: time.Duration(30) * time.Second, Timeout: time.Duration(30) * time.Second,
MaxActive: 10, MaxActive: 10,
Username: cfg.Config.Redis.Username, Username: common.Config.Redis.Username,
Password: cfg.Config.Redis.Password, Password: common.Config.Redis.Password,
Database: cfg.Config.Redis.Database, Database: common.Config.Redis.Database,
Prefix: cfg.Config.Redis.Prefix, Prefix: common.Config.Redis.Prefix,
Driver: redis.GoRedis(), // defaults to this driver. Driver: redis.GoRedis(), // defaults to this driver.
// To set a custom, existing go-redis client, use the "SetClient" method: // To set a custom, existing go-redis client, use the "SetClient" method:
// Driver: redis.GoRedis().SetClient(customGoRedisClient) // Driver: redis.GoRedis().SetClient(customGoRedisClient)
@ -86,18 +84,18 @@ func createApp() *iris.Application {
accessLog := createAccessLog() accessLog := createAccessLog()
app := iris.New() app := iris.New()
app.Logger().SetLevel(cfg.Config.Application.LogLevel) app.Logger().SetLevel(common.Config.Application.LogLevel)
app.Use(sessionsEngine.Handler()) app.Use(sessionsEngine.Handler())
app.UseRouter(accessLog.Handler) app.UseRouter(accessLog.Handler)
app.RegisterView(iris.Jet("./app/templates", ".jet").Reload(true)) app.RegisterView(iris.Django("./app/templates", ".html").Reload(true))
views.CreateRouter(app) views.CreateRouter(app)
return app return app
} }
func main() { func main() {
cfg.InitCfg() common.InitCfg()
logging.InitLogging() common.InitLogging()
db.InitDB() common.InitDB()
app := createApp() app := createApp()
defer redisDB.Close() defer redisDB.Close()

View File

@ -1,8 +1,6 @@
package models package models
import ( import (
"iris-test/app/lib/auth"
"iris-test/app/lib/cfg"
"time" "time"
) )
@ -21,12 +19,8 @@ func (u *User) TableName() string {
return "users" return "users"
} }
func (u *User) SetPassword(password string) error { // func (u *User) SetPassword(password string) (string, error) {
secretKey := cfg.Config.Application.SecretKey // secretKey := Config.Application.SecretKey
hashedPassword, err := auth.HashPassword(password, secretKey) // bytes, err := bcrypt.GenerateFromPassword([]byte(password), 14)
if err != nil { // return string(bytes), err
return err // }
}
u.Password = hashedPassword
return nil
}

View File

@ -1,4 +1,4 @@
{{ title := "Hello world" }} {% set title = "Hello world" %}
<!DOCTYPE html> <!DOCTYPE html>
<html lang="en"> <html lang="en">
@ -26,7 +26,7 @@
</ul> </ul>
<main> <main>
{{ yield mainContent() }} {% block mainContent %}{% endblock %}
</main> </main>
</div> </div>
</body> </body>

View File

@ -1,4 +1,4 @@
{{ block usersTable(users) }} {% macro usersTable(users) %}
<table class="table table-hover"> <table class="table table-hover">
<thead> <thead>
<tr> <tr>
@ -9,16 +9,16 @@
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
{{ range users }} {% for user in users %}
<tr> <tr>
<td> <td>
<a href="/users/{{ .Id }}">{{ .Id }}</a> <a href="/users/{{ user.Id }}">{{ user.Id }}</a>
</td> </td>
<td>{{ .FirstName }}</td> <td>{{ user.FirstName }}</td>
<td>{{ .LastName }}</td> <td>{{ user.LastName }}</td>
<td>{{ .Email }}</td> <td>{{ user.Email }}</td>
</tr> </tr>
{{ end }} {% endfor %}
</tbody> </tbody>
</table> </table>
{{ end }} {% endmacro %}

View File

@ -0,0 +1,28 @@
{% extends "/base/base.html" %}
{% block mainContent %}
<div class="row">
<form class="mb-5 col-4 ms-auto me-auto" method="post" action="/">
<div class="mb-3">
<label class="form-label">Email address</label>
<input type="email" name="email" class="form-control" value="edkirin@gmail.com">
</div>
<div class="mb-3">
<label class="form-label">Password</label>
<input type="text" name="password" class="form-control" value="tralala">
</div>
<button type="submit" class="btn btn-success">
Submit
</button>
</form>
</div>
<div class="lead">
<p>Bacon ipsum dolor amet leberkas kevin meatball pork loin beef ribs prosciutto, turducken bacon bresaola tri-tip. Strip steak flank shankle, sirloin short ribs shoulder meatball pork chop kevin ribeye jowl ham pork belly turducken jerky. Flank tongue short loin ham hock brisket turducken tail filet mignon cupim. Pork capicola buffalo kevin jowl chicken. Filet mignon brisket pig, landjaeger sausage cow fatback drumstick chicken buffalo tenderloin spare ribs.</p>
<p>Swine shankle porchetta pancetta. Buffalo chicken turducken ground round kevin shoulder, salami pig t-bone beef ribs tri-tip tongue pork belly doner. Landjaeger meatloaf short loin biltong. Alcatra tongue shankle, tri-tip pancetta porchetta tenderloin corned beef pastrami rump. Bresaola chislic beef kielbasa sausage, ball tip burgdoggen boudin capicola short loin tenderloin buffalo landjaeger.</p>
</div>
{% endblock %}

View File

@ -1,5 +1,5 @@
{{ extends "/base/base.jet" }} {{ extends "/base/base.html" }}
{{ import "/components/table_component.jet" }} {{ import "/components/table_component.html" }}
{{ block mainContent() }} {{ block mainContent() }}

View File

@ -20,10 +20,6 @@
<label class="form-label">Email</label> <label class="form-label">Email</label>
<input type="email" name="email" class="form-control" value="{{ user.Email }}" required> <input type="email" name="email" class="form-control" value="{{ user.Email }}" required>
</div> </div>
<div class="mb-3">
<label class="form-label">Password</label>
<input type="text" name="password" class="form-control">
</div>
<div class="d-flex"> <div class="d-flex">
<a href="/users" class="btn btn-outline-secondary ms-auto me-2"> <a href="/users" class="btn btn-outline-secondary ms-auto me-2">

View File

@ -7,7 +7,7 @@ import (
) )
func GetIndexPage(ctx iris.Context) { func GetIndexPage(ctx iris.Context) {
if err := ctx.View("pages/index.jet"); err != nil { if err := ctx.View("pages/index.html"); err != nil {
showError(ctx, err) showError(ctx, err)
return return
} }

View File

@ -1,8 +1,6 @@
package views package views
import ( import (
"fmt"
"iris-test/app/lib/auth"
"iris-test/app/repository" "iris-test/app/repository"
"github.com/kataras/iris/v12" "github.com/kataras/iris/v12"
@ -12,7 +10,6 @@ type editUserForm struct {
FirstName string `form:"first-name"` FirstName string `form:"first-name"`
LastName string `form:"last-name"` LastName string `form:"last-name"`
Email string `form:"email"` Email string `form:"email"`
Password string `form:"password"`
} }
func GetUsersPage(ctx iris.Context) { func GetUsersPage(ctx iris.Context) {
@ -69,13 +66,6 @@ func SaveUserPage(ctx iris.Context) {
user.LastName = form.LastName user.LastName = form.LastName
user.Email = form.Email user.Email = form.Email
if len(form.Password) > 0 {
user.SetPassword(form.Password)
fmt.Printf("Set password: %s\n", user.Password)
fmt.Printf("IsPasswordGoodEnough: %v\n", auth.IsPasswordGoodEnough(form.Password))
}
userRepository.Save(user) userRepository.Save(user)
ctx.Redirect("/users") ctx.Redirect("/users")

1
go.mod
View File

@ -23,6 +23,7 @@ require (
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
github.com/fatih/structs v1.1.0 // indirect github.com/fatih/structs v1.1.0 // indirect
github.com/flosch/pongo2/v4 v4.0.2 // indirect github.com/flosch/pongo2/v4 v4.0.2 // indirect
github.com/flosch/pongo2/v6 v6.0.0 // indirect
github.com/golang/snappy v0.0.4 // indirect github.com/golang/snappy v0.0.4 // indirect
github.com/gomarkdown/markdown v0.0.0-20230922112808-5421fefb8386 // indirect github.com/gomarkdown/markdown v0.0.0-20230922112808-5421fefb8386 // indirect
github.com/google/uuid v1.3.1 // indirect github.com/google/uuid v1.3.1 // indirect

2
go.sum
View File

@ -30,6 +30,8 @@ github.com/fatih/structs v1.1.0 h1:Q7juDM0QtcnhCpeyLGQKyg4TOIghuNXrkL32pHAUMxo=
github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M= github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M=
github.com/flosch/pongo2/v4 v4.0.2 h1:gv+5Pe3vaSVmiJvh/BZa82b7/00YUGm0PIyVVLop0Hw= github.com/flosch/pongo2/v4 v4.0.2 h1:gv+5Pe3vaSVmiJvh/BZa82b7/00YUGm0PIyVVLop0Hw=
github.com/flosch/pongo2/v4 v4.0.2/go.mod h1:B5ObFANs/36VwxxlgKpdchIJHMvHB562PW+BWPhwZD8= github.com/flosch/pongo2/v4 v4.0.2/go.mod h1:B5ObFANs/36VwxxlgKpdchIJHMvHB562PW+BWPhwZD8=
github.com/flosch/pongo2/v6 v6.0.0 h1:lsGru8IAzHgIAw6H2m4PCyleO58I40ow6apih0WprMU=
github.com/flosch/pongo2/v6 v6.0.0/go.mod h1:CuDpFm47R0uGGE7z13/tTlt1Y6zdxvr2RLT5LJhsHEU=
github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y= github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y=
github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8= github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8=
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=