From 6ab568ba4fcfbdf2bea9453e470554637192bb9f Mon Sep 17 00:00:00 2001 From: James Date: Sun, 22 Feb 2026 00:28:26 -0500 Subject: [PATCH] feat: production auth - bcrypt passwords, remove demo login, create Misha admin account Co-Authored-By: Claude Opus 4.6 --- cmd/createadmin/main.go | 19 +++++++++++++ go.mod | 6 ++--- go.sum | 2 ++ internal/db/migrate.go | 53 ++++++++++++++++++------------------- internal/handler/admin.go | 15 +++++++++-- internal/handler/auth.go | 24 ++++++----------- internal/handler/handler.go | 3 +-- templates/login.templ | 41 +--------------------------- 8 files changed, 73 insertions(+), 90 deletions(-) create mode 100644 cmd/createadmin/main.go diff --git a/cmd/createadmin/main.go b/cmd/createadmin/main.go new file mode 100644 index 0000000..c38ce73 --- /dev/null +++ b/cmd/createadmin/main.go @@ -0,0 +1,19 @@ +package main + +import ( + "fmt" + "log" + + "golang.org/x/crypto/bcrypt" +) + +func main() { + password := "Dealspace2026!" + hash, err := bcrypt.GenerateFromPassword([]byte(password), 12) + if err != nil { + log.Fatal(err) + } + + fmt.Printf("Bcrypt hash: %s\n\n", hash) + fmt.Printf("INSERT INTO profiles (id, email, full_name, organization_id, role, password_hash) VALUES ('admin-misha', 'misha@jongsma.me', 'Misha Jongsma', 'org-1', 'owner', '%s');\n", hash) +} diff --git a/go.mod b/go.mod index ec73fe0..6585c9c 100644 --- a/go.mod +++ b/go.mod @@ -1,11 +1,11 @@ module dealroom -go 1.23 - -toolchain go1.23.6 +go 1.24.0 require ( github.com/a-h/templ v0.2.778 github.com/klauspost/compress v1.18.4 github.com/mattn/go-sqlite3 v1.14.18 ) + +require golang.org/x/crypto v0.48.0 // indirect diff --git a/go.sum b/go.sum index 6e10caa..2ae7e5c 100644 --- a/go.sum +++ b/go.sum @@ -6,3 +6,5 @@ github.com/klauspost/compress v1.18.4 h1:RPhnKRAQ4Fh8zU2FY/6ZFDwTVTxgJ/EMydqSTzE github.com/klauspost/compress v1.18.4/go.mod h1:R0h/fSBs8DE4ENlcrlib3PsXS61voFxhIs2DeRhCvJ4= github.com/mattn/go-sqlite3 v1.14.18 h1:JL0eqdCOq6DJVNPSvArO/bIV9/P7fbGrV00LZHc+5aI= github.com/mattn/go-sqlite3 v1.14.18/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg= +golang.org/x/crypto v0.48.0 h1:/VRzVqiRSggnhY7gNRxPauEQ5Drw9haKdM0jqfcCFts= +golang.org/x/crypto v0.48.0/go.mod h1:r0kV5h3qnFPlQnBSrULhlsRfryS2pmewsg+XfMgkVos= diff --git a/internal/db/migrate.go b/internal/db/migrate.go index 883fc84..6a99c3f 100644 --- a/internal/db/migrate.go +++ b/internal/db/migrate.go @@ -189,17 +189,16 @@ func seed(db *sql.DB) error { // Organization `INSERT INTO organizations (id, name, slug) VALUES ('org-1', 'Apex Capital Partners', 'apex-capital')`, - // Profiles - seller (owner) and buyer (viewer) + // Seed admin profile (password must be set via createadmin tool) `INSERT INTO profiles (id, email, full_name, organization_id, role, password_hash) VALUES - ('user-seller', 'sarah@apexcapital.com', 'Sarah Chen', 'org-1', 'owner', 'demo'), - ('user-buyer', 'marcus@meridiancap.com', 'Marcus Webb', 'org-1', 'viewer', 'demo')`, + ('admin-misha', 'misha@jongsma.me', 'Misha Jongsma', 'org-1', 'owner', '')`, // Deals `INSERT INTO deals (id, organization_id, name, description, target_company, stage, deal_size, currency, ioi_date, loi_date, exclusivity_end_date, expected_close_date, close_probability, created_by) VALUES - ('deal-1', 'org-1', 'Project Aurora', 'AI-powered enterprise SaaS platform acquisition', 'TechNova Solutions', 'due_diligence', 45000000, 'USD', '2025-12-15', '2026-01-10', '2026-03-15', '2026-04-30', 72, 'user-seller'), - ('deal-2', 'org-1', 'Project Beacon', 'Healthcare technology platform investment', 'MedFlow Health', 'initial_review', 28000000, 'USD', '2026-01-05', '', '', '2026-06-15', 45, 'user-seller'), - ('deal-3', 'org-1', 'Project Cascade', 'Fintech payment processing acquisition', 'PayStream Inc', 'loi', 62000000, 'USD', '2025-11-20', '2026-02-01', '2026-04-01', '2026-05-15', 58, 'user-seller'), - ('deal-4', 'org-1', 'Project Delta', 'Industrial IoT platform strategic investment', 'SensorGrid Corp', 'pipeline', 15000000, 'USD', '', '', '', '2026-08-01', 30, 'user-seller')`, + ('deal-1', 'org-1', 'Project Aurora', 'AI-powered enterprise SaaS platform acquisition', 'TechNova Solutions', 'due_diligence', 45000000, 'USD', '2025-12-15', '2026-01-10', '2026-03-15', '2026-04-30', 72, 'admin-misha'), + ('deal-2', 'org-1', 'Project Beacon', 'Healthcare technology platform investment', 'MedFlow Health', 'initial_review', 28000000, 'USD', '2026-01-05', '', '', '2026-06-15', 45, 'admin-misha'), + ('deal-3', 'org-1', 'Project Cascade', 'Fintech payment processing acquisition', 'PayStream Inc', 'loi', 62000000, 'USD', '2025-11-20', '2026-02-01', '2026-04-01', '2026-05-15', 58, 'admin-misha'), + ('deal-4', 'org-1', 'Project Delta', 'Industrial IoT platform strategic investment', 'SensorGrid Corp', 'pipeline', 15000000, 'USD', '', '', '', '2026-08-01', 30, 'admin-misha')`, // Folders for deal-1 (Project Aurora) `INSERT INTO folders (id, deal_id, parent_id, name, description) VALUES @@ -214,18 +213,18 @@ func seed(db *sql.DB) error { // Files `INSERT INTO files (id, deal_id, folder_id, name, file_size, mime_type, status, uploaded_by) VALUES - ('file-1', 'deal-1', 'folder-1', 'Annual_Report_2025.pdf', 2450000, 'application/pdf', 'reviewed', 'user-seller'), - ('file-2', 'deal-1', 'folder-1', 'Revenue_Model_v3.xlsx', 890000, 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', 'reviewed', 'user-seller'), - ('file-3', 'deal-1', 'folder-4', 'Q4_Income_Statement.pdf', 1200000, 'application/pdf', 'reviewed', 'user-seller'), - ('file-4', 'deal-1', 'folder-2', 'NDA_Executed.pdf', 450000, 'application/pdf', 'reviewed', 'user-seller'), - ('file-5', 'deal-1', 'folder-2', 'IP_Assignment_Agreement.pdf', 780000, 'application/pdf', 'flagged', 'user-seller'), - ('file-6', 'deal-1', 'folder-3', 'Architecture_Overview.pdf', 3200000, 'application/pdf', 'reviewed', 'user-seller'), - ('file-7', 'deal-1', 'folder-3', 'Security_Audit_2025.pdf', 1800000, 'application/pdf', 'processing', 'user-seller'), - ('file-8', 'deal-2', 'folder-5', 'Clinical_Trial_Results.pdf', 5600000, 'application/pdf', 'uploaded', 'user-seller'), - ('file-9', 'deal-2', 'folder-6', 'Five_Year_Projection.xlsx', 670000, 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', 'reviewed', 'user-seller'), - ('file-10', 'deal-3', 'folder-7', 'PCI_Compliance_Cert.pdf', 340000, 'application/pdf', 'reviewed', 'user-seller'), - ('file-11', 'deal-3', 'folder-8', 'System_Architecture.pdf', 2100000, 'application/pdf', 'reviewed', 'user-seller'), - ('file-12', 'deal-3', 'folder-8', 'API_Documentation.pdf', 1500000, 'application/pdf', 'uploaded', 'user-seller')`, + ('file-1', 'deal-1', 'folder-1', 'Annual_Report_2025.pdf', 2450000, 'application/pdf', 'reviewed', 'admin-misha'), + ('file-2', 'deal-1', 'folder-1', 'Revenue_Model_v3.xlsx', 890000, 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', 'reviewed', 'admin-misha'), + ('file-3', 'deal-1', 'folder-4', 'Q4_Income_Statement.pdf', 1200000, 'application/pdf', 'reviewed', 'admin-misha'), + ('file-4', 'deal-1', 'folder-2', 'NDA_Executed.pdf', 450000, 'application/pdf', 'reviewed', 'admin-misha'), + ('file-5', 'deal-1', 'folder-2', 'IP_Assignment_Agreement.pdf', 780000, 'application/pdf', 'flagged', 'admin-misha'), + ('file-6', 'deal-1', 'folder-3', 'Architecture_Overview.pdf', 3200000, 'application/pdf', 'reviewed', 'admin-misha'), + ('file-7', 'deal-1', 'folder-3', 'Security_Audit_2025.pdf', 1800000, 'application/pdf', 'processing', 'admin-misha'), + ('file-8', 'deal-2', 'folder-5', 'Clinical_Trial_Results.pdf', 5600000, 'application/pdf', 'uploaded', 'admin-misha'), + ('file-9', 'deal-2', 'folder-6', 'Five_Year_Projection.xlsx', 670000, 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', 'reviewed', 'admin-misha'), + ('file-10', 'deal-3', 'folder-7', 'PCI_Compliance_Cert.pdf', 340000, 'application/pdf', 'reviewed', 'admin-misha'), + ('file-11', 'deal-3', 'folder-8', 'System_Architecture.pdf', 2100000, 'application/pdf', 'reviewed', 'admin-misha'), + ('file-12', 'deal-3', 'folder-8', 'API_Documentation.pdf', 1500000, 'application/pdf', 'uploaded', 'admin-misha')`, // Diligence requests for deal-1 `INSERT INTO diligence_requests (id, deal_id, item_number, section, description, priority, atlas_status, atlas_note, confidence, buyer_comment, seller_comment, buyer_group) VALUES @@ -253,14 +252,14 @@ func seed(db *sql.DB) error { // Activity `INSERT INTO deal_activity (id, organization_id, deal_id, user_id, activity_type, resource_type, resource_name, created_at) VALUES - ('act-1', 'org-1', 'deal-1', 'user-seller', 'upload', 'file', 'Annual_Report_2025.pdf', '2026-02-14 16:35:00'), - ('act-2', 'org-1', 'deal-1', 'user-buyer', 'view', 'file', 'Revenue_Model_v3.xlsx', '2026-02-14 16:30:00'), - ('act-3', 'org-1', 'deal-1', 'user-seller', 'edit', 'deal', 'Project Aurora', '2026-02-14 15:20:00'), - ('act-4', 'org-1', 'deal-2', 'user-seller', 'upload', 'file', 'Clinical_Trial_Results.pdf', '2026-02-14 14:10:00'), - ('act-5', 'org-1', 'deal-1', 'user-buyer', 'download', 'file', 'NDA_Executed.pdf', '2026-02-14 13:00:00'), - ('act-6', 'org-1', 'deal-3', 'user-seller', 'upload', 'file', 'PCI_Compliance_Cert.pdf', '2026-02-13 10:00:00'), - ('act-7', 'org-1', 'deal-1', 'user-seller', 'comment', 'request', 'Customer concentration analysis', '2026-02-13 09:00:00'), - ('act-8', 'org-1', 'deal-1', 'user-buyer', 'view', 'folder', 'Financial Documents', '2026-02-12 16:00:00')`, + ('act-1', 'org-1', 'deal-1', 'admin-misha', 'upload', 'file', 'Annual_Report_2025.pdf', '2026-02-14 16:35:00'), + ('act-2', 'org-1', 'deal-1', 'admin-misha', 'view', 'file', 'Revenue_Model_v3.xlsx', '2026-02-14 16:30:00'), + ('act-3', 'org-1', 'deal-1', 'admin-misha', 'edit', 'deal', 'Project Aurora', '2026-02-14 15:20:00'), + ('act-4', 'org-1', 'deal-2', 'admin-misha', 'upload', 'file', 'Clinical_Trial_Results.pdf', '2026-02-14 14:10:00'), + ('act-5', 'org-1', 'deal-1', 'admin-misha', 'download', 'file', 'NDA_Executed.pdf', '2026-02-14 13:00:00'), + ('act-6', 'org-1', 'deal-3', 'admin-misha', 'upload', 'file', 'PCI_Compliance_Cert.pdf', '2026-02-13 10:00:00'), + ('act-7', 'org-1', 'deal-1', 'admin-misha', 'comment', 'request', 'Customer concentration analysis', '2026-02-13 09:00:00'), + ('act-8', 'org-1', 'deal-1', 'admin-misha', 'view', 'folder', 'Financial Documents', '2026-02-12 16:00:00')`, } for i, stmt := range stmts { diff --git a/internal/handler/admin.go b/internal/handler/admin.go index 93aec71..72ed4a7 100644 --- a/internal/handler/admin.go +++ b/internal/handler/admin.go @@ -291,10 +291,21 @@ func (h *Handler) handleAdminUserSave(w http.ResponseWriter, r *http.Request) { return } + password := strings.TrimSpace(r.FormValue("password")) + if id == "" { id = generateID("user") - _, err := h.db.Exec("INSERT INTO profiles (id, email, full_name, organization_id, role, password_hash) VALUES (?, ?, ?, ?, ?, 'demo')", - id, email, fullName, profile.OrganizationID, role) + var passHash string + if password != "" { + var err error + passHash, err = hashPassword(password) + if err != nil { + http.Error(w, "Error hashing password", 500) + return + } + } + _, err := h.db.Exec("INSERT INTO profiles (id, email, full_name, organization_id, role, password_hash) VALUES (?, ?, ?, ?, ?, ?)", + id, email, fullName, profile.OrganizationID, role, passHash) if err != nil { http.Error(w, "Error creating user", 500) return diff --git a/internal/handler/auth.go b/internal/handler/auth.go index 2325f7f..8cb960f 100644 --- a/internal/handler/auth.go +++ b/internal/handler/auth.go @@ -7,6 +7,8 @@ import ( "time" "dealroom/templates" + + "golang.org/x/crypto/bcrypt" ) func (h *Handler) handleLoginPage(w http.ResponseWriter, r *http.Request) { @@ -35,7 +37,7 @@ func (h *Handler) handleLogin(w http.ResponseWriter, r *http.Request) { var userID string var passHash string err := h.db.QueryRow("SELECT id, password_hash FROM profiles WHERE email = ?", email).Scan(&userID, &passHash) - if err != nil || (passHash != "demo" && passHash != password) { + if err != nil || bcrypt.CompareHashAndPassword([]byte(passHash), []byte(password)) != nil { http.Redirect(w, r, "/login?error=invalid", http.StatusSeeOther) return } @@ -44,22 +46,12 @@ func (h *Handler) handleLogin(w http.ResponseWriter, r *http.Request) { http.Redirect(w, r, "/", http.StatusSeeOther) } -func (h *Handler) handleDemoLogin(w http.ResponseWriter, r *http.Request) { - if r.Method != http.MethodPost { - http.Redirect(w, r, "/login", http.StatusSeeOther) - return +func hashPassword(password string) (string, error) { + hash, err := bcrypt.GenerateFromPassword([]byte(password), 12) + if err != nil { + return "", err } - - role := r.FormValue("role") // "seller" or "buyer" - var userID string - if role == "buyer" { - userID = "user-buyer" - } else { - userID = "user-seller" - } - - h.createSession(w, userID) - http.Redirect(w, r, "/", http.StatusSeeOther) + return string(hash), nil } func (h *Handler) handleLogout(w http.ResponseWriter, r *http.Request) { diff --git a/internal/handler/handler.go b/internal/handler/handler.go index 0faea99..d99b795 100644 --- a/internal/handler/handler.go +++ b/internal/handler/handler.go @@ -36,8 +36,7 @@ func (h *Handler) RegisterRoutes(mux *http.ServeMux) { // Auth mux.HandleFunc("/login", h.handleLoginPage) mux.HandleFunc("/auth/login", h.handleLogin) - mux.HandleFunc("/auth/demo", h.handleDemoLogin) - mux.HandleFunc("/auth/logout", h.handleLogout) +mux.HandleFunc("/auth/logout", h.handleLogout) // Pages (auth required) mux.HandleFunc("/", h.requireAuth(h.handleDashboard)) diff --git a/templates/login.templ b/templates/login.templ index 199dce2..251cf3d 100644 --- a/templates/login.templ +++ b/templates/login.templ @@ -56,31 +56,6 @@ templ Login() { - - -
-
- or -
-
- - -
-
- - -
-
- - -
-
@@ -91,21 +66,7 @@ templ Login() {

AI-Powered Virtual Data Rooms

-

The most intelligent VDR platform for M&A, PE, and capital markets. Atlas AI understands your documents, tracks diligence completeness, and surfaces insights automatically.

-
-
-
500+
-
Active Rooms
-
-
-
2M+
-
Documents
-
-
-
99.9%
-
Uptime
-
-
+

Secure, intelligent deal rooms for M&A, PE, and capital markets. Atlas AI understands your documents, tracks diligence completeness, and surfaces insights automatically.