update configuration
This commit is contained in:
parent
9fe6a6beb1
commit
fda0f59fd3
5 changed files with 144 additions and 48 deletions
|
|
@ -8,6 +8,11 @@ PORT=9000
|
||||||
DB_DRIVER=sqlite
|
DB_DRIVER=sqlite
|
||||||
DB_CONNECTION="./data/local.db?_pragma=foreign_keys(1)&_pragma=journal_mode(WAL)"
|
DB_CONNECTION="./data/local.db?_pragma=foreign_keys(1)&_pragma=journal_mode(WAL)"
|
||||||
|
|
||||||
|
#Generate all secret values by running go run ./cmd/generate_secrets
|
||||||
|
ID_ENCODING_ALPHABET=
|
||||||
|
ID_ENCODING_PRIME=
|
||||||
|
ID_ENCODING_INVERSE=
|
||||||
|
ID_ENCODINDG_XOR_KEY=
|
||||||
JWT_SECRET=
|
JWT_SECRET=
|
||||||
# Go duration format
|
# Go duration format
|
||||||
JWT_EXPIRY=168h
|
JWT_EXPIRY=168h
|
||||||
|
|
@ -19,6 +24,3 @@ MAILER_IMAP_PORT=
|
||||||
MAILER_USERNAME=
|
MAILER_USERNAME=
|
||||||
MAILER_PASSWORD=
|
MAILER_PASSWORD=
|
||||||
MAILER_EMAIL_FROM=
|
MAILER_EMAIL_FROM=
|
||||||
MAILER_ENVELOPE_FROM=
|
|
||||||
MAILER_SUPPORT_EMAIL=
|
|
||||||
MAILER_SUPPORT_ENVELOPE_FROM=
|
|
||||||
|
|
|
||||||
61
cmd/generate_secrets/main.go
Normal file
61
cmd/generate_secrets/main.go
Normal file
|
|
@ -0,0 +1,61 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/rand"
|
||||||
|
"encoding/hex"
|
||||||
|
"fmt"
|
||||||
|
"math/big"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
// 1. Generate Shuffled Alphabet
|
||||||
|
// We start with standard base62
|
||||||
|
chars := []rune("0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz")
|
||||||
|
|
||||||
|
// Fisher-Yates Shuffle
|
||||||
|
// We use crypto/rand for the swap index to ensure high entropy
|
||||||
|
for i := len(chars) - 1; i > 0; i-- {
|
||||||
|
nBig, _ := rand.Int(rand.Reader, big.NewInt(int64(i+1)))
|
||||||
|
j := int(nBig.Int64())
|
||||||
|
chars[i], chars[j] = chars[j], chars[i]
|
||||||
|
}
|
||||||
|
alphabet := string(chars)
|
||||||
|
|
||||||
|
// 2. Generate Random 64-bit Prime
|
||||||
|
// crypto/rand.Prime automatically generates a number of the given bit length
|
||||||
|
// that is prime with high probability.
|
||||||
|
primeBig, err := rand.Prime(rand.Reader, 64)
|
||||||
|
if err != nil {
|
||||||
|
panic(fmt.Errorf("failed to generate prime: %v", err))
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. Calculate Modular Inverse
|
||||||
|
// (Prime * Inverse) % 2^64 == 1
|
||||||
|
// We use 1 << 64 as the modulus
|
||||||
|
modulus := new(big.Int).Lsh(big.NewInt(1), 64)
|
||||||
|
inverseBig := new(big.Int).ModInverse(primeBig, modulus)
|
||||||
|
|
||||||
|
// 4. Generate Random 64-bit XOR Key
|
||||||
|
// Just a random 64-bit integer
|
||||||
|
xorKeyBig, _ := rand.Int(rand.Reader, modulus)
|
||||||
|
|
||||||
|
// 5. Generate Hex-encoded JWT Secret (32 bytes / 256 bits)
|
||||||
|
jwtBytes := make([]byte, 32)
|
||||||
|
if _, err := rand.Read(jwtBytes); err != nil {
|
||||||
|
panic(fmt.Errorf("failed to generate jwt secret: %v", err))
|
||||||
|
}
|
||||||
|
jwtSecret := hex.EncodeToString(jwtBytes)
|
||||||
|
|
||||||
|
// --- OUTPUT ---
|
||||||
|
fmt.Println("Here are your generated secrets. Copy these into your .env or config file.")
|
||||||
|
fmt.Println(strings.Repeat("-", 60))
|
||||||
|
|
||||||
|
fmt.Printf("ALPHABET = \"%s\"\n", alphabet)
|
||||||
|
fmt.Printf("PRIME = %s\n", primeBig.String())
|
||||||
|
fmt.Printf("INVERSE = %s\n", inverseBig.String())
|
||||||
|
fmt.Printf("XOR_KEY = %s\n", xorKeyBig.String())
|
||||||
|
fmt.Printf("JWT_SECRET = \"%s\"\n", jwtSecret)
|
||||||
|
|
||||||
|
fmt.Println(strings.Repeat("-", 60))
|
||||||
|
}
|
||||||
|
|
@ -29,13 +29,26 @@ func New(cfg *config.Config) (*App, error) {
|
||||||
return nil, fmt.Errorf("failed to run migrations: %w", err)
|
return nil, fmt.Errorf("failed to run migrations: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
emailClient := service.NewEmailClient(cfg.MailerSMTPHost, cfg.MailerSMTPPort, cfg.MailerIMAPHost, cfg.MailerIMAPPort, cfg.MailerUsername, cfg.MailerPassword)
|
emailClient := service.NewEmailClient(
|
||||||
|
cfg.MailerSMTPHost,
|
||||||
|
cfg.MailerSMTPPort,
|
||||||
|
cfg.MailerIMAPHost,
|
||||||
|
cfg.MailerIMAPPort,
|
||||||
|
cfg.MailerUsername,
|
||||||
|
cfg.MailerPassword,
|
||||||
|
)
|
||||||
|
|
||||||
userRepository := repository.NewUserRepository(database)
|
userRepository := repository.NewUserRepository(database)
|
||||||
|
|
||||||
userService := service.NewUserService(userRepository)
|
userService := service.NewUserService(userRepository)
|
||||||
authService := service.NewAuthService(userRepository)
|
authService := service.NewAuthService(userRepository)
|
||||||
emailService := service.NewEmailService(emailClient, cfg.MailerEmailFrom, cfg.MailerEnvelopeFrom, cfg.MailerSupportFrom, cfg.MailerSupportEnvelopeFrom, cfg.AppURL, cfg.AppName, cfg.AppEnv == "development")
|
emailService := service.NewEmailService(
|
||||||
|
emailClient,
|
||||||
|
cfg.MailerEmailFrom,
|
||||||
|
cfg.AppURL,
|
||||||
|
cfg.AppName,
|
||||||
|
cfg.AppEnv == "development",
|
||||||
|
)
|
||||||
|
|
||||||
return &App{
|
return &App{
|
||||||
Cfg: cfg,
|
Cfg: cfg,
|
||||||
|
|
|
||||||
|
|
@ -20,19 +20,21 @@ type Config struct {
|
||||||
DBDriver string
|
DBDriver string
|
||||||
DBConnection string
|
DBConnection string
|
||||||
|
|
||||||
|
IDEncodingAlphabet string
|
||||||
|
IDEncodingPrime uint64
|
||||||
|
IDEncodingInverse uint64
|
||||||
|
IDEncodingXorKey uint64
|
||||||
|
|
||||||
JWTSecret string
|
JWTSecret string
|
||||||
JWTExpiry time.Duration
|
JWTExpiry time.Duration
|
||||||
|
|
||||||
MailerSMTPHost string
|
MailerSMTPHost string
|
||||||
MailerSMTPPort int
|
MailerSMTPPort int
|
||||||
MailerIMAPHost string
|
MailerIMAPHost string
|
||||||
MailerIMAPPort int
|
MailerIMAPPort int
|
||||||
MailerUsername string
|
MailerUsername string
|
||||||
MailerPassword string
|
MailerPassword string
|
||||||
MailerEmailFrom string
|
MailerEmailFrom string
|
||||||
MailerEnvelopeFrom string
|
|
||||||
MailerSupportFrom string
|
|
||||||
MailerSupportEnvelopeFrom string
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func Load() *Config {
|
func Load() *Config {
|
||||||
|
|
@ -52,19 +54,21 @@ func Load() *Config {
|
||||||
DBDriver: envString("DB_DRIVER", "sqlite"),
|
DBDriver: envString("DB_DRIVER", "sqlite"),
|
||||||
DBConnection: envString("DB_CONNECTION", "./data/local.db?_pragma=foreign_keys(1)&_pragma=journal_mode(WAL)"),
|
DBConnection: envString("DB_CONNECTION", "./data/local.db?_pragma=foreign_keys(1)&_pragma=journal_mode(WAL)"),
|
||||||
|
|
||||||
|
IDEncodingAlphabet: envRequired("ID_ENCODING_ALPHABET"),
|
||||||
|
IDEncodingPrime: envRequiredUint64("ID_ENCODING_PRIME"),
|
||||||
|
IDEncodingInverse: envRequiredUint64("ID_ENCODING_INVERSE"),
|
||||||
|
IDEncodingXorKey: envRequiredUint64("ID_ENCODING_XOR_KEY"),
|
||||||
|
|
||||||
JWTSecret: envRequired("JWT_SECRET"),
|
JWTSecret: envRequired("JWT_SECRET"),
|
||||||
JWTExpiry: envDuration("JWT_EXPIRY", 168*time.Hour), // 7 days default
|
JWTExpiry: envDuration("JWT_EXPIRY", 168*time.Hour), // 7 days default
|
||||||
|
|
||||||
MailerSMTPHost: envString("MAILER_SMTP_HOST", ""),
|
MailerSMTPHost: envString("MAILER_SMTP_HOST", ""),
|
||||||
MailerSMTPPort: envInt("MAILER_SMTP_PORT", 587),
|
MailerSMTPPort: envInt("MAILER_SMTP_PORT", 587),
|
||||||
MailerIMAPHost: envString("MAILER_IMAP_HOST", ""),
|
MailerIMAPHost: envString("MAILER_IMAP_HOST", ""),
|
||||||
MailerIMAPPort: envInt("MAILER_IMAP_PORT", 993),
|
MailerIMAPPort: envInt("MAILER_IMAP_PORT", 993),
|
||||||
MailerUsername: envString("MAILER_USERNAME", ""),
|
MailerUsername: envString("MAILER_USERNAME", ""),
|
||||||
MailerPassword: envString("MAILER_PASSWORD", ""),
|
MailerPassword: envString("MAILER_PASSWORD", ""),
|
||||||
MailerEmailFrom: envString("MAILER_EMAIL_FROM", ""),
|
MailerEmailFrom: envString("MAILER_EMAIL_FROM", ""),
|
||||||
MailerEnvelopeFrom: envString("MAILER_ENVELOPE_FROM", ""),
|
|
||||||
MailerSupportFrom: envString("MAILER_SUPPORT_EMAIL_FROM", ""),
|
|
||||||
MailerSupportEnvelopeFrom: envString("MAILER_SUPPORT_ENVELOPE_FROM", ""),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return cfg
|
return cfg
|
||||||
|
|
@ -85,8 +89,7 @@ func (c *Config) Sanitized() *Config {
|
||||||
Port: c.Port,
|
Port: c.Port,
|
||||||
AppTagline: c.AppTagline,
|
AppTagline: c.AppTagline,
|
||||||
|
|
||||||
MailerEmailFrom: c.MailerEmailFrom,
|
MailerEmailFrom: c.MailerEmailFrom,
|
||||||
MailerEnvelopeFrom: c.MailerEnvelopeFrom,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -111,6 +114,20 @@ func envInt(key string, def int) int {
|
||||||
return int(i)
|
return int(i)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func envRequiredUint64(key string) uint64 {
|
||||||
|
if value := os.Getenv(key); value != "" {
|
||||||
|
i64, err := parseUint64(value)
|
||||||
|
if err != nil {
|
||||||
|
slog.Error("config invalid required uint64", "key", key, "value", value, "error", err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
return i64
|
||||||
|
}
|
||||||
|
slog.Error("config required uint64 env var missing", "key", key)
|
||||||
|
os.Exit(1)
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
func envDuration(key string, def time.Duration) time.Duration {
|
func envDuration(key string, def time.Duration) time.Duration {
|
||||||
value, ok := os.LookupEnv(key)
|
value, ok := os.LookupEnv(key)
|
||||||
if !ok || value == "" {
|
if !ok || value == "" {
|
||||||
|
|
@ -132,3 +149,7 @@ func envRequired(key string) string {
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func parseUint64(s string) (uint64, error) {
|
||||||
|
return strconv.ParseUint(s, 10, 64)
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -126,26 +126,20 @@ func (nc *EmailClient) connectToIMAP() (*client.Client, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
type EmailService struct {
|
type EmailService struct {
|
||||||
client *EmailClient
|
client *EmailClient
|
||||||
fromEmail string
|
fromEmail string
|
||||||
fromEnvelope string
|
isDev bool
|
||||||
supportEmail string
|
appURL string
|
||||||
supportEnvelope string
|
appName string
|
||||||
isDev bool
|
|
||||||
appURL string
|
|
||||||
appName string
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewEmailService(client *EmailClient, fromEmail, fromEnvelope, supportEmail, supportEnvelope, appURL, appName string, isDev bool) *EmailService {
|
func NewEmailService(client *EmailClient, fromEmail, appURL, appName string, isDev bool) *EmailService {
|
||||||
return &EmailService{
|
return &EmailService{
|
||||||
client: client,
|
client: client,
|
||||||
fromEmail: fromEmail,
|
fromEmail: fromEmail,
|
||||||
fromEnvelope: fromEnvelope,
|
isDev: isDev,
|
||||||
supportEmail: supportEmail,
|
appURL: appURL,
|
||||||
supportEnvelope: supportEnvelope,
|
appName: appName,
|
||||||
isDev: isDev,
|
|
||||||
appURL: appURL,
|
|
||||||
appName: appName,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -159,10 +153,11 @@ func (s *EmailService) SendMagicLinkEmail(email, token, name string) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
params := &EmailParams{
|
params := &EmailParams{
|
||||||
From: s.fromEmail,
|
From: s.fromString(),
|
||||||
To: []string{email},
|
EnvelopeFrom: s.fromEmail,
|
||||||
Subject: subject,
|
To: []string{email},
|
||||||
Text: body,
|
Subject: subject,
|
||||||
|
Text: body,
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err := s.client.SendWithContext(context.Background(), params)
|
_, err := s.client.SendWithContext(context.Background(), params)
|
||||||
|
|
@ -172,6 +167,10 @@ func (s *EmailService) SendMagicLinkEmail(email, token, name string) error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *EmailService) fromString() string {
|
||||||
|
return fmt.Sprintf("%s <%s>", s.appName, s.fromEmail)
|
||||||
|
}
|
||||||
|
|
||||||
func magicLinkEmailTemplate(magicURL, appName string) (string, string) {
|
func magicLinkEmailTemplate(magicURL, appName string) (string, string) {
|
||||||
subject := fmt.Sprintf("Sign in to %s", appName)
|
subject := fmt.Sprintf("Sign in to %s", appName)
|
||||||
body := fmt.Sprintf(`Click this link to sign in to your account:
|
body := fmt.Sprintf(`Click this link to sign in to your account:
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue