Compare commits
7 Commits
6927ebf0d3
...
cb91a10192
Author | SHA1 | Date | |
---|---|---|---|
cb91a10192
|
|||
1a2130992b
|
|||
9fad610a70
|
|||
1e7ac51ca0
|
|||
8e181ccc07
|
|||
f63b0e9731
|
|||
ca0d7930b1
|
@ -6,6 +6,9 @@ ADMIN_NAME="admin"
|
|||||||
ADMIN_EMAIL="admin@test.net"
|
ADMIN_EMAIL="admin@test.net"
|
||||||
ADMIN_PASSWORD="secret"
|
ADMIN_PASSWORD="secret"
|
||||||
|
|
||||||
|
JWT_PRIVATE_KEY="ecdsa"
|
||||||
|
JWT_PUBLIC_KEY="ecdsa"
|
||||||
|
|
||||||
GOOSE_DRIVER="postgres"
|
GOOSE_DRIVER="postgres"
|
||||||
GOOSE_DBSTRING=$DATABASE_URL
|
GOOSE_DBSTRING=$DATABASE_URL
|
||||||
GOOSE_MIGRATION_DIR="./migrations"
|
GOOSE_MIGRATION_DIR="./migrations"
|
||||||
|
3
.gitignore
vendored
3
.gitignore
vendored
@ -25,3 +25,6 @@ go.work.sum
|
|||||||
|
|
||||||
# env file
|
# env file
|
||||||
.env
|
.env
|
||||||
|
|
||||||
|
# key files
|
||||||
|
*.pem
|
||||||
|
89
internal/auth/jwt.go
Normal file
89
internal/auth/jwt.go
Normal file
@ -0,0 +1,89 @@
|
|||||||
|
package auth
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/ecdsa"
|
||||||
|
"crypto/x509"
|
||||||
|
"encoding/base64"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"github.com/golang-jwt/jwt/v5"
|
||||||
|
)
|
||||||
|
|
||||||
|
func parseBase64PrivateKey(envVar string) (*ecdsa.PrivateKey, error) {
|
||||||
|
b64 := os.Getenv(envVar)
|
||||||
|
if b64 == "" {
|
||||||
|
return nil, fmt.Errorf("env var %s is empty", envVar)
|
||||||
|
}
|
||||||
|
|
||||||
|
decoded, err := base64.StdEncoding.DecodeString(b64)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to decode base64 key: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return x509.ParseECPrivateKey(decoded)
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseBase64PublicKey(envVar string) (*ecdsa.PublicKey, error) {
|
||||||
|
b64 := os.Getenv(envVar)
|
||||||
|
if b64 == "" {
|
||||||
|
return nil, fmt.Errorf("env var %s is empty", envVar)
|
||||||
|
}
|
||||||
|
|
||||||
|
decoded, err := base64.StdEncoding.DecodeString(b64)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to decode base64 key: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
pubInterface, err := x509.ParsePKIXPublicKey(decoded)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to parse public key: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
pubKey, ok := pubInterface.(*ecdsa.PublicKey)
|
||||||
|
if !ok {
|
||||||
|
return nil, fmt.Errorf("not an ECDSA public key")
|
||||||
|
}
|
||||||
|
|
||||||
|
return pubKey, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func SignJwtToken(claims jwt.Claims) (string, error) {
|
||||||
|
privateKey, err := parseBase64PrivateKey("JWT_PRIVATE_KEY")
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
token := jwt.NewWithClaims(jwt.SigningMethodES256, claims)
|
||||||
|
s, err := token.SignedString(privateKey)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
return s, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func VerifyToken(token string, claims jwt.Claims) (*jwt.Token, error) {
|
||||||
|
publicKey, err := parseBase64PublicKey("JWT_PUBLIC_KEY")
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
parsed, err := jwt.ParseWithClaims(token, claims, func(t *jwt.Token) (any, error) {
|
||||||
|
if _, ok := t.Method.(*jwt.SigningMethodECDSA); !ok {
|
||||||
|
return nil, fmt.Errorf("unexpected signing method: %v", t.Header["alg"])
|
||||||
|
}
|
||||||
|
return publicKey, nil
|
||||||
|
})
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("invalid token: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !parsed.Valid {
|
||||||
|
return nil, fmt.Errorf("token is not valid")
|
||||||
|
}
|
||||||
|
|
||||||
|
return parsed, nil
|
||||||
|
}
|
||||||
|
|
21
internal/auth/routes.go
Normal file
21
internal/auth/routes.go
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
package auth
|
||||||
|
|
||||||
|
import (
|
||||||
|
"gitea.local/admin/hspguard/internal/repository"
|
||||||
|
"github.com/go-chi/chi/v5"
|
||||||
|
)
|
||||||
|
|
||||||
|
type AuthHandler struct {
|
||||||
|
repo *repository.Queries
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewAuthHandler(repo *repository.Queries) *AuthHandler {
|
||||||
|
return &AuthHandler{
|
||||||
|
repo: repo,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *AuthHandler) RegisterRoutes(router chi.Router, api chi.Router) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -3,11 +3,15 @@ package user
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"gitea.local/admin/hspguard/internal/auth"
|
||||||
"gitea.local/admin/hspguard/internal/repository"
|
"gitea.local/admin/hspguard/internal/repository"
|
||||||
"gitea.local/admin/hspguard/internal/web"
|
"gitea.local/admin/hspguard/internal/web"
|
||||||
"github.com/go-chi/chi/v5"
|
"github.com/go-chi/chi/v5"
|
||||||
|
"github.com/golang-jwt/jwt/v5"
|
||||||
)
|
)
|
||||||
|
|
||||||
type UserHandler struct {
|
type UserHandler struct {
|
||||||
@ -24,6 +28,7 @@ func (h *UserHandler) RegisterRoutes(router chi.Router, api chi.Router) {
|
|||||||
router.Get("/login", h.loginPage)
|
router.Get("/login", h.loginPage)
|
||||||
router.Get("/register", h.registerPage)
|
router.Get("/register", h.registerPage)
|
||||||
api.Post("/register", h.register)
|
api.Post("/register", h.register)
|
||||||
|
api.Post("/login", h.login)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *UserHandler) loginPage(w http.ResponseWriter, r *http.Request) {
|
func (h *UserHandler) loginPage(w http.ResponseWriter, r *http.Request) {
|
||||||
@ -92,3 +97,63 @@ func (h *UserHandler) register(w http.ResponseWriter, r *http.Request) {
|
|||||||
web.Error(w, "failed to encode response", http.StatusInternalServerError)
|
web.Error(w, "failed to encode response", http.StatusInternalServerError)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type LoginParams struct {
|
||||||
|
Email string `json:"email"`
|
||||||
|
Password string `json:"password"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type UserClaims struct {
|
||||||
|
UserID string `json:"user_id"`
|
||||||
|
// Role
|
||||||
|
jwt.RegisteredClaims
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *UserHandler) login(w http.ResponseWriter, r *http.Request) {
|
||||||
|
var params LoginParams
|
||||||
|
|
||||||
|
decoder := json.NewDecoder(r.Body)
|
||||||
|
if err := decoder.Decode(¶ms); err != nil {
|
||||||
|
web.Error(w, "failed to parse request body", http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if params.Email == "" || params.Password == "" {
|
||||||
|
web.Error(w, "missing required fields", http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
user, err := h.repo.FindUserEmail(context.Background(), params.Email)
|
||||||
|
if err != nil {
|
||||||
|
web.Error(w, "user with provided email does not exists", http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
claims := UserClaims{
|
||||||
|
UserID: user.ID.String(),
|
||||||
|
RegisteredClaims: jwt.RegisteredClaims{
|
||||||
|
Issuer: "hspguard",
|
||||||
|
Subject: user.Email,
|
||||||
|
IssuedAt: jwt.NewNumericDate(time.Now()),
|
||||||
|
ExpiresAt: jwt.NewNumericDate(time.Now().Add(time.Hour)),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
token, err := auth.SignJwtToken(claims)
|
||||||
|
if err != nil {
|
||||||
|
web.Error(w, fmt.Sprintf("failed to generate access token: %v", err), http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
encoder := json.NewEncoder(w)
|
||||||
|
|
||||||
|
type Response struct {
|
||||||
|
Token string `json:"token"`
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := encoder.Encode(Response{
|
||||||
|
Token: token,
|
||||||
|
}); err != nil {
|
||||||
|
web.Error(w, "failed to encode response", http.StatusInternalServerError)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
26
scripts/generate-jwt-keys.sh
Executable file
26
scripts/generate-jwt-keys.sh
Executable file
@ -0,0 +1,26 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# Generate private key
|
||||||
|
# openssl ecparam -genkey -name prime256v1 -noout -out ec256-private.pem
|
||||||
|
# openssl ec -in ec256-private.pem -outform DER | base64 -w 0
|
||||||
|
|
||||||
|
# Extract public key
|
||||||
|
# openssl ec -in ec256-private.pem -pubout -out ec256-public.pem
|
||||||
|
# openssl ec -in ec256-private.pem -pubout -outform DER | base64 -w 0
|
||||||
|
|
||||||
|
# Generate private key
|
||||||
|
openssl ecparam -genkey -name prime256v1 -noout -out ec256-private.pem
|
||||||
|
|
||||||
|
# Extract public key
|
||||||
|
openssl ec -in ec256-private.pem -pubout -out ec256-public.pem
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "Private Key (DER base64):"
|
||||||
|
openssl ec -in ec256-private.pem -outform DER | base64 -w 0
|
||||||
|
|
||||||
|
echo "
|
||||||
|
--------------------------------"
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "Public Key (DER base64):"
|
||||||
|
openssl ec -in ec256-private.pem -pubout -outform DER | base64 -w 0
|
Reference in New Issue
Block a user