Initial scaffold: Go backend, React frontend, Docker Compose

- Go backend: config, db (PostgreSQL migrations), auth (argon2id + sessions),
  middleware (cookie sessions, RequireAuth), chi router, cmd/server
- React frontend: Vite + React 18 + TS + Tailwind + Gruvbox palette + terminal CSS
- Docker: compose.yml (Postgres, Valkey, minio, LiveKit, coturn, Caddy),
  livekit.yaml, Caddyfile, Dockerfile (multi-stage Go+Node+Alpine)
- .env.example, .gitignore, README.md
This commit is contained in:
2026-06-26 14:41:10 -04:00
commit e499ce884d
41 changed files with 4451 additions and 0 deletions
+72
View File
@@ -0,0 +1,72 @@
package auth
import (
"crypto/rand"
"crypto/subtle"
"encoding/base64"
"fmt"
"golang.org/x/crypto/argon2"
)
type passwordConfig struct {
time uint32
memory uint32
threads uint8
keyLen uint32
saltLen uint32
}
var defaultParams = passwordConfig{
time: 3,
memory: 64 * 1024,
threads: 4,
keyLen: 32,
saltLen: 16,
}
// HashPassword returns an Argon2id encoded string.
func HashPassword(password string) (string, error) {
salt := make([]byte, defaultParams.saltLen)
if _, err := rand.Read(salt); err != nil {
return "", fmt.Errorf("generate salt: %w", err)
}
hash := argon2.IDKey([]byte(password), salt, defaultParams.time, defaultParams.memory, defaultParams.threads, defaultParams.keyLen)
b64Salt := base64.RawStdEncoding.EncodeToString(salt)
b64Hash := base64.RawStdEncoding.EncodeToString(hash)
encodedHash := fmt.Sprintf("$argon2id$v=%d$m=%d,t=%d,p=%d$%s$%s", argon2.Version, defaultParams.memory, defaultParams.time, defaultParams.threads, b64Salt, b64Hash)
return encodedHash, nil
}
// VerifyPassword compares a password against an encoded Argon2id hash.
func VerifyPassword(password, encodedHash string) (bool, error) {
var version int
var memory, time uint32
var threads uint8
var salt, hash []byte
_, err := fmt.Sscanf(encodedHash, "$argon2id$v=%d$m=%d,t=%d,p=%d$%s$%s", &version, &memory, &time, &threads, &salt, &hash)
if err != nil {
return false, fmt.Errorf("parse hash: %w", err)
}
decodedSalt, err := base64.RawStdEncoding.DecodeString(string(salt))
if err != nil {
return false, fmt.Errorf("decode salt: %w", err)
}
decodedHash, err := base64.RawStdEncoding.DecodeString(string(hash))
if err != nil {
return false, fmt.Errorf("decode hash: %w", err)
}
computed := argon2.IDKey([]byte(password), decodedSalt, time, memory, threads, uint32(len(decodedHash)))
if subtle.ConstantTimeCompare(computed, decodedHash) == 1 {
return true, nil
}
return false, nil
}