Compare commits

...

3 Commits

Author SHA1 Message Date
07b9b94143 feat: refactor for using app config 2025-05-25 16:22:28 +02:00
b95dcc6230 feat: rename env vars + add jwt issuer 2025-05-25 16:18:30 +02:00
e8bad71f21 feat: smart config 2025-05-25 16:18:21 +02:00
15 changed files with 253 additions and 166 deletions

View File

@ -1,21 +1,21 @@
PORT=3001 GUARD_PORT=3001
HOST="127.0.0.1" GUARD_HOST="127.0.0.1"
DATABASE_URL="postgres://<user>:<user>@<host>:<port>/<db>?sslmode=disable" GUARD_DB_URL="postgres://<user>:<user>@<host>:<port>/<db>?sslmode=disable"
ADMIN_NAME="admin" GUARD_ADMIN_NAME="admin"
ADMIN_EMAIL="admin@test.net" GUARD_ADMIN_EMAIL="admin@test.net"
ADMIN_PASSWORD="secret" GUARD_ADMIN_PASSWORD="secret"
JWT_PRIVATE_KEY="rsa" GUARD_JWT_PRIVATE="rsa"
JWT_PUBLIC_KEY="rsa" GUARD_JWT_PUBLIC="rsa"
GUARD_JWT_KID="my-rsa-key-1"
GUARD_JWT_ISSUER="http://localhost:3001"
JWT_KID="my-rsa-key-1" GUARD_MINIO_ENDPOINT="localhost:9000"
GUARD_MINIO_ACCESS_KEY=""
MINIO_ENDPOINT="localhost:9000" GUARD_MINIO_SECRET_KEY=""
MINIO_ACCESS_KEY=""
MINIO_SECRET_KEY=""
GOOSE_DRIVER="postgres" GOOSE_DRIVER="postgres"
GOOSE_DBSTRING=$DATABASE_URL GOOSE_DBSTRING=$DATABASE_URL

View File

@ -7,6 +7,7 @@ import (
"os" "os"
"gitea.local/admin/hspguard/internal/auth" "gitea.local/admin/hspguard/internal/auth"
"gitea.local/admin/hspguard/internal/config"
imiddleware "gitea.local/admin/hspguard/internal/middleware" imiddleware "gitea.local/admin/hspguard/internal/middleware"
"gitea.local/admin/hspguard/internal/oauth" "gitea.local/admin/hspguard/internal/oauth"
"gitea.local/admin/hspguard/internal/repository" "gitea.local/admin/hspguard/internal/repository"
@ -20,9 +21,10 @@ type APIServer struct {
addr string addr string
repo *repository.Queries repo *repository.Queries
storage *storage.FileStorage storage *storage.FileStorage
cfg *config.AppConfig
} }
func NewAPIServer(addr string, db *repository.Queries, minio *storage.FileStorage) *APIServer { func NewAPIServer(addr string, db *repository.Queries, minio *storage.FileStorage, cfg *config.AppConfig) *APIServer {
return &APIServer{ return &APIServer{
addr: addr, addr: addr,
repo: db, repo: db,
@ -38,10 +40,10 @@ func (s *APIServer) Run() error {
// staticDir := http.Dir(filepath.Join(workDir, "static")) // staticDir := http.Dir(filepath.Join(workDir, "static"))
// FileServer(router, "/static", staticDir) // FileServer(router, "/static", staticDir)
oauthHandler := oauth.NewOAuthHandler(s.repo) oauthHandler := oauth.NewOAuthHandler(s.repo, s.cfg)
router.Route("/api/v1", func(r chi.Router) { router.Route("/api/v1", func(r chi.Router) {
r.Use(imiddleware.WithSkipper(imiddleware.AuthMiddleware, "/api/v1/login", "/api/v1/register", "/api/v1/oauth/token")) r.Use(imiddleware.WithSkipper(imiddleware.AuthMiddleware(s.cfg), "/api/v1/login", "/api/v1/register", "/api/v1/oauth/token"))
userHandler := user.NewUserHandler(s.repo, s.storage) userHandler := user.NewUserHandler(s.repo, s.storage)
userHandler.RegisterRoutes(r) userHandler.RegisterRoutes(r)
@ -52,8 +54,8 @@ func (s *APIServer) Run() error {
oauthHandler.RegisterRoutes(r) oauthHandler.RegisterRoutes(r)
}) })
router.Get("/.well-known/jwks.json", auth.WriteJWKS) router.Get("/.well-known/jwks.json", oauthHandler.WriteJWKS)
router.Get("/.well-known/openid-configuration", auth.OpenIdConfiguration) router.Get("/.well-known/openid-configuration", oauth.OpenIdConfiguration)
router.Get("/*", func(w http.ResponseWriter, r *http.Request) { router.Get("/*", func(w http.ResponseWriter, r *http.Request) {
path := "./dist" + r.URL.Path path := "./dist" + r.URL.Path

View File

@ -4,9 +4,9 @@ import (
"context" "context"
"fmt" "fmt"
"log" "log"
"os"
"gitea.local/admin/hspguard/cmd/hspguard/api" "gitea.local/admin/hspguard/cmd/hspguard/api"
"gitea.local/admin/hspguard/internal/config"
"gitea.local/admin/hspguard/internal/repository" "gitea.local/admin/hspguard/internal/repository"
"gitea.local/admin/hspguard/internal/storage" "gitea.local/admin/hspguard/internal/storage"
"gitea.local/admin/hspguard/internal/user" "gitea.local/admin/hspguard/internal/user"
@ -21,9 +21,17 @@ func main() {
return return
} }
var cfg config.AppConfig
err = config.LoadEnv(&cfg)
if err != nil {
log.Fatal(err)
return
}
ctx := context.Background() ctx := context.Background()
conn, err := pgx.Connect(ctx, os.Getenv("DATABASE_URL")) conn, err := pgx.Connect(ctx, cfg.DatabaseURL)
if err != nil { if err != nil {
log.Fatalln("ERR: Failed to connect to db:", err) log.Fatalln("ERR: Failed to connect to db:", err)
return return
@ -31,21 +39,11 @@ func main() {
repo := repository.New(conn) repo := repository.New(conn)
fStorage := storage.New() fStorage := storage.New(&cfg)
user.EnsureAdminUser(ctx, repo) user.EnsureAdminUser(ctx, &cfg, repo)
host := os.Getenv("HOST") server := api.NewAPIServer(fmt.Sprintf("%s:%s", cfg.Host, cfg.Port), repo, fStorage, &cfg)
if host == "" {
host = "127.0.0.1"
}
port := os.Getenv("PORT")
if port == "" {
port = "3000"
}
server := api.NewAPIServer(fmt.Sprintf("%s:%s", host, port), repo, fStorage)
if err := server.Run(); err != nil { if err := server.Run(); err != nil {
log.Fatalln("ERR: Failed to start server:", err) log.Fatalln("ERR: Failed to start server:", err)
} }

View File

@ -4,71 +4,13 @@ import (
"crypto/rsa" "crypto/rsa"
"crypto/x509" "crypto/x509"
"encoding/base64" "encoding/base64"
"encoding/json"
"fmt" "fmt"
"net/http"
"os"
"gitea.local/admin/hspguard/internal/types" "gitea.local/admin/hspguard/internal/types"
"gitea.local/admin/hspguard/internal/web"
"github.com/golang-jwt/jwt/v5" "github.com/golang-jwt/jwt/v5"
) )
func WriteJWKS(w http.ResponseWriter, r *http.Request) { func ParseBase64PrivateKey(b64 string) (*rsa.PrivateKey, error) {
pubKey, err := parseBase64PublicKey("JWT_PUBLIC_KEY")
if err != nil {
web.Error(w, "failed to parse public key", http.StatusInternalServerError)
}
n := base64.RawURLEncoding.EncodeToString(pubKey.N.Bytes())
e := base64.RawURLEncoding.EncodeToString([]byte{1, 0, 1}) // 65537 = 0x010001
jwks := map[string]interface{}{
"keys": []map[string]string{
{
"kty": "RSA",
"kid": "my-rsa-key-1",
"use": "sig",
"alg": "RS256",
"n": n,
"e": e,
},
},
}
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(jwks)
}
func OpenIdConfiguration(w http.ResponseWriter, r *http.Request) {
type Response struct {
TokenEndpoint string `json:"token_endpoint"`
AuthorizationEndpoint string `json:"authorization_endpoint"`
JwksURI string `json:"jwks_uri"`
Issuer string `json:"issuer"`
EndSessionEndpoint string `json:"end_session_endpoint"`
}
w.Header().Set("Content-Type", "application/json")
encoder := json.NewEncoder(w)
if err := encoder.Encode(Response{
TokenEndpoint: "https://cb5f-2a00-10-5b00-c801-e955-5c68-63d0-b777.ngrok-free.app/api/v1/oauth/token",
AuthorizationEndpoint: "https://cb5f-2a00-10-5b00-c801-e955-5c68-63d0-b777.ngrok-free.app/authorize",
JwksURI: "https://cb5f-2a00-10-5b00-c801-e955-5c68-63d0-b777.ngrok-free.app/.well-known/jwks.json",
Issuer: "https://cb5f-2a00-10-5b00-c801-e955-5c68-63d0-b777.ngrok-free.app",
EndSessionEndpoint: "https://cb5f-2a00-10-5b00-c801-e955-5c68-63d0-b777.ngrok-free.app/api/v1/oauth/logout",
}); err != nil {
web.Error(w, "failed to encode response", http.StatusInternalServerError)
}
}
func parseBase64PrivateKey(envVar string) (*rsa.PrivateKey, error) {
b64 := os.Getenv(envVar)
if b64 == "" {
return nil, fmt.Errorf("env var %s is empty", envVar)
}
decoded, err := base64.StdEncoding.DecodeString(b64) decoded, err := base64.StdEncoding.DecodeString(b64)
if err != nil { if err != nil {
return nil, fmt.Errorf("failed to decode base64 key: %v", err) return nil, fmt.Errorf("failed to decode base64 key: %v", err)
@ -78,12 +20,7 @@ func parseBase64PrivateKey(envVar string) (*rsa.PrivateKey, error) {
return key.(*rsa.PrivateKey), err return key.(*rsa.PrivateKey), err
} }
func parseBase64PublicKey(envVar string) (*rsa.PublicKey, error) { func ParseBase64PublicKey(b64 string) (*rsa.PublicKey, error) {
b64 := os.Getenv(envVar)
if b64 == "" {
return nil, fmt.Errorf("env var %s is empty", envVar)
}
decoded, err := base64.StdEncoding.DecodeString(b64) decoded, err := base64.StdEncoding.DecodeString(b64)
if err != nil { if err != nil {
return nil, fmt.Errorf("failed to decode base64 key: %v", err) return nil, fmt.Errorf("failed to decode base64 key: %v", err)
@ -102,8 +39,8 @@ func parseBase64PublicKey(envVar string) (*rsa.PublicKey, error) {
return pubKey, nil return pubKey, nil
} }
func SignJwtToken(claims jwt.Claims) (string, error) { func SignJwtToken(claims jwt.Claims, key string) (string, error) {
privateKey, err := parseBase64PrivateKey("JWT_PRIVATE_KEY") privateKey, err := ParseBase64PrivateKey(key)
if err != nil { if err != nil {
return "", err return "", err
} }
@ -120,8 +57,8 @@ func SignJwtToken(claims jwt.Claims) (string, error) {
return s, nil return s, nil
} }
func VerifyToken(token string) (*jwt.Token, *types.UserClaims, error) { func VerifyToken(token string, key string) (*jwt.Token, *types.UserClaims, error) {
publicKey, err := parseBase64PublicKey("JWT_PUBLIC_KEY") publicKey, err := ParseBase64PublicKey(key)
if err != nil { if err != nil {
return nil, nil, err return nil, nil, err
} }

View File

@ -6,6 +6,7 @@ import (
"net/http" "net/http"
"time" "time"
"gitea.local/admin/hspguard/internal/config"
"gitea.local/admin/hspguard/internal/repository" "gitea.local/admin/hspguard/internal/repository"
"gitea.local/admin/hspguard/internal/types" "gitea.local/admin/hspguard/internal/types"
"gitea.local/admin/hspguard/internal/util" "gitea.local/admin/hspguard/internal/util"
@ -17,6 +18,7 @@ import (
type AuthHandler struct { type AuthHandler struct {
repo *repository.Queries repo *repository.Queries
cfg *config.AppConfig
} }
func NewAuthHandler(repo *repository.Queries) *AuthHandler { func NewAuthHandler(repo *repository.Queries) *AuthHandler {
@ -89,6 +91,7 @@ func (h *AuthHandler) login(w http.ResponseWriter, r *http.Request) {
accessClaims := types.UserClaims{ accessClaims := types.UserClaims{
UserEmail: user.Email, UserEmail: user.Email,
IsAdmin: user.IsAdmin,
RegisteredClaims: jwt.RegisteredClaims{ RegisteredClaims: jwt.RegisteredClaims{
Issuer: "hspguard", Issuer: "hspguard",
Subject: user.ID.String(), Subject: user.ID.String(),
@ -97,7 +100,7 @@ func (h *AuthHandler) login(w http.ResponseWriter, r *http.Request) {
}, },
} }
accessToken, err := SignJwtToken(accessClaims) accessToken, err := SignJwtToken(accessClaims, h.cfg.Jwt.PrivateKey)
if err != nil { if err != nil {
web.Error(w, fmt.Sprintf("failed to generate access token: %v", err), http.StatusBadRequest) web.Error(w, fmt.Sprintf("failed to generate access token: %v", err), http.StatusBadRequest)
return return
@ -105,6 +108,7 @@ func (h *AuthHandler) login(w http.ResponseWriter, r *http.Request) {
refreshClaims := types.UserClaims{ refreshClaims := types.UserClaims{
UserEmail: user.Email, UserEmail: user.Email,
IsAdmin: user.IsAdmin,
RegisteredClaims: jwt.RegisteredClaims{ RegisteredClaims: jwt.RegisteredClaims{
Issuer: "hspguard", Issuer: "hspguard",
Subject: user.ID.String(), Subject: user.ID.String(),
@ -113,7 +117,7 @@ func (h *AuthHandler) login(w http.ResponseWriter, r *http.Request) {
}, },
} }
refreshToken, err := SignJwtToken(refreshClaims) refreshToken, err := SignJwtToken(refreshClaims, h.cfg.Jwt.PrivateKey)
if err != nil { if err != nil {
web.Error(w, fmt.Sprintf("failed to generate refresh token: %v", err), http.StatusBadRequest) web.Error(w, fmt.Sprintf("failed to generate refresh token: %v", err), http.StatusBadRequest)
return return

7
internal/config/admin.go Normal file
View File

@ -0,0 +1,7 @@
package config
type AdminConfig struct {
Name string `env:"GUARD_ADMIN_NAME" default:"Admin"`
Email string `env:"GUARD_ADMIN_EMAIL" required:"true"`
Password string `env:"GUARD_ADMIN_PASSWORD" required:"true"`
}

8
internal/config/jwt.go Normal file
View File

@ -0,0 +1,8 @@
package config
type JwtConfig struct {
PrivateKey string `env:"GUARD_JWT_PRIVATE" required:"true"`
PublicKey string `env:"GUARD_JWT_PUBLIC" required:"true"`
KID string `env:"GUARD_JWT_KID" default:"guard-rsa"`
Issuer string `env:"GUARD_JWT_ISSUER" required:"true"`
}

7
internal/config/minio.go Normal file
View File

@ -0,0 +1,7 @@
package config
type MinioConfig struct {
Endpoint string `env:"GUARD_MINIO_ENDPOINT" default:"localhost:9000"`
AccessKey string `env:"GUARD_MINIO_ACCESS_KEY" required:"true"`
SecretKey string `env:"GUARD_MINIO_SECRET_KEY" required:"true"`
}

98
internal/config/mod.go Normal file
View File

@ -0,0 +1,98 @@
package config
import (
"errors"
"fmt"
"os"
"reflect"
"strconv"
"strings"
)
type AppConfig struct {
Port string `env:"GUARD_PORT" default:"3001"`
Host string `env:"GUARD_HOST" default:"127.0.0.1"`
DatabaseURL string `env:"GUARD_DB_URL" required:"true"`
Admin AdminConfig
Jwt JwtConfig
Minio MinioConfig
}
func LoadEnv(target any) error {
v := reflect.ValueOf(target)
if v.Kind() != reflect.Pointer || v.Elem().Kind() != reflect.Struct {
return &InvalidTargetError{}
}
return loadStruct(v.Elem(), "")
}
type InvalidTargetError struct{}
func (e *InvalidTargetError) Error() string {
return "target must be a pointer to a struct"
}
var ErrMissingRequiredEnv = errors.New("missing required environment variable")
func loadStruct(v reflect.Value, prefix string) error {
t := v.Type()
for i := 0; i < t.NumField(); i++ {
field := t.Field(i)
valueField := v.Field(i)
if !valueField.CanSet() {
continue
}
envKey := field.Tag.Get("env")
if envKey == "" {
envKey = strings.ToUpper(prefix + "_" + field.Name)
envKey = strings.TrimPrefix(envKey, "_")
}
required := field.Tag.Get("required") == "true"
defaultVal := field.Tag.Get("default")
if field.Type.Kind() == reflect.Struct {
err := loadStruct(valueField, envKey)
if err != nil {
return err
}
continue
}
envVal := os.Getenv(envKey)
if envVal == "" {
if defaultVal != "" {
envVal = defaultVal
} else if required {
return fmt.Errorf("%w: %s", ErrMissingRequiredEnv, envKey)
} else {
continue
}
}
switch field.Type.Kind() {
case reflect.String:
valueField.SetString(envVal)
case reflect.Int, reflect.Int64:
i, err := strconv.ParseInt(envVal, 10, 64)
if err != nil {
return fmt.Errorf("invalid int for %s: %w", envKey, err)
}
valueField.SetInt(i)
case reflect.Bool:
b, err := strconv.ParseBool(envVal)
if err != nil {
return fmt.Errorf("invalid bool for %s: %w", envKey, err)
}
valueField.SetBool(b)
default:
return fmt.Errorf("unsupported type for field %s", field.Name)
}
}
return nil
}

View File

@ -7,11 +7,13 @@ import (
"strings" "strings"
"gitea.local/admin/hspguard/internal/auth" "gitea.local/admin/hspguard/internal/auth"
"gitea.local/admin/hspguard/internal/config"
"gitea.local/admin/hspguard/internal/types" "gitea.local/admin/hspguard/internal/types"
"gitea.local/admin/hspguard/internal/web" "gitea.local/admin/hspguard/internal/web"
) )
func AuthMiddleware(next http.Handler) http.Handler { func AuthMiddleware(cfg *config.AppConfig) func(http.Handler) http.Handler {
return func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
authHeader := r.Header.Get("Authorization") authHeader := r.Header.Get("Authorization")
if authHeader == "" { if authHeader == "" {
@ -26,7 +28,7 @@ func AuthMiddleware(next http.Handler) http.Handler {
} }
tokenStr := parts[1] tokenStr := parts[1]
token, userClaims, err := auth.VerifyToken(tokenStr) token, userClaims, err := auth.VerifyToken(tokenStr, cfg.Jwt.PublicKey)
if err != nil || !token.Valid { if err != nil || !token.Valid {
http.Error(w, fmt.Sprintf("invalid token: %v", err), http.StatusUnauthorized) http.Error(w, fmt.Sprintf("invalid token: %v", err), http.StatusUnauthorized)
return return
@ -36,6 +38,7 @@ func AuthMiddleware(next http.Handler) http.Handler {
next.ServeHTTP(w, r.WithContext(ctx)) next.ServeHTTP(w, r.WithContext(ctx))
}) })
} }
}
func WithSkipper(mw func(http.Handler) http.Handler, excludedPaths ...string) func(http.Handler) http.Handler { func WithSkipper(mw func(http.Handler) http.Handler, excludedPaths ...string) func(http.Handler) http.Handler {
return func(next http.Handler) http.Handler { return func(next http.Handler) http.Handler {

View File

@ -10,6 +10,7 @@ import (
"time" "time"
"gitea.local/admin/hspguard/internal/auth" "gitea.local/admin/hspguard/internal/auth"
"gitea.local/admin/hspguard/internal/config"
"gitea.local/admin/hspguard/internal/repository" "gitea.local/admin/hspguard/internal/repository"
"gitea.local/admin/hspguard/internal/types" "gitea.local/admin/hspguard/internal/types"
"gitea.local/admin/hspguard/internal/util" "gitea.local/admin/hspguard/internal/util"
@ -21,11 +22,13 @@ import (
type OAuthHandler struct { type OAuthHandler struct {
repo *repository.Queries repo *repository.Queries
cfg *config.AppConfig
} }
func NewOAuthHandler(repo *repository.Queries) *OAuthHandler { func NewOAuthHandler(repo *repository.Queries, cfg *config.AppConfig) *OAuthHandler {
return &OAuthHandler{ return &OAuthHandler{
repo, repo,
cfg,
} }
} }
@ -35,6 +38,55 @@ func (h *OAuthHandler) RegisterRoutes(r chi.Router) {
r.Post("/oauth/code", h.getAuthCode) r.Post("/oauth/code", h.getAuthCode)
} }
func (h *OAuthHandler) WriteJWKS(w http.ResponseWriter, r *http.Request) {
pubKey, err := auth.ParseBase64PublicKey(h.cfg.Jwt.PublicKey)
if err != nil {
web.Error(w, "failed to parse public key", http.StatusInternalServerError)
}
n := base64.RawURLEncoding.EncodeToString(pubKey.N.Bytes())
e := base64.RawURLEncoding.EncodeToString([]byte{1, 0, 1}) // 65537 = 0x010001
jwks := map[string]interface{}{
"keys": []map[string]string{
{
"kty": "RSA",
"kid": "my-rsa-key-1",
"use": "sig",
"alg": "RS256",
"n": n,
"e": e,
},
},
}
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(jwks)
}
func OpenIdConfiguration(w http.ResponseWriter, r *http.Request) {
type Response struct {
TokenEndpoint string `json:"token_endpoint"`
AuthorizationEndpoint string `json:"authorization_endpoint"`
JwksURI string `json:"jwks_uri"`
Issuer string `json:"issuer"`
EndSessionEndpoint string `json:"end_session_endpoint"`
}
w.Header().Set("Content-Type", "application/json")
encoder := json.NewEncoder(w)
if err := encoder.Encode(Response{
TokenEndpoint: "https://cb5f-2a00-10-5b00-c801-e955-5c68-63d0-b777.ngrok-free.app/api/v1/oauth/token",
AuthorizationEndpoint: "https://cb5f-2a00-10-5b00-c801-e955-5c68-63d0-b777.ngrok-free.app/authorize",
JwksURI: "https://cb5f-2a00-10-5b00-c801-e955-5c68-63d0-b777.ngrok-free.app/.well-known/jwks.json",
Issuer: "https://cb5f-2a00-10-5b00-c801-e955-5c68-63d0-b777.ngrok-free.app",
EndSessionEndpoint: "https://cb5f-2a00-10-5b00-c801-e955-5c68-63d0-b777.ngrok-free.app/api/v1/oauth/logout",
}); err != nil {
web.Error(w, "failed to encode response", http.StatusInternalServerError)
}
}
func (h *OAuthHandler) getAuthCode(w http.ResponseWriter, r *http.Request) { func (h *OAuthHandler) getAuthCode(w http.ResponseWriter, r *http.Request) {
userId, ok := util.GetRequestUserId(r.Context()) userId, ok := util.GetRequestUserId(r.Context())
if !ok { if !ok {
@ -155,7 +207,7 @@ func (h *OAuthHandler) tokenEndpoint(w http.ResponseWriter, r *http.Request) {
}, },
} }
idToken, err := auth.SignJwtToken(claims) idToken, err := auth.SignJwtToken(claims, h.cfg.Jwt.PrivateKey)
if err != nil { if err != nil {
web.Error(w, "failed to sign id token", http.StatusInternalServerError) web.Error(w, "failed to sign id token", http.StatusInternalServerError)
return return

View File

@ -5,8 +5,8 @@ import (
"io" "io"
"log" "log"
"net/url" "net/url"
"os"
"gitea.local/admin/hspguard/internal/config"
"github.com/minio/minio-go/v7" "github.com/minio/minio-go/v7"
"github.com/minio/minio-go/v7/pkg/credentials" "github.com/minio/minio-go/v7/pkg/credentials"
) )
@ -15,27 +15,9 @@ type FileStorage struct {
client *minio.Client client *minio.Client
} }
func New() *FileStorage { func New(cfg *config.AppConfig) *FileStorage {
endpoint := os.Getenv("MINIO_ENDPOINT") client, err := minio.New(cfg.Minio.Endpoint, &minio.Options{
if endpoint == "" { Creds: credentials.NewStaticV4(cfg.Minio.AccessKey, cfg.Minio.SecretKey, ""),
log.Fatalln("MINIO_ENDPOINT env var is required")
return nil
}
accessKey := os.Getenv("MINIO_ACCESS_KEY")
if accessKey == "" {
log.Fatalln("MINIO_ACCESS_KEY env var is required")
return nil
}
secretKey := os.Getenv("MINIO_SECRET_KEY")
if secretKey == "" {
log.Fatalln("MINIO_SECRET_KEY env var is required")
return nil
}
client, err := minio.New(endpoint, &minio.Options{
Creds: credentials.NewStaticV4(accessKey, secretKey, ""),
Secure: false, Secure: false,
}) })
if err != nil { if err != nil {

View File

@ -4,6 +4,7 @@ import "github.com/golang-jwt/jwt/v5"
type UserClaims struct { type UserClaims struct {
UserEmail string `json:"user_email"` UserEmail string `json:"user_email"`
IsAdmin bool `json:"is_admin"`
jwt.RegisteredClaims jwt.RegisteredClaims
} }

View File

@ -4,33 +4,21 @@ import (
"context" "context"
"fmt" "fmt"
"log" "log"
"os"
"gitea.local/admin/hspguard/internal/config"
"gitea.local/admin/hspguard/internal/repository" "gitea.local/admin/hspguard/internal/repository"
"gitea.local/admin/hspguard/internal/util" "gitea.local/admin/hspguard/internal/util"
"github.com/google/uuid" "github.com/google/uuid"
) )
func EnsureAdminUser(ctx context.Context, repo *repository.Queries) { func EnsureAdminUser(ctx context.Context, cfg *config.AppConfig, repo *repository.Queries) {
adminName := os.Getenv("ADMIN_NAME") _, err := repo.FindUserEmail(ctx, cfg.Admin.Email)
if adminName == "" {
adminName = "admin"
}
adminEmail := os.Getenv("ADMIN_EMAIL")
adminPassword := os.Getenv("ADMIN_PASSWORD")
if adminEmail == "" {
log.Fatalln("ERR: ADMIN_EMAIL env variable is required")
}
_, err := repo.FindUserEmail(ctx, adminEmail)
if err != nil { if err != nil {
if adminPassword == "" { if cfg.Admin.Password == "" {
log.Fatalln("ERR: ADMIN_PASSWORD env variable is required") log.Fatalln("ERR: ADMIN_PASSWORD env variable is required")
} }
if _, err := createAdmin(ctx, adminName, adminEmail, adminPassword, repo); err != nil { if _, err := createAdmin(ctx, cfg.Admin.Name, cfg.Admin.Email, cfg.Admin.Password, repo); err != nil {
log.Fatalln("ERR: Failed to create admin account:", err) log.Fatalln("ERR: Failed to create admin account:", err)
} }
} }

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.9 MiB

After

Width:  |  Height:  |  Size: 416 KiB