Refactor everything again

This commit is contained in:
Andrew-71 2024-10-13 21:03:44 +03:00
parent 7fdb0bf0f4
commit 96c369e4b1
14 changed files with 72 additions and 53 deletions

View file

@ -1,14 +1,11 @@
# Auth microservice # Pye Auth
**Mission**: Science compels us to create a microservice! **Mission**: Science compels us to create a microservice!
This is the repository for my **JWT auth microservice assignment** This is the repository for my **JWT authentication microservice**
with(out) blazingly fast cloud-native web3 memory-safe blockchain reactive AI with(out) blazingly fast cloud-native web3 memory-safe blockchain reactive AI
(insert a dozen more buzzwords of your choosing) technologies. (insert a dozen more buzzwords of your choosing) technologies.
This should be done by **October 17th 2024**. Or, at the very least,
in a state that proves I am competent Go developer.
## Usage ## Usage
``` ```
@ -33,6 +30,7 @@ Use "pye [command] --help" for more information about a command.
## Technologies used ## Technologies used
* **Storage** - [SQLite](https://github.com/mattn/go-sqlite3) and a PEM file * **Storage** - [SQLite](https://github.com/mattn/go-sqlite3) and a
[PEM](https://en.wikipedia.org/wiki/Privacy-Enhanced_Mail) file
* **HTTP routing** - [Chi](https://go-chi.io), just for logging... * **HTTP routing** - [Chi](https://go-chi.io), just for logging...
* **CLI management** - [Cobra](https://cobra.dev/) * **CLI management** - [Cobra](https://cobra.dev/)

View file

@ -1,9 +1,10 @@
package cmd package app
import ( import (
"fmt" "fmt"
"git.a71.su/Andrew71/pye/storage" "git.a71.su/Andrew71/pye/internal/models/user"
"git.a71.su/Andrew71/pye/internal/storage"
"github.com/spf13/cobra" "github.com/spf13/cobra"
) )
@ -20,7 +21,7 @@ var findUserCmd = &cobra.Command{
} }
func findUser(cmd *cobra.Command, args []string) { func findUser(cmd *cobra.Command, args []string) {
var user storage.User var user user.User
var ok bool var ok bool
if args[0] == "email" { if args[0] == "email" {
user, ok = storage.Data.ByEmail(args[1]) user, ok = storage.Data.ByEmail(args[1])

View file

@ -1,21 +1,21 @@
package cmd package app
import ( import (
"fmt" "fmt"
"os" "os"
"git.a71.su/Andrew71/pye/auth" "git.a71.su/Andrew71/pye/internal/auth"
"git.a71.su/Andrew71/pye/config" "git.a71.su/Andrew71/pye/internal/config"
"git.a71.su/Andrew71/pye/logging" "git.a71.su/Andrew71/pye/internal/logging"
"git.a71.su/Andrew71/pye/storage" "git.a71.su/Andrew71/pye/internal/storage"
"git.a71.su/Andrew71/pye/storage/sqlite" "git.a71.su/Andrew71/pye/internal/storage/sqlite"
"github.com/spf13/cobra" "github.com/spf13/cobra"
) )
var rootCmd = &cobra.Command{ var rootCmd = &cobra.Command{
Use: "pye", Use: "pye",
Short: "Pye is a simple JWT system", Short: "Pye is a simple JWT system",
Long: `A bare-bones authentication system built by Andrew71 as an assignment`, Long: `A bare-bones authentication system with RS256`,
} }
var ( var (

View file

@ -1,12 +1,12 @@
package cmd package app
import ( import (
"log/slog" "log/slog"
"net/http" "net/http"
"strconv" "strconv"
"git.a71.su/Andrew71/pye/auth" "git.a71.su/Andrew71/pye/internal/auth"
"git.a71.su/Andrew71/pye/config" "git.a71.su/Andrew71/pye/internal/config"
"github.com/go-chi/chi" "github.com/go-chi/chi"
"github.com/go-chi/chi/middleware" "github.com/go-chi/chi/middleware"
"github.com/spf13/cobra" "github.com/spf13/cobra"

View file

@ -1,11 +1,11 @@
package cmd package app
import ( import (
"fmt" "fmt"
"log/slog" "log/slog"
"os" "os"
"git.a71.su/Andrew71/pye/auth" "git.a71.su/Andrew71/pye/internal/auth"
"github.com/golang-jwt/jwt/v5" "github.com/golang-jwt/jwt/v5"
"github.com/spf13/cobra" "github.com/spf13/cobra"
) )

View file

@ -1,12 +1,13 @@
package auth package auth
import ( import (
"errors"
"log/slog" "log/slog"
"net/http" "net/http"
"net/mail" "net/mail"
"strings" "strings"
"git.a71.su/Andrew71/pye/storage" "git.a71.su/Andrew71/pye/internal/storage"
) )
func validEmail(email string) bool { func validEmail(email string) bool {
@ -25,7 +26,7 @@ func Register(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)
if !(validEmail(email) && validPass(password) && !storage.Data.Taken(email)) { if !(validEmail(email) && validPass(password)) {
slog.Debug("outcome", slog.Debug("outcome",
"valid_email", validEmail(email), "valid_email", validEmail(email),
"valid_pass", validPass(password), "valid_pass", validPass(password),
@ -35,12 +36,15 @@ func Register(w http.ResponseWriter, r *http.Request) {
} }
err := storage.Data.Add(email, password) err := storage.Data.Add(email, password)
if err != nil { if err != nil {
slog.Error("error adding a new user", "error", err) if errors.Is(err, storage.ErrExist) {
http.Error(w, "invalid auth credentials", http.StatusBadRequest)
} else {
http.Error(w, "error adding a new user", http.StatusInternalServerError) http.Error(w, "error adding a new user", http.StatusInternalServerError)
}
return return
} }
w.WriteHeader(http.StatusCreated) w.WriteHeader(http.StatusCreated)
w.Write([]byte("User created")) w.Write([]byte("user created"))
return return
} }

View file

@ -11,8 +11,8 @@ import (
"os" "os"
"time" "time"
"git.a71.su/Andrew71/pye/config" "git.a71.su/Andrew71/pye/internal/config"
"git.a71.su/Andrew71/pye/storage" "git.a71.su/Andrew71/pye/internal/models/user"
"github.com/golang-jwt/jwt/v5" "github.com/golang-jwt/jwt/v5"
) )
@ -66,7 +66,7 @@ func ServePublicKey(w http.ResponseWriter, r *http.Request) {
} }
// Create creates a JSON Web Token that expires after a week // Create creates a JSON Web Token that expires after a week
func Create(user storage.User) (token string, err error) { func Create(user user.User) (token string, err error) {
t := jwt.NewWithClaims(jwt.SigningMethodRS256, t := jwt.NewWithClaims(jwt.SigningMethodRS256,
jwt.MapClaims{ jwt.MapClaims{
"iss": "pye", "iss": "pye",

View file

@ -6,7 +6,7 @@ import (
"log/slog" "log/slog"
"os" "os"
"git.a71.su/Andrew71/pye/config" "git.a71.su/Andrew71/pye/internal/config"
"github.com/go-chi/chi/middleware" "github.com/go-chi/chi/middleware"
) )

View file

@ -1,4 +1,4 @@
package storage package user
import ( import (
"log/slog" "log/slog"

View file

@ -3,11 +3,13 @@ package sqlite
import ( import (
"database/sql" "database/sql"
"errors" "errors"
"fmt"
"log/slog" "log/slog"
"os" "os"
"git.a71.su/Andrew71/pye/storage" "git.a71.su/Andrew71/pye/internal/models/user"
_ "github.com/mattn/go-sqlite3" "git.a71.su/Andrew71/pye/internal/storage"
sqlite "github.com/mattn/go-sqlite3"
) )
const create string = ` const create string = `
@ -24,30 +26,36 @@ type SQLiteStorage struct {
} }
func (s SQLiteStorage) Add(email, password string) error { func (s SQLiteStorage) Add(email, password string) error {
user, err := storage.New(email, password) user, err := user.New(email, password)
if err != nil { if err != nil {
return err return err
} }
_, err = s.db.Exec("insert into users (uuid, email, password) values ($1, $2, $3)", _, err = s.db.Exec("insert into users (uuid, email, password) values ($1, $2, $3)",
user.Uuid.String(), user.Email, user.Hash) user.Uuid.String(), user.Email, user.Hash)
if err != nil { if err != nil {
e, ok := err.(sqlite.Error)
if ok && errors.Is(e.ExtendedCode, sqlite.ErrConstraintUnique) {
// Return a standard error if the user already exists
slog.Info("can't add user because email already taken", "user", user)
return fmt.Errorf("%w (%s)", storage.ErrExist, user.Email)
}
slog.Error("error adding user to database", "error", err, "user", user) slog.Error("error adding user to database", "error", err, "user", user)
return err return err
} }
return nil return nil
} }
func (s SQLiteStorage) ById(uuid string) (storage.User, bool) { func (s SQLiteStorage) ById(uuid string) (user.User, bool) {
row := s.db.QueryRow("select * from users where uuid = $1", uuid) row := s.db.QueryRow("select * from users where uuid = $1", uuid)
user := storage.User{} user := user.User{}
err := row.Scan(&user.Uuid, &user.Email, &user.Hash) err := row.Scan(&user.Uuid, &user.Email, &user.Hash)
return user, err == nil return user, err == nil
} }
func (s SQLiteStorage) ByEmail(email string) (storage.User, bool) { func (s SQLiteStorage) ByEmail(email string) (user.User, bool) {
row := s.db.QueryRow("select * from users where email = $1", email) row := s.db.QueryRow("select * from users where email = $1", email)
user := storage.User{} user := user.User{}
err := row.Scan(&user.Uuid, &user.Email, &user.Hash) err := row.Scan(&user.Uuid, &user.Email, &user.Hash)
return user, err == nil return user, err == nil

View file

@ -0,0 +1,21 @@
package storage
import (
"errors"
"git.a71.su/Andrew71/pye/internal/models/user"
)
var ErrExist = errors.New("user already exists")
// Storage is an interface for arbitrary storage
type Storage interface {
Add(email, password string) error // Add inserts a user into data
ById(uuid string) (user.User, bool) // ById retrieves a user by their UUID
ByEmail(email string) (user.User, bool) // ByEmail retrieves a user by their email
Taken(email string) bool // Taken checks whether an email is taken
}
// Data stores active information for the app.
// It should be populated on startup
var Data Storage

View file

@ -1,7 +1,7 @@
package main package main
import "git.a71.su/Andrew71/pye/cmd" import "git.a71.su/Andrew71/pye/internal/app"
func main() { func main() {
cmd.Execute() app.Execute()
} }

View file

@ -1,13 +0,0 @@
package storage
// Storage is an interface for arbitrary storage
type Storage interface {
Add(email, password string) error // Add inserts a user into data
ById(uuid string) (User, bool) // ById retrieves a user by their UUID
ByEmail(email string) (User, bool) // ByEmail retrieves a user by their email
Taken(email string) bool // Taken checks whether an email is taken
}
// Data stores active information for the app.
// It should be populated on startup
var Data Storage