Compare commits

...

8 Commits

22 changed files with 491 additions and 154 deletions

View File

@ -4,19 +4,19 @@ tmp_dir = "tmp"
[build] [build]
args_bin = [ args_bin = [
"-aes tmp/cpolis.aes",
"-articles tmp/articles", "-articles tmp/articles",
"-config tmp/config.toml", "-config tmp/config.toml",
"-desc 'Freiheit, Gleichheit, Brüderlichkeit, Toleranz und Humanität'", "-desc 'Freiheit, Gleichheit, Brüderlichkeit, Toleranz und Humanität'",
"-domain localhost", "-domain localhost",
"-feed tmp/cpolis.atom", "-feed tmp/cpolis.atom",
"-firebase tmp/firebase.json", "-firebase tmp/firebase.json",
"-key tmp/key.gob", "-gob tmp/cpolis.gob",
"-link https://distrikt-ni-st.de", "-link https://distrikt-ni-st.de",
"-log tmp/cpolis.log", "-log tmp/cpolis.log",
"-pdfs tmp/pdfs", "-pdfs tmp/pdfs",
"-pics tmp/pics", "-pics tmp/pics",
"-port 8080", "-port 8080",
"-rss tmp/orientexpress_alle.rss",
"-title 'Freimaurer Distrikt Niedersachsen und Sachsen-Anhalt'", "-title 'Freimaurer Distrikt Niedersachsen und Sachsen-Anhalt'",
"-web web", "-web web",
"-width 1024", "-width 1024",

View File

@ -18,7 +18,7 @@ func GenerateAtomFeed(c *Config, db *DB) (*string, error) {
feed.Generator = atom.NewGenerator("cpolis") feed.Generator = atom.NewGenerator("cpolis")
feed.Generator.URI = "https://git.streifling.com/jason/cpolis" feed.Generator.URI = "https://git.streifling.com/jason/cpolis"
feed.Generator.Version = "0.13" feed.Generator.Version = c.Version
articles, err := db.GetCertainArticles("published", true) articles, err := db.GetCertainArticles("published", true)
if err != nil { if err != nil {
@ -39,7 +39,7 @@ func GenerateAtomFeed(c *Config, db *DB) (*string, error) {
entry.Links[linkID].Rel = "enclosure" entry.Links[linkID].Rel = "enclosure"
entry.Links[linkID].Type = "image/webp" entry.Links[linkID].Type = "image/webp"
user, err := db.GetUser(article.AuthorID) user, err := db.GetUser(c, article.AuthorID)
if err != nil { if err != nil {
return nil, fmt.Errorf("error getting user user info for RSS feed: %v", err) return nil, fmt.Errorf("error getting user user info for RSS feed: %v", err)
} }

View File

@ -12,6 +12,7 @@ import (
) )
type Config struct { type Config struct {
AESKeyFile string
ArticleDir string ArticleDir string
ConfigFile string ConfigFile string
DBName string DBName string
@ -19,14 +20,14 @@ type Config struct {
Domain string Domain string
AtomFeed string AtomFeed string
FirebaseKey string FirebaseKey string
KeyFile string GOBKeyFile string
Link string Link string
LogFile string LogFile string
PDFDir string PDFDir string
PicsDir string PicsDir string
Port string Port string
RSSFile string
Title string Title string
Version string
WebDir string WebDir string
MaxImgHeight int MaxImgHeight int
MaxImgWidth int MaxImgWidth int
@ -34,19 +35,20 @@ type Config struct {
func newConfig() *Config { func newConfig() *Config {
return &Config{ return &Config{
AESKeyFile: "/var/www/cpolis/aes.key",
ArticleDir: "/var/www/cpolis/articles", ArticleDir: "/var/www/cpolis/articles",
ConfigFile: "/etc/cpolis/config.toml", ConfigFile: "/etc/cpolis/config.toml",
DBName: "cpolis", DBName: "cpolis",
AtomFeed: "/var/www/cpolis/cpolis.atom", AtomFeed: "/var/www/cpolis/cpolis.atom",
FirebaseKey: "/var/www/cpolis/serviceAccountKey.json", FirebaseKey: "/var/www/cpolis/serviceAccountKey.json",
KeyFile: "/var/www/cpolis/cpolis.key", GOBKeyFile: "/var/www/cpolis/gob.key",
LogFile: "/var/log/cpolis.log", LogFile: "/var/log/cpolis.log",
MaxImgHeight: 1080, MaxImgHeight: 1080,
MaxImgWidth: 1920, MaxImgWidth: 1920,
PDFDir: "/var/www/cpolis/pdfs", PDFDir: "/var/www/cpolis/pdfs",
PicsDir: "/var/www/cpolis/pics", PicsDir: "/var/www/cpolis/pics",
Port: ":8080", Port: ":8080",
RSSFile: "/var/www/cpolis/cpolis.rss", Version: "v0.13.0",
WebDir: "/var/www/cpolis/web", WebDir: "/var/www/cpolis/web",
} }
} }
@ -102,6 +104,7 @@ func (c *Config) handleCliArgs() error {
var port int var port int
var err error var err error
flag.StringVar(&c.AESKeyFile, "aes", c.AESKeyFile, "aes key file")
flag.StringVar(&c.ArticleDir, "articles", c.ArticleDir, "articles directory") flag.StringVar(&c.ArticleDir, "articles", c.ArticleDir, "articles directory")
flag.StringVar(&c.AtomFeed, "feed", c.AtomFeed, "atom feed file") flag.StringVar(&c.AtomFeed, "feed", c.AtomFeed, "atom feed file")
flag.StringVar(&c.ConfigFile, "config", c.ConfigFile, "config file") flag.StringVar(&c.ConfigFile, "config", c.ConfigFile, "config file")
@ -109,12 +112,11 @@ func (c *Config) handleCliArgs() error {
flag.StringVar(&c.Description, "desc", c.Description, "channel description") flag.StringVar(&c.Description, "desc", c.Description, "channel description")
flag.StringVar(&c.Domain, "domain", c.Domain, "domain name") flag.StringVar(&c.Domain, "domain", c.Domain, "domain name")
flag.StringVar(&c.FirebaseKey, "firebase", c.FirebaseKey, "Firebase service account key file") flag.StringVar(&c.FirebaseKey, "firebase", c.FirebaseKey, "Firebase service account key file")
flag.StringVar(&c.KeyFile, "key", c.KeyFile, "key file") flag.StringVar(&c.GOBKeyFile, "gob", c.GOBKeyFile, "gob key file")
flag.StringVar(&c.Link, "link", c.Link, "channel Link") flag.StringVar(&c.Link, "link", c.Link, "channel Link")
flag.StringVar(&c.LogFile, "log", c.LogFile, "log file") flag.StringVar(&c.LogFile, "log", c.LogFile, "log file")
flag.StringVar(&c.PDFDir, "pdfs", c.PDFDir, "pdf directory") flag.StringVar(&c.PDFDir, "pdfs", c.PDFDir, "pdf directory")
flag.StringVar(&c.PicsDir, "pics", c.PicsDir, "pictures directory") flag.StringVar(&c.PicsDir, "pics", c.PicsDir, "pictures directory")
flag.StringVar(&c.RSSFile, "rss", c.RSSFile, "RSS file")
flag.StringVar(&c.Title, "title", c.Title, "channel title") flag.StringVar(&c.Title, "title", c.Title, "channel title")
flag.StringVar(&c.WebDir, "web", c.WebDir, "web directory") flag.StringVar(&c.WebDir, "web", c.WebDir, "web directory")
flag.IntVar(&c.MaxImgHeight, "height", c.MaxImgHeight, "maximum image height") flag.IntVar(&c.MaxImgHeight, "height", c.MaxImgHeight, "maximum image height")
@ -147,6 +149,14 @@ func (c *Config) setupConfig(cliConfig *Config) error {
var err error var err error
defaultConfig := newConfig() defaultConfig := newConfig()
if cliConfig.AESKeyFile != defaultConfig.AESKeyFile {
c.AESKeyFile = cliConfig.AESKeyFile
}
c.AESKeyFile, err = mkFile(c.AESKeyFile, 0600, 0700)
if err != nil {
return fmt.Errorf("error setting up file: %v", err)
}
if cliConfig.ArticleDir != defaultConfig.ArticleDir { if cliConfig.ArticleDir != defaultConfig.ArticleDir {
c.ArticleDir = cliConfig.ArticleDir c.ArticleDir = cliConfig.ArticleDir
} }
@ -187,10 +197,10 @@ func (c *Config) setupConfig(cliConfig *Config) error {
return fmt.Errorf("error setting up file: %v", err) return fmt.Errorf("error setting up file: %v", err)
} }
if cliConfig.KeyFile != defaultConfig.KeyFile { if cliConfig.GOBKeyFile != defaultConfig.GOBKeyFile {
c.KeyFile = cliConfig.KeyFile c.GOBKeyFile = cliConfig.GOBKeyFile
} }
c.KeyFile, err = mkFile(c.KeyFile, 0600, 0700) c.GOBKeyFile, err = mkFile(c.GOBKeyFile, 0600, 0700)
if err != nil { if err != nil {
return fmt.Errorf("error setting up file: %v", err) return fmt.Errorf("error setting up file: %v", err)
} }
@ -235,14 +245,6 @@ func (c *Config) setupConfig(cliConfig *Config) error {
c.Port = cliConfig.Port c.Port = cliConfig.Port
} }
if cliConfig.RSSFile != defaultConfig.RSSFile {
c.RSSFile = cliConfig.RSSFile
}
c.RSSFile, err = mkFile(c.RSSFile, 0600, 0700)
if err != nil {
return fmt.Errorf("error setting up file: %v", err)
}
if cliConfig.Title != defaultConfig.Title { if cliConfig.Title != defaultConfig.Title {
c.Title = cliConfig.Title c.Title = cliConfig.Title
} }

View File

@ -39,7 +39,7 @@ func GenerateRSS(c *Config, db *DB) (*string, error) {
tagNames = append(tagNames, "autogenerated") tagNames = append(tagNames, "autogenerated")
} }
user, err := db.GetUser(article.AuthorID) user, err := db.GetUser(c, article.AuthorID)
if err != nil { if err != nil {
return nil, fmt.Errorf("error getting user user info for RSS feed: %v", err) return nil, fmt.Errorf("error getting user user info for RSS feed: %v", err)
} }

View File

@ -2,9 +2,16 @@ package backend
import ( import (
"context" "context"
"crypto/aes"
"crypto/cipher"
"crypto/rand"
"database/sql" "database/sql"
"encoding/base64"
"errors"
"fmt" "fmt"
"io"
"log" "log"
"os"
"golang.org/x/crypto/bcrypt" "golang.org/x/crypto/bcrypt"
) )
@ -18,24 +25,135 @@ const (
) )
type User struct { type User struct {
UserName string UserName string
FirstName string FirstName string
LastName string LastName string
ID int64 Email string
Role int ProfilePicLink string
ID int64
Role int
} }
func (db *DB) AddUser(u *User, pass string) (int64, error) { func readKey(filename string) ([]byte, error) {
key, err := os.ReadFile(filename)
if err != nil {
return nil, fmt.Errorf("error reading from aes key file: %v", err)
}
if len(key) != 44 {
return nil, errors.New("key is not 32 bytes long")
}
key, err = base64.StdEncoding.DecodeString(string(key))
if err != nil {
return nil, fmt.Errorf("error base64 decoding key: %v", err)
}
return key, nil
}
func key(c *Config) ([]byte, error) {
key, err := readKey(c.AESKeyFile)
if err != nil {
key = make([]byte, 32)
if _, err := rand.Read(key); err != nil {
return nil, fmt.Errorf("error generating random key: %v", err)
}
fileKey := make([]byte, 44)
base64.StdEncoding.Encode(fileKey, key)
if err = os.WriteFile(c.AESKeyFile, fileKey, 0600); err != nil {
return nil, fmt.Errorf("error writing key to file: %v", err)
}
}
return key, nil
}
func aesEncrypt(c *Config, plaintext string) (string, error) {
key, err := key(c)
if err != nil {
return "", fmt.Errorf("error retrieving key: %v", err)
}
block, err := aes.NewCipher(key)
if err != nil {
return "", fmt.Errorf("error creating cipher block: %v", err)
}
gcm, err := cipher.NewGCM(block)
if err != nil {
return "", fmt.Errorf("error creating new gcm: %v", err)
}
nonce := make([]byte, gcm.NonceSize())
if _, err := io.ReadFull(rand.Reader, nonce); err != nil {
return "", fmt.Errorf("error creating nonce: %v", err)
}
cipherText := gcm.Seal(nonce, nonce, []byte(plaintext), nil)
return base64.StdEncoding.EncodeToString(cipherText), nil
}
func aesDecrypt(c *Config, ciphertext string) (string, error) {
key, err := key(c)
if err != nil {
return "", fmt.Errorf("error retrieving key: %v", err)
}
block, err := aes.NewCipher(key)
if err != nil {
return "", fmt.Errorf("error creating cipher block: %v", err)
}
gcm, err := cipher.NewGCM(block)
if err != nil {
return "", fmt.Errorf("error creating new gcm: %v", err)
}
data, err := base64.StdEncoding.DecodeString(ciphertext)
if err != nil {
return "", fmt.Errorf("error base64 decoding ciphertext: %v", err)
}
nonceSize := gcm.NonceSize()
nonce, cipherText := data[:nonceSize], data[nonceSize:]
plaintext, err := gcm.Open(nil, nonce, cipherText, nil)
if err != nil {
return "", fmt.Errorf("error aes decoding ciphertext: %v", err)
}
return string(plaintext), nil
}
func (db *DB) AddUser(c *Config, u *User, pass string) (int64, error) {
hashedPass, err := bcrypt.GenerateFromPassword([]byte(pass), bcrypt.DefaultCost) hashedPass, err := bcrypt.GenerateFromPassword([]byte(pass), bcrypt.DefaultCost)
if err != nil { if err != nil {
return 0, fmt.Errorf("error creating password hash: %v", err) return 0, fmt.Errorf("error creating password hash: %v", err)
} }
aesFirstName, err := aesEncrypt(c, u.FirstName)
if err != nil {
return 0, fmt.Errorf("error encrypting first name: %v", err)
}
aesLastName, err := aesEncrypt(c, u.LastName)
if err != nil {
return 0, fmt.Errorf("error encrypting last name: %v", err)
}
aesEmail, err := aesEncrypt(c, u.Email)
if err != nil {
return 0, fmt.Errorf("error encrypting email: %v", err)
}
query := ` query := `
INSERT INTO users (username, password, first_name, last_name, role) INSERT INTO users (username, password, first_name, last_name, email, role)
VALUES (?, ?, ?, ?, ?) VALUES (?, ?, ?, ?, ?, ?)
` `
result, err := db.Exec(query, u.UserName, string(hashedPass), u.FirstName, u.LastName, u.Role)
result, err := db.Exec(query, u.UserName, string(hashedPass), aesFirstName, aesLastName, aesEmail, u.Role)
if err != nil { if err != nil {
return 0, fmt.Errorf("error inserting new user %v into DB: %v", u.UserName, err) return 0, fmt.Errorf("error inserting new user %v into DB: %v", u.UserName, err)
} }
@ -129,34 +247,44 @@ func (tx *Tx) ChangePassword(id int64, oldPass, newPass string) error {
} }
// TODO: No need for ID field in general // TODO: No need for ID field in general
func (db *DB) GetUser(id int64) (*User, error) { func (db *DB) GetUser(c *Config, id int64) (*User, error) {
var aesFirstName, aesLastName, aesEmail string
var err error
user := new(User) user := new(User)
query := ` query := `
SELECT id, username, first_name, last_name, role SELECT id, username, first_name, last_name, email, role
FROM users FROM users
WHERE id = ? WHERE id = ?
` `
row := db.QueryRow(query, id) row := db.QueryRow(query, id)
if err := row.Scan(&user.ID, &user.UserName, &user.FirstName, if err := row.Scan(&user.ID, &user.UserName, &aesFirstName, &aesLastName, &aesEmail, &user.Role); err != nil {
&user.LastName, &user.Role); err != nil {
return nil, fmt.Errorf("error reading user information: %v", err) return nil, fmt.Errorf("error reading user information: %v", err)
} }
user.FirstName, err = aesDecrypt(c, aesFirstName)
if err != nil {
return nil, fmt.Errorf("error decrypting first name: %v", err)
}
user.LastName, err = aesDecrypt(c, aesLastName)
if err != nil {
return nil, fmt.Errorf("error decrypting last name: %v", err)
}
user.Email, err = aesDecrypt(c, aesEmail)
if err != nil {
return nil, fmt.Errorf("error decrypting email: %v", err)
}
return user, nil return user, nil
} }
func (db *DB) UpdateOwnUserAttributes(id int64, user, first, last, oldPass, newPass, newPass2 string) error { func (db *DB) UpdateOwnUserAttributes(c *Config, id int64, userName, firstName, lastName, email, oldPass, newPass string) error {
passwordEmpty := true
if len(newPass) > 0 || len(newPass2) > 0 {
if newPass != newPass2 {
return fmt.Errorf("error: passwords do not match")
}
passwordEmpty = false
}
tx := new(Tx)
var err error var err error
tx := new(Tx)
passwordEmpty := len(newPass) > 0
for i := 0; i < TxMaxRetries; i++ { for i := 0; i < TxMaxRetries; i++ {
err := func() error { err := func() error {
@ -174,10 +302,35 @@ func (db *DB) UpdateOwnUserAttributes(id int64, user, first, last, oldPass, newP
} }
} }
aesFirstName, err := aesEncrypt(c, firstName)
if err != nil {
if rollbackErr := tx.Rollback(); rollbackErr != nil {
log.Fatalf("transaction error: %v, rollback error: %v", err, rollbackErr)
}
return fmt.Errorf("error encrypting first name: %v", err)
}
aesLastName, err := aesEncrypt(c, lastName)
if err != nil {
if rollbackErr := tx.Rollback(); rollbackErr != nil {
log.Fatalf("transaction error: %v, rollback error: %v", err, rollbackErr)
}
return fmt.Errorf("error encrypting last name: %v", err)
}
aesEmail, err := aesEncrypt(c, email)
if err != nil {
if rollbackErr := tx.Rollback(); rollbackErr != nil {
log.Fatalf("transaction error: %v, rollback error: %v", err, rollbackErr)
}
return fmt.Errorf("error encrypting email: %v", err)
}
if err = tx.UpdateAttributes( if err = tx.UpdateAttributes(
&Attribute{Table: "users", ID: id, AttName: "username", Value: user}, &Attribute{Table: "users", ID: id, AttName: "username", Value: userName},
&Attribute{Table: "users", ID: id, AttName: "first_name", Value: first}, &Attribute{Table: "users", ID: id, AttName: "first_name", Value: aesFirstName},
&Attribute{Table: "users", ID: id, AttName: "last_name", Value: last}, &Attribute{Table: "users", ID: id, AttName: "last_name", Value: aesLastName},
&Attribute{Table: "users", ID: id, AttName: "email", Value: aesEmail},
); err != nil { ); err != nil {
if rollbackErr := tx.Rollback(); rollbackErr != nil { if rollbackErr := tx.Rollback(); rollbackErr != nil {
log.Fatalf("transaction error: %v, rollback error: %v", err, rollbackErr) log.Fatalf("transaction error: %v, rollback error: %v", err, rollbackErr)
@ -202,13 +355,13 @@ func (db *DB) UpdateOwnUserAttributes(id int64, user, first, last, oldPass, newP
return fmt.Errorf("error: %v unsuccessful retries for DB operation, aborting", TxMaxRetries) return fmt.Errorf("error: %v unsuccessful retries for DB operation, aborting", TxMaxRetries)
} }
func (db *DB) AddFirstUser(u *User, pass string) (int64, error) { func (db *DB) AddFirstUser(c *Config, u *User, pass string) (int64, error) {
var numUsers int64 var numUsers int64
txOptions := &sql.TxOptions{Isolation: sql.LevelSerializable} txOptions := &sql.TxOptions{Isolation: sql.LevelSerializable}
selectQuery := "SELECT COUNT(*) FROM users" selectQuery := "SELECT COUNT(*) FROM users"
insertQuery := ` insertQuery := `
INSERT INTO users (username, password, first_name, last_name, role) INSERT INTO users (username, password, first_name, last_name, email, role)
VALUES (?, ?, ?, ?, ?) VALUES (?, ?, ?, ?, ?, ?)
` `
for i := 0; i < TxMaxRetries; i++ { for i := 0; i < TxMaxRetries; i++ {
@ -239,7 +392,31 @@ func (db *DB) AddFirstUser(u *User, pass string) (int64, error) {
return 0, fmt.Errorf("error creating password hash: %v", err) return 0, fmt.Errorf("error creating password hash: %v", err)
} }
result, err := tx.Exec(insertQuery, u.UserName, string(hashedPass), u.FirstName, u.LastName, u.Role) aesFirstName, err := aesEncrypt(c, u.FirstName)
if err != nil {
if rollbackErr := tx.Rollback(); rollbackErr != nil {
log.Fatalf("transaction error: %v, rollback error: %v", err, rollbackErr)
}
return 0, fmt.Errorf("error encrypting first name: %v", err)
}
aesLastName, err := aesEncrypt(c, u.LastName)
if err != nil {
if rollbackErr := tx.Rollback(); rollbackErr != nil {
log.Fatalf("transaction error: %v, rollback error: %v", err, rollbackErr)
}
return 0, fmt.Errorf("error encrypting last name: %v", err)
}
aesEmail, err := aesEncrypt(c, u.Email)
if err != nil {
if rollbackErr := tx.Rollback(); rollbackErr != nil {
log.Fatalf("transaction error: %v, rollback error: %v", err, rollbackErr)
}
return 0, fmt.Errorf("error encrypting email: %v", err)
}
result, err := tx.Exec(insertQuery, u.UserName, string(hashedPass), aesFirstName, aesLastName, aesEmail, u.Role)
if err != nil { if err != nil {
if rollbackErr := tx.Rollback(); rollbackErr != nil { if rollbackErr := tx.Rollback(); rollbackErr != nil {
log.Fatalf("transaction error: %v, rollback error: %v", err, rollbackErr) log.Fatalf("transaction error: %v, rollback error: %v", err, rollbackErr)
@ -270,8 +447,11 @@ func (db *DB) AddFirstUser(u *User, pass string) (int64, error) {
return 0, fmt.Errorf("error: %v unsuccessful retries for DB operation, aborting", TxMaxRetries) return 0, fmt.Errorf("error: %v unsuccessful retries for DB operation, aborting", TxMaxRetries)
} }
func (db *DB) GetAllUsers() (map[int64]*User, error) { func (db *DB) GetAllUsers(c *Config) (map[int64]*User, error) {
query := "SELECT id, username, first_name, last_name, role FROM users" var aesFirstName, aesLastName, aesEmail string
var err error
query := "SELECT id, username, first_name, last_name, email, role FROM users"
rows, err := db.Query(query) rows, err := db.Query(query)
if err != nil { if err != nil {
@ -281,10 +461,25 @@ func (db *DB) GetAllUsers() (map[int64]*User, error) {
users := make(map[int64]*User, 0) users := make(map[int64]*User, 0)
for rows.Next() { for rows.Next() {
user := new(User) user := new(User)
if err = rows.Scan(&user.ID, &user.UserName, &user.FirstName, if err = rows.Scan(&user.ID, &user.UserName, &aesFirstName, &aesLastName, &aesEmail, &user.Role); err != nil {
&user.LastName, &user.Role); err != nil {
return nil, fmt.Errorf("error getting user info: %v", err) return nil, fmt.Errorf("error getting user info: %v", err)
} }
user.FirstName, err = aesDecrypt(c, aesFirstName)
if err != nil {
return nil, fmt.Errorf("error decrypting first name: %v", err)
}
user.LastName, err = aesDecrypt(c, aesLastName)
if err != nil {
return nil, fmt.Errorf("error decrypting last name: %v", err)
}
user.Email, err = aesDecrypt(c, aesEmail)
if err != nil {
return nil, fmt.Errorf("error decrypting email: %v", err)
}
users[user.ID] = user users[user.ID] = user
} }
@ -311,17 +506,10 @@ func (tx *Tx) SetPassword(id int64, newPass string) error {
return nil return nil
} }
func (db *DB) UpdateUserAttributes(id int64, user, first, last, newPass, newPass2 string, role int) error { func (db *DB) UpdateUserAttributes(c *Config, id int64, userName, firstName, lastName, email, newPass string, role int) error {
passwordEmpty := true
if len(newPass) > 0 || len(newPass2) > 0 {
if newPass != newPass2 {
return fmt.Errorf("error: passwords do not match")
}
passwordEmpty = false
}
tx := new(Tx)
var err error var err error
tx := new(Tx)
passwordEmpty := len(newPass) > 0
for i := 0; i < TxMaxRetries; i++ { for i := 0; i < TxMaxRetries; i++ {
err := func() error { err := func() error {
@ -339,10 +527,35 @@ func (db *DB) UpdateUserAttributes(id int64, user, first, last, newPass, newPass
} }
} }
aesFirstName, err := aesEncrypt(c, firstName)
if err != nil {
if rollbackErr := tx.Rollback(); rollbackErr != nil {
log.Fatalf("transaction error: %v, rollback error: %v", err, rollbackErr)
}
return fmt.Errorf("error encrypting first name: %v", err)
}
aesLastName, err := aesEncrypt(c, lastName)
if err != nil {
if rollbackErr := tx.Rollback(); rollbackErr != nil {
log.Fatalf("transaction error: %v, rollback error: %v", err, rollbackErr)
}
return fmt.Errorf("error encrypting last name: %v", err)
}
aesEmail, err := aesEncrypt(c, email)
if err != nil {
if rollbackErr := tx.Rollback(); rollbackErr != nil {
log.Fatalf("transaction error: %v, rollback error: %v", err, rollbackErr)
}
return fmt.Errorf("error encrypting email: %v", err)
}
if err = tx.UpdateAttributes( if err = tx.UpdateAttributes(
&Attribute{Table: "users", ID: id, AttName: "username", Value: user}, &Attribute{Table: "users", ID: id, AttName: "username", Value: userName},
&Attribute{Table: "users", ID: id, AttName: "first_name", Value: first}, &Attribute{Table: "users", ID: id, AttName: "first_name", Value: aesFirstName},
&Attribute{Table: "users", ID: id, AttName: "last_name", Value: last}, &Attribute{Table: "users", ID: id, AttName: "last_name", Value: aesLastName},
&Attribute{Table: "users", ID: id, AttName: "email", Value: aesEmail},
&Attribute{Table: "users", ID: id, AttName: "role", Value: role}, &Attribute{Table: "users", ID: id, AttName: "role", Value: role},
); err != nil { ); err != nil {
if rollbackErr := tx.Rollback(); rollbackErr != nil { if rollbackErr := tx.Rollback(); rollbackErr != nil {

View File

@ -1,17 +0,0 @@
package calls
import (
"net/http"
b "streifling.com/jason/cpolis/cmd/backend"
)
func ServeRSS(c *b.Config) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
if !tokenIsVerified(w, r, c) {
return
}
http.ServeFile(w, r, c.RSSFile)
}
}

View File

@ -146,9 +146,12 @@ func SubmitArticle(c *b.Config, db *b.DB, s *b.CookieStore) http.HandlerFunc {
return return
} }
data := new(struct{ Role int })
data.Role = session.Values["role"].(int)
tmpl, err := template.ParseFiles(c.WebDir + "/templates/hub.html") tmpl, err := template.ParseFiles(c.WebDir + "/templates/hub.html")
tmpl = template.Must(tmpl, err) tmpl = template.Must(tmpl, err)
if err = tmpl.ExecuteTemplate(w, "page-content", session.Values["role"]); err != nil { if err = tmpl.ExecuteTemplate(w, "page-content", data); err != nil {
log.Println(err) log.Println(err)
http.Error(w, err.Error(), http.StatusInternalServerError) http.Error(w, err.Error(), http.StatusInternalServerError)
return return
@ -225,9 +228,12 @@ func ResubmitArticle(c *b.Config, db *b.DB, s *b.CookieStore) http.HandlerFunc {
return return
} }
data := new(struct{ Role int })
data.Role = session.Values["role"].(int)
tmpl, err := template.ParseFiles(c.WebDir + "/templates/hub.html") tmpl, err := template.ParseFiles(c.WebDir + "/templates/hub.html")
tmpl = template.Must(tmpl, err) tmpl = template.Must(tmpl, err)
if err = tmpl.ExecuteTemplate(w, "page-content", session.Values["role"]); err != nil { if err = tmpl.ExecuteTemplate(w, "page-content", data); err != nil {
log.Println(err) log.Println(err)
http.Error(w, err.Error(), http.StatusInternalServerError) http.Error(w, err.Error(), http.StatusInternalServerError)
return return
@ -469,9 +475,12 @@ func PublishArticle(c *b.Config, db *b.DB, s *b.CookieStore) http.HandlerFunc {
return return
} }
data := new(struct{ Role int })
data.Role = session.Values["role"].(int)
tmpl, err := template.ParseFiles(c.WebDir + "/templates/hub.html") tmpl, err := template.ParseFiles(c.WebDir + "/templates/hub.html")
tmpl = template.Must(tmpl, err) tmpl = template.Must(tmpl, err)
if err = tmpl.ExecuteTemplate(w, "page-content", session.Values["role"]); err != nil { if err = tmpl.ExecuteTemplate(w, "page-content", data); err != nil {
log.Println(err) log.Println(err)
http.Error(w, err.Error(), http.StatusInternalServerError) http.Error(w, err.Error(), http.StatusInternalServerError)
return return
@ -501,9 +510,12 @@ func RejectArticle(c *b.Config, db *b.DB, s *b.CookieStore) http.HandlerFunc {
return return
} }
data := new(struct{ Role int })
data.Role = session.Values["role"].(int)
tmpl, err := template.ParseFiles(c.WebDir + "/templates/hub.html") tmpl, err := template.ParseFiles(c.WebDir + "/templates/hub.html")
tmpl = template.Must(tmpl, err) tmpl = template.Must(tmpl, err)
if err = tmpl.ExecuteTemplate(w, "page-content", session.Values["role"]); err != nil { if err = tmpl.ExecuteTemplate(w, "page-content", data); err != nil {
log.Println(err) log.Println(err)
http.Error(w, err.Error(), http.StatusInternalServerError) http.Error(w, err.Error(), http.StatusInternalServerError)
return return
@ -698,9 +710,12 @@ func DeleteArticle(c *b.Config, db *b.DB, s *b.CookieStore) http.HandlerFunc {
return return
} }
data := new(struct{ Role int })
data.Role = session.Values["role"].(int)
tmpl, err := template.ParseFiles(c.WebDir + "/templates/hub.html") tmpl, err := template.ParseFiles(c.WebDir + "/templates/hub.html")
tmpl = template.Must(tmpl, err) tmpl = template.Must(tmpl, err)
if err = tmpl.ExecuteTemplate(w, "page-content", session.Values["role"].(int)); err != nil { if err = tmpl.ExecuteTemplate(w, "page-content", data); err != nil {
log.Println(err) log.Println(err)
http.Error(w, err.Error(), http.StatusInternalServerError) http.Error(w, err.Error(), http.StatusInternalServerError)
return return
@ -755,8 +770,11 @@ func AllowEditArticle(c *b.Config, db *b.DB, s *b.CookieStore) http.HandlerFunc
return return
} }
data := new(struct{ Role int })
data.Role = session.Values["role"].(int)
tmpl := template.Must(template.ParseFiles(c.WebDir + "/templates/hub.html")) tmpl := template.Must(template.ParseFiles(c.WebDir + "/templates/hub.html"))
if err = tmpl.ExecuteTemplate(w, "page-content", session.Values["role"].(int)); err != nil { if err = tmpl.ExecuteTemplate(w, "page-content", data); err != nil {
log.Println(err) log.Println(err)
http.Error(w, err.Error(), http.StatusInternalServerError) http.Error(w, err.Error(), http.StatusInternalServerError)
return return

View File

@ -110,9 +110,12 @@ func PublishLatestIssue(c *b.Config, db *b.DB, s *b.CookieStore) http.HandlerFun
return return
} }
data := new(struct{ Role int })
data.Role = session.Values["role"].(int)
tmpl, err := template.ParseFiles(c.WebDir + "/templates/hub.html") tmpl, err := template.ParseFiles(c.WebDir + "/templates/hub.html")
tmpl = template.Must(tmpl, err) tmpl = template.Must(tmpl, err)
if err = tmpl.ExecuteTemplate(w, "page-content", session.Values["role"]); err != nil { if err = tmpl.ExecuteTemplate(w, "page-content", data); err != nil {
log.Println(err) log.Println(err)
http.Error(w, err.Error(), http.StatusInternalServerError) http.Error(w, err.Error(), http.StatusInternalServerError)
return return

View File

@ -56,12 +56,18 @@ func HomePage(c *b.Config, db *b.DB, s *b.CookieStore) http.HandlerFunc {
log.Fatalln(err) log.Fatalln(err)
} }
data := new(struct {
Version string
Role int
})
data.Version = c.Version
files := make([]string, 2) files := make([]string, 2)
files[0] = c.WebDir + "/templates/index.html" files[0] = c.WebDir + "/templates/index.html"
if numRows == 0 { if numRows == 0 {
files[1] = c.WebDir + "/templates/first-user.html" files[1] = c.WebDir + "/templates/first-user.html"
tmpl, err := template.ParseFiles(files...) tmpl, err := template.ParseFiles(files...)
if err = template.Must(tmpl, err).Execute(w, nil); err != nil { if err = template.Must(tmpl, err).Execute(w, data); err != nil {
log.Println(err) log.Println(err)
http.Error(w, err.Error(), http.StatusInternalServerError) http.Error(w, err.Error(), http.StatusInternalServerError)
return return
@ -74,9 +80,10 @@ func HomePage(c *b.Config, db *b.DB, s *b.CookieStore) http.HandlerFunc {
return return
} }
if auth, ok := session.Values["authenticated"].(bool); auth && ok { if auth, ok := session.Values["authenticated"].(bool); auth && ok {
data.Role = session.Values["role"].(int)
files[1] = c.WebDir + "/templates/hub.html" files[1] = c.WebDir + "/templates/hub.html"
tmpl, err := template.ParseFiles(files...) tmpl, err := template.ParseFiles(files...)
if err = template.Must(tmpl, err).Execute(w, session.Values["role"]); err != nil { if err = template.Must(tmpl, err).Execute(w, data); err != nil {
log.Println(err) log.Println(err)
http.Error(w, err.Error(), http.StatusInternalServerError) http.Error(w, err.Error(), http.StatusInternalServerError)
return return
@ -84,7 +91,7 @@ func HomePage(c *b.Config, db *b.DB, s *b.CookieStore) http.HandlerFunc {
} else { } else {
files[1] = c.WebDir + "/templates/login.html" files[1] = c.WebDir + "/templates/login.html"
tmpl, err := template.ParseFiles(files...) tmpl, err := template.ParseFiles(files...)
if err = template.Must(tmpl, err).Execute(w, nil); err != nil { if err = template.Must(tmpl, err).Execute(w, data); err != nil {
log.Println(err) log.Println(err)
http.Error(w, err.Error(), http.StatusInternalServerError) http.Error(w, err.Error(), http.StatusInternalServerError)
return return
@ -111,7 +118,7 @@ func Login(c *b.Config, db *b.DB, s *b.CookieStore) http.HandlerFunc {
return return
} }
user, err := db.GetUser(id) user, err := db.GetUser(c, id)
if err != nil { if err != nil {
log.Println(err) log.Println(err)
http.Error(w, err.Error(), http.StatusInternalServerError) http.Error(w, err.Error(), http.StatusInternalServerError)
@ -125,7 +132,7 @@ func Login(c *b.Config, db *b.DB, s *b.CookieStore) http.HandlerFunc {
} }
tmpl, err := template.ParseFiles(c.WebDir + "/templates/hub.html") tmpl, err := template.ParseFiles(c.WebDir + "/templates/hub.html")
if err = template.Must(tmpl, err).ExecuteTemplate(w, "page-content", user.Role); err != nil { if err = template.Must(tmpl, err).ExecuteTemplate(w, "page-content", user); err != nil {
log.Println(err) log.Println(err)
http.Error(w, err.Error(), http.StatusInternalServerError) http.Error(w, err.Error(), http.StatusInternalServerError)
return return
@ -174,8 +181,11 @@ func ShowHub(c *b.Config, db *b.DB, s *b.CookieStore) http.HandlerFunc {
return return
} }
data := new(struct{ Role int })
data.Role = session.Values["role"].(int)
tmpl, err := template.ParseFiles(c.WebDir + "/templates/hub.html") tmpl, err := template.ParseFiles(c.WebDir + "/templates/hub.html")
if err = template.Must(tmpl, err).ExecuteTemplate(w, "page-content", session.Values["role"].(int)); err != nil { if err = template.Must(tmpl, err).ExecuteTemplate(w, "page-content", data); err != nil {
log.Println(err) log.Println(err)
http.Error(w, err.Error(), http.StatusInternalServerError) http.Error(w, err.Error(), http.StatusInternalServerError)
return return

View File

@ -41,9 +41,12 @@ func AddTag(c *b.Config, db *b.DB, s *b.CookieStore) http.HandlerFunc {
} }
db.AddTag(tag) db.AddTag(tag)
data := new(struct{ Role int })
data.Role = session.Values["role"].(int)
tmpl, err := template.ParseFiles(c.WebDir + "/templates/hub.html") tmpl, err := template.ParseFiles(c.WebDir + "/templates/hub.html")
tmpl = template.Must(tmpl, err) tmpl = template.Must(tmpl, err)
if err = tmpl.ExecuteTemplate(w, "page-content", session.Values["role"]); err != nil { if err = tmpl.ExecuteTemplate(w, "page-content", data); err != nil {
log.Println(err) log.Println(err)
http.Error(w, err.Error(), http.StatusInternalServerError) http.Error(w, err.Error(), http.StatusInternalServerError)
return return

View File

@ -11,8 +11,8 @@ import (
) )
func checkUserStrings(user *b.User) (string, int, bool) { func checkUserStrings(user *b.User) (string, int, bool) {
userLen := 15 userLen := 255
nameLen := 50 nameLen := 56 // max value when aes encrypting up to 255 bytes
if len(user.UserName) > userLen { if len(user.UserName) > userLen {
return "Benutzername", userLen, false return "Benutzername", userLen, false
@ -55,12 +55,13 @@ func AddUser(c *b.Config, db *b.DB, s *b.CookieStore) http.HandlerFunc {
UserName: r.PostFormValue("username"), UserName: r.PostFormValue("username"),
FirstName: r.PostFormValue("first-name"), FirstName: r.PostFormValue("first-name"),
LastName: r.PostFormValue("last-name"), LastName: r.PostFormValue("last-name"),
Email: r.PostFormValue("email"),
} }
pass := r.PostFormValue("password") pass := r.PostFormValue("password")
pass2 := r.PostFormValue("password2") pass2 := r.PostFormValue("password2")
email2 := r.PostFormValue("email2")
if len(user.UserName) == 0 || len(user.FirstName) == 0 || if len(user.UserName) == 0 || len(user.FirstName) == 0 || len(user.LastName) == 0 || len(user.Email) == 0 || len(email2) == 0 || len(pass) == 0 || len(pass2) == 0 {
len(user.LastName) == 0 || len(pass) == 0 || len(pass2) == 0 {
http.Error(w, "Bitte alle Felder ausfüllen.", http.StatusBadRequest) http.Error(w, "Bitte alle Felder ausfüllen.", http.StatusBadRequest)
return return
} }
@ -76,6 +77,11 @@ func AddUser(c *b.Config, db *b.DB, s *b.CookieStore) http.HandlerFunc {
return return
} }
if user.Email != email2 {
http.Error(w, "Die Emailadressen stimmen nicht überein.", http.StatusBadRequest)
return
}
if pass != pass2 { if pass != pass2 {
http.Error(w, "Die Passwörter stimmen nicht überein.", http.StatusBadRequest) http.Error(w, "Die Passwörter stimmen nicht überein.", http.StatusBadRequest)
return return
@ -94,16 +100,19 @@ func AddUser(c *b.Config, db *b.DB, s *b.CookieStore) http.HandlerFunc {
return return
} }
_, err = db.AddUser(user, pass) _, err = db.AddUser(c, user, pass)
if err != nil { if err != nil {
log.Println(err) log.Println(err)
http.Error(w, err.Error(), http.StatusInternalServerError) http.Error(w, err.Error(), http.StatusInternalServerError)
return return
} }
data := new(struct{ Role int })
data.Role = session.Values["role"].(int)
tmpl, err := template.ParseFiles(c.WebDir + "/templates/hub.html") tmpl, err := template.ParseFiles(c.WebDir + "/templates/hub.html")
tmpl = template.Must(tmpl, err) tmpl = template.Must(tmpl, err)
if err = tmpl.ExecuteTemplate(w, "page-content", session.Values["role"].(int)); err != nil { if err = tmpl.ExecuteTemplate(w, "page-content", data); err != nil {
log.Println(err) log.Println(err)
http.Error(w, err.Error(), http.StatusInternalServerError) http.Error(w, err.Error(), http.StatusInternalServerError)
return return
@ -120,7 +129,7 @@ func EditSelf(c *b.Config, db *b.DB, s *b.CookieStore) http.HandlerFunc {
return return
} }
user, err := db.GetUser(session.Values["id"].(int64)) user, err := db.GetUser(c, session.Values["id"].(int64))
if err != nil { if err != nil {
log.Println(err) log.Println(err)
http.Error(w, err.Error(), http.StatusInternalServerError) http.Error(w, err.Error(), http.StatusInternalServerError)
@ -150,11 +159,13 @@ func UpdateSelf(c *b.Config, db *b.DB, s *b.CookieStore) http.HandlerFunc {
UserName: r.PostFormValue("username"), UserName: r.PostFormValue("username"),
FirstName: r.PostFormValue("first-name"), FirstName: r.PostFormValue("first-name"),
LastName: r.PostFormValue("last-name"), LastName: r.PostFormValue("last-name"),
Email: r.PostFormValue("email"),
} }
oldPass := r.PostFormValue("old-password") oldPass := r.PostFormValue("old-password")
newPass := r.PostFormValue("password") newPass := r.PostFormValue("password")
newPass2 := r.PostFormValue("password2") newPass2 := r.PostFormValue("password2")
email2 := r.PostFormValue("email2")
if len(user.UserName) == 0 { if len(user.UserName) == 0 {
http.Error(w, "Bitte den Benutzernamen ausfüllen.", http.StatusBadRequest) http.Error(w, "Bitte den Benutzernamen ausfüllen.", http.StatusBadRequest)
@ -166,6 +177,20 @@ func UpdateSelf(c *b.Config, db *b.DB, s *b.CookieStore) http.HandlerFunc {
return return
} }
if len(user.Email) == 0 || len(email2) == 0 {
http.Error(w, "Bitte die Emailadresse ausfüllen.", http.StatusBadRequest)
return
}
if user.Email != email2 {
http.Error(w, "Die Emailadressen stimmen nicht überein", http.StatusBadRequest)
return
}
if newPass != newPass2 {
http.Error(w, "Die Passwörter stimmen nicht überein", http.StatusBadRequest)
return
}
userString, stringLen, ok := checkUserStrings(user) userString, stringLen, ok := checkUserStrings(user)
if !ok { if !ok {
http.Error(w, fmt.Sprint(userString, " ist zu lang. Maximal ", stringLen, " Zeichen erlaubt."), http.StatusBadRequest) http.Error(w, fmt.Sprint(userString, " ist zu lang. Maximal ", stringLen, " Zeichen erlaubt."), http.StatusBadRequest)
@ -177,15 +202,18 @@ func UpdateSelf(c *b.Config, db *b.DB, s *b.CookieStore) http.HandlerFunc {
return return
} }
if err = db.UpdateOwnUserAttributes(user.ID, user.UserName, user.FirstName, user.LastName, oldPass, newPass, newPass2); err != nil { if err = db.UpdateOwnUserAttributes(c, user.ID, user.UserName, user.FirstName, user.LastName, user.Email, oldPass, newPass); err != nil {
log.Println("error: user:", user.ID, err) log.Println("error: user:", user.ID, err)
http.Error(w, "Benutzerdaten konnten nicht aktualisiert werden.", http.StatusInternalServerError) http.Error(w, "Benutzerdaten konnten nicht aktualisiert werden.", http.StatusInternalServerError)
return return
} }
data := new(struct{ Role int })
data.Role = session.Values["role"].(int)
tmpl, err := template.ParseFiles(c.WebDir + "/templates/hub.html") tmpl, err := template.ParseFiles(c.WebDir + "/templates/hub.html")
tmpl = template.Must(tmpl, err) tmpl = template.Must(tmpl, err)
if err = tmpl.ExecuteTemplate(w, "page-content", session.Values["role"].(int)); err != nil { if err = tmpl.ExecuteTemplate(w, "page-content", data); err != nil {
log.Println(err) log.Println(err)
http.Error(w, err.Error(), http.StatusInternalServerError) http.Error(w, err.Error(), http.StatusInternalServerError)
return return
@ -200,13 +228,14 @@ func AddFirstUser(c *b.Config, db *b.DB, s *b.CookieStore) http.HandlerFunc {
UserName: r.PostFormValue("username"), UserName: r.PostFormValue("username"),
FirstName: r.PostFormValue("first-name"), FirstName: r.PostFormValue("first-name"),
LastName: r.PostFormValue("last-name"), LastName: r.PostFormValue("last-name"),
Email: r.PostFormValue("email"),
Role: b.Admin, Role: b.Admin,
} }
pass := r.PostFormValue("password") pass := r.PostFormValue("password")
pass2 := r.PostFormValue("password2") pass2 := r.PostFormValue("password2")
email2 := r.PostFormValue("email2")
if len(user.UserName) == 0 || len(user.FirstName) == 0 || if len(user.UserName) == 0 || len(user.FirstName) == 0 || len(user.LastName) == 0 || len(user.Email) == 0 || len(email2) == 0 || len(pass) == 0 || len(pass2) == 0 {
len(user.LastName) == 0 || len(pass) == 0 || len(pass2) == 0 {
http.Error(w, "Bitte alle Felder ausfüllen.", http.StatusBadRequest) http.Error(w, "Bitte alle Felder ausfüllen.", http.StatusBadRequest)
return return
} }
@ -217,12 +246,17 @@ func AddFirstUser(c *b.Config, db *b.DB, s *b.CookieStore) http.HandlerFunc {
return return
} }
if user.Email != email2 {
http.Error(w, "Die Emailadressen stimmen nicht überein.", http.StatusBadRequest)
return
}
if pass != pass2 { if pass != pass2 {
http.Error(w, "Die Passwörter stimmen nicht überein.", http.StatusBadRequest) http.Error(w, "Die Passwörter stimmen nicht überein.", http.StatusBadRequest)
return return
} }
user.ID, err = db.AddFirstUser(user, pass) user.ID, err = db.AddFirstUser(c, user, pass)
if err != nil { if err != nil {
log.Println(err) log.Println(err)
http.Error(w, err.Error(), http.StatusInternalServerError) http.Error(w, err.Error(), http.StatusInternalServerError)
@ -245,8 +279,11 @@ func AddFirstUser(c *b.Config, db *b.DB, s *b.CookieStore) http.HandlerFunc {
return return
} }
data := new(struct{ Role int })
data.Role = 0
tmpl, err := template.ParseFiles(c.WebDir + "/templates/hub.html") tmpl, err := template.ParseFiles(c.WebDir + "/templates/hub.html")
if err = template.Must(tmpl, err).ExecuteTemplate(w, "page-content", 0); err != nil { if err = template.Must(tmpl, err).ExecuteTemplate(w, "page-content", data); err != nil {
log.Println(err) log.Println(err)
http.Error(w, err.Error(), http.StatusInternalServerError) http.Error(w, err.Error(), http.StatusInternalServerError)
return return
@ -269,7 +306,7 @@ func ShowAllUsers(c *b.Config, db *b.DB, s *b.CookieStore, action string) http.H
}) })
data.Action = action data.Action = action
data.Users, err = db.GetAllUsers() data.Users, err = db.GetAllUsers(c)
if err != nil { if err != nil {
log.Println(err) log.Println(err)
http.Error(w, err.Error(), http.StatusInternalServerError) http.Error(w, err.Error(), http.StatusInternalServerError)
@ -301,7 +338,7 @@ func EditUser(c *b.Config, db *b.DB, s *b.CookieStore) http.HandlerFunc {
return return
} }
user, err := db.GetUser(id) user, err := db.GetUser(c, id)
if err != nil { if err != nil {
log.Println(err) log.Println(err)
http.Error(w, err.Error(), http.StatusInternalServerError) http.Error(w, err.Error(), http.StatusInternalServerError)
@ -343,19 +380,34 @@ func UpdateUser(c *b.Config, db *b.DB, s *b.CookieStore) http.HandlerFunc {
user.UserName = r.PostFormValue("username") user.UserName = r.PostFormValue("username")
if len(user.UserName) == 0 { if len(user.UserName) == 0 {
http.Error(w, "Bitte den Benutzernamen ausfüllen.", http.StatusInternalServerError) http.Error(w, "Bitte den Benutzernamen ausfüllen.", http.StatusBadRequest)
return return
} }
user.FirstName = r.PostFormValue("first-name") user.FirstName = r.PostFormValue("first-name")
user.LastName = r.PostFormValue("last-name") user.LastName = r.PostFormValue("last-name")
if len(user.FirstName) == 0 || len(user.LastName) == 0 { if len(user.FirstName) == 0 || len(user.LastName) == 0 {
http.Error(w, "Bitte den vollständigen Namen ausfüllen.", http.StatusInternalServerError) http.Error(w, "Bitte den vollständigen Namen ausfüllen.", http.StatusBadRequest)
return
}
user.Email = r.PostFormValue("email")
email2 := r.PostFormValue("email2")
if len(user.Email) == 0 || len(email2) == 0 {
http.Error(w, "Bitte die Emailadresse ausfüllen.", http.StatusBadRequest)
return
}
if user.Email != email2 {
http.Error(w, "Die Emailadressen stimmen nicht überein.", http.StatusBadRequest)
return return
} }
newPass := r.PostFormValue("password") newPass := r.PostFormValue("password")
newPass2 := r.PostFormValue("password2") newPass2 := r.PostFormValue("password2")
if newPass != newPass2 {
http.Error(w, "Die Passwörter stimmen nicht überein.", http.StatusBadRequest)
return
}
userString, stringLen, ok := checkUserStrings(user) userString, stringLen, ok := checkUserStrings(user)
if !ok { if !ok {
@ -368,14 +420,17 @@ func UpdateUser(c *b.Config, db *b.DB, s *b.CookieStore) http.HandlerFunc {
return return
} }
if err = db.UpdateUserAttributes(user.ID, user.UserName, user.FirstName, user.LastName, newPass, newPass2, user.Role); err != nil { if err = db.UpdateUserAttributes(c, user.ID, user.UserName, user.FirstName, user.LastName, user.Email, newPass, user.Role); err != nil {
log.Println("error: user:", user.ID, err) log.Println("error: user:", user.ID, err)
http.Error(w, "Benutzerdaten konnten nicht aktualisiert werden.", http.StatusInternalServerError) http.Error(w, "Benutzerdaten konnten nicht aktualisiert werden.", http.StatusInternalServerError)
return return
} }
data := new(struct{ Role int })
data.Role = session.Values["role"].(int)
tmpl := template.Must(template.ParseFiles(c.WebDir + "/templates/hub.html")) tmpl := template.Must(template.ParseFiles(c.WebDir + "/templates/hub.html"))
if err = tmpl.ExecuteTemplate(w, "page-content", session.Values["role"].(int)); err != nil { if err = tmpl.ExecuteTemplate(w, "page-content", data); err != nil {
log.Println(err) log.Println(err)
http.Error(w, err.Error(), http.StatusInternalServerError) http.Error(w, err.Error(), http.StatusInternalServerError)
return return
@ -405,9 +460,12 @@ func DeleteUser(c *b.Config, db *b.DB, s *b.CookieStore) http.HandlerFunc {
return return
} }
data := new(struct{ Role int })
data.Role = session.Values["role"].(int)
tmpl, err := template.ParseFiles(c.WebDir + "/templates/hub.html") tmpl, err := template.ParseFiles(c.WebDir + "/templates/hub.html")
tmpl = template.Must(tmpl, err) tmpl = template.Must(tmpl, err)
if err = tmpl.ExecuteTemplate(w, "page-content", session.Values["role"].(int)); err != nil { if err = tmpl.ExecuteTemplate(w, "page-content", data); err != nil {
log.Println(err) log.Println(err)
http.Error(w, err.Error(), http.StatusInternalServerError) http.Error(w, err.Error(), http.StatusInternalServerError)
return return

View File

@ -34,13 +34,13 @@ func main() {
} }
defer db.Close() defer db.Close()
key, err := b.LoadKey(config.KeyFile) key, err := b.LoadKey(config.GOBKeyFile)
if err != nil { if err != nil {
key, err = b.NewKey() key, err = b.NewKey()
if err != nil { if err != nil {
log.Fatalln(err) log.Fatalln(err)
} }
if err = b.SaveKey(key, config.KeyFile); err != nil { if err = b.SaveKey(key, config.GOBKeyFile); err != nil {
log.Fatalln(err) log.Fatalln(err)
} }
} }
@ -72,7 +72,6 @@ func main() {
mux.HandleFunc("GET /logout", f.Logout(config, store)) mux.HandleFunc("GET /logout", f.Logout(config, store))
mux.HandleFunc("GET /pdf/get-list", c.ServePDFList(config)) mux.HandleFunc("GET /pdf/get-list", c.ServePDFList(config))
mux.HandleFunc("GET /pdf/serve/{id}", c.ServePDF(config)) mux.HandleFunc("GET /pdf/serve/{id}", c.ServePDF(config))
mux.HandleFunc("GET /rss/serve", c.ServeRSS(config))
mux.HandleFunc("GET /tag/create", f.CreateTag(config, store)) mux.HandleFunc("GET /tag/create", f.CreateTag(config, store))
mux.HandleFunc("GET /user/create", f.CreateUser(config, store)) mux.HandleFunc("GET /user/create", f.CreateUser(config, store))
mux.HandleFunc("GET /user/delete/{id}", f.DeleteUser(config, db, store)) mux.HandleFunc("GET /user/delete/{id}", f.DeleteUser(config, db, store))

View File

@ -5,12 +5,14 @@ DROP TABLE IF EXISTS issues;
DROP TABLE IF EXISTS users; DROP TABLE IF EXISTS users;
CREATE TABLE users ( CREATE TABLE users (
id INT AUTO_INCREMENT, id INT AUTO_INCREMENT,
username VARCHAR(15) NOT NULL UNIQUE, username VARCHAR(255) NOT NULL UNIQUE,
password VARCHAR(60) NOT NULL, password VARCHAR(60) NOT NULL,
first_name VARCHAR(50) NOT NULL, first_name VARCHAR(255) NOT NULL,
last_name VARCHAR(50) NOT NULL, last_name VARCHAR(255) NOT NULL,
role INT NOT NULL, email VARCHAR(255) NOT NULL,
-- profile_pic_link VARCHAR(255) NOT NULL,
role INT NOT NULL,
PRIMARY KEY (id) PRIMARY KEY (id)
); );
@ -24,12 +26,9 @@ CREATE TABLE articles (
id INT AUTO_INCREMENT, id INT AUTO_INCREMENT,
title VARCHAR(255) NOT NULL, title VARCHAR(255) NOT NULL,
created TIMESTAMP DEFAULT CURRENT_TIMESTAMP, created TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
banner_link VARCHAR(255), banner_link VARCHAR(255) NOT NULL,
summary TEXT NOT NULL, summary TEXT NOT NULL,
content_link VARCHAR(255), content_link VARCHAR(255) NOT NULL,
enc_url VARCHAR(255),
enc_length INT,
enc_type VARCHAR(255),
published BOOL NOT NULL, published BOOL NOT NULL,
rejected BOOL NOT NULL, rejected BOOL NOT NULL,
author_id INT NOT NULL, author_id INT NOT NULL,

View File

@ -32,11 +32,11 @@ textarea {
} }
.btn { .btn {
@apply bg-slate-200 dark:bg-slate-800 hover:bg-slate-100 dark:hover:bg-slate-900 border border-slate-200 dark:border-slate-800 my-2 px-3 py-2 rounded-md w-full; @apply bg-slate-200 dark:bg-slate-800 hover:bg-slate-100 dark:hover:bg-slate-900 border border-slate-200 dark:border-slate-800 cursor-pointer my-2 px-3 py-2 rounded-md w-full;
} }
.action-btn { .action-btn {
@apply bg-slate-800 dark:bg-slate-200 hover:bg-slate-700 dark:hover:bg-slate-300 my-2 px-3 py-2 rounded-md text-slate-50 dark:text-slate-950 w-full; @apply bg-slate-800 dark:bg-slate-200 hover:bg-slate-700 dark:hover:bg-slate-300 cursor-pointer my-2 px-3 py-2 rounded-md text-slate-50 dark:text-slate-950 w-full;
} }
.EasyMDEContainer .CodeMirror { .EasyMDEContainer .CodeMirror {

View File

@ -7,22 +7,36 @@
<label for="username">Benutzername</label> <label for="username">Benutzername</label>
<input class="w-full" required name="username" type="text" value="{{.UserName}}" /> <input class="w-full" required name="username" type="text" value="{{.UserName}}" />
</div> </div>
<div> <div>
<label for="password">Passwort</label> <label for="password">Passwort</label>
<input class="w-full" required name="password" placeholder="***" type="password" /> <input class="w-full" required name="password" placeholder="***" type="password" />
</div> </div>
<div> <div>
<label for="password2">Passwort wiederholen</label> <label for="password2">Passwort wiederholen</label>
<input class="w-full" required name="password2" placeholder="***" type="password" /> <input class="w-full" required name="password2" placeholder="***" type="password" />
</div> </div>
<div> <div>
<label for="first-name">Vorname</label> <label for="first-name">Vorname</label>
<input class="w-full" required name="first-name" type="text" value="{{.FirstName}}" /> <input class="w-full" required name="first-name" type="text" value="{{.FirstName}}" />
</div> </div>
<div> <div>
<label for="last-name">Nachname</label> <label for="last-name">Nachname</label>
<input class="w-full" required name="last-name" type="text" value="{{.LastName}}" /> <input class="w-full" required name="last-name" type="text" value="{{.LastName}}" />
</div> </div>
<div>
<label for="email">Emailadresse</label>
<input class="w-full" required name="email" type="text" value="{{.Email}}" />
</div>
<div>
<label for="email2">Emailadresse wiederholen</label>
<input class="w-full" required name="email2" type="text" value="{{.Email}}" />
</div>
</div> </div>
<div class="flex flex-wrap gap-4"> <div class="flex flex-wrap gap-4">

View File

@ -13,7 +13,7 @@
</div> </div>
<div class="grid grid-cols-1 items-center"> <div class="grid grid-cols-1 items-center">
<label class="btn cursor-pointer text-center" for="issue-banner-upload">Titelbild</label> <label class="btn text-center" for="issue-banner-upload">Titelbild</label>
<input class="hidden" id="issue-banner-upload" name="issue-banner" type="file" required <input class="hidden" id="issue-banner-upload" name="issue-banner" type="file" required
hx-post="/issue/upload-banner" hx-target="#issue-banner-container" /> hx-post="/issue/upload-banner" hx-target="#issue-banner-container" />
</div> </div>

View File

@ -32,6 +32,16 @@
<label for="password2">Passwort wiederholen</label> <label for="password2">Passwort wiederholen</label>
<input class="w-full" name="password2" placeholder="***" type="password" /> <input class="w-full" name="password2" placeholder="***" type="password" />
</div> </div>
<div>
<label for="email">Emailadresse</label>
<input class="w-full" required name="email" type="text" value="{{.Email}}" />
</div>
<div>
<label for="email2">Emailadresse wiederholen</label>
<input class="w-full" required name="email2" type="text" value="{{.Email}}" />
</div>
</div> </div>
<div class="btn-area"> <div class="btn-area">

View File

@ -27,6 +27,16 @@
<label for="password2">Passwort wiederholen</label> <label for="password2">Passwort wiederholen</label>
<input class="w-full" name="password2" placeholder="***" type="password" /> <input class="w-full" name="password2" placeholder="***" type="password" />
</div> </div>
<div>
<label for="email">Emailadresse</label>
<input class="w-full" required name="email" type="text" value="{{.Email}}" />
</div>
<div>
<label for="email2">Emailadresse wiederholen</label>
<input class="w-full" required name="email2" type="text" value="{{.Email}}" />
</div>
</div> </div>
<div class="flex flex-wrap gap-4"> <div class="flex flex-wrap gap-4">

View File

@ -15,7 +15,7 @@
</div> </div>
<div class="grid grid-cols-1 items-center"> <div class="grid grid-cols-1 items-center">
<label class="btn cursor-pointer text-center" for="article-banner">Titelbild</label> <label class="btn text-center" for="article-banner">Titelbild</label>
<input class="hidden" id="article-banner" name="article-banner" type="file" required <input class="hidden" id="article-banner" name="article-banner" type="file" required
hx-post="/article/upload-banner" hx-target="#article-banner-container" /> hx-post="/article/upload-banner" hx-target="#article-banner-container" />
</div> </div>

View File

@ -5,23 +5,37 @@
<div class="grid grid-cols-3 gap-4"> <div class="grid grid-cols-3 gap-4">
<div> <div>
<label for="username">Benutzername</label> <label for="username">Benutzername</label>
<input class="w-full" required name="username" type="text" value="{{.UserName}}" /> <input class="w-full" required name="username" type="text" />
</div> </div>
<div> <div>
<label for="password">Passwort</label> <label for="password">Passwort</label>
<input class="w-full" required name="password" placeholder="***" type="password" /> <input class="w-full" required name="password" placeholder="***" type="password" />
</div> </div>
<div> <div>
<label for="password2">Passwort wiederholen</label> <label for="password2">Passwort wiederholen</label>
<input class="w-full" required name="password2" placeholder="***" type="password" /> <input class="w-full" required name="password2" placeholder="***" type="password" />
</div> </div>
<div> <div>
<label for="first-name">Vorname</label> <label for="first-name">Vorname</label>
<input class="w-full" required name="first-name" type="text" value="{{.FirstName}}" /> <input class="w-full" required name="first-name" type="text" />
</div> </div>
<div> <div>
<label for="last-name">Nachname</label> <label for="last-name">Nachname</label>
<input class="w-full" required name="last-name" type="text" value="{{.LastName}}" /> <input class="w-full" required name="last-name" type="text" />
</div>
<div>
<label for="email">Emailadresse</label>
<input class="w-full" required name="email" type="text" />
</div>
<div>
<label for="email2">Emailadresse wiederholen</label>
<input class="w-full" required name="email2" type="text" />
</div> </div>
</div> </div>

View File

@ -2,30 +2,31 @@
<div class="flex flex-col gap-4"> <div class="flex flex-col gap-4">
<button class="btn" hx-get="/logout" hx-target="#page-content">Abmelden</button> <button class="btn" hx-get="/logout" hx-target="#page-content">Abmelden</button>
{{if lt . 4}} {{if lt .Role 4}}
<div class="mb-3"> <div class="mb-3">
<h2>Artikel</h2> <h2>Artikel</h2>
<div class="grid grid-cols-1 md:grid-cols-2 gap-x-4 gap-y-2"> <div class="grid grid-cols-1 md:grid-cols-2 gap-x-4 gap-y-2">
<button class="btn" hx-get="/article/write" hx-target="#page-content">Artikel schreiben</button> <button class="btn" hx-get="/article/write" hx-target="#page-content">Artikel schreiben</button>
<button class="btn" hx-get="/article/all-rejected" hx-target="#page-content">Artikel bearbeiten</button> <button class="btn" hx-get="/article/all-rejected" hx-target="#page-content">Artikel bearbeiten</button>
{{if lt . 3}}<button class="btn" hx-get="/article/all-unpublished-unrejected-and-published-rejected" {{if lt .Role 3}}<button class="btn" hx-get="/article/all-unpublished-unrejected-and-published-rejected"
hx-target="#page-content">Artikel veröffentlichen</button>{{end}} hx-target="#page-content">Artikel veröffentlichen</button>{{end}}
{{if lt . 2}}<button class="btn" hx-get="/article/all-published/delete" hx-target="#page-content">Artikel {{if lt .Role 2}}<button class="btn" hx-get="/article/all-published/delete"
löschen</button>{{end}} hx-target="#page-content">Artikel löschen</button>{{end}}
{{if lt . 2}}<button class="btn" hx-get="/article/all-published/review-edit" {{if lt .Role 2}}<button class="btn" hx-get="/article/all-published/review-edit"
hx-target="#page-content">Artikel bearbeiten lassen</button>{{end}} hx-target="#page-content">Artikel bearbeiten lassen</button>{{end}}
{{if lt . 3}}<button class="btn" hx-get="/tag/create" hx-target="#page-content">Neuer Tag</button>{{end}} {{if lt .Role 3}}<button class="btn" hx-get="/tag/create" hx-target="#page-content">Neuer
Tag</button>{{end}}
</div> </div>
</div> </div>
{{end}} {{end}}
{{if lt . 2}} {{if lt .Role 2}}
<div class="mb-3"> <div class="mb-3">
<h2>Ausgabe</h2> <h2>Ausgabe</h2>
<div class="grid grid-cols-1 md:grid-cols-2 gap-x-4 gap-y-2"> <div class="grid grid-cols-1 md:grid-cols-2 gap-x-4 gap-y-2">
<button class="btn" hx-get="/issue/this" hx-target="#page-content">Diese Ausgabe</button> <button class="btn" hx-get="/issue/this" hx-target="#page-content">Diese Ausgabe</button>
<form class="flex" hx-encoding="multipart/form-data"> <form class="flex" hx-encoding="multipart/form-data">
<label class="btn cursor-pointer text-center" for="pdf-upload">PDF hochladen</label> <label class="btn text-center" for="pdf-upload">PDF hochladen</label>
<input accept=".pdf" class="hidden" id="pdf-upload" name="pdf-upload" type="file" <input accept=".pdf" class="hidden" id="pdf-upload" name="pdf-upload" type="file"
hx-post="/pdf/upload" /> hx-post="/pdf/upload" />
</form> </form>
@ -33,16 +34,16 @@
{{end}} {{end}}
</div> </div>
{{if lt . 4}} {{if lt .Role 4}}
<div class="mb-3"> <div class="mb-3">
<h2>Benutzer</h2> <h2>Benutzer</h2>
<div class="grid grid-cols-1 md:grid-cols-2 gap-x-4 gap-y-2"> <div class="grid grid-cols-1 md:grid-cols-2 gap-x-4 gap-y-2">
<button class="btn" hx-get="/user/edit/self" hx-target="#page-content">Mein Profil bearbeiten</button> <button class="btn" hx-get="/user/edit/self" hx-target="#page-content">Mein Profil bearbeiten</button>
{{if eq . 0}}<button class="btn" hx-get="/user/create" hx-target="#page-content">Benutzer {{if eq .Role 0}}<button class="btn" hx-get="/user/create" hx-target="#page-content">Benutzer
hinzufügen</button>{{end}} hinzufügen</button>{{end}}
{{if eq . 0}}<button class="btn" hx-get="/user/show-all/edit" hx-target="#page-content">Benutzer {{if eq .Role 0}}<button class="btn" hx-get="/user/show-all/edit" hx-target="#page-content">Benutzer
bearbeiten</button>{{end}} bearbeiten</button>{{end}}
{{if eq . 0}}<button class="btn" hx-get="/user/show-all/delete" hx-target="#page-content">Benutzer {{if eq .Role 0}}<button class="btn" hx-get="/user/show-all/delete" hx-target="#page-content">Benutzer
löschen</button>{{end}} löschen</button>{{end}}
</div> </div>
</div> </div>

View File

@ -39,7 +39,7 @@
<footer class="text-center text-gray-500 my-8"> <footer class="text-center text-gray-500 my-8">
<p>&copy; 2024 Jason Streifling. Alle Rechte vorbehalten.</p> <p>&copy; 2024 Jason Streifling. Alle Rechte vorbehalten.</p>
<p>v0.13.0 - <strong>Alpha: Drastische Änderungen und Fehler vorbehalten.</strong></p> <p>{{.Version}} - <strong>Alpha: Drastische Änderungen und Fehler vorbehalten.</strong></p>
</footer> </footer>
<script src="https://unpkg.com/htmx.org@2.0.2"></script> <script src="https://unpkg.com/htmx.org@2.0.2"></script>