better castle
This commit is contained in:
21
main.go
Normal file
21
main.go
Normal file
@@ -0,0 +1,21 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
|
||||
"github.com/alexedwards/argon2id"
|
||||
)
|
||||
|
||||
func main() {
|
||||
password := "mmmmmmmm"
|
||||
|
||||
// Use default recommended parameters
|
||||
hash, err := argon2id.CreateHash(password, argon2id.DefaultParams)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
fmt.Println("Argon2 Hash:")
|
||||
fmt.Println(hash)
|
||||
}
|
||||
67
server/db/db.go
Normal file
67
server/db/db.go
Normal file
@@ -0,0 +1,67 @@
|
||||
package db
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
|
||||
"github.com/alexedwards/argon2id"
|
||||
)
|
||||
|
||||
var DB map[string]interface{}
|
||||
|
||||
type GetService struct{}
|
||||
var Get = GetService{}
|
||||
|
||||
func (g GetService) User(id string) (map[string]interface{}, error) {
|
||||
raw, ok := DB[id]
|
||||
if !ok {
|
||||
return nil, errors.New("user not found")
|
||||
}
|
||||
|
||||
userData, ok := raw.(map[string]interface{})
|
||||
log.Println(userData)
|
||||
if !ok {
|
||||
return nil, errors.New("user data is not in expected format")
|
||||
}
|
||||
|
||||
return userData, nil
|
||||
}
|
||||
|
||||
func InitDB() error {
|
||||
file, err := os.Open("../db/users.json")
|
||||
if err != nil {
|
||||
fmt.Println("Error opening file:", err)
|
||||
return errors.New("Failed to read db")
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
var data interface{}
|
||||
|
||||
err = json.NewDecoder(file).Decode(&data)
|
||||
if err != nil {
|
||||
fmt.Println("Error decoding JSON:", err)
|
||||
return errors.New("Failed to decode db")
|
||||
}
|
||||
|
||||
result, ok := data.(map[string]interface{})
|
||||
if !ok {
|
||||
fmt.Println("Data is not a JSON object (map)")
|
||||
return errors.New("Db is in the wrong format")
|
||||
}
|
||||
|
||||
DB = result
|
||||
|
||||
// Use default recommended parameters
|
||||
hash, err := argon2id.CreateHash("hunter2", argon2id.DefaultParams)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
fmt.Println("Argon2 Hash:")
|
||||
fmt.Println(hash)
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -1,8 +1,8 @@
|
||||
module hyperia
|
||||
|
||||
go 1.23.0
|
||||
go 1.24.0
|
||||
|
||||
toolchain go1.24.2
|
||||
toolchain go1.24.5
|
||||
|
||||
require (
|
||||
github.com/alexedwards/argon2id v1.0.0
|
||||
@@ -23,6 +23,11 @@ require (
|
||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 // indirect
|
||||
github.com/modern-go/reflect2 v1.0.2 // indirect
|
||||
github.com/yuin/goldmark v1.7.13 // indirect
|
||||
golang.org/x/crypto v0.14.0 // indirect
|
||||
golang.org/x/mod v0.27.0 // indirect
|
||||
golang.org/x/sys v0.35.0 // indirect
|
||||
golang.org/x/tools v0.36.1-0.20250903222949-a5c0eb837c9f // indirect
|
||||
golang.org/x/tools/cmd/godoc v0.1.0-deprecated // indirect
|
||||
golang.org/x/tools/godoc v0.1.0-deprecated // indirect
|
||||
)
|
||||
|
||||
@@ -46,12 +46,16 @@ github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UV
|
||||
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
|
||||
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
||||
github.com/yuin/goldmark v1.7.13 h1:GPddIs617DnBLFFVJFgpo1aBfe/4xcvMc3SB5t/D0pA=
|
||||
github.com/yuin/goldmark v1.7.13/go.mod h1:ip/1k0VRfGynBgxOz0yCqHrbZXhcjxyuS66Brc7iBKg=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/crypto v0.14.0 h1:wBqGXzWJW6m1XrIKlAH0Hs1JJ7+9KBwnIO8v66Q9cHc=
|
||||
golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4=
|
||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
||||
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||
golang.org/x/mod v0.27.0 h1:kb+q2PyFnEADO2IEF935ehFUXlWiNjJWtRNgBLSfbxQ=
|
||||
golang.org/x/mod v0.27.0/go.mod h1:rWI627Fq0DEoudcK+MBkNkCe0EetEaDSwJJkCcjpazc=
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||
@@ -88,6 +92,12 @@ golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGm
|
||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
||||
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
|
||||
golang.org/x/tools v0.36.1-0.20250903222949-a5c0eb837c9f h1:jDEaVlf+r7N8Re8Es5pGylGkfnqcx9dfUCsd1T+biTs=
|
||||
golang.org/x/tools v0.36.1-0.20250903222949-a5c0eb837c9f/go.mod h1:n+8pplxVZfXnmHBxWsfPnQRJ5vWroQDk+U2MFpjwtFY=
|
||||
golang.org/x/tools/cmd/godoc v0.1.0-deprecated h1:sEGTwp9aZNTHsdf/2BGaRqE4ZLndRVH17rbQ2OVun9Q=
|
||||
golang.org/x/tools/cmd/godoc v0.1.0-deprecated/go.mod h1:J6VY4iFch6TIm456U3fnw1EJZaIqcYlhHu6GpHQ9HJk=
|
||||
golang.org/x/tools/godoc v0.1.0-deprecated h1:o+aZ1BOj6Hsx/GBdJO/s815sqftjSnrZZwyYTHODvtk=
|
||||
golang.org/x/tools/godoc v0.1.0-deprecated/go.mod h1:qM63CriJ961IHWmnWa9CjZnBndniPt4a3CK0PVB9bIg=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
gopkg.in/natefinch/lumberjack.v2 v2.2.1 h1:bBRl1b0OH9s/DuPhuXpNl+VtCaJXFZ5/uEFST95x9zc=
|
||||
gopkg.in/natefinch/lumberjack.v2 v2.2.1/go.mod h1:YD8tP3GAjkrDg1eZH7EGmyESg/lsYskCTPBJVb9jqSc=
|
||||
|
||||
@@ -2,23 +2,24 @@ package handlers
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
// "errors"
|
||||
"errors"
|
||||
"log"
|
||||
"net/http"
|
||||
// "strings"
|
||||
|
||||
"hyperia/db"
|
||||
|
||||
"github.com/rs/zerolog/log"
|
||||
|
||||
// "github.com/alexedwards/argon2id"
|
||||
"github.com/alexedwards/argon2id"
|
||||
)
|
||||
|
||||
type loginRequest struct {
|
||||
Name string `json:"name"`
|
||||
Email string `json:"email"`
|
||||
Password string `json:"password"`
|
||||
}
|
||||
|
||||
type user struct {
|
||||
ID int `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Email string `json:"email"`
|
||||
}
|
||||
|
||||
func HandleLogin(w http.ResponseWriter, r *http.Request) {
|
||||
@@ -33,75 +34,38 @@ func HandleLogin(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
// user, err := getUserByCredentials(creds.Name, creds.Password)
|
||||
// if err != nil {
|
||||
// http.Error(w, "Unauthorized: "+err.Error(), http.StatusUnauthorized)
|
||||
// return
|
||||
// }
|
||||
user, err := getUserByCredentials(creds)
|
||||
if err != nil {
|
||||
http.Error(w, "Unauthorized: "+ err.Error(), http.StatusUnauthorized)
|
||||
return
|
||||
}
|
||||
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
http.Error(w, "Not implemented", http.StatusMethodNotAllowed)
|
||||
// json.NewEncoder(w).Encode(user)
|
||||
http.Error(w, "Not implemented", http.StatusMethodNotAllowed)
|
||||
json.NewEncoder(w).Encode(user)
|
||||
}
|
||||
|
||||
// func getUserByCredentials(name string, password string) (*user, error) {
|
||||
// var id int
|
||||
// var dbName, dbHash string
|
||||
func getUserByCredentials(loginCreds loginRequest) (map[string]interface{}, error) {
|
||||
|
||||
// name = strings.TrimSpace(strings.ToLower(name))
|
||||
// err := DB.QueryRow("SELECT id, name, password FROM users WHERE LOWER(name) = LOWER($1)", name).Scan(&id, &dbName, &dbHash)
|
||||
// if err != nil {
|
||||
// return nil, errors.New("user not found")
|
||||
// }
|
||||
|
||||
// match, err := argon2id.ComparePasswordAndHash(password, dbHash)
|
||||
// if err != nil || !match {
|
||||
// return nil, errors.New("invalid password")
|
||||
// }
|
||||
|
||||
// return &user{
|
||||
// ID: id,
|
||||
// Name: dbName,
|
||||
// }, nil
|
||||
// }
|
||||
|
||||
func HandleApplicantLogin(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != http.MethodPost {
|
||||
http.Error(w, "Only POST allowed", http.StatusMethodNotAllowed)
|
||||
return
|
||||
}
|
||||
|
||||
var creds loginRequest
|
||||
if err := json.NewDecoder(r.Body).Decode(&creds); err != nil {
|
||||
http.Error(w, "Invalid JSON", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
// exists, err := EmailExists(creds.Name)
|
||||
// if err != nil {
|
||||
// log.Err(err).Msg("error checking email")
|
||||
// http.Error(w, "Internal server error", http.StatusInternalServerError)
|
||||
// return
|
||||
// }
|
||||
// if !exists {
|
||||
// http.Error(w, "Email does not exist.", http.StatusConflict)
|
||||
// return
|
||||
// }
|
||||
|
||||
token, err := generateVerificationToken(creds.Name)
|
||||
// email := strings.TrimSpace(strings.ToLower(loginCreds.Email))
|
||||
|
||||
user, err := db.Get.User("1")
|
||||
// err := DB.QueryRow("SELECT id, name, password FROM users WHERE LOWER(name) = LOWER($1)", name).Scan(&id, &dbName, &dbHash)
|
||||
if err != nil {
|
||||
log.Err(err).Msg("error generating verification token")
|
||||
http.Error(w, "Error, please try again later.", http.StatusInternalServerError)
|
||||
return
|
||||
return nil, errors.New("user not found")
|
||||
}
|
||||
|
||||
err = sendWelcomeEmail(creds.Name, token)
|
||||
if err != nil {
|
||||
log.Err(err).Msg("error sending welcome email")
|
||||
http.Error(w, "Failed to send email", http.StatusInternalServerError)
|
||||
return
|
||||
dbPassword, ok := user["password"].(string)
|
||||
if !ok {
|
||||
return nil, errors.New("password format is invalid")
|
||||
}
|
||||
|
||||
w.WriteHeader(http.StatusOK)
|
||||
w.Write([]byte("OK"))
|
||||
}
|
||||
log.Println("pass: ", loginCreds, loginCreds.Password, dbPassword)
|
||||
|
||||
match, err := argon2id.ComparePasswordAndHash(loginCreds.Password, dbPassword)
|
||||
if err != nil || !match {
|
||||
return nil, errors.New("invalid password")
|
||||
}
|
||||
|
||||
return user, nil
|
||||
}
|
||||
@@ -5,6 +5,7 @@ import (
|
||||
"net/http"
|
||||
"path/filepath"
|
||||
"hyperia/config"
|
||||
"hyperia/db"
|
||||
"hyperia/handlers"
|
||||
"hyperia/logger"
|
||||
"runtime/debug"
|
||||
@@ -16,15 +17,14 @@ import (
|
||||
|
||||
func main() {
|
||||
config.SetConfiguration()
|
||||
|
||||
logger.ConfigureLogger()
|
||||
|
||||
// err := handlers.InitDB()
|
||||
// if err != nil {
|
||||
// log.Fatal().Msgf("failed to connect to database: %v", err)
|
||||
// } else {
|
||||
// log.Info().Msg("successfully connected to PostgreSQL")
|
||||
// }
|
||||
err := db.InitDB()
|
||||
if err != nil {
|
||||
log.Fatal().Msgf("failed to connect to database: %v", err)
|
||||
} else {
|
||||
log.Info().Msg("successfully connected to PostgreSQL")
|
||||
}
|
||||
|
||||
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
|
||||
defer func() {
|
||||
@@ -55,7 +55,7 @@ func main() {
|
||||
})
|
||||
|
||||
log.Info().Msgf("Server starting on http://localhost: %s", config.PORT)
|
||||
err := http.ListenAndServe(":"+config.PORT, nil)
|
||||
err = http.ListenAndServe(":"+config.PORT, nil)
|
||||
if err != nil {
|
||||
log.Fatal().Msgf("failed to start server: %v", err)
|
||||
}
|
||||
@@ -67,10 +67,6 @@ func handlePublic(w http.ResponseWriter, r *http.Request) {
|
||||
handlers.HandleLogin(w, r)
|
||||
return
|
||||
}
|
||||
if r.URL.Path == "/api/applicantlogin" {
|
||||
handlers.HandleApplicantLogin(w, r)
|
||||
return
|
||||
}
|
||||
if r.URL.Path == "/api/join" {
|
||||
handlers.HandleJoin(w, r)
|
||||
return
|
||||
|
||||
3094
ui/_/images/castle-dark3.svg
Normal file
3094
ui/_/images/castle-dark3.svg
Normal file
File diff suppressed because one or more lines are too long
|
After Width: | Height: | Size: 1.6 MiB |
@@ -20,12 +20,12 @@
|
||||
}}
|
||||
@media (prefers-color-scheme: dark) {
|
||||
.main-image {
|
||||
content:url("_/images/castle-dark2.svg");
|
||||
height: 120vmin; bottom: -17vmin; left: 31vw;
|
||||
content:url("_/images/castle-dark3.svg");
|
||||
height: 1000px; bottom: -34vmin; left: 26vw;
|
||||
}}
|
||||
@media (prefers-color-scheme: dark) and (max-width: 600px) {
|
||||
.main-image {
|
||||
max-width: 195vw; height: 80vh; bottom: -17vmin; left: 0vw;
|
||||
max-width: 195vw; height: 80vh; left: 0vw;
|
||||
}}
|
||||
</style>
|
||||
<script src="_/code/util.js"></script>
|
||||
@@ -44,6 +44,15 @@
|
||||
<span>|</span>
|
||||
<a href="signin">sign in</a>
|
||||
</div>
|
||||
|
||||
<div style="display: flex; flex-direction: column; top: 110vh; left: 50vw; transform: translateX(-50%); align-items: left; position: absolute;">
|
||||
<h1 style="font-family: Canterbury; font-size: 4rem; margin-left: auto">A Classical Christian Society</h1>
|
||||
<p>Hyperia is a society for people who want to uphold European tradition and Christian values.</p>
|
||||
<p>Inspired by the Classical Christian schooling movement that began in the 1990s, Hyperia aims to create a similar space for adults.</p>
|
||||
<div style="height: 20vh"></div>
|
||||
</div>
|
||||
|
||||
|
||||
<style>
|
||||
.link, a {
|
||||
transition: background-color 0.3s ease, scale 0.3s;
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
<style>
|
||||
#items {
|
||||
position: absolute;
|
||||
top: 45vh;
|
||||
top: 50vh;
|
||||
left: 50vw;
|
||||
transform: translate(-50%, -50%);
|
||||
|
||||
@@ -18,29 +18,6 @@
|
||||
text-align: center; /* ensures text inside spans is centered */
|
||||
}
|
||||
|
||||
a {
|
||||
cursor: default;
|
||||
text-decoration: underline;
|
||||
text-underline-offset: 5px;
|
||||
transition: background .02s, color .2s;
|
||||
user-select: none;
|
||||
padding: 4px;
|
||||
border-radius: 5px;
|
||||
display: inline-block; /* makes background and padding behave */
|
||||
padding: 0.2em 0.5em; /* adds breathing room */
|
||||
}
|
||||
|
||||
a:hover {
|
||||
text-decoration: none;
|
||||
background: var(--green);
|
||||
color: var(--tan);
|
||||
}
|
||||
|
||||
a:active {
|
||||
background: var(--red); /* background color works now */
|
||||
color: white; /* optional: change text color for contrast */
|
||||
}
|
||||
|
||||
input {
|
||||
background-color: transparent;
|
||||
border: none;
|
||||
@@ -78,8 +55,6 @@
|
||||
width: 50vw
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
</style>
|
||||
<script src="_/code/util.js"></script>
|
||||
<script type="module">
|
||||
@@ -91,9 +66,43 @@
|
||||
<body>
|
||||
<span id="title" onclick='console.log("hey"); window.location.href="/"'>hyperia</span>
|
||||
<div id="items">
|
||||
<input placeholder="email"></input>
|
||||
<input id="email" placeholder="email"></input>
|
||||
<br>
|
||||
<input placeholder="password"></input>
|
||||
<input id="password" placeholder="password"></input>
|
||||
<br>
|
||||
<p id="applicant-message" style="color: green; margin-left: 10px; display: inline-block; margin: 0px; margin-left: 20px; font-size: 1em"></p>
|
||||
<br>
|
||||
<div>
|
||||
</div>
|
||||
|
||||
<button onclick="login()" style="background-color: rgb(193, 135, 29)">Sign In
|
||||
<script>
|
||||
async function login() {
|
||||
const email = document.getElementById('email').value;
|
||||
const password = document.getElementById('password').value;
|
||||
const messageEl = document.getElementById('applicant-message');
|
||||
|
||||
const res = await fetch('/api/login', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({ email, password }),
|
||||
});
|
||||
|
||||
if (res.ok) {
|
||||
const data = await res.text();
|
||||
messageEl.style.color = "green"
|
||||
messageEl.textContent = "Check your email for a login link.";
|
||||
} else {
|
||||
const error = await res.text();
|
||||
console.log(error)
|
||||
messageEl.style.color = "red"
|
||||
messageEl.textContent = "Error: " + error;
|
||||
}
|
||||
}
|
||||
</script>
|
||||
</button>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,4 +1,8 @@
|
||||
See https://github.com/return-to-the-land/go-backend for instructions.
|
||||
|
||||
# Style Guidelines
|
||||
- Font size is defined in the index.html. Only use rem as a unit.
|
||||
- Font size is defined in the index.html. Only use rem as a unit.
|
||||
|
||||
# Documentation
|
||||
```go install golang.org/x/tools/cmd/godoc@latest```
|
||||
```$HOME/go/bin/godoc -http=:6060``` (to run on Mac)
|
||||
Reference in New Issue
Block a user