Refactor everything
This commit is contained in:
parent
b07f1b080a
commit
cda8f0cc1b
11 changed files with 162 additions and 59 deletions
76
auth/auth.go
Normal file
76
auth/auth.go
Normal file
|
@ -0,0 +1,76 @@
|
|||
package auth
|
||||
|
||||
import (
|
||||
"log/slog"
|
||||
"net/http"
|
||||
"net/mail"
|
||||
"strings"
|
||||
|
||||
"git.a71.su/Andrew71/pye/storage"
|
||||
)
|
||||
|
||||
func validEmail(email string) bool {
|
||||
_, err := mail.ParseAddress(email)
|
||||
return err == nil
|
||||
}
|
||||
func validPass(pass string) bool {
|
||||
// TODO: Obviously, we *might* want something more sophisticated here
|
||||
return len(pass) >= 8
|
||||
}
|
||||
|
||||
func Register(w http.ResponseWriter, r *http.Request, data storage.Storage) {
|
||||
email, password, ok := r.BasicAuth()
|
||||
|
||||
if ok {
|
||||
email = strings.TrimSpace(email)
|
||||
password = strings.TrimSpace(password)
|
||||
if !(validEmail(email) && validPass(password) && !data.EmailExists(email)) {
|
||||
slog.Debug("Outcome",
|
||||
"email", validEmail(email),
|
||||
"pass", validPass(password),
|
||||
"taken", !data.EmailExists(email))
|
||||
http.Error(w, "invalid auth credentials", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
err := data.AddUser(email, password)
|
||||
if err != nil {
|
||||
slog.Error("error adding a new user", "error", err)
|
||||
http.Error(w, "error adding a new user", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
w.WriteHeader(http.StatusCreated)
|
||||
w.Write([]byte("User created"))
|
||||
return
|
||||
}
|
||||
|
||||
// No email and password was provided
|
||||
w.Header().Set("WWW-Authenticate", `Basic realm="restricted", charset="UTF-8"`)
|
||||
http.Error(w, "This API requires authorization", http.StatusUnauthorized)
|
||||
}
|
||||
|
||||
func Login(w http.ResponseWriter, r *http.Request, data storage.Storage) {
|
||||
email, password, ok := r.BasicAuth()
|
||||
|
||||
if ok {
|
||||
email = strings.TrimSpace(email)
|
||||
password = strings.TrimSpace(password)
|
||||
user, ok := data.ByEmail(email)
|
||||
if !ok || !user.PasswordFits(password) {
|
||||
w.Header().Set("WWW-Authenticate", `Basic realm="restricted", charset="UTF-8"`)
|
||||
http.Error(w, "you did something wrong", http.StatusUnauthorized)
|
||||
return
|
||||
}
|
||||
|
||||
s, err := CreateJWT(user)
|
||||
if err != nil {
|
||||
http.Error(w, "error creating jwt", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
w.Write([]byte(s))
|
||||
return
|
||||
}
|
||||
|
||||
// No email and password was provided
|
||||
w.Header().Set("WWW-Authenticate", `Basic realm="restricted", charset="UTF-8"`)
|
||||
http.Error(w, "This API requires authorization", http.StatusUnauthorized)
|
||||
}
|
105
auth/jwt.go
Normal file
105
auth/jwt.go
Normal file
|
@ -0,0 +1,105 @@
|
|||
package auth
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"crypto/rsa"
|
||||
"crypto/x509"
|
||||
"encoding/pem"
|
||||
"errors"
|
||||
"log/slog"
|
||||
"net/http"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"git.a71.su/Andrew71/pye/config"
|
||||
"git.a71.su/Andrew71/pye/storage"
|
||||
"github.com/golang-jwt/jwt/v5"
|
||||
)
|
||||
|
||||
var (
|
||||
key *rsa.PrivateKey
|
||||
)
|
||||
|
||||
// LoadKey attempts to load a private key from KeyFile.
|
||||
// If the file does not exist, it generates a new key (and saves it)
|
||||
func MustLoadKey() {
|
||||
// If the key doesn't exist, create it
|
||||
if _, err := os.Stat(config.Cfg.KeyFile); errors.Is(err, os.ErrNotExist) {
|
||||
key, err = rsa.GenerateKey(rand.Reader, 4096)
|
||||
if err != nil {
|
||||
slog.Error("error generating key", "error", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
// Save key to disk
|
||||
km := x509.MarshalPKCS1PrivateKey(key)
|
||||
block := pem.Block{Bytes: km, Type: "RSA PRIVATE KEY"}
|
||||
f, err := os.OpenFile(config.Cfg.KeyFile, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0644)
|
||||
if err != nil {
|
||||
slog.Error("error opening/creating file", "error", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
f.Write(pem.EncodeToMemory(&block))
|
||||
if err := f.Close(); err != nil {
|
||||
slog.Error("error closing file", "error", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
slog.Info("generated new key", "file", config.Cfg.KeyFile)
|
||||
} else {
|
||||
km, err := os.ReadFile(config.Cfg.KeyFile)
|
||||
if err != nil {
|
||||
slog.Error("error reading key", "error", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
key, err = jwt.ParseRSAPrivateKeyFromPEM(km)
|
||||
if err != nil {
|
||||
slog.Error("error parsing key", "error", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
slog.Info("loaded private key", "file", config.Cfg.KeyFile)
|
||||
}
|
||||
}
|
||||
|
||||
func init() {
|
||||
MustLoadKey()
|
||||
}
|
||||
|
||||
// PublicKey returns our public key as PEM block over http
|
||||
func PublicKey(w http.ResponseWriter, r *http.Request) {
|
||||
key_marshalled := x509.MarshalPKCS1PublicKey(&key.PublicKey)
|
||||
block := pem.Block{Bytes: key_marshalled, Type: "RSA PUBLIC KEY"}
|
||||
pem.Encode(w, &block)
|
||||
}
|
||||
|
||||
func CreateJWT(user storage.User) (string, error) {
|
||||
t := jwt.NewWithClaims(jwt.SigningMethodRS256,
|
||||
jwt.MapClaims{
|
||||
"iss": "pye",
|
||||
"uid": user.Uuid,
|
||||
"sub": user.Email,
|
||||
"iat": time.Now().Unix(),
|
||||
"exp": time.Now().Add(time.Hour * 24 * 7).Unix(),
|
||||
})
|
||||
s, err := t.SignedString(key)
|
||||
if err != nil {
|
||||
slog.Error("error creating JWT", "error", err)
|
||||
return "", err
|
||||
}
|
||||
return s, nil
|
||||
}
|
||||
|
||||
// VerifyToken receives a JWT and PEM-encoded public key,
|
||||
// then returns whether the token is valid
|
||||
func VerifyJWT(token string, publicKey []byte) (*jwt.Token, error) {
|
||||
t, err := jwt.Parse(token, func(token *jwt.Token) (interface{}, error) {
|
||||
key, err := jwt.ParseRSAPublicKeyFromPEM(publicKey)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if _, ok := token.Method.(*jwt.SigningMethodRSA); !ok {
|
||||
return nil, err
|
||||
}
|
||||
return key, nil
|
||||
})
|
||||
return t, err
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue