140 lines
4.1 KiB
Go
140 lines
4.1 KiB
Go
package app
|
|
|
|
import (
|
|
"bytes"
|
|
"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 export"),
|
|
P(Text("Download your MaxWarden vault in one of Bitwarden's native import formats.")),
|
|
P(
|
|
InlineStyle("$me { margin-top: $3; }"),
|
|
Text("In Bitwarden, choose the File format that exactly matches the download: Bitwarden (csv) or Bitwarden (json). CSV is recommended if Bitwarden reports a generic JSON import error."),
|
|
),
|
|
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(),
|
|
Div(
|
|
InlineStyle("$me { display: flex; flex-wrap: wrap; gap: $3; margin-top: $3; }"),
|
|
ButtonUIDanger(Type("submit"), Name("format"), Value("csv"), Text("Download Bitwarden CSV (recommended)")),
|
|
ButtonUIOutline(Type("submit"), Name("format"), Value("json"), Text("Download Bitwarden JSON")),
|
|
),
|
|
),
|
|
),
|
|
).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
|
|
}
|
|
|
|
format := r.Form.Get("format")
|
|
var document []byte
|
|
var extension string
|
|
var contentType string
|
|
|
|
switch format {
|
|
case "", "csv":
|
|
var csvDocument bytes.Buffer
|
|
if err := entries.WriteBitwardenCSV(&csvDocument, secretList); err != nil {
|
|
http.Error(w, "Unable to create export", http.StatusInternalServerError)
|
|
return
|
|
}
|
|
document = csvDocument.Bytes()
|
|
extension = "csv"
|
|
contentType = "text/csv; charset=utf-8"
|
|
case "json":
|
|
document, err = json.MarshalIndent(entries.NewBitwardenExport(secretList), "", " ")
|
|
if err != nil {
|
|
http.Error(w, "Unable to create export", http.StatusInternalServerError)
|
|
return
|
|
}
|
|
extension = "json"
|
|
contentType = "application/json; charset=utf-8"
|
|
default:
|
|
http.Error(w, "Unsupported export format", http.StatusBadRequest)
|
|
return
|
|
}
|
|
|
|
filename := "maxwarden-bitwarden-export-" + time.Now().UTC().Format("2006-01-02") + "." + extension
|
|
w.Header().Set("Cache-Control", "no-store")
|
|
w.Header().Set("Content-Disposition", `attachment; filename="`+filename+`"`)
|
|
w.Header().Set("Content-Type", contentType)
|
|
w.Header().Set("Pragma", "no-cache")
|
|
w.Header().Set("X-Content-Type-Options", "nosniff")
|
|
w.Write(document)
|
|
}
|