feat: production auth - bcrypt passwords, remove demo login, create Misha admin account

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
James 2026-02-22 00:28:26 -05:00
parent eb103b4813
commit 6ab568ba4f
8 changed files with 73 additions and 90 deletions

19
cmd/createadmin/main.go Normal file
View File

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

6
go.mod
View File

@ -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

2
go.sum
View File

@ -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=

View File

@ -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 {

View File

@ -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

View File

@ -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) {

View File

@ -36,7 +36,6 @@ 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)
// Pages (auth required)

View File

@ -56,31 +56,6 @@ templ Login() {
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M14 5l7 7m0 0l-7 7m7-7H3"></path></svg>
</button>
</form>
<!-- Divider -->
<div class="flex items-center gap-3 my-6">
<div class="flex-1 h-px bg-gray-200"></div>
<span class="text-xs text-gray-400">or</span>
<div class="flex-1 h-px bg-gray-200"></div>
</div>
<!-- Demo Buttons -->
<div class="grid grid-cols-2 gap-3">
<form action="/auth/demo" method="POST">
<input type="hidden" name="role" value="seller"/>
<button type="submit" class="w-full h-11 rounded-lg border border-gray-200 text-sm font-medium text-gray-700 hover:bg-gray-50 transition flex items-center justify-center gap-2">
<svg class="w-4 h-4 text-gray-500" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 21V5a2 2 0 00-2-2H7a2 2 0 00-2 2v16m14 0h2m-2 0h-5m-9 0H3m2 0h5M9 7h1m-1 4h1m4-4h1m-1 4h1m-5 10v-5a1 1 0 011-1h2a1 1 0 011 1v5m-4 0h4"></path></svg>
Seller Demo
</button>
</form>
<form action="/auth/demo" method="POST">
<input type="hidden" name="role" value="buyer"/>
<button type="submit" class="w-full h-11 rounded-lg border border-gray-200 text-sm font-medium text-gray-700 hover:bg-gray-50 transition flex items-center justify-center gap-2">
<svg class="w-4 h-4 text-gray-500" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 3h2l.4 2M7 13h10l4-8H5.4M7 13L5.4 5M7 13l-2.293 2.293c-.63.63-.184 1.707.707 1.707H17m0 0a2 2 0 100 4 2 2 0 000-4zm-8 2a2 2 0 100 4 2 2 0 000-4z"></path></svg>
Buyer Demo
</button>
</form>
</div>
</div>
</div>
@ -91,21 +66,7 @@ templ Login() {
<svg class="w-8 h-8 text-teal-500" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M20 7l-8-4-8 4m16 0l-8 4m8-4v10l-8 4m0-10L4 7m8 4v10M4 7v10l8 4"></path></svg>
</div>
<h2 class="text-2xl font-bold text-gray-900 mb-4">AI-Powered Virtual Data Rooms</h2>
<p class="text-gray-500 mb-8">The most intelligent VDR platform for M&amp;A, PE, and capital markets. Atlas AI understands your documents, tracks diligence completeness, and surfaces insights automatically.</p>
<div class="flex justify-center gap-12">
<div>
<div class="text-2xl font-bold text-teal-500">500+</div>
<div class="text-xs text-gray-500">Active Rooms</div>
</div>
<div>
<div class="text-2xl font-bold text-teal-500">2M+</div>
<div class="text-xs text-gray-500">Documents</div>
</div>
<div>
<div class="text-2xl font-bold text-teal-500">99.9%</div>
<div class="text-xs text-gray-500">Uptime</div>
</div>
</div>
<p class="text-gray-500">Secure, intelligent deal rooms for M&amp;A, PE, and capital markets. Atlas AI understands your documents, tracks diligence completeness, and surfaces insights automatically.</p>
</div>
</div>
</body>