From e5cd6e0be3a78093b9fdc24cf8439baf42771dfc Mon Sep 17 00:00:00 2001 From: Paul Date: Mon, 29 Jan 2018 09:18:19 +0100 Subject: [PATCH] Add User management --- assets/static/img/logo.png | Bin 11173 -> 0 bytes assets/static/img/logo.svg | 59 ++++++++ assets/templates/errors/403.gohtml | 21 +++ assets/templates/layouts/admin.gohtml | 4 +- assets/templates/layouts/application.gohtml | 19 ++- assets/templates/layouts/auth.gohtml | 56 ++++++++ assets/templates/shared/header.gohtml | 56 +++++--- assets/templates/views/cert_list.gohtml | 1 - assets/templates/views/forgot-password.gohtml | 24 ++++ assets/templates/views/login.gohtml | 55 +++++--- assets/templates/views/register.gohtml | 41 ++++++ handlers/auth.go | 80 +++++++++++ handlers/{gencert.go => cert.go} | 89 ++++++++---- handlers/handlers.go | 5 + handlers/login.go | 33 ----- main.go | 8 ++ middleware/requirelogin.go | 21 ++- models/models.go | 52 +++++-- router/router.go | 53 +++++-- services/db.go | 49 ++++++- services/sessions.go | 129 ++++++++++++++++-- views/funcs.go | 13 +- views/templates.go | 6 +- views/views.go | 11 +- 24 files changed, 721 insertions(+), 164 deletions(-) delete mode 100644 assets/static/img/logo.png create mode 100644 assets/static/img/logo.svg create mode 100644 assets/templates/errors/403.gohtml create mode 100644 assets/templates/layouts/auth.gohtml create mode 100644 assets/templates/views/forgot-password.gohtml create mode 100644 assets/templates/views/register.gohtml create mode 100644 handlers/auth.go rename handlers/{gencert.go => cert.go} (52%) delete mode 100644 handlers/login.go diff --git a/assets/static/img/logo.png b/assets/static/img/logo.png deleted file mode 100644 index a016c2b51a4fce1ed5615ede48ffa7c729c9b986..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 11173 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\h57X8Ll>zs4K|gYNCt_c&bJnXJC01QK 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 diff --git a/assets/static/img/logo.svg b/assets/static/img/logo.svg new file mode 100644 index 0000000..cd6e87b --- /dev/null +++ b/assets/static/img/logo.svg @@ -0,0 +1,59 @@ + +image/svg+xmlONEOFFONEOFFCreated with Sketch. \ No newline at end of file diff --git a/assets/templates/errors/403.gohtml b/assets/templates/errors/403.gohtml new file mode 100644 index 0000000..10b4e81 --- /dev/null +++ b/assets/templates/errors/403.gohtml @@ -0,0 +1,21 @@ +{{ define "meta" }} + Forbidden +{{ end }} + +{{ define "content" }} +
+
+
+
+
+

Forbidden

+

+ The received the request was not processed. + For example, the CSRF validation may have failed. +

+
+
+
+
+
+{{ end }} \ No newline at end of file diff --git a/assets/templates/layouts/admin.gohtml b/assets/templates/layouts/admin.gohtml index 3e00c6e..a6d2c64 100644 --- a/assets/templates/layouts/admin.gohtml +++ b/assets/templates/layouts/admin.gohtml @@ -6,11 +6,11 @@ - + Admin - + diff --git a/assets/templates/layouts/application.gohtml b/assets/templates/layouts/application.gohtml index abf3795..7a231d9 100644 --- a/assets/templates/layouts/application.gohtml +++ b/assets/templates/layouts/application.gohtml @@ -6,24 +6,29 @@ - + - - + + {{ template "meta" . }} {{ if eq .Meta.Env "production" }} {{ end }} + - {{ template "header" . }} + {{ if .flashes}}
{{range .flashes}} -
{{ .Message }}
+ {{ .Render }} {{end}}
+ {{ end }} + {{ template "header" . }}
{{ template "content" . }} @@ -32,8 +37,8 @@ {{ template "footer" . }} {{ template "sink" .}} - - + + diff --git a/assets/templates/layouts/auth.gohtml b/assets/templates/layouts/auth.gohtml new file mode 100644 index 0000000..8f2b65a --- /dev/null +++ b/assets/templates/layouts/auth.gohtml @@ -0,0 +1,56 @@ +{{ define "base" }} + + + + + + + + + + + + + {{ template "meta" . }} + {{ if eq .Meta.Env "production" }} + + {{ end }} + + + + +
+ {{ if .flashes}} +
+ {{range .flashes}} + {{ .Render }} + {{end}} +
+ {{ end }} +
+
+
+
+
+
+ +
+
+ {{ template "content" . }} +
+
+
+
+
+ + + + + + + +{{ end }} + +{{ define "meta"}}{{end}} \ No newline at end of file diff --git a/assets/templates/shared/header.gohtml b/assets/templates/shared/header.gohtml index d3cfbd9..5d3cbe7 100644 --- a/assets/templates/shared/header.gohtml +++ b/assets/templates/shared/header.gohtml @@ -1,25 +1,37 @@ {{ define "header" }} - +
+
+
+
+ + {{ end }} \ No newline at end of file diff --git a/assets/templates/views/cert_list.gohtml b/assets/templates/views/cert_list.gohtml index 3cd0983..f667653 100644 --- a/assets/templates/views/cert_list.gohtml +++ b/assets/templates/views/cert_list.gohtml @@ -1,6 +1,5 @@ {{ define "meta" }} Log in - {{ end}} {{ define "content" }} diff --git a/assets/templates/views/forgot-password.gohtml b/assets/templates/views/forgot-password.gohtml new file mode 100644 index 0000000..2996737 --- /dev/null +++ b/assets/templates/views/forgot-password.gohtml @@ -0,0 +1,24 @@ +{{ define "meta" }}Forgot Password{{ end }} + +{{ define "content" }} +
+
+

Reset Password

+
+
+ +
+ + + + +
+
+ {{ .csrfField }} +
+ +
+
+
+
+{{ end }} \ No newline at end of file diff --git a/assets/templates/views/login.gohtml b/assets/templates/views/login.gohtml index 5218dec..7dcd655 100644 --- a/assets/templates/views/login.gohtml +++ b/assets/templates/views/login.gohtml @@ -1,24 +1,41 @@ -{{ define "meta" }} - Log in - -{{ end}} +{{ define "meta" }}Log In{{ end }} {{ define "content" }} -
-
-
-
-
-

Hello, World!

-
- - - {{ .csrfField }} - -
+
+
+

Log In

+
+
+ +
+ + + +
-
+
+ +
+ + + + +
+
+ {{ .csrfField }} +
+ +
+ +
-
-{{ end}} +
+
+

+ Don't have an account? Sign Up +

+
+{{ end }} \ No newline at end of file diff --git a/assets/templates/views/register.gohtml b/assets/templates/views/register.gohtml new file mode 100644 index 0000000..44ee692 --- /dev/null +++ b/assets/templates/views/register.gohtml @@ -0,0 +1,41 @@ +{{ define "meta" }}Log In{{ end }} + +{{ define "content" }} +
+
+

Sign Up

+
+
+ +
+ + + + +
+
+
+ +
+ + + + +
+
+ {{ .csrfField }} +
+ +
+
+

By signing up you agree to the Terms of Service

+
+
+
+
+
+

+ Already have an account? Log In +

+
+{{ end }} \ No newline at end of file diff --git a/handlers/auth.go b/handlers/auth.go new file mode 100644 index 0000000..5968650 --- /dev/null +++ b/handlers/auth.go @@ -0,0 +1,80 @@ +package handlers + +import ( + "net/http" + + "git.klink.asia/paul/certman/services" + + "git.klink.asia/paul/certman/models" +) + +func RegisterHandler(w http.ResponseWriter, req *http.Request) { + // Get parameters + email := req.Form.Get("email") + password := req.Form.Get("password") + + user := models.User{} + user.Email = email + user.SetPassword(password) + + err := services.Database.Create(&user).Error + if err != nil { + panic(err.Error) + } + + services.SessionStore.Flash(w, req, + services.Flash{ + Type: "success", + Message: "The user was created. Check your inbox for the confirmation email.", + }, + ) + + http.Redirect(w, req, "/login", http.StatusFound) + return +} + +func LoginHandler(w http.ResponseWriter, req *http.Request) { + // Get parameters + email := req.Form.Get("email") + password := req.Form.Get("password") + + user := models.User{} + + err := services.Database.Where(&models.User{Email: email}).Find(&user).Error + if err != nil { + // could not find user + services.SessionStore.Flash( + w, req, services.Flash{ + Type: "warning", Message: "Invalid Email or Password.", + }, + ) + http.Redirect(w, req, "/login", http.StatusFound) + return + } + + if !user.EmailValid { + services.SessionStore.Flash( + w, req, services.Flash{ + Type: "warning", Message: "You need to confirm your email before logging in.", + }, + ) + http.Redirect(w, req, "/login", http.StatusFound) + return + } + + if err := user.CheckPassword(password); err != nil { + // wrong password + services.SessionStore.Flash( + w, req, services.Flash{ + Type: "warning", Message: "Invalid Email or Password.", + }, + ) + http.Redirect(w, req, "/login", http.StatusFound) + return + } + + // user is logged in, set cookie + services.SessionStore.SetUserEmail(w, req, email) + + http.Redirect(w, req, "/certs", http.StatusSeeOther) +} diff --git a/handlers/gencert.go b/handlers/cert.go similarity index 52% rename from handlers/gencert.go rename to handlers/cert.go index ac033a0..5f59ab6 100644 --- a/handlers/gencert.go +++ b/handlers/cert.go @@ -13,40 +13,75 @@ import ( "net/http" "time" - "git.klink.asia/paul/certman/views" + "git.klink.asia/paul/certman/models" + "git.klink.asia/paul/certman/services" - "github.com/jinzhu/gorm" + "git.klink.asia/paul/certman/views" ) -func ListCertHandler(db *gorm.DB) http.HandlerFunc { - return func(w http.ResponseWriter, req *http.Request) { - v := views.New(req) - v.Render(w, "cert_list") - } +func ListCertHandler(w http.ResponseWriter, req *http.Request) { + v := views.New(req) + v.Render(w, "cert_list") } -func GenCertHandler(db *gorm.DB) http.HandlerFunc { - return func(w http.ResponseWriter, req *http.Request) { - v := views.New(req) +func CreateCertHandler(w http.ResponseWriter, req *http.Request) { + email := services.SessionStore.GetUserEmail(req) + certname := req.FormValue("certname") - key, err := rsa.GenerateKey(rand.Reader, 2048) - if err != nil { - log.Fatalf("Could not generate keypair: %s", err) - } - - caCert, caKey, err := loadX509KeyPair("ca.crt", "ca.key") - if err != nil { - v.Render(w, "500") - log.Fatalf("error loading ca keyfiles: %s", err) - } - - derBytes, err := CreateCertificate(key, caCert, caKey) - pem.Encode(w, &pem.Block{Type: "CERTIFICATE", Bytes: derBytes}) - - pkBytes := x509.MarshalPKCS1PrivateKey(key) - pem.Encode(w, &pem.Block{Type: "RSA PRIVATE KEY", Bytes: pkBytes}) - return + user := models.User{} + err := services.Database.Where(&models.User{Email: email}).Find(&user).Error + if err != nil { + fmt.Printf("Could not fetch user for mail %s\n", email) } + + // Load CA master certificate + caCert, caKey, err := loadX509KeyPair("ca.crt", "ca.key") + if err != nil { + log.Fatalf("error loading ca keyfiles: %s", err) + panic(err.Error()) + } + + // Generate Keypair + key, err := rsa.GenerateKey(rand.Reader, 2048) + if err != nil { + log.Fatalf("Could not generate keypair: %s", err) + } + + // Generate Certificate + derBytes, err := CreateCertificate(key, caCert, caKey) + + // Initialize new client config + client := models.Client{ + Name: certname, + PrivateKey: x509.MarshalPKCS1PrivateKey(key), + Cert: derBytes, + UserID: user.ID, + } + + // Insert client into database + if err := services.Database.Create(&client).Error; err != nil { + panic(err.Error()) + } + + services.SessionStore.Flash(w, req, + services.Flash{ + Type: "success", + Message: "The certificate was created successfully.", + }, + ) + + http.Redirect(w, req, "/certs", http.StatusFound) +} + +func DownloadCertHandler(w http.ResponseWriter, req *http.Request) { + //v := views.New(req) + // + //derBytes, err := CreateCertificate(key, caCert, caKey) + //pem.Encode(w, &pem.Block{Type: "CERTIFICATE", Bytes: derBytes}) + // + //pkBytes := x509.MarshalPKCS1PrivateKey(key) + //pem.Encode(w, &pem.Block{Type: "RSA PRIVATE KEY", Bytes: pkBytes}) + return } func loadX509KeyPair(certFile, keyFile string) (*x509.Certificate, *rsa.PrivateKey, error) { diff --git a/handlers/handlers.go b/handlers/handlers.go index f02ea5a..d131713 100644 --- a/handlers/handlers.go +++ b/handlers/handlers.go @@ -15,3 +15,8 @@ func ErrorHandler(w http.ResponseWriter, req *http.Request) { view := views.New(req) view.RenderError(w, http.StatusInternalServerError) } + +func CSRFErrorHandler(w http.ResponseWriter, req *http.Request) { + view := views.New(req) + view.RenderError(w, http.StatusForbidden) +} diff --git a/handlers/login.go b/handlers/login.go deleted file mode 100644 index 25ebc88..0000000 --- a/handlers/login.go +++ /dev/null @@ -1,33 +0,0 @@ -package handlers - -import ( - "net/http" - - "git.klink.asia/paul/certman/models" - "github.com/jinzhu/gorm" -) - -func LoginHandler(db *gorm.DB) http.HandlerFunc { - return func(w http.ResponseWriter, req *http.Request) { - // Get parameters - username := req.Form.Get("username") - password := req.Form.Get("password") - - user := models.User{} - - err := db.Where(&models.User{Username: username}).Find(&user).Error - if err != nil { - // could not find user - http.Redirect(w, req, "/login", http.StatusFound) - } - - if err := user.CheckPassword(password); err != nil { - // wrong password - http.Redirect(w, req, "/login", http.StatusFound) - } - - // user is logged in - // set cookie - http.Redirect(w, req, "/certs", http.StatusFound) - } -} diff --git a/main.go b/main.go index 63f8475..15f0c37 100644 --- a/main.go +++ b/main.go @@ -18,6 +18,14 @@ func main() { // Connect to the database db := services.InitDB() + services.InitSession() + + //user := models.User{} + //user.Username = "test" + //user.SetPassword("test") + //fmt.Println(user.HashedPassword) + //fmt.Println(db.Create(&user).Error) + // load and parse template files views.LoadTemplates() diff --git a/middleware/requirelogin.go b/middleware/requirelogin.go index d8178c2..a1917a2 100644 --- a/middleware/requirelogin.go +++ b/middleware/requirelogin.go @@ -1,25 +1,20 @@ package middleware import ( - "log" "net/http" - "runtime/debug" - "git.klink.asia/paul/certman/handlers" + "git.klink.asia/paul/certman/services" ) +// RequireLogin is a middleware that checks for a username in the active +// session, and redirects to `/login` if no username was found. func RequireLogin(next http.Handler) http.Handler { - fn := func(w http.ResponseWriter, r *http.Request) { - defer func() { - if rvr := recover(); rvr != nil { - log.Println(rvr) - log.Println(string(debug.Stack())) - handlers.ErrorHandler(w, r) - } - }() + fn := func(w http.ResponseWriter, req *http.Request) { + if username := services.SessionStore.GetUserEmail(req); username == "" { + http.Redirect(w, req, "/login", http.StatusFound) + } - next.ServeHTTP(w, r) + next.ServeHTTP(w, req) } - return http.HandlerFunc(fn) } diff --git a/models/models.go b/models/models.go index 49a93cc..00764fe 100644 --- a/models/models.go +++ b/models/models.go @@ -2,8 +2,9 @@ package models import ( "errors" + "time" - "github.com/jinzhu/gorm" + "golang.org/x/crypto/bcrypt" ) var ( @@ -12,31 +13,64 @@ var ( ErrNotImplemented = errors.New("Not implemented") ) +// Model is a base model definition, including helpful fields for dealing with +// models in a database +type Model struct { + ID uint `gorm:"primary_key"` + CreatedAt time.Time + UpdatedAt time.Time + DeletedAt *time.Time `sql:"index"` +} + // User represents a User of the system which is able to log in type User struct { - gorm.Model - Username string + Model + Email string + EmailValid bool + DisplayName string HashedPassword []byte IsAdmin bool } // SetPassword sets the password of an user struct, but does not save it yet func (u *User) SetPassword(password string) error { - return ErrNotImplemented + bytes, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost) + if err != nil { + return err + } + u.HashedPassword = bytes + return nil } // CheckPassword compares a supplied plain text password with the internally // stored password hash, returns error=nil on success. func (u *User) CheckPassword(password string) error { - return ErrNotImplemented + return bcrypt.CompareHashAndPassword(u.HashedPassword, []byte(password)) } -// ClientConf represent the OpenVPN client configuration -type ClientConf struct { - gorm.Model +type UserProvider interface { + CountUsers() (uint, error) + CreateUser(*User) (*User, error) + ListUsers(count, offset int) ([]*User, error) + GetUserByID(id uint) (*User, error) + GetUserByEmail(email string) (*User, error) + DeleteUser(id uint) error +} + +// Client represent the OpenVPN client configuration +type Client struct { + Model Name string User User + UserID uint Cert []byte - PublicKey []byte PrivateKey []byte } + +type ClientProvider interface { + CountClients() (uint, error) + CreateClient(*User) (*User, error) + ListClients(count, offset int) ([]*User, error) + GetClientByID(id uint) (*User, error) + DeleteClient(id uint) error +} diff --git a/router/router.go b/router/router.go index 00a4c78..5883cbe 100644 --- a/router/router.go +++ b/router/router.go @@ -5,6 +5,8 @@ import ( "os" "strings" + "git.klink.asia/paul/certman/services" + "git.klink.asia/paul/certman/assets" "git.klink.asia/paul/certman/handlers" "git.klink.asia/paul/certman/views" @@ -27,13 +29,16 @@ var ( func HandleRoutes(db *gorm.DB) http.Handler { mux := chi.NewMux() - // mux.Use(middleware.RequestID) - mux.Use(middleware.Logger) - mux.Use(middleware.RealIP) - mux.Use(middleware.RedirectSlashes) - mux.Use(mw.Recoverer) + //mux.Use(middleware.RequestID) + mux.Use(middleware.Logger) // log requests + mux.Use(middleware.RealIP) // use proxy headers + mux.Use(middleware.RedirectSlashes) // redirect trailing slashes + mux.Use(mw.Recoverer) // recover on panic + mux.Use(services.SessionStore.Use) // use session storage // we are serving the static files directly from the assets package + // this either means we use the embedded files, or live-load + // from the file system (if `--tags="dev"` is used). fileServer(mux, "/static", assets.Assets) mux.Route("/", func(r chi.Router) { @@ -43,21 +48,35 @@ func HandleRoutes(db *gorm.DB) http.Handler { csrf.Secure(false), csrf.CookieName(csrfCookieName), csrf.FieldName(csrfFieldName), + csrf.ErrorHandler(http.HandlerFunc(handlers.CSRFErrorHandler)), )) } - r.HandleFunc("/", func(w http.ResponseWriter, req *http.Request) { - view := views.New(req) - view.Render(w, "debug") + r.HandleFunc("/", v("debug")) + + r.Route("/register", func(r chi.Router) { + r.Get("/", v("register")) + r.Post("/", handlers.RegisterHandler) }) - r.Get("/login", func(w http.ResponseWriter, req *http.Request) { - view := views.New(req) - view.Render(w, "login") + r.Route("/login", func(r chi.Router) { + r.Get("/", v("login")) + r.Post("/", handlers.LoginHandler) }) - r.Get("/certs", handlers.ListCertHandler(db)) - r.HandleFunc("/certs/new", handlers.GenCertHandler(db)) + //r.Post("/confirm-email/{token}", handlers.ConfirmEmailHandler(db)) + + r.Route("/forgot-password", func(r chi.Router) { + r.Get("/", v("forgot-password")) + r.Post("/", handlers.LoginHandler) + }) + + r.Route("/certs", func(r chi.Router) { + r.Use(mw.RequireLogin) + r.Get("/", handlers.ListCertHandler) + r.Post("/new", handlers.CreateCertHandler) + r.HandleFunc("/download/{ID}", handlers.DownloadCertHandler) + }) r.HandleFunc("/500", func(w http.ResponseWriter, req *http.Request) { panic("500") @@ -70,6 +89,14 @@ func HandleRoutes(db *gorm.DB) http.Handler { return mux } +// v is a helper function for quickly displaying a view +func v(template string) http.HandlerFunc { + return func(w http.ResponseWriter, req *http.Request) { + view := views.New(req) + view.Render(w, template) + } +} + // fileServer sets up a http.FileServer handler to serve // static files from a http.FileSystem. func fileServer(r chi.Router, path string, root http.FileSystem) { diff --git a/services/db.go b/services/db.go index f88e28d..f86af66 100644 --- a/services/db.go +++ b/services/db.go @@ -1,6 +1,7 @@ package services import ( + "errors" "log" "git.klink.asia/paul/certman/models" @@ -8,7 +9,17 @@ import ( "github.com/jinzhu/gorm" ) -var DB *gorm.DB +// Error Definitions +var ( + ErrNotImplemented = errors.New("Not implemented") +) + +var Database *gorm.DB + +// DB is a wrapper around gorm.DB to provide custom methods +type DB struct { + *gorm.DB +} func InitDB() *gorm.DB { dsn := settings.Get("DATABASE_URL", "db.sqlite3") @@ -20,7 +31,41 @@ func InitDB() *gorm.DB { } // Migrate models - db.AutoMigrate(models.User{}, models.ClientConf{}) + db.AutoMigrate(models.User{}, models.Client{}) + db.LogMode(true) + Database = db return db } + +// CountUsers returns the number of Users in the datastore +func (db *DB) CountUsers() (uint, error) { + return 0, ErrNotImplemented +} + +// CreateUser inserts a user into the datastore +func (db *DB) CreateUser(*models.User) (*models.User, error) { + return nil, ErrNotImplemented +} + +// ListUsers returns a slice of 'count' users, starting at 'offset' +func (db *DB) ListUsers(count, offset int) ([]*models.User, error) { + var users = make([]*models.User, 0) + + return users, ErrNotImplemented +} + +// GetUserByID returns a single user by ID +func (db *DB) GetUserByID(id uint) (*models.User, error) { + return nil, ErrNotImplemented +} + +// GetUserByEmail returns a single user by email +func (db *DB) GetUserByEmail(email string) (*models.User, error) { + return nil, ErrNotImplemented +} + +// DeleteUser removes a user from the datastore +func (db *DB) DeleteUser(id uint) error { + return ErrNotImplemented +} diff --git a/services/sessions.go b/services/sessions.go index 2afbe0b..9efe6a5 100644 --- a/services/sessions.go +++ b/services/sessions.go @@ -1,21 +1,126 @@ package services import ( + "encoding/gob" + "fmt" + "html/template" + "log" + "net/http" + "time" + "git.klink.asia/paul/certman/settings" + "github.com/alexedwards/scs" "github.com/gorilla/securecookie" - "github.com/gorilla/sessions" ) -var Sessions sessions.Store +var ( + // SessionName is the name of the session cookie + SessionName = "session" + // CookieKey is the key the cookies are encrypted and signed with + CookieKey = string(securecookie.GenerateRandomKey(32)) + // FlashesKey is the key used for the flashes in the cookie + FlashesKey = "_flashes" + // UserEmailKey is the key used to reference usernames + UserEmailKey = "_user_email" +) -func InitSession() { - store := sessions.NewCookieStore( - securecookie.GenerateRandomKey(32), // signing key - securecookie.GenerateRandomKey(32), // encryption key - ) - store.Options.HttpOnly = true - store.Options.MaxAge = 7 * 24 * 60 * 60 // 1 Week - store.Options.Secure = settings.Get("ENVIRONMENT", "") == "production" - - Sessions = store +func init() { + // Register the Flash message type, so gob can serialize it + gob.Register(Flash{}) +} + +// SessionStore is a globally accessible sessions store for the application +var SessionStore *Store + +// Store is a wrapped scs.Store in order to implement custom +// logic +type Store struct { + *scs.Manager +} + +// InitSession populates the default sessions Store +func InitSession() { + store := scs.NewCookieManager( + CookieKey, + ) + store.HttpOnly(true) + store.Lifetime(24 * time.Hour) + + // Use secure cookies (HTTPS only) in production + store.Secure(settings.Get("ENVIRONMENT", "") == "production") + + SessionStore = &Store{store} +} + +func (store *Store) GetUserEmail(req *http.Request) string { + if store == nil { + // if store was not initialized, all requests fail + log.Println("Zero pointer when checking session for username") + return "" + } + + sess := store.Load(req) + + email, err := sess.GetString(UserEmailKey) + if err != nil { + // Username found + return "" + + } + + // User is logged in + return email +} + +func (store *Store) SetUserEmail(w http.ResponseWriter, req *http.Request, email string) { + if store == nil { + // if store was not initialized, do nothing + return + } + + sess := store.Load(req) + + // renew token to avoid session pinning/fixation attack + sess.RenewToken(w) + + sess.PutString(w, UserEmailKey, email) + +} + +type Flash struct { + Message template.HTML + Type string +} + +// Render renders the flash message as a notification box +func (flash Flash) Render() template.HTML { + return template.HTML( + fmt.Sprintf( + "
%s
", + flash.Type, flash.Message, + ), + ) +} + +// Flash add flash message to session data +func (store *Store) Flash(w http.ResponseWriter, req *http.Request, flash Flash) error { + var flashes []Flash + + sess := store.Load(req) + + if err := sess.GetObject(FlashesKey, &flashes); err != nil { + return err + } + + flashes = append(flashes, flash) + + return sess.PutObject(w, FlashesKey, flashes) +} + +// Flashes returns a slice of flash messages from session data +func (store *Store) Flashes(w http.ResponseWriter, req *http.Request) []Flash { + var flashes []Flash + sess := store.Load(req) + sess.PopObject(w, FlashesKey, &flashes) + return flashes } diff --git a/views/funcs.go b/views/funcs.go index 099785f..2023d79 100644 --- a/views/funcs.go +++ b/views/funcs.go @@ -8,11 +8,13 @@ import ( ) var funcs = template.FuncMap{ - "assetURL": assetURLFn, + "asset": assetURLFn, + "url": relURLFn, "lower": lower, "upper": upper, "date": dateFn, "humanDate": readableDateFn, + "t": translateFn, } func lower(input string) string { @@ -28,6 +30,11 @@ func assetURLFn(input string) string { return fmt.Sprintf("%s%s", url, input) } +func relURLFn(input string) string { + url := "/" //os.Getenv("ASSET_URL") + return fmt.Sprintf("%s%s", url, input) +} + func dateFn(format string, input interface{}) string { var t time.Time switch date := input.(type) { @@ -40,6 +47,10 @@ func dateFn(format string, input interface{}) string { return t.Format(format) } +func translateFn(language string, text string) string { + return text +} + func readableDateFn(t time.Time) string { if time.Now().Before(t) { return "in the future" diff --git a/views/templates.go b/views/templates.go index a8c444e..9d000ff 100644 --- a/views/templates.go +++ b/views/templates.go @@ -18,11 +18,15 @@ var templates map[string]*template.Template func LoadTemplates() { templates = map[string]*template.Template{ "401": newTemplate("layouts/application.gohtml", "errors/401.gohtml"), + "403": newTemplate("layouts/application.gohtml", "errors/403.gohtml"), "404": newTemplate("layouts/application.gohtml", "errors/404.gohtml"), "500": newTemplate("layouts/application.gohtml", "errors/500.gohtml"), + "login": newTemplate("layouts/auth.gohtml", "views/login.gohtml"), + "register": newTemplate("layouts/auth.gohtml", "views/register.gohtml"), + "forgot-password": newTemplate("layouts/auth.gohtml", "views/forgot-password.gohtml"), + "debug": newTemplate("layouts/application.gohtml", "shared/header.gohtml", "shared/footer.gohtml", "views/debug.gohtml"), - "login": newTemplate("layouts/application.gohtml", "shared/header.gohtml", "shared/footer.gohtml", "views/login.gohtml"), "cert_list": newTemplate("layouts/application.gohtml", "shared/header.gohtml", "shared/footer.gohtml", "views/cert_list.gohtml"), } return diff --git a/views/views.go b/views/views.go index 69c0bb2..563d31c 100644 --- a/views/views.go +++ b/views/views.go @@ -7,6 +7,8 @@ import ( "log" "net/http" + "git.klink.asia/paul/certman/services" + "github.com/gorilla/csrf" ) @@ -21,10 +23,12 @@ func New(req *http.Request) *View { Vars: map[string]interface{}{ "CSRF_TOKEN": csrf.Token(req), "csrfField": csrf.TemplateField(req), + "username": services.SessionStore.GetUserEmail(req), "Meta": map[string]interface{}{ "Path": req.URL.Path, "Env": "develop", }, + "flashes": []services.Flash{}, }, } } @@ -39,6 +43,9 @@ func (view View) Render(w http.ResponseWriter, name string) { return } + // add flashes to template + view.Vars["flashes"] = services.SessionStore.Flashes(w, view.Request) + w.Header().Set("Content-Type", "text/html; charset=utf-8") w.WriteHeader(http.StatusOK) t.Execute(w, view.Vars) @@ -49,12 +56,12 @@ func (view View) RenderError(w http.ResponseWriter, status int) { var name string switch status { - case http.StatusNotFound: - name = "404" case http.StatusUnauthorized: name = "401" case http.StatusForbidden: name = "403" + case http.StatusNotFound: + name = "404" default: name = "500" }