Replace JSON with SQLite
This commit is contained in:
parent
170416e791
commit
363f557c35
10 changed files with 101 additions and 96 deletions
2
.gitignore
vendored
2
.gitignore
vendored
|
@ -1,3 +1,3 @@
|
||||||
pye-auth
|
pye-auth
|
||||||
private.key
|
private.key
|
||||||
data.json
|
data.db
|
|
@ -9,14 +9,12 @@ with(out) blazingly fast cloud-native web3 memory-safe blockchain reactive AI
|
||||||
This should be done by **October 17th 2024**. Or, at the very least,
|
This should be done by **October 17th 2024**. Or, at the very least,
|
||||||
in a state that proves I am competent Go developer.
|
in a state that proves I am competent Go developer.
|
||||||
|
|
||||||
Note: **JSON** is used for storage at proof-of-concept stage for ease of use,
|
|
||||||
obviously I'd use **SQL** for production
|
|
||||||
|
|
||||||
## Current functionality
|
## Current functionality
|
||||||
|
|
||||||
* Port `7102`
|
* Port `7102`
|
||||||
* `POST /register` - register a user with Basic Auth
|
* `POST /register` - register a user with Basic Auth
|
||||||
* `POST /login` - get a JWT token by Basic Auth
|
* `POST /login` - get a JWT token by Basic Auth
|
||||||
* `GET /pem` - get PEM-encoded public RS256 key
|
* `GET /pem` - get PEM-encoded public RS256 key
|
||||||
* Data persistently stored in... `data.json`, for convenience
|
* Data persistently stored in an SQLite database `data.db`
|
||||||
|
(requires creation of empty db)
|
||||||
* RS256 key loaded from `private.key` file or generated on startup if missing
|
* RS256 key loaded from `private.key` file or generated on startup if missing
|
31
auth.go
31
auth.go
|
@ -7,38 +7,41 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
func ValidEmail(email string) bool {
|
func validEmail(email string) bool {
|
||||||
_, err := mail.ParseAddress(email)
|
_, err := mail.ParseAddress(email)
|
||||||
return err == nil
|
return err == nil
|
||||||
}
|
}
|
||||||
func ValidPass(pass string) bool {
|
func validPass(pass string) bool {
|
||||||
// TODO: Obviously, we *might* want something more sophisticated here
|
// TODO: Obviously, we *might* want something more sophisticated here
|
||||||
return len(pass) >= 8
|
return len(pass) >= 8
|
||||||
}
|
}
|
||||||
func EmailTaken(email string) bool {
|
|
||||||
// FIXME: Implement properly
|
|
||||||
return EmailExists(email)
|
|
||||||
}
|
|
||||||
func Register(w http.ResponseWriter, r *http.Request) {
|
func Register(w http.ResponseWriter, r *http.Request) {
|
||||||
email, password, ok := r.BasicAuth()
|
email, password, ok := r.BasicAuth()
|
||||||
|
|
||||||
if ok {
|
if ok {
|
||||||
email = strings.TrimSpace(email)
|
email = strings.TrimSpace(email)
|
||||||
password = strings.TrimSpace(password)
|
password = strings.TrimSpace(password)
|
||||||
if !(ValidEmail(email) && ValidPass(password) && !EmailTaken(email)) {
|
if !(validEmail(email) && validPass(password) && !emailExists(email)) {
|
||||||
slog.Info("Outcome",
|
slog.Debug("Outcome",
|
||||||
"email", ValidEmail(email),
|
"email", validEmail(email),
|
||||||
"pass", ValidPass(password),
|
"pass", validPass(password),
|
||||||
"taken", !EmailTaken(email))
|
"taken", !emailExists(email))
|
||||||
http.Error(w, "invalid auth credentials", http.StatusBadRequest)
|
http.Error(w, "invalid auth credentials", http.StatusBadRequest)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
user, err := NewUser(email, password)
|
user, err := NewUser(email, password)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
slog.Error("error creating a new user", "error", err)
|
slog.Error("error creating a new user", "error", err)
|
||||||
|
http.Error(w, "error creating a new user", http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = addUser(user)
|
||||||
|
if err != nil {
|
||||||
|
slog.Error("error saving a new user", "error", err)
|
||||||
|
http.Error(w, "error saving a new user", http.StatusInternalServerError)
|
||||||
|
return
|
||||||
}
|
}
|
||||||
slog.Info("user", "user", user)
|
|
||||||
AddUser(user)
|
|
||||||
w.WriteHeader(http.StatusCreated)
|
w.WriteHeader(http.StatusCreated)
|
||||||
w.Write([]byte("User created"))
|
w.Write([]byte("User created"))
|
||||||
return
|
return
|
||||||
|
@ -55,7 +58,7 @@ func Login(w http.ResponseWriter, r *http.Request) {
|
||||||
if ok {
|
if ok {
|
||||||
email = strings.TrimSpace(email)
|
email = strings.TrimSpace(email)
|
||||||
password = strings.TrimSpace(password)
|
password = strings.TrimSpace(password)
|
||||||
user, ok := ByEmail(email)
|
user, ok := byEmail(email)
|
||||||
if !ok || !user.PasswordFits(password) {
|
if !ok || !user.PasswordFits(password) {
|
||||||
http.Error(w, "you did something wrong", http.StatusUnauthorized)
|
http.Error(w, "you did something wrong", http.StatusUnauthorized)
|
||||||
return
|
return
|
||||||
|
|
73
db.go
Normal file
73
db.go
Normal file
|
@ -0,0 +1,73 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"database/sql"
|
||||||
|
"errors"
|
||||||
|
"log/slog"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
_ "github.com/mattn/go-sqlite3"
|
||||||
|
)
|
||||||
|
|
||||||
|
const create string = `
|
||||||
|
CREATE TABLE "users" (
|
||||||
|
"uuid" TEXT NOT NULL UNIQUE,
|
||||||
|
"email" TEXT NOT NULL UNIQUE,
|
||||||
|
"password" TEXT NOT NULL,
|
||||||
|
PRIMARY KEY("uuid")
|
||||||
|
);`
|
||||||
|
|
||||||
|
var (
|
||||||
|
DataFile = "data.db"
|
||||||
|
db *sql.DB = LoadDb()
|
||||||
|
)
|
||||||
|
|
||||||
|
func LoadDb() *sql.DB {
|
||||||
|
// I *think* we need some file, even if only empty
|
||||||
|
if _, err := os.Stat(DataFile); errors.Is(err, os.ErrNotExist) {
|
||||||
|
slog.Error("sqlite3 database file required", "file", DataFile)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
db, err := sql.Open("sqlite3", DataFile)
|
||||||
|
if err != nil {
|
||||||
|
slog.Error("error opening database", "error", err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
if _, err := db.Exec(create); err != nil && err.Error() != "table \"users\" already exists" {
|
||||||
|
slog.Info("error initialising database table", "error", err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
slog.Info("loaded database")
|
||||||
|
return db
|
||||||
|
}
|
||||||
|
|
||||||
|
func addUser(user User) error {
|
||||||
|
_, err := db.Exec("insert into users (uuid, email, password) values ($1, $2, $3)",
|
||||||
|
user.Uuid.String(), user.Email, user.Hash)
|
||||||
|
if err != nil {
|
||||||
|
slog.Error("error adding user", "error", err, "user", user)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func byId(uuid string) (User, bool) {
|
||||||
|
row := db.QueryRow("select * from users where uuid = $1", uuid)
|
||||||
|
user := User{}
|
||||||
|
err := row.Scan(&user.Uuid, &user.Email, &user.Hash)
|
||||||
|
|
||||||
|
return user, err == nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func byEmail(email string) (User, bool) {
|
||||||
|
row := db.QueryRow("select * from users where email = $1", email)
|
||||||
|
user := User{}
|
||||||
|
err := row.Scan(&user.Uuid, &user.Email, &user.Hash)
|
||||||
|
|
||||||
|
return user, err == nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func emailExists(email string) bool {
|
||||||
|
_, ok := byEmail(email)
|
||||||
|
return ok
|
||||||
|
}
|
1
go.mod
1
go.mod
|
@ -5,5 +5,6 @@ go 1.22
|
||||||
require (
|
require (
|
||||||
github.com/golang-jwt/jwt/v5 v5.2.1
|
github.com/golang-jwt/jwt/v5 v5.2.1
|
||||||
github.com/google/uuid v1.6.0
|
github.com/google/uuid v1.6.0
|
||||||
|
github.com/mattn/go-sqlite3 v1.14.24
|
||||||
golang.org/x/crypto v0.28.0
|
golang.org/x/crypto v0.28.0
|
||||||
)
|
)
|
||||||
|
|
2
go.sum
2
go.sum
|
@ -2,5 +2,7 @@ github.com/golang-jwt/jwt/v5 v5.2.1 h1:OuVbFODueb089Lh128TAcimifWaLhJwVflnrgM17w
|
||||||
github.com/golang-jwt/jwt/v5 v5.2.1/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk=
|
github.com/golang-jwt/jwt/v5 v5.2.1/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk=
|
||||||
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||||
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||||
|
github.com/mattn/go-sqlite3 v1.14.24 h1:tpSp2G2KyMnnQu99ngJ47EIkWVmliIizyZBfPrBWDRM=
|
||||||
|
github.com/mattn/go-sqlite3 v1.14.24/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
|
||||||
golang.org/x/crypto v0.28.0 h1:GBDwsMXVQi34v5CCYUm2jkJvu4cbtru2U4TN2PSyQnw=
|
golang.org/x/crypto v0.28.0 h1:GBDwsMXVQi34v5CCYUm2jkJvu4cbtru2U4TN2PSyQnw=
|
||||||
golang.org/x/crypto v0.28.0/go.mod h1:rmgy+3RHxRZMyY0jjAJShp2zgEdOqj2AO7U0pYmeQ7U=
|
golang.org/x/crypto v0.28.0/go.mod h1:rmgy+3RHxRZMyY0jjAJShp2zgEdOqj2AO7U0pYmeQ7U=
|
||||||
|
|
3
jwt.go
3
jwt.go
|
@ -14,9 +14,8 @@ import (
|
||||||
"github.com/golang-jwt/jwt/v5"
|
"github.com/golang-jwt/jwt/v5"
|
||||||
)
|
)
|
||||||
|
|
||||||
var KeyFile = "private.key"
|
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
KeyFile = "private.key"
|
||||||
key *rsa.PrivateKey
|
key *rsa.PrivateKey
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
4
main.go
4
main.go
|
@ -15,7 +15,9 @@ func main() {
|
||||||
router.HandleFunc("POST /register", Register)
|
router.HandleFunc("POST /register", Register)
|
||||||
router.HandleFunc("POST /login", Login)
|
router.HandleFunc("POST /login", Login)
|
||||||
|
|
||||||
router.HandleFunc("GET /login", Login) // TODO: temp
|
// Note: likely temporary, possibly to be replaced by a fake "frontend"
|
||||||
|
router.HandleFunc("GET /login", Login)
|
||||||
|
router.HandleFunc("GET /register", Register)
|
||||||
|
|
||||||
http.ListenAndServe(":7102", router)
|
http.ListenAndServe(":7102", router)
|
||||||
}
|
}
|
||||||
|
|
68
pseudo_db.go
68
pseudo_db.go
|
@ -1,68 +0,0 @@
|
||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/json"
|
|
||||||
"log/slog"
|
|
||||||
"os"
|
|
||||||
)
|
|
||||||
|
|
||||||
// SQLite seems to hate my Mac.
|
|
||||||
// And I'd rather deal with something easily tinker-able in PoC stage
|
|
||||||
// So.................
|
|
||||||
// JSON.
|
|
||||||
//
|
|
||||||
// TODO: Kill this, preferably with fire.
|
|
||||||
|
|
||||||
func ReadUsers() []User {
|
|
||||||
data, err := os.ReadFile("./data.json")
|
|
||||||
if err != nil {
|
|
||||||
slog.Error("error reading file", "error", err)
|
|
||||||
}
|
|
||||||
var users []User
|
|
||||||
err = json.Unmarshal(data, &users)
|
|
||||||
if err != nil {
|
|
||||||
slog.Error("error unmarshalling data", "error", err)
|
|
||||||
}
|
|
||||||
return users
|
|
||||||
}
|
|
||||||
|
|
||||||
func AddUser(user User) {
|
|
||||||
users := ReadUsers()
|
|
||||||
users = append(users, user)
|
|
||||||
// slog.Info("users", "users", users)
|
|
||||||
data, err := json.Marshal(users)
|
|
||||||
if err != nil {
|
|
||||||
slog.Error("error marshalling", "error", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
f, err := os.OpenFile("./data.json", os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0644)
|
|
||||||
if err != nil {
|
|
||||||
slog.Error("error opening/creating file data.json")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if _, err := f.Write(data); err != nil {
|
|
||||||
slog.Error("error writing to file data.json", "error", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func EmailExists(email string) bool {
|
|
||||||
users := ReadUsers()
|
|
||||||
for i := 0; i < len(users); i++ {
|
|
||||||
if users[i].Email == email {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
func UserByEmail(email string) (User, bool) {
|
|
||||||
users := ReadUsers()
|
|
||||||
for i := 0; i < len(users); i++ {
|
|
||||||
if users[i].Email == email {
|
|
||||||
return users[i], true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return User{}, false
|
|
||||||
}
|
|
5
user.go
5
user.go
|
@ -23,8 +23,3 @@ func NewUser(email, password string) (User, error) {
|
||||||
}
|
}
|
||||||
return User{uuid.New(), email, hash}, nil
|
return User{uuid.New(), email, hash}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Implement
|
|
||||||
func ByEmail(email string) (User, bool) {
|
|
||||||
return UserByEmail(email)
|
|
||||||
}
|
|
||||||
|
|
Loading…
Reference in a new issue