add password generator

This commit is contained in:
2025-03-07 12:33:40 -05:00
parent 116be25489
commit 0d386e2d9f
27 changed files with 17895 additions and 42 deletions

View File

@@ -2,9 +2,7 @@ package app
import (
"maxwarden/entries"
"maxwarden/security"
. "maxwarden/ui"
"maxwarden/users"
. "maragu.dev/gomponents"
. "maragu.dev/gomponents/html"
@@ -56,6 +54,7 @@ func EditorHandler(w http.ResponseWriter, r *http.Request) {
url := r.FormValue("url")
secret = entries.Secret{
ID: r.PathValue("id"),
Description: desc,
URL: url,
Notes: notes,
@@ -63,42 +62,26 @@ func EditorHandler(w http.ResponseWriter, r *http.Request) {
Username: username,
}
user, _ := users.FetchById(identity.UserID)
// Get current secret store
secrets, _ := security.DecryptDataWithKey[[]entries.Secret](user.Data, identity.MasterKey)
if secrets == nil {
http.Redirect(w, r, "/app", http.StatusFound)
return
}
if editorType == EDITOR_TYPE_ADD {
secret.ID = security.RandBase58String(32)
*secrets = append(*secrets, secret)
entries.Add(identity.UserID, identity.MasterKey, secret)
} else {
secret.ID = r.PathValue("id")
// linear search and replace
for i, v := range *secrets {
if v.ID == secret.ID {
(*secrets)[i] = secret
}
}
entries.Update(identity.UserID, identity.MasterKey, secret)
}
// Serialize and encrypt modified store using master key
enc, _ := security.EncryptDataWithKey(secrets, identity.MasterKey)
user.Data = enc
users.Update(user)
http.Redirect(w, r, "/app", http.StatusFound)
return
}
AppLayout(title, *identity, session,
Modal(
"password_generator",
Text("Passkey Generator"),
HxLoad("/app/generator-hx"),
[]Node {
ButtonUIOutline(ModalCloser(), Text("Close")),
},
),
If(editorType == EDITOR_TYPE_EDIT,
Group{
Modal(
@@ -106,7 +89,7 @@ func EditorHandler(w http.ResponseWriter, r *http.Request) {
Text("Warning!"),
Text("Are you sure you want to delete this entry? This action cannot be undone."),
[]Node{
A(Href("/app/delete/" + secret.ID), ButtonUIDanger(Text("Delete"))),
A(Href("/app/delete/"+secret.ID), ButtonUIDanger(Text("Delete"))),
ButtonUIOutline(ModalCloser(), Text("Close")),
},
),
@@ -119,16 +102,41 @@ func EditorHandler(w http.ResponseWriter, r *http.Request) {
Form(
AutoComplete("off"),
Method("POST"),
FormLabel(Text("Description")),
FormInput(Type("text"), Name("description"), Value(secret.Description)),
FormInput(Type("text"), Name("description"), Value(secret.Description), AutoFocus(), Required()),
Br(),
FormLabel(Text("Username")),
FormInput(Type("text"), Name("un"), Value(secret.Username)),
Br(),
FormLabel(Text("Password")),
FormInput(Type("password"), Name("pas"), Value(secret.Password)),
Div(
FlexLeftRight(
FormLabel(Text("Password")),
ModalActuator("password_generator", ButtonUI(Type("button"), Text("Generate a secure password"))),
),
Br(),
FormInput(Class("password"), Type("password"), Name("pas"), Value(secret.Password)),
Br(),
FormLabel(Text("Show password?"), For("show")),
FormCheck(Class("checkbox"), ID("show")),
InlineScript(`
let check = me(".checkbox", me());
let passInput = me(".password", me());
check.on("click", () => {
if (passInput.type === "password") {
passInput.type = "text";
} else if (passInput.type === "text") {
passInput.type = "password";
}
});
`),
),
Br(),
FormLabel(Text("URL")),
@@ -144,6 +152,15 @@ func EditorHandler(w http.ResponseWriter, r *http.Request) {
ButtonUISuccess(Text(btnLabel), Type("submit")),
A(Href("/app"), ButtonUIOutline(Text("Close"), Type("button"))),
),
If(editorType == EDITOR_TYPE_EDIT,
Div(
InlineStyle("$me { margin-top: $3; color: $color(neutral-400); font-size: var(--text-sm);}"),
Text("Last modified: "), FormatDateTime(secret.Modified),
Br(),
Text("Created: "), FormatDateTime(secret.Created),
),
),
),
).Render(w)
}

17
handlers/app/generator.go Normal file
View File

@@ -0,0 +1,17 @@
package app
import (
"net/http"
"maxwarden/middleware"
. "maxwarden/ui"
)
func GeneratorHandler(w http.ResponseWriter, r *http.Request) {
identity := middleware.GetIdentity(r)
session := middleware.GetSession(r)
AppLayout("Passkey Generator", *identity, session,
HxLoad("/app/generator-hx"),
).Render(w)
}

View File

@@ -0,0 +1,114 @@
package app
import (
"maxwarden/generator"
. "maxwarden/ui"
"net/http"
"strconv"
. "maxwarden/basic"
. "maragu.dev/gomponents"
. "maragu.dev/gomponents/html"
hx "maragu.dev/gomponents-htmx"
)
func GeneratorHxHandler(w http.ResponseWriter, r *http.Request) {
r.ParseForm()
wordCount, _ := strconv.Atoi(r.FormValue("wordCount"))
if wordCount <= 0 {
wordCount = 6
}
phraseOutput := generator.GeneratePassphrase(wordCount)
length, _ := strconv.Atoi(r.FormValue("length"))
if length <= 0 {
length = 24
}
numbers, _ := strconv.Atoi(r.FormValue("numbers"))
if numbers <= 0 {
numbers = 5
}
symbols, _ := strconv.Atoi(r.FormValue("symbols"))
if symbols <= 0 {
symbols = 5
}
passwordOutput := generator.GeneratePassword(length, numbers, symbols, false, true)
customFormInput := func(children ...Node) Node {
return Div(
InlineStyle(`
$me {
border: 1px solid $color(neutral-300);
margin-bottom: $3;
}
`),
FormInput(
Group(children),
),
)
}
Div(ID("generator"),
Card(
Heading("Generate Passphrase"),
Form(
hx.Post(r.URL.Path),
hx.Swap("outerHTML"),
hx.Target("#generator"),
FormLabel(Text("Word Count")),
customFormInput(Type("number"), Name("wordCount"), Value(ToString(wordCount))),
ButtonUI(Text("Generate"), Type("submit")),
),
Br(),
FormLabel(Text("Output")),
customFormInput(
Value(phraseOutput),
),
),
Br(),
Card(
Heading("Generate Password"),
Form(
hx.Post(r.URL.Path),
hx.Swap("outerHTML"),
hx.Target("#generator"),
Flex(
Div(
FormLabel(Text("Password length")),
customFormInput(Type("number"), Name("length"), Value(ToString(length))),
),
Div(
FormLabel(Text("Number Count")),
customFormInput(Type("number"), Name("numbers"), Value(ToString(numbers))),
),
Div(
FormLabel(Text("Symbol Count")),
customFormInput(Type("number"), Name("symbols"), Value(ToString(symbols))),
),
),
ButtonUI(Text("Generate"), Type("submit")),
),
Br(),
FormLabel(Text("Output")),
customFormInput(
Value(passwordOutput),
),
),
).Render(w)
}

View File

@@ -25,7 +25,7 @@ func VaultHandler(w http.ResponseWriter, r *http.Request) {
margin-bottom: $5;
}
`),
A(Href("/app/editor/add"), ButtonUI(Text("+ Add Item"))),
A(Href("/app/editor/add"), ButtonUI(Text("+ Add Credentials"))),
),
HxLoad("/app/vault-hx"),
).Render(w)

View File

@@ -72,10 +72,58 @@ func VaultHxHandler(w http.ResponseWriter, r *http.Request) {
func(entry entries.Secret) Node {
return Tr(
TdLeft(Text(entry.Description)),
TdLeft(Text(entry.Username)),
TdLeft(Text("********")),
TdLeft(PageLink(security.SanitizationPolicy.Sanitize(entry.URL), Text(entry.URL), false)),
TdCenter(A(Href("/app/editor/edit/" + entry.ID), ButtonUIOutline(Icon(ICON_PENCIL, 16)))),
TdLeft(
IfElse(entry.Username != "",
Flex(
P(Text(entry.Username)),
Span(
Title("Click to copy username"),
InlineStyle("$me { cursor: pointer; }"),
Icon(ICON_COPY, 16),
),
InlineScript(`
let btn = me("span", me());
let text = me("p", me());
btn.on("click", () => {
navigator.clipboard.writeText(text.innerHTML);
});
`),
),
Text("---"),
),
),
TdLeft(
IfElse(entry.Password != "",
Flex(
P(Text("•••••••")),
Input(Type("hidden"), Value(entry.Password)),
Span(
Class("copy"),
Title("Click to copy password"),
InlineStyle("$me { cursor: pointer; }"),
Icon(ICON_COPY, 16),
),
InlineScript(`
let copyBtn = me(".copy", me());
let password = me("input", me()).value;
copyBtn.on("click", () => {
navigator.clipboard.writeText(password);
});
`),
),
Text("---"),
),
),
TdLeft(
IfElse(entry.URL != "",
PageLink(security.SanitizationPolicy.Sanitize(entry.URL), Text(entry.URL), true),
Text("---"),
),
),
TdCenter(A(Href("/app/editor/edit/"+entry.ID), ButtonUIOutline(Icon(ICON_PENCIL, 16)))),
)
},
nil,