update configuration

This commit is contained in:
juancwu 2026-01-03 16:48:31 -05:00
commit fda0f59fd3
5 changed files with 144 additions and 48 deletions

View file

@ -8,6 +8,11 @@ PORT=9000
DB_DRIVER=sqlite
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=
# Go duration format
JWT_EXPIRY=168h
@ -19,6 +24,3 @@ MAILER_IMAP_PORT=
MAILER_USERNAME=
MAILER_PASSWORD=
MAILER_EMAIL_FROM=
MAILER_ENVELOPE_FROM=
MAILER_SUPPORT_EMAIL=
MAILER_SUPPORT_ENVELOPE_FROM=

View 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))
}

View file

@ -29,13 +29,26 @@ func New(cfg *config.Config) (*App, error) {
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)
userService := service.NewUserService(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{
Cfg: cfg,

View file

@ -20,19 +20,21 @@ type Config struct {
DBDriver string
DBConnection string
IDEncodingAlphabet string
IDEncodingPrime uint64
IDEncodingInverse uint64
IDEncodingXorKey uint64
JWTSecret string
JWTExpiry time.Duration
MailerSMTPHost string
MailerSMTPPort int
MailerIMAPHost string
MailerIMAPPort int
MailerUsername string
MailerPassword string
MailerEmailFrom string
MailerEnvelopeFrom string
MailerSupportFrom string
MailerSupportEnvelopeFrom string
MailerSMTPHost string
MailerSMTPPort int
MailerIMAPHost string
MailerIMAPPort int
MailerUsername string
MailerPassword string
MailerEmailFrom string
}
func Load() *Config {
@ -52,19 +54,21 @@ func Load() *Config {
DBDriver: envString("DB_DRIVER", "sqlite"),
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"),
JWTExpiry: envDuration("JWT_EXPIRY", 168*time.Hour), // 7 days default
MailerSMTPHost: envString("MAILER_SMTP_HOST", ""),
MailerSMTPPort: envInt("MAILER_SMTP_PORT", 587),
MailerIMAPHost: envString("MAILER_IMAP_HOST", ""),
MailerIMAPPort: envInt("MAILER_IMAP_PORT", 993),
MailerUsername: envString("MAILER_USERNAME", ""),
MailerPassword: envString("MAILER_PASSWORD", ""),
MailerEmailFrom: envString("MAILER_EMAIL_FROM", ""),
MailerEnvelopeFrom: envString("MAILER_ENVELOPE_FROM", ""),
MailerSupportFrom: envString("MAILER_SUPPORT_EMAIL_FROM", ""),
MailerSupportEnvelopeFrom: envString("MAILER_SUPPORT_ENVELOPE_FROM", ""),
MailerSMTPHost: envString("MAILER_SMTP_HOST", ""),
MailerSMTPPort: envInt("MAILER_SMTP_PORT", 587),
MailerIMAPHost: envString("MAILER_IMAP_HOST", ""),
MailerIMAPPort: envInt("MAILER_IMAP_PORT", 993),
MailerUsername: envString("MAILER_USERNAME", ""),
MailerPassword: envString("MAILER_PASSWORD", ""),
MailerEmailFrom: envString("MAILER_EMAIL_FROM", ""),
}
return cfg
@ -85,8 +89,7 @@ func (c *Config) Sanitized() *Config {
Port: c.Port,
AppTagline: c.AppTagline,
MailerEmailFrom: c.MailerEmailFrom,
MailerEnvelopeFrom: c.MailerEnvelopeFrom,
MailerEmailFrom: c.MailerEmailFrom,
}
}
@ -111,6 +114,20 @@ func envInt(key string, def int) int {
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 {
value, ok := os.LookupEnv(key)
if !ok || value == "" {
@ -132,3 +149,7 @@ func envRequired(key string) string {
os.Exit(1)
return ""
}
func parseUint64(s string) (uint64, error) {
return strconv.ParseUint(s, 10, 64)
}

View file

@ -126,26 +126,20 @@ func (nc *EmailClient) connectToIMAP() (*client.Client, error) {
}
type EmailService struct {
client *EmailClient
fromEmail string
fromEnvelope string
supportEmail string
supportEnvelope string
isDev bool
appURL string
appName string
client *EmailClient
fromEmail 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{
client: client,
fromEmail: fromEmail,
fromEnvelope: fromEnvelope,
supportEmail: supportEmail,
supportEnvelope: supportEnvelope,
isDev: isDev,
appURL: appURL,
appName: appName,
client: client,
fromEmail: fromEmail,
isDev: isDev,
appURL: appURL,
appName: appName,
}
}
@ -159,10 +153,11 @@ func (s *EmailService) SendMagicLinkEmail(email, token, name string) error {
}
params := &EmailParams{
From: s.fromEmail,
To: []string{email},
Subject: subject,
Text: body,
From: s.fromString(),
EnvelopeFrom: s.fromEmail,
To: []string{email},
Subject: subject,
Text: body,
}
_, err := s.client.SendWithContext(context.Background(), params)
@ -172,6 +167,10 @@ func (s *EmailService) SendMagicLinkEmail(email, token, name string) error {
return err
}
func (s *EmailService) fromString() string {
return fmt.Sprintf("%s <%s>", s.appName, s.fromEmail)
}
func magicLinkEmailTemplate(magicURL, appName string) (string, string) {
subject := fmt.Sprintf("Sign in to %s", appName)
body := fmt.Sprintf(`Click this link to sign in to your account: