Compare commits
3 Commits
pongo-temp
...
50187f5a34
| Author | SHA1 | Date | |
|---|---|---|---|
| 50187f5a34 | |||
| dd671d561c | |||
| 7512d75a4d |
@ -16,6 +16,11 @@
|
||||
- [Password Hashing (bcrypt)](https://gowebexamples.com/password-hashing/)
|
||||
|
||||
|
||||
## Tools
|
||||
|
||||
- [Bombardier benchmarking](https://github.com/codesenberg/bombardier)
|
||||
|
||||
|
||||
## Bombardier benchmark
|
||||
|
||||
Pandora - Jet templating engine
|
||||
@ -56,3 +61,5 @@ Statistics Avg Stdev Max
|
||||
others - 0
|
||||
Throughput: 19.91MB/s
|
||||
```
|
||||
|
||||
|
||||
|
||||
1
app/lib/auth/auth.go
Normal file
1
app/lib/auth/auth.go
Normal file
@ -0,0 +1 @@
|
||||
package auth
|
||||
41
app/lib/auth/passwords.go
Normal file
41
app/lib/auth/passwords.go
Normal file
@ -0,0 +1,41 @@
|
||||
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
|
||||
}
|
||||
@ -1,4 +1,4 @@
|
||||
package common
|
||||
package cfg
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
@ -1,7 +1,9 @@
|
||||
package common
|
||||
package db
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"iris-test/app/lib/cfg"
|
||||
"iris-test/app/lib/logging"
|
||||
"iris-test/app/repository"
|
||||
"strconv"
|
||||
"strings"
|
||||
@ -20,18 +22,18 @@ var DBConn *gorm.DB
|
||||
func InitDB() *gorm.DB {
|
||||
var connectionString = strings.Join([]string{
|
||||
"postgres://",
|
||||
Config.Database.Username, ":",
|
||||
Config.Database.Password, "@",
|
||||
Config.Database.Host, ":",
|
||||
Config.Database.Port, "/",
|
||||
Config.Database.Name,
|
||||
cfg.Config.Database.Username, ":",
|
||||
cfg.Config.Database.Password, "@",
|
||||
cfg.Config.Database.Host, ":",
|
||||
cfg.Config.Database.Port, "/",
|
||||
cfg.Config.Database.Name,
|
||||
"?sslmode=disable",
|
||||
"&TimeZone=UTC",
|
||||
"&connect_timeout=", strconv.Itoa(DB_CONNECTION_TIMEOUT),
|
||||
}, "")
|
||||
|
||||
var logLevel = gormLogger.Silent
|
||||
if Config.Application.DebugSQL {
|
||||
if cfg.Config.Application.DebugSQL {
|
||||
logLevel = gormLogger.Info
|
||||
}
|
||||
|
||||
@ -41,7 +43,7 @@ func InitDB() *gorm.DB {
|
||||
})
|
||||
if err != nil {
|
||||
msg := fmt.Sprintf("Error connecting to database: %s. Terminating!", err)
|
||||
Log.Error(msg)
|
||||
logging.Error(msg)
|
||||
panic(msg)
|
||||
}
|
||||
|
||||
@ -1,9 +1,10 @@
|
||||
package common
|
||||
package logging
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"iris-test/app/lib/cfg"
|
||||
"os"
|
||||
|
||||
"github.com/sirupsen/logrus"
|
||||
@ -28,9 +29,9 @@ func Warn(message string) {
|
||||
}
|
||||
|
||||
func InitLogging() {
|
||||
logLevel, err := logrus.ParseLevel(Config.Application.LogLevel)
|
||||
logLevel, err := logrus.ParseLevel(cfg.Config.Application.LogLevel)
|
||||
if err != nil {
|
||||
panic(fmt.Sprintf("Invalid configured logLevel: %s\n", Config.Application.LogLevel))
|
||||
panic(fmt.Sprintf("Invalid configured logLevel: %s\n", cfg.Config.Application.LogLevel))
|
||||
}
|
||||
|
||||
Log.SetLevel(logLevel)
|
||||
@ -42,14 +43,14 @@ func InitLogging() {
|
||||
DisableQuote: true,
|
||||
})
|
||||
|
||||
LogFile := Config.Application.LogFile
|
||||
LogFile := cfg.Config.Application.LogFile
|
||||
file, err := os.OpenFile(
|
||||
LogFile,
|
||||
os.O_CREATE|os.O_WRONLY|os.O_APPEND,
|
||||
0655,
|
||||
)
|
||||
if err != nil {
|
||||
msg := fmt.Sprintf("Failed to log to file %s: %s", Config.Application.LogFile, err)
|
||||
msg := fmt.Sprintf("Failed to log to file %s: %s", cfg.Config.Application.LogFile, err)
|
||||
Log.Warning(msg)
|
||||
panic(msg)
|
||||
}
|
||||
@ -57,7 +58,7 @@ func InitLogging() {
|
||||
mw := io.MultiWriter(os.Stdout, file)
|
||||
Log.SetOutput(mw)
|
||||
|
||||
configJson, err := json.Marshal(Config)
|
||||
configJson, err := json.Marshal(cfg.Config)
|
||||
if err == nil {
|
||||
Info(fmt.Sprintf("Using config: %s", configJson))
|
||||
}
|
||||
24
app/main.go
24
app/main.go
@ -2,7 +2,9 @@ package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"iris-test/app/common"
|
||||
"iris-test/app/lib/cfg"
|
||||
"iris-test/app/lib/db"
|
||||
"iris-test/app/lib/logging"
|
||||
"iris-test/app/views"
|
||||
"os"
|
||||
"time"
|
||||
@ -16,17 +18,17 @@ import (
|
||||
var redisDB *redis.Database
|
||||
|
||||
func createSessionEngine() *sessions.Sessions {
|
||||
redisAddr := fmt.Sprintf("%s:%d", common.Config.Redis.Host, common.Config.Redis.Port)
|
||||
redisAddr := fmt.Sprintf("%s:%d", cfg.Config.Redis.Host, cfg.Config.Redis.Port)
|
||||
|
||||
redisDB = redis.New(redis.Config{
|
||||
Network: "tcp",
|
||||
Addr: redisAddr,
|
||||
Timeout: time.Duration(30) * time.Second,
|
||||
MaxActive: 10,
|
||||
Username: common.Config.Redis.Username,
|
||||
Password: common.Config.Redis.Password,
|
||||
Database: common.Config.Redis.Database,
|
||||
Prefix: common.Config.Redis.Prefix,
|
||||
Username: cfg.Config.Redis.Username,
|
||||
Password: cfg.Config.Redis.Password,
|
||||
Database: cfg.Config.Redis.Database,
|
||||
Prefix: cfg.Config.Redis.Prefix,
|
||||
Driver: redis.GoRedis(), // defaults to this driver.
|
||||
// To set a custom, existing go-redis client, use the "SetClient" method:
|
||||
// Driver: redis.GoRedis().SetClient(customGoRedisClient)
|
||||
@ -84,18 +86,18 @@ func createApp() *iris.Application {
|
||||
accessLog := createAccessLog()
|
||||
|
||||
app := iris.New()
|
||||
app.Logger().SetLevel(common.Config.Application.LogLevel)
|
||||
app.Logger().SetLevel(cfg.Config.Application.LogLevel)
|
||||
app.Use(sessionsEngine.Handler())
|
||||
app.UseRouter(accessLog.Handler)
|
||||
app.RegisterView(iris.Django("./app/templates", ".html").Reload(true))
|
||||
app.RegisterView(iris.Jet("./app/templates", ".jet").Reload(true))
|
||||
views.CreateRouter(app)
|
||||
return app
|
||||
}
|
||||
|
||||
func main() {
|
||||
common.InitCfg()
|
||||
common.InitLogging()
|
||||
common.InitDB()
|
||||
cfg.InitCfg()
|
||||
logging.InitLogging()
|
||||
db.InitDB()
|
||||
|
||||
app := createApp()
|
||||
defer redisDB.Close()
|
||||
|
||||
@ -1,6 +1,8 @@
|
||||
package models
|
||||
|
||||
import (
|
||||
"iris-test/app/lib/auth"
|
||||
"iris-test/app/lib/cfg"
|
||||
"time"
|
||||
)
|
||||
|
||||
@ -19,8 +21,12 @@ func (u *User) TableName() string {
|
||||
return "users"
|
||||
}
|
||||
|
||||
// func (u *User) SetPassword(password string) (string, error) {
|
||||
// secretKey := Config.Application.SecretKey
|
||||
// bytes, err := bcrypt.GenerateFromPassword([]byte(password), 14)
|
||||
// return string(bytes), err
|
||||
// }
|
||||
func (u *User) SetPassword(password string) error {
|
||||
secretKey := cfg.Config.Application.SecretKey
|
||||
hashedPassword, err := auth.HashPassword(password, secretKey)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
u.Password = hashedPassword
|
||||
return nil
|
||||
}
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
{% set title = "Hello world" %}
|
||||
{{ title := "Hello world" }}
|
||||
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
@ -26,7 +26,7 @@
|
||||
</ul>
|
||||
|
||||
<main>
|
||||
{% block mainContent %}{% endblock %}
|
||||
{{ yield mainContent() }}
|
||||
</main>
|
||||
</div>
|
||||
</body>
|
||||
@ -1,4 +1,4 @@
|
||||
{% macro usersTable(users) %}
|
||||
{{ block usersTable(users) }}
|
||||
<table class="table table-hover">
|
||||
<thead>
|
||||
<tr>
|
||||
@ -9,16 +9,16 @@
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for user in users %}
|
||||
{{ range users }}
|
||||
<tr>
|
||||
<td>
|
||||
<a href="/users/{{ user.Id }}">{{ user.Id }}</a>
|
||||
<a href="/users/{{ .Id }}">{{ .Id }}</a>
|
||||
</td>
|
||||
<td>{{ user.FirstName }}</td>
|
||||
<td>{{ user.LastName }}</td>
|
||||
<td>{{ user.Email }}</td>
|
||||
<td>{{ .FirstName }}</td>
|
||||
<td>{{ .LastName }}</td>
|
||||
<td>{{ .Email }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
{{ end }}
|
||||
</tbody>
|
||||
</table>
|
||||
{% endmacro %}
|
||||
{{ end }}
|
||||
@ -1,28 +0,0 @@
|
||||
{% 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 %}
|
||||
@ -1,5 +1,5 @@
|
||||
{{ extends "/base/base.html" }}
|
||||
{{ import "/components/table_component.html" }}
|
||||
{{ extends "/base/base.jet" }}
|
||||
{{ import "/components/table_component.jet" }}
|
||||
|
||||
|
||||
{{ block mainContent() }}
|
||||
@ -20,6 +20,10 @@
|
||||
<label class="form-label">Email</label>
|
||||
<input type="email" name="email" class="form-control" value="{{ user.Email }}" required>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label class="form-label">Password</label>
|
||||
<input type="text" name="password" class="form-control">
|
||||
</div>
|
||||
|
||||
<div class="d-flex">
|
||||
<a href="/users" class="btn btn-outline-secondary ms-auto me-2">
|
||||
|
||||
@ -7,7 +7,7 @@ import (
|
||||
)
|
||||
|
||||
func GetIndexPage(ctx iris.Context) {
|
||||
if err := ctx.View("pages/index.html"); err != nil {
|
||||
if err := ctx.View("pages/index.jet"); err != nil {
|
||||
showError(ctx, err)
|
||||
return
|
||||
}
|
||||
|
||||
@ -1,6 +1,8 @@
|
||||
package views
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"iris-test/app/lib/auth"
|
||||
"iris-test/app/repository"
|
||||
|
||||
"github.com/kataras/iris/v12"
|
||||
@ -10,6 +12,7 @@ type editUserForm struct {
|
||||
FirstName string `form:"first-name"`
|
||||
LastName string `form:"last-name"`
|
||||
Email string `form:"email"`
|
||||
Password string `form:"password"`
|
||||
}
|
||||
|
||||
func GetUsersPage(ctx iris.Context) {
|
||||
@ -66,6 +69,13 @@ func SaveUserPage(ctx iris.Context) {
|
||||
user.LastName = form.LastName
|
||||
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)
|
||||
|
||||
ctx.Redirect("/users")
|
||||
|
||||
1
go.mod
1
go.mod
@ -23,7 +23,6 @@ require (
|
||||
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
|
||||
github.com/fatih/structs v1.1.0 // 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/gomarkdown/markdown v0.0.0-20230922112808-5421fefb8386 // indirect
|
||||
github.com/google/uuid v1.3.1 // indirect
|
||||
|
||||
2
go.sum
2
go.sum
@ -30,8 +30,6 @@ github.com/fatih/structs v1.1.0 h1:Q7juDM0QtcnhCpeyLGQKyg4TOIghuNXrkL32pHAUMxo=
|
||||
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/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/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8=
|
||||
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
|
||||
|
||||
Reference in New Issue
Block a user