commit de52d92eb72b6b539f4d4f850723d31422194711 Author: Vicente Ferrari Smith Date: Mon May 13 17:55:11 2024 +0000 initial commit git-svn-id: svn://losandesgames.com/alfheim-website@1 15359d88-9307-4e75-a9c1-e5686e5897df diff --git a/account/index.html b/account/index.html new file mode 100644 index 0000000..eccdfec --- /dev/null +++ b/account/index.html @@ -0,0 +1,6 @@ +{{define "body"}} +hello +
{{.Username}}
+
{{.Color}}
+whut +{{end}} diff --git a/alfheimgame b/alfheimgame new file mode 100755 index 0000000..8d99515 Binary files /dev/null and b/alfheimgame differ diff --git a/base.html b/base.html new file mode 100644 index 0000000..7e336e7 --- /dev/null +++ b/base.html @@ -0,0 +1,36 @@ + + + + + Alfheim + + + + + + + + + +
+ +
+ +
+ {{template "body" .}} +
+ + + + diff --git a/favicon.ico b/favicon.ico new file mode 100755 index 0000000..fd13ee6 Binary files /dev/null and b/favicon.ico differ diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..acf5126 --- /dev/null +++ b/go.mod @@ -0,0 +1,11 @@ +module alfheimgame + +go 1.22.2 + +require ( + github.com/gorilla/sessions v1.2.2 + github.com/lib/pq v1.10.9 + golang.org/x/crypto v0.23.0 +) + +require github.com/gorilla/securecookie v1.1.2 // indirect diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..c160a72 --- /dev/null +++ b/go.sum @@ -0,0 +1,10 @@ +github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= +github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/gorilla/securecookie v1.1.2 h1:YCIWL56dvtr73r6715mJs5ZvhtnY73hBvEF8kXD8ePA= +github.com/gorilla/securecookie v1.1.2/go.mod h1:NfCASbcHqRSY+3a8tlWJwsQap2VX5pwzwo4h3eOamfo= +github.com/gorilla/sessions v1.2.2 h1:lqzMYz6bOfvn2WriPUjNByzeXIlVzURcPmgMczkmTjY= +github.com/gorilla/sessions v1.2.2/go.mod h1:ePLdVu+jbEgHH+KWw8I1z2wqd0BAdAQh/8LRvBeoNcQ= +github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw= +github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= +golang.org/x/crypto v0.23.0 h1:dIJU/v2J8Mdglj/8rJ6UUOM3Zc9zLZxVZwwxMooUSAI= +golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8= diff --git a/handlers.go b/handlers.go new file mode 100644 index 0000000..60c8bdd --- /dev/null +++ b/handlers.go @@ -0,0 +1,180 @@ +package main + +import "fmt" +import "log" +import "net/http" +import "html/template" +import "strconv" +import "strings" +import "unicode/utf8" +import "alfheimgame/models" + +type templatedata struct { + Formerrors map[string]string +} + +func favicon(w http.ResponseWriter, r *http.Request) { + http.ServeFile(w, r, "favicon.ico") +} + +func home(w http.ResponseWriter, r *http.Request) { + if r.URL.Path != "/" { + http.NotFound(w, r); + return + } + + text, err := template.ParseFiles("base.html", "index.html") + if err != nil { + http.Error(w, "Internal Server Error", 500) + log.Fatal(err) + } + + switch r.Method { + case http.MethodGet: + + err = text.Execute(w, true) + if err != nil { + log.Fatal(err) + http.Error(w, "Internal Server Error", 500) + } + + case http.MethodPost: + data := LoginData{username: r.FormValue("username"), password: r.FormValue("password")} + fmt.Println(data) + err = text.Execute(w, false) + if err != nil { + log.Fatal(err) + http.Error(w, "Internal Server Error", 500) + } + } +} + +func login(w http.ResponseWriter, r *http.Request) { + text, err := template.ParseFiles("base.html", "login/index.html") + if err != nil { + http.Error(w, "Internal Server Error", 500) + log.Fatal(err) + } + + switch r.Method { + case http.MethodGet: + + text.Execute(w, templatedata{}) + if err != nil { + log.Fatal(err) + http.Error(w, "Internal Server Error", 500) + } + + case http.MethodPost: + logindata := LoginData{username: r.FormValue("username"), password: r.FormValue("password")} + + errors := make(map[string]string) + + if strings.TrimSpace(logindata.username) == "" { + errors["username"] = "This field cannot be blank" + } else if utf8.RuneCountInString(logindata.username) > 20 { + errors["username"] = "This field is too long (the maximum is 20 characters)" + } + + if strings.TrimSpace(logindata.password) == "" { + errors["password"] = "This field cannot be blank" + } else if utf8.RuneCountInString(logindata.password) < 8 { + errors["password"] = "This field is too short (the minimum is 8 characters)" + } + + if len(errors) > 0 { + + text.Execute(w, templatedata{errors}) + if err != nil { + log.Fatal(err) + http.Error(w, "Internal Server Error", 500) + } + } + } +} + +func register(w http.ResponseWriter, r *http.Request) { + text, err := template.ParseFiles("base.html", "register/index.html") + if err != nil { + http.Error(w, "Internal Server Error", 500) + log.Fatal(err) + } + + switch r.Method { + case http.MethodGet: + + text.Execute(w, templatedata{}) + if err != nil { + log.Fatal(err) + http.Error(w, "Internal Server Error", 500) + } + + case http.MethodPost: + account := models.Account{Username: r.FormValue("username"), Password: []byte(r.FormValue("password")), Firstname: r.FormValue("firstname"), Lastname: r.FormValue("lastname"), Email: r.FormValue("email")} + + errors := make(map[string]string) + + if strings.TrimSpace(account.Username) == "" { + errors["username"] = "This field cannot be blank" + } else if utf8.RuneCountInString(account.Username) > 20 { + errors["username"] = "This field is too long (the maximum is 20 characters)" + } + + if strings.TrimSpace(string(account.Password)) == "" { + errors["password"] = "This field cannot be blank" + } else if utf8.RuneCountInString(string(account.Password)) < 8 { + errors["password"] = "This field is too short (the minimum is 8 characters)" + } + + if len(errors) > 0 { + + text.Execute(w, templatedata{errors}) + if err != nil { + log.Fatal(err) + http.Error(w, "Internal Server Error", 500) + } + } + + fmt.Println(account) + + users.Insert(account.Username, string(account.Password), account.Firstname, account.Lastname, account.Email) + http.Redirect(w, r, "/login", http.StatusSeeOther) + } +} + +func account(w http.ResponseWriter, r *http.Request) { + id, err := strconv.Atoi(r.URL.Query().Get("id")) + if err != nil || id < 1 { + http.NotFound(w, r) + return + } + account, err := users.Get_account(int32(id)); + if err != nil { + log.Fatal(err); + } + + text, err := template.ParseFiles("base.html", "account/index.html") + if err != nil { + http.Error(w, "Internal Server Error", 500) + log.Fatal(err) + } + + switch r.Method { + case http.MethodGet: + text.Execute(w, account) + if err != nil { + log.Fatal(err) + http.Error(w, "Internal Server Error", 500) + } + fmt.Printf("executed"); + + //case http.MethodPost: + // data := LoginData{username: r.FormValue("username"), password: r.FormValue("password")} + // fmt.Println(data) + // text.Execute(w, false) + // if err != nil { + // log.Fatal(err) + // http.Error(w, "Internal Server Error", 500) + // } + } +} diff --git a/index.html b/index.html new file mode 100644 index 0000000..7d6e6f5 --- /dev/null +++ b/index.html @@ -0,0 +1,3 @@ +{{define "body"}} +
This is a great game coming soon.
+{{end}} diff --git a/login/index.html b/login/index.html new file mode 100644 index 0000000..78ed925 --- /dev/null +++ b/login/index.html @@ -0,0 +1,35 @@ +{{define "body"}} +
+
+

Log in

+ + {{with .Formerrors.username}} + + {{end}} + +
+ +
+
+ + {{with .Formerrors.password}} + + {{end}} + +
+ +
+
+ + +
+ Have you forgotten your password? + + +
+
+{{end}} diff --git a/main.go b/main.go new file mode 100644 index 0000000..8a7cb17 --- /dev/null +++ b/main.go @@ -0,0 +1,101 @@ +package main + +import "fmt" +import "log" +import "flag" +import "net/http" +import _ "github.com/lib/pq" +import "database/sql" +import "github.com/gorilla/sessions" +import "regexp" +import "alfheimgame/models" +//import "golang.org/x/crypto/bcrypt" + +var users *models.Usermodel + +var key = []byte("super-secret-key") +var store = sessions.NewCookieStore(key) + +var emailrx = regexp.MustCompile("/^[a-zA-Z0-9.!#$%&'*+\\/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$/"); + +type LoginData struct { + username string + password string +} + + +func secureheaders(next http.Handler) http.Handler { + fn := func(w http.ResponseWriter, r *http.Request) { + w.Header().Set("X-XSS-Protection", "1; mode=block") + w.Header().Set("X-Frame-Options", "deny") + + next.ServeHTTP(w, r) + } + + return http.HandlerFunc(fn) +} + +func main() { + addr := flag.String("addr", ":8080", "HTTP network address") + flag.Parse() + fmt.Println("Hello, Sailor!") + + var err error + db, err := sql.Open("postgres", "postgres://elves_database:iK2SoVbDhdCki5n3LxGyP6zKpLspt4@80.240.25.87/elves_database") + if err != nil { + log.Fatal(err) + } + defer db.Close() + + users = &models.Usermodel{db} + + mux := http.NewServeMux() + + + //rows, err := db.Query("SELECT * FROM accounts") + //if err != nil { + // log.Fatal(err) + //} + //defer rows.Close() + + //accounts := make([]*Account, 0) + //for rows.Next() { + // acc := new(Account) + // err := rows.Scan(&acc.id, &acc.Username, &acc.password, &acc.Color) + // if err != nil { + // log.Fatal(err) + // } + // accounts = append(accounts, acc) + //} + + //if err = rows.Err(); err != nil { + // log.Fatal(err) + //} + + //for _, acc := range accounts { + // fmt.Println(acc) + //} + + mux.Handle("/static/", http.StripPrefix("/static/", http.FileServer(http.Dir("static")))) + + mux.HandleFunc("/favicon.ico", favicon) + + mux.HandleFunc("/", home) + mux.HandleFunc("/login", login) + mux.HandleFunc("/register", register) + mux.HandleFunc("/account", account) + + + + log.Fatal(http.ListenAndServe(*addr, secureheaders(mux))) +} + +//cookie := http.Cookie{ +// Name: "exampleCookie", +// Value: "Hello world!", +// Path: "/", +// HttpOnly: true, +// Secure: true, +// SameSite: http.SameSiteLaxMode, +//} +//http.SetCookie(w, &cookie) diff --git a/models/models.go b/models/models.go new file mode 100644 index 0000000..9e83fd4 --- /dev/null +++ b/models/models.go @@ -0,0 +1,61 @@ +package models + +import "errors" +import "time" +import "golang.org/x/crypto/bcrypt" +import "database/sql" +import _ "github.com/lib/pq" + +var Errnorecord = errors.New("no matching record found") +var Errinvalidcredentials = errors.New("invalid credentials") +var ErrDuplicateemail = errors.New("duplicate email") + +type Account struct { + Id int32 + Username string + Password []byte + Color int32 + Firstname string + Lastname string + Email string + Created time.Time +} + +type Usermodel struct { + DB *sql.DB +} + +func (m *Usermodel) Insert(username string, password string, firstname string, lastname string, email string) error { + hashedpassword, err := bcrypt.GenerateFromPassword([]byte(password), 12) + + stmt := `INSERT INTO accounts (username, password, firstname, lastname, email, created) VALUES ($1, $2, $3, $4, $5, NOW());` + + _, err = m.DB.Exec(stmt, username, string(hashedpassword), firstname, lastname, email) + if err != nil { + + } + + return nil +} + +func (m *Usermodel) Get_account(id int32) (Account, error) { + stmt := `SELECT id, username, password, color FROM accounts WHERE id = $1;` + row := m.DB.QueryRow(stmt, id) + var account Account + err := row.Scan(&account.Id, &account.Username, &account.Password, &account.Color) + if err == sql.ErrNoRows { + return Account{}, sql.ErrNoRows + } else if err != nil { + return Account{}, err + } + + return account, nil +} + +func (m *Usermodel) Authenticate(username string, password string) (int, error) { + var id int32 + var hashedpassword []byte + row := m.DB.QueryRow("SELECT id, password FROM accounts WHERE username = $1", username) + err := row.Scan(&id, &hashedpassword) + +} diff --git a/neovide_backtraces.log b/neovide_backtraces.log new file mode 100644 index 0000000..445bf8a --- /dev/null +++ b/neovide_backtraces.log @@ -0,0 +1,20 @@ +2024-04-30 13:18:48 - Neovide panicked with the message 'called `Result::unwrap()` on an `Err` value: Connection(IoError(Custom { kind: Other, error: UnknownError }))'. (File: /build/.cargo/registry/src/index.crates.io-6f17d22bba15001f/winit-0.29.10/src/platform_impl/linux/x11/window.rs; Line: 1316, Column: 14) + 0: + 1: + 2: + 3: + 4: + 5: + 6: + 7: + 8: + 9: + 10: + 11: + 12: + 13: + 14: + 15: + 16: __libc_start_main + 17: + diff --git a/register/index.html b/register/index.html new file mode 100644 index 0000000..089e96d --- /dev/null +++ b/register/index.html @@ -0,0 +1,45 @@ +{{define "body"}} +
+
+

Register

+ + {{with .Formerrors.username}} + + {{end}} + +
+ +
+
+ + +
+ +
+
+ +
+ +
+
+ +
+ +
+
+ + {{with .Formerrors.password}} + + {{end}} + +
+ +
+
+ + +
+
+{{end}} diff --git a/static/image.png b/static/image.png new file mode 100644 index 0000000..0475eda Binary files /dev/null and b/static/image.png differ diff --git a/static/style.css b/static/style.css new file mode 100644 index 0000000..52b771c --- /dev/null +++ b/static/style.css @@ -0,0 +1,156 @@ +html { + height: 100%; +} +body { + background-color: #3475CB; + font-family: "Vollkorn"; + color: white; + margin: 0px; + background: no-repeat url(/static/image.png); + background-size: cover; + background-position: center; + height: 100%; +} + +h1 { + font-size: 96px; + margin: 0px; +} + +nav { + display: flex; + align-items: center; + justify-content: space-around; + background-color: white; +} + +.navbuttons a { + display: inline-block; +} + +.maintitle { + color: black; +} + +.loginbutton { + align-self: center; + display: flex; + background-color: #3475CB; + padding-top: 15px; + padding-right: 20px; + padding-bottom: 15px; + padding-left: 20px; + border-radius: 5px; + color: black; + /*box-shadow: 10px 10px 5px lightblue;*/ +} + +main { + margin: 8px; + display: flex; + justify-content: center; + align-content: center; + max-width: 900px; + margin-left: auto; + margin-right: auto; +} + +.wrapper { + background: transparent; + border: 2px solid white; + backdrop-filter: blur(20px); + box-shadow: 0 0 10px rgba(0, 0, 0, 0.2); + border-radius: 15px; + padding: 30px 40px; + +} + +.wrapper h1 { + font-size: xx-large; + text-align: center; +} + +.wrapper .input-box { + width: 100%; +} + +.input-box input { + box-sizing: border-box; + background: transparent; + width: 100%; + height: 100%; + outline: none; + border: 2px solid rgba(255, 255, 255, .2); + border-radius: 25px; + padding: 10px 45px 10px 20px +} + +.input-box input::placeholder { + color: white; +} + +.wrapper .input-error { + width: 100%; +} + +.error { + color: rgba(240, 0, 0, .8); +} + +.input-error input { + box-sizing: border-box; + background: transparent; + width: 100%; + height: 100%; + outline: none; + border: 2px solid rgba(255, 0, 0, .2); + border-radius: 25px; + padding: 10px 45px 10px 20px +} + +.input-error input::placeholder { + color: red; +} + +.login-btn-wrapper { + display: flex; + justify-content: center; +} + +.wrapper .btn { + width: 100%; + outline: none; + border: none; + border-radius: 20px; +} + +.wrapper .register-link { + display: flex; + justify-content: center; +} + +footer { + position: fixed; + left: 0; + bottom: 0; + width: 100%; + background-color: #132123; + color: white; + text-align: center; +} + +a:link { + text-decoration: none; +} + +.wrapper a:link { + text-decoration: none; +} + +.wrapper a:visited { + text-decoration: none; +} + +.wrapper a:hover { + text-decoration: underline; +}