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,6 +20,11 @@ type Config struct {
DBDriver string
DBConnection string
IDEncodingAlphabet string
IDEncodingPrime uint64
IDEncodingInverse uint64
IDEncodingXorKey uint64
JWTSecret string
JWTExpiry time.Duration
@ -30,9 +35,6 @@ type Config struct {
MailerUsername string
MailerPassword string
MailerEmailFrom string
MailerEnvelopeFrom string
MailerSupportFrom string
MailerSupportEnvelopeFrom string
}
func Load() *Config {
@ -52,6 +54,11 @@ 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
@ -62,9 +69,6 @@ func Load() *Config {
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", ""),
}
return cfg
@ -86,7 +90,6 @@ func (c *Config) Sanitized() *Config {
AppTagline: c.AppTagline,
MailerEmailFrom: c.MailerEmailFrom,
MailerEnvelopeFrom: c.MailerEnvelopeFrom,
}
}
@ -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

@ -128,21 +128,15 @@ 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
}
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,
@ -159,7 +153,8 @@ func (s *EmailService) SendMagicLinkEmail(email, token, name string) error {
}
params := &EmailParams{
From: s.fromEmail,
From: s.fromString(),
EnvelopeFrom: s.fromEmail,
To: []string{email},
Subject: subject,
Text: body,
@ -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: