Files
maxwarden/handlers/app/export_bitwarden.go
2026-09-03 10:58:42 -04:00

109 lines
3.0 KiB
Go

package app
import (
"encoding/json"
"maxwarden/entries"
"maxwarden/middleware"
"maxwarden/query"
"maxwarden/security"
. "maxwarden/ui"
"maxwarden/users"
"net/http"
"time"
. "maragu.dev/gomponents"
. "maragu.dev/gomponents/html"
)
func ExportBitwardenHandler(w http.ResponseWriter, r *http.Request) {
identity := middleware.GetIdentity(r)
session := middleware.GetSession(r)
renderView := func(errorMessage string) {
AppLayout("Export Vault", *identity, session,
Card(
Heading("Bitwarden JSON export"),
P(Text("Download your MaxWarden vault as a plaintext JSON file that can be imported into Bitwarden.")),
P(
InlineStyle("$me { margin-top: $3; color: $color(red-700); font-weight: var(--font-weight-semibold); }"),
Text("Warning: the downloaded file contains all usernames and passwords in plaintext. Store it securely and delete it when you are finished."),
),
If(errorMessage != "",
P(
InlineStyle("$me { margin-top: $3; color: $color(red-700); }"),
Text(errorMessage),
),
),
Form(
InlineStyle("$me { margin-top: $5; }"),
Action("/app/export/bitwarden"),
Method(http.MethodPost),
FormLabel(Text("Confirm your master key")),
FormInput(
Type("password"),
Name("password"),
AutoComplete("current-password"),
Required(),
),
Br(),
ButtonUIDanger(Type("submit"), Text("Download plaintext export")),
),
),
).Render(w)
}
switch r.Method {
case http.MethodGet:
renderView("")
return
case http.MethodPost:
// Continue below.
default:
w.Header().Set("Allow", http.MethodGet+", "+http.MethodPost)
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
return
}
r.Body = http.MaxBytesReader(w, r.Body, 1<<20)
if err := r.ParseForm(); err != nil {
http.Error(w, "Invalid export request", http.StatusBadRequest)
return
}
user, err := users.FetchById(identity.UserID)
if err != nil {
http.Error(w, "Unable to verify account", http.StatusInternalServerError)
return
}
if !security.ComparePasswords(r.Form.Get("password"), user.Password) {
w.WriteHeader(http.StatusUnauthorized)
renderView("The master key is incorrect.")
return
}
secretList, err := entries.Filter(entries.EntryFilter{
Filter: query.NewFilterFromSearch(map[string]string{}),
UserId: identity.UserID,
MasterKey: identity.MasterKey,
})
if err != nil {
http.Error(w, "Unable to decrypt vault", http.StatusInternalServerError)
return
}
document, err := json.MarshalIndent(entries.NewBitwardenExport(secretList), "", " ")
if err != nil {
http.Error(w, "Unable to create export", http.StatusInternalServerError)
return
}
filename := "maxwarden-bitwarden-export-" + time.Now().UTC().Format("2006-01-02") + ".json"
w.Header().Set("Cache-Control", "no-store")
w.Header().Set("Content-Disposition", `attachment; filename="`+filename+`"`)
w.Header().Set("Content-Type", "application/json; charset=utf-8")
w.Header().Set("Pragma", "no-cache")
w.Header().Set("X-Content-Type-Options", "nosniff")
w.Write(document)
}