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
|
||||
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,
|
||||
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
|
||||
|
||||
* Port `7102`
|
||||
* `POST /register` - register a user with Basic Auth
|
||||
* `POST /login` - get a JWT token by Basic Auth
|
||||
* `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
|
31
auth.go
31
auth.go
|
@ -7,38 +7,41 @@ import (
|
|||
"strings"
|
||||
)
|
||||
|
||||
func ValidEmail(email string) bool {
|
||||
func validEmail(email string) bool {
|
||||
_, err := mail.ParseAddress(email)
|
||||
return err == nil
|
||||
}
|
||||
func ValidPass(pass string) bool {
|
||||
func validPass(pass string) bool {
|
||||
// TODO: Obviously, we *might* want something more sophisticated here
|
||||
return len(pass) >= 8
|
||||
}
|
||||
func EmailTaken(email string) bool {
|
||||
// FIXME: Implement properly
|
||||
return EmailExists(email)
|
||||
}
|
||||
|
||||
func Register(w http.ResponseWriter, r *http.Request) {
|
||||
email, password, ok := r.BasicAuth()
|
||||
|
||||
if ok {
|
||||
email = strings.TrimSpace(email)
|
||||
password = strings.TrimSpace(password)
|
||||
if !(ValidEmail(email) && ValidPass(password) && !EmailTaken(email)) {
|
||||
slog.Info("Outcome",
|
||||
"email", ValidEmail(email),
|
||||
"pass", ValidPass(password),
|
||||
"taken", !EmailTaken(email))
|
||||
if !(validEmail(email) && validPass(password) && !emailExists(email)) {
|
||||
slog.Debug("Outcome",
|
||||
"email", validEmail(email),
|
||||
"pass", validPass(password),
|
||||
"taken", !emailExists(email))
|
||||
http.Error(w, "invalid auth credentials", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
user, err := NewUser(email, password)
|
||||
if err != nil {
|
||||
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.Write([]byte("User created"))
|
||||
return
|
||||
|
@ -55,7 +58,7 @@ func Login(w http.ResponseWriter, r *http.Request) {
|
|||
if ok {
|
||||
email = strings.TrimSpace(email)
|
||||
password = strings.TrimSpace(password)
|
||||
user, ok := ByEmail(email)
|
||||
user, ok := byEmail(email)
|
||||
if !ok || !user.PasswordFits(password) {
|
||||
http.Error(w, "you did something wrong", http.StatusUnauthorized)
|
||||
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 (
|
||||
github.com/golang-jwt/jwt/v5 v5.2.1
|
||||
github.com/google/uuid v1.6.0
|
||||
github.com/mattn/go-sqlite3 v1.14.24
|
||||
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/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/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/go.mod h1:rmgy+3RHxRZMyY0jjAJShp2zgEdOqj2AO7U0pYmeQ7U=
|
||||
|
|
3
jwt.go
3
jwt.go
|
@ -14,9 +14,8 @@ import (
|
|||
"github.com/golang-jwt/jwt/v5"
|
||||
)
|
||||
|
||||
var KeyFile = "private.key"
|
||||
|
||||
var (
|
||||
KeyFile = "private.key"
|
||||
key *rsa.PrivateKey
|
||||
)
|
||||
|
||||
|
|
4
main.go
4
main.go
|
@ -15,7 +15,9 @@ func main() {
|
|||
router.HandleFunc("POST /register", Register)
|
||||
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)
|
||||
}
|
||||
|
|
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
|
||||
}
|
||||
|
||||
// TODO: Implement
|
||||
func ByEmail(email string) (User, bool) {
|
||||
return UserByEmail(email)
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue