package main import ( "context" "crypto/sha256" "embed" "fmt" "log" "net/http" "github.com/jmoiron/sqlx" _ "modernc.org/sqlite" // Imported for side effects: registers DB driver "git.c3pb.de/gbe/invinoveritas/auth" ) //go:embed templates/*.tpl var templateFS embed.FS //go:embed static/* var staticFS embed.FS type Handler struct { db *sqlx.DB } func httpError(w http.ResponseWriter, msg string, err error, status int) { if err != nil { msg += ": " + err.Error() } log.Println(msg) http.Error(w, msg, status) } func hashPassword(ctx context.Context, db *sqlx.DB, user, pass string) (string, error) { // Look up password salt from DB and hash password with it query := `SELECT val FROM state WHERE key = 'pwsalt'` var salt []byte err := db.GetContext(ctx, &salt, query) if err != nil { return "", err } h := sha256.New() _, err = h.Write(salt) if err != nil { return "", err } fmt.Fprint(h, ":", user, ":", pass) return fmt.Sprintf("%02x", h.Sum(nil)), nil } type authProvider struct { db *sqlx.DB } func (a authProvider) Valid(ctx context.Context, userName, pass string) (*auth.User, error) { query := `SELECT count(*) FROM users;` var count int err := a.db.GetContext(ctx, &count, query) if err != nil { return nil, err } user := &auth.User{ Name: userName, } // If no entry in user DB, just allow everything, so that initial user creation works. if count == 0 { log.Println("user database empty, allowing access by", user, "regardless of password") return user, nil } hashedPw, err := hashPassword(ctx, a.db, userName, pass) log.Printf("hashed pw for %s: %s", userName, hashedPw) query = `SELECT rowid, name FROM users WHERE name = ? AND password = ?` err = a.db.GetContext(ctx, user, query, userName, hashedPw) if err != nil { return nil, err } return user, nil } func logRequest(r *http.Request) { log.Println("handling", r.Method, r.URL, "from", r.RemoteAddr, "by", auth.Get(r)) } func main() { db, err := sqlx.Open("sqlite", "vino.sqlite") if err != nil { log.Fatalln("can't open DB:", err) } defer db.Close() err = initDB(db) if err != nil { log.Fatalln("can't initialize DB:", err) } http.HandleFunc("/favicon.ico", http.NotFound) http.Handle("/static/", http.FileServer(http.FS(staticFS))) handler := Handler{ db: db, } ap := authProvider{ db: db, } http.HandleFunc("/details/img", auth.Require(http.HandlerFunc(handler.img), ap)) http.HandleFunc("/details/", auth.Require(http.HandlerFunc(handler.details), ap)) http.HandleFunc("/", auth.Require(http.HandlerFunc(handler.index), ap)) const listenAddr = ":7878" log.Printf("here we go, listening on http://%s", listenAddr) err = http.ListenAndServe(listenAddr, nil) if err != nil { log.Fatalln("http handler failed:", err) } }