add password generator
This commit is contained in:
@@ -7,6 +7,7 @@ import (
|
|||||||
"maxwarden/users"
|
"maxwarden/users"
|
||||||
"sort"
|
"sort"
|
||||||
"strings"
|
"strings"
|
||||||
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Secret struct {
|
type Secret struct {
|
||||||
@@ -16,6 +17,8 @@ type Secret struct {
|
|||||||
Notes string
|
Notes string
|
||||||
Username string
|
Username string
|
||||||
Password string
|
Password string
|
||||||
|
Created time.Time
|
||||||
|
Modified time.Time
|
||||||
}
|
}
|
||||||
|
|
||||||
type EntryFilter struct {
|
type EntryFilter struct {
|
||||||
@@ -123,3 +126,53 @@ func DeleteSecret(userId int32, masterKey string, secretId string) error {
|
|||||||
|
|
||||||
return userErr
|
return userErr
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func Update(userId int32, masterKey string, secret Secret) error {
|
||||||
|
user, _ := users.FetchById(userId)
|
||||||
|
|
||||||
|
secrets, _ := security.DecryptDataWithKey[[]Secret](user.Data, masterKey)
|
||||||
|
if secrets == nil {
|
||||||
|
return errors.New("user secrets are null")
|
||||||
|
}
|
||||||
|
|
||||||
|
secret.Modified = time.Now()
|
||||||
|
|
||||||
|
// linear search and replace
|
||||||
|
for i, v := range *secrets {
|
||||||
|
if v.ID == secret.ID {
|
||||||
|
created := (*secrets)[i].Created
|
||||||
|
secret.Created = created
|
||||||
|
|
||||||
|
(*secrets)[i] = secret
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
enc, _ := security.EncryptDataWithKey(secrets, masterKey)
|
||||||
|
|
||||||
|
user.Data = enc
|
||||||
|
_, updateErr := users.Update(user)
|
||||||
|
|
||||||
|
return updateErr
|
||||||
|
}
|
||||||
|
|
||||||
|
func Add(userId int32, masterKey string, secret Secret) error {
|
||||||
|
user, _ := users.FetchById(userId)
|
||||||
|
|
||||||
|
secrets, _ := security.DecryptDataWithKey[[]Secret](user.Data, masterKey)
|
||||||
|
if secrets == nil {
|
||||||
|
return errors.New("user secrets are null")
|
||||||
|
}
|
||||||
|
|
||||||
|
secret.ID = security.RandBase58String(32)
|
||||||
|
secret.Modified = time.Now()
|
||||||
|
secret.Created = time.Now()
|
||||||
|
|
||||||
|
*secrets = append(*secrets, secret)
|
||||||
|
|
||||||
|
enc, _ := security.EncryptDataWithKey(secrets, masterKey)
|
||||||
|
|
||||||
|
user.Data = enc
|
||||||
|
_, updateErr := users.Update(user)
|
||||||
|
|
||||||
|
return updateErr
|
||||||
|
}
|
||||||
13
generator/passphrase.go
Normal file
13
generator/passphrase.go
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
package generator
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/sethvargo/go-diceware/diceware"
|
||||||
|
)
|
||||||
|
|
||||||
|
func GeneratePassphrase(words int) string {
|
||||||
|
list, _ := diceware.Generate(words)
|
||||||
|
|
||||||
|
return strings.Join(list, " ")
|
||||||
|
}
|
||||||
26
generator/password.go
Normal file
26
generator/password.go
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
package generator
|
||||||
|
|
||||||
|
import "github.com/sethvargo/go-password/password"
|
||||||
|
|
||||||
|
func GeneratePassword(length int, digits int, symbols int, disableUpper bool, allowRepeats bool) string {
|
||||||
|
if length <= 0 {
|
||||||
|
length = 16
|
||||||
|
}
|
||||||
|
|
||||||
|
if digits <= 0 {
|
||||||
|
digits = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
if symbols <= 0 {
|
||||||
|
symbols = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
result, _ := password.Generate(length, digits, symbols, disableUpper, allowRepeats)
|
||||||
|
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
func GenerateDefault() string {
|
||||||
|
result, _ := password.Generate(24, 5, 5, false, false)
|
||||||
|
return result
|
||||||
|
}
|
||||||
2
go.mod
2
go.mod
@@ -30,6 +30,8 @@ require (
|
|||||||
github.com/kr/pretty v0.3.1 // indirect
|
github.com/kr/pretty v0.3.1 // indirect
|
||||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||||
github.com/robfig/cron/v3 v3.0.1 // indirect
|
github.com/robfig/cron/v3 v3.0.1 // indirect
|
||||||
|
github.com/sethvargo/go-diceware v0.5.0 // indirect
|
||||||
|
github.com/sethvargo/go-password v0.3.1 // indirect
|
||||||
github.com/stretchr/testify v1.10.0 // indirect
|
github.com/stretchr/testify v1.10.0 // indirect
|
||||||
go.uber.org/atomic v1.7.0 // indirect
|
go.uber.org/atomic v1.7.0 // indirect
|
||||||
golang.org/x/exp v0.0.0-20240613232115-7f521ea00fb8 // indirect
|
golang.org/x/exp v0.0.0-20240613232115-7f521ea00fb8 // indirect
|
||||||
|
|||||||
4
go.sum
4
go.sum
@@ -70,6 +70,10 @@ github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzG
|
|||||||
github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
|
github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
|
||||||
github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8=
|
github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8=
|
||||||
github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4=
|
github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4=
|
||||||
|
github.com/sethvargo/go-diceware v0.5.0 h1:exrQ7GpaBo00GqRVM1N8ChXSsi3oS7tjQiIehsD+yR0=
|
||||||
|
github.com/sethvargo/go-diceware v0.5.0/go.mod h1:Lg1SyPS7yQO6BBgTN5r4f2MUDkqGfLWsOjHPY0kA8iw=
|
||||||
|
github.com/sethvargo/go-password v0.3.1 h1:WqrLTjo7X6AcVYfC6R7GtSyuUQR9hGyAj/f1PYQZCJU=
|
||||||
|
github.com/sethvargo/go-password v0.3.1/go.mod h1:rXofC1zT54N7R8K/h1WDUdkf9BOx5OptoxrMBcrXzvs=
|
||||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||||
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
|
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
|
||||||
|
|||||||
@@ -2,9 +2,7 @@ package app
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"maxwarden/entries"
|
"maxwarden/entries"
|
||||||
"maxwarden/security"
|
|
||||||
. "maxwarden/ui"
|
. "maxwarden/ui"
|
||||||
"maxwarden/users"
|
|
||||||
|
|
||||||
. "maragu.dev/gomponents"
|
. "maragu.dev/gomponents"
|
||||||
. "maragu.dev/gomponents/html"
|
. "maragu.dev/gomponents/html"
|
||||||
@@ -56,6 +54,7 @@ func EditorHandler(w http.ResponseWriter, r *http.Request) {
|
|||||||
url := r.FormValue("url")
|
url := r.FormValue("url")
|
||||||
|
|
||||||
secret = entries.Secret{
|
secret = entries.Secret{
|
||||||
|
ID: r.PathValue("id"),
|
||||||
Description: desc,
|
Description: desc,
|
||||||
URL: url,
|
URL: url,
|
||||||
Notes: notes,
|
Notes: notes,
|
||||||
@@ -63,42 +62,26 @@ func EditorHandler(w http.ResponseWriter, r *http.Request) {
|
|||||||
Username: username,
|
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 {
|
if editorType == EDITOR_TYPE_ADD {
|
||||||
secret.ID = security.RandBase58String(32)
|
entries.Add(identity.UserID, identity.MasterKey, secret)
|
||||||
*secrets = append(*secrets, secret)
|
|
||||||
} else {
|
} else {
|
||||||
secret.ID = r.PathValue("id")
|
entries.Update(identity.UserID, identity.MasterKey, secret)
|
||||||
|
|
||||||
// linear search and replace
|
|
||||||
for i, v := range *secrets {
|
|
||||||
if v.ID == secret.ID {
|
|
||||||
(*secrets)[i] = 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)
|
http.Redirect(w, r, "/app", http.StatusFound)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
AppLayout(title, *identity, session,
|
AppLayout(title, *identity, session,
|
||||||
|
Modal(
|
||||||
|
"password_generator",
|
||||||
|
Text("Passkey Generator"),
|
||||||
|
HxLoad("/app/generator-hx"),
|
||||||
|
[]Node {
|
||||||
|
ButtonUIOutline(ModalCloser(), Text("Close")),
|
||||||
|
},
|
||||||
|
),
|
||||||
|
|
||||||
If(editorType == EDITOR_TYPE_EDIT,
|
If(editorType == EDITOR_TYPE_EDIT,
|
||||||
Group{
|
Group{
|
||||||
Modal(
|
Modal(
|
||||||
@@ -106,7 +89,7 @@ func EditorHandler(w http.ResponseWriter, r *http.Request) {
|
|||||||
Text("Warning!"),
|
Text("Warning!"),
|
||||||
Text("Are you sure you want to delete this entry? This action cannot be undone."),
|
Text("Are you sure you want to delete this entry? This action cannot be undone."),
|
||||||
[]Node{
|
[]Node{
|
||||||
A(Href("/app/delete/" + secret.ID), ButtonUIDanger(Text("Delete"))),
|
A(Href("/app/delete/"+secret.ID), ButtonUIDanger(Text("Delete"))),
|
||||||
ButtonUIOutline(ModalCloser(), Text("Close")),
|
ButtonUIOutline(ModalCloser(), Text("Close")),
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
@@ -119,16 +102,41 @@ func EditorHandler(w http.ResponseWriter, r *http.Request) {
|
|||||||
Form(
|
Form(
|
||||||
AutoComplete("off"),
|
AutoComplete("off"),
|
||||||
Method("POST"),
|
Method("POST"),
|
||||||
|
|
||||||
FormLabel(Text("Description")),
|
FormLabel(Text("Description")),
|
||||||
FormInput(Type("text"), Name("description"), Value(secret.Description)),
|
FormInput(Type("text"), Name("description"), Value(secret.Description), AutoFocus(), Required()),
|
||||||
Br(),
|
Br(),
|
||||||
|
|
||||||
FormLabel(Text("Username")),
|
FormLabel(Text("Username")),
|
||||||
FormInput(Type("text"), Name("un"), Value(secret.Username)),
|
FormInput(Type("text"), Name("un"), Value(secret.Username)),
|
||||||
Br(),
|
Br(),
|
||||||
|
|
||||||
|
Div(
|
||||||
|
FlexLeftRight(
|
||||||
FormLabel(Text("Password")),
|
FormLabel(Text("Password")),
|
||||||
FormInput(Type("password"), Name("pas"), Value(secret.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(),
|
Br(),
|
||||||
|
|
||||||
FormLabel(Text("URL")),
|
FormLabel(Text("URL")),
|
||||||
@@ -144,6 +152,15 @@ func EditorHandler(w http.ResponseWriter, r *http.Request) {
|
|||||||
ButtonUISuccess(Text(btnLabel), Type("submit")),
|
ButtonUISuccess(Text(btnLabel), Type("submit")),
|
||||||
A(Href("/app"), ButtonUIOutline(Text("Close"), Type("button"))),
|
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)
|
).Render(w)
|
||||||
}
|
}
|
||||||
|
|||||||
17
handlers/app/generator.go
Normal file
17
handlers/app/generator.go
Normal 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)
|
||||||
|
}
|
||||||
114
handlers/app/generator_hx.go
Normal file
114
handlers/app/generator_hx.go
Normal 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)
|
||||||
|
}
|
||||||
@@ -25,7 +25,7 @@ func VaultHandler(w http.ResponseWriter, r *http.Request) {
|
|||||||
margin-bottom: $5;
|
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"),
|
HxLoad("/app/vault-hx"),
|
||||||
).Render(w)
|
).Render(w)
|
||||||
|
|||||||
@@ -72,10 +72,58 @@ func VaultHxHandler(w http.ResponseWriter, r *http.Request) {
|
|||||||
func(entry entries.Secret) Node {
|
func(entry entries.Secret) Node {
|
||||||
return Tr(
|
return Tr(
|
||||||
TdLeft(Text(entry.Description)),
|
TdLeft(Text(entry.Description)),
|
||||||
TdLeft(Text(entry.Username)),
|
TdLeft(
|
||||||
TdLeft(Text("********")),
|
IfElse(entry.Username != "",
|
||||||
TdLeft(PageLink(security.SanitizationPolicy.Sanitize(entry.URL), Text(entry.URL), false)),
|
Flex(
|
||||||
TdCenter(A(Href("/app/editor/edit/" + entry.ID), ButtonUIOutline(Icon(ICON_PENCIL, 16)))),
|
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,
|
nil,
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ var NavGroups = []NavGroup{
|
|||||||
{
|
{
|
||||||
Title: "Tools",
|
Title: "Tools",
|
||||||
SubGroup: []NavGroup{
|
SubGroup: []NavGroup{
|
||||||
{SectionId: LAYOUT_SECTION_TOOLS, Title: "Password Generator", URL: "/app/examples/forms"},
|
{SectionId: LAYOUT_SECTION_TOOLS, Title: "Passkey Generator", URL: "/app/generator"},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|||||||
22
ui/forms.go
22
ui/forms.go
@@ -1,6 +1,8 @@
|
|||||||
package ui
|
package ui
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"strconv"
|
||||||
|
|
||||||
. "maragu.dev/gomponents"
|
. "maragu.dev/gomponents"
|
||||||
. "maragu.dev/gomponents/html"
|
. "maragu.dev/gomponents/html"
|
||||||
)
|
)
|
||||||
@@ -74,3 +76,23 @@ func FormTextarea(children ...Node) Node {
|
|||||||
func FormLabel(children ...Node) Node {
|
func FormLabel(children ...Node) Node {
|
||||||
return Label(InlineStyle("$me { color: $color(neutral-900); font-size: var(--text-sm); } "), Group(children))
|
return Label(InlineStyle("$me { color: $color(neutral-900); font-size: var(--text-sm); } "), Group(children))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func FormCheck(children ...Node) Node {
|
||||||
|
return Input(
|
||||||
|
InlineStyle(`
|
||||||
|
$me {
|
||||||
|
margin-left: $3;
|
||||||
|
}
|
||||||
|
`),
|
||||||
|
Type("checkbox"),
|
||||||
|
Group(children),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
func FormSlider(min int, max int, children ...Node) Node {
|
||||||
|
return Input(
|
||||||
|
Type("range"),
|
||||||
|
Min(strconv.Itoa(min)),
|
||||||
|
Max(strconv.Itoa(max)),
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -59,6 +59,13 @@ func Flex(n ...Node) Node {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func FlexLeftRight(n ...Node) Node {
|
||||||
|
return Div(
|
||||||
|
InlineStyle("$me { display: flex; align-items: center; flex-direction: row; gap: $3; justify-content: space-between; }"),
|
||||||
|
Group(n),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
func CardNoPadding(body ...Node) Node {
|
func CardNoPadding(body ...Node) Node {
|
||||||
return Div(
|
return Div(
|
||||||
InlineStyle(`
|
InlineStyle(`
|
||||||
@@ -109,6 +116,12 @@ func ToText(i interface{}) Node {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// TEXT
|
// TEXT
|
||||||
|
func Heading(text string) Node {
|
||||||
|
return H1(
|
||||||
|
InlineStyle("$me { font-weight: bold; font-size: var(--text-2xl); letter-spacing: var(--tracking-tight); color: $color(black); }"),
|
||||||
|
Text(text),
|
||||||
|
)
|
||||||
|
}
|
||||||
func PageLink(location string, display Node, newPage bool) Node {
|
func PageLink(location string, display Node, newPage bool) Node {
|
||||||
return A(
|
return A(
|
||||||
Href(location),
|
Href(location),
|
||||||
|
|||||||
@@ -38,6 +38,7 @@ const (
|
|||||||
ICON_X_DIALOG_CLOSE = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" aria-hidden="true" stroke="currentColor" fill="none" stroke-width="1.4" class="w-5 h-5"><path stroke-linecap="round" stroke-linejoin="round" d="M6 18L18 6M6 6l12 12"/></svg>`
|
ICON_X_DIALOG_CLOSE = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" aria-hidden="true" stroke="currentColor" fill="none" stroke-width="1.4" class="w-5 h-5"><path stroke-linecap="round" stroke-linejoin="round" d="M6 18L18 6M6 6l12 12"/></svg>`
|
||||||
ICON_PENCIL = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-pencil"><path d="M21.174 6.812a1 1 0 0 0-3.986-3.987L3.842 16.174a2 2 0 0 0-.5.83l-1.321 4.352a.5.5 0 0 0 .623.622l4.353-1.32a2 2 0 0 0 .83-.497z"/><path d="m15 5 4 4"/></svg>`
|
ICON_PENCIL = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-pencil"><path d="M21.174 6.812a1 1 0 0 0-3.986-3.987L3.842 16.174a2 2 0 0 0-.5.83l-1.321 4.352a.5.5 0 0 0 .623.622l4.353-1.32a2 2 0 0 0 .83-.497z"/><path d="m15 5 4 4"/></svg>`
|
||||||
ICON_LOCK_KEYHOLE = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-lock-keyhole"><circle cx="12" cy="16" r="1"/><rect x="3" y="10" width="18" height="12" rx="2"/><path d="M7 10V7a5 5 0 0 1 10 0v3"/></svg>`
|
ICON_LOCK_KEYHOLE = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-lock-keyhole"><circle cx="12" cy="16" r="1"/><rect x="3" y="10" width="18" height="12" rx="2"/><path d="M7 10V7a5 5 0 0 1 10 0v3"/></svg>`
|
||||||
|
ICON_COPY = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-copy"><rect width="14" height="14" x="8" y="8" rx="2" ry="2"/><path d="M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2"/></svg>`
|
||||||
|
|
||||||
//material
|
//material
|
||||||
ICON_SEARCH = `<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="3" stroke="currentColor" class="w-8 h-8 text-slate-600"><path stroke-linecap="round" stroke-linejoin="round" d="m21 21-5.197-5.197m0 0A7.5 7.5 0 1 0 5.196 5.196a7.5 7.5 0 0 0 10.607 10.607Z" /></svg>`
|
ICON_SEARCH = `<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="3" stroke="currentColor" class="w-8 h-8 text-slate-600"><path stroke-linecap="round" stroke-linejoin="round" d="m21 21-5.197-5.197m0 0A7.5 7.5 0 1 0 5.196 5.196a7.5 7.5 0 0 0 10.607 10.607Z" /></svg>`
|
||||||
|
|||||||
20
vendor/github.com/sethvargo/go-diceware/LICENSE
generated
vendored
Normal file
20
vendor/github.com/sethvargo/go-diceware/LICENSE
generated
vendored
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
Copyright 2017 Seth Vargo <seth@sethvargo.com>
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining
|
||||||
|
a copy of this software and associated documentation files (the
|
||||||
|
"Software"), to deal in the Software without restriction, including
|
||||||
|
without limitation the rights to use, copy, modify, merge, publish,
|
||||||
|
distribute, sublicense, and/or sell copies of the Software, and to
|
||||||
|
permit persons to whom the Software is furnished to do so, subject to
|
||||||
|
the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be
|
||||||
|
included in all copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||||
|
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||||
|
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||||
|
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||||
|
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||||
|
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||||
|
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
195
vendor/github.com/sethvargo/go-diceware/diceware/generate.go
generated
vendored
Normal file
195
vendor/github.com/sethvargo/go-diceware/diceware/generate.go
generated
vendored
Normal file
@@ -0,0 +1,195 @@
|
|||||||
|
package diceware
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/rand"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"math"
|
||||||
|
"math/big"
|
||||||
|
)
|
||||||
|
|
||||||
|
// sides is the number of sides on a die.
|
||||||
|
var sides = big.NewInt(6)
|
||||||
|
|
||||||
|
var _ DicewareGenerator = (*Generator)(nil)
|
||||||
|
|
||||||
|
// Generator is the stateful generator which can be used to customize the word
|
||||||
|
// list and other generation options.
|
||||||
|
type Generator struct {
|
||||||
|
wordList WordList
|
||||||
|
randReader io.Reader
|
||||||
|
}
|
||||||
|
|
||||||
|
// GeneratorInput is used as input to the NewGenerator function.
|
||||||
|
type GeneratorInput struct {
|
||||||
|
// WordList is the word list to use. There are built-in word lists like
|
||||||
|
// WordListEffBig (default), WordListEffSmall, and WordListOriginal. You can
|
||||||
|
// also bring your own word list by implementing the WordList interface.
|
||||||
|
WordList WordList
|
||||||
|
|
||||||
|
// RandReader is an optional reader to use in place of the default
|
||||||
|
// (crypto/rand.Reader), which can be used to generate repeatable sets of
|
||||||
|
// words
|
||||||
|
RandReader io.Reader
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewGenerator creates a new Generator from the specified configuration. If no
|
||||||
|
// input is given, all the default values are used. This function is safe for
|
||||||
|
// concurrent use.
|
||||||
|
func NewGenerator(i *GeneratorInput) (*Generator, error) {
|
||||||
|
if i == nil {
|
||||||
|
i = new(GeneratorInput)
|
||||||
|
}
|
||||||
|
|
||||||
|
if i.WordList == nil {
|
||||||
|
i.WordList = WordListEffLarge()
|
||||||
|
}
|
||||||
|
|
||||||
|
gen := &Generator{
|
||||||
|
wordList: i.WordList,
|
||||||
|
randReader: i.RandReader,
|
||||||
|
}
|
||||||
|
|
||||||
|
if gen.randReader == nil {
|
||||||
|
gen.randReader = rand.Reader
|
||||||
|
}
|
||||||
|
|
||||||
|
return gen, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generate generates a collection of diceware words, specified by the numWords
|
||||||
|
// parameter.
|
||||||
|
//
|
||||||
|
// The algorithm is fast, but it's not designed to be performant, favoring
|
||||||
|
// entropy over speed.
|
||||||
|
//
|
||||||
|
// This function is safe for concurrent use, but there is a possibility of
|
||||||
|
// concurrent invocations generating overlapping words. To generate multiple
|
||||||
|
// non-overlapping words, use a single invocation of the function and split the
|
||||||
|
// resulting string list.
|
||||||
|
func (g *Generator) Generate(numWords int) ([]string, error) {
|
||||||
|
if typ, ok := g.wordList.(WordListNumWordser); ok {
|
||||||
|
if l := typ.NumWords(); numWords > l {
|
||||||
|
return nil, fmt.Errorf("number of requested words (%d) cannot exceed the size of the wordlist (%d)",
|
||||||
|
numWords, l)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
list := make([]string, 0, numWords)
|
||||||
|
seen := make(map[string]struct{}, numWords)
|
||||||
|
|
||||||
|
for i := 0; i < numWords; i++ {
|
||||||
|
n, err := g.RollWord(g.wordList.Digits())
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
word := g.wordList.WordAt(n)
|
||||||
|
if _, ok := seen[word]; ok {
|
||||||
|
i--
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
list = append(list, word)
|
||||||
|
seen[word] = struct{}{}
|
||||||
|
}
|
||||||
|
|
||||||
|
return list, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// MustGenerate is the same as Generate, but panics on error.
|
||||||
|
func (g *Generator) MustGenerate(numWords int) []string {
|
||||||
|
list, err := g.Generate(numWords)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
return list
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generate - see Generator.Generate for usage.
|
||||||
|
func Generate(numWords int) ([]string, error) {
|
||||||
|
gen, err := NewGenerator(nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return gen.Generate(numWords)
|
||||||
|
}
|
||||||
|
|
||||||
|
// MustGenerate - see Generator.MustGenerate for usage.
|
||||||
|
func MustGenerate(numWords int) []string {
|
||||||
|
gen, err := NewGenerator(nil)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
return gen.MustGenerate(numWords)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GenerateWithWordList generates a list of the given number of words from the
|
||||||
|
// given word list.
|
||||||
|
func GenerateWithWordList(numWords int, wordList WordList) ([]string, error) {
|
||||||
|
gen, err := NewGenerator(&GeneratorInput{
|
||||||
|
WordList: wordList,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return gen.Generate(numWords)
|
||||||
|
}
|
||||||
|
|
||||||
|
// WordAt retrieves the word at the given index from EFF's large wordlist.
|
||||||
|
//
|
||||||
|
// Deprecated: Use WordList.WordAt instead.
|
||||||
|
func WordAt(i int) string {
|
||||||
|
return WordListEffLarge().WordAt(i)
|
||||||
|
}
|
||||||
|
|
||||||
|
// RollDie rolls a single 6-sided die and returns a value between [1,6].
|
||||||
|
//
|
||||||
|
// Internally this creates a new Generator with a nil configuration and calls
|
||||||
|
// Generator.RollDie.
|
||||||
|
func RollDie() (int, error) {
|
||||||
|
gen, err := NewGenerator(nil)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
return gen.RollDie()
|
||||||
|
}
|
||||||
|
|
||||||
|
// RollWord rolls and aggregates dice to represent one word in the list. The
|
||||||
|
// result is the index of the word in the list.
|
||||||
|
//
|
||||||
|
// Internally this creates a new Generator with a nil configuration and calls
|
||||||
|
// Generator.RollWord.
|
||||||
|
func RollWord(d int) (int, error) {
|
||||||
|
gen, err := NewGenerator(nil)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
return gen.RollWord(d)
|
||||||
|
}
|
||||||
|
|
||||||
|
// RollDie rolls a single 6-sided die and returns a value between [1,6].
|
||||||
|
func (g *Generator) RollDie() (int, error) {
|
||||||
|
r, err := rand.Int(g.randReader, sides)
|
||||||
|
if err != nil {
|
||||||
|
return 0, fmt.Errorf("failed to generate a random number: %w", err)
|
||||||
|
}
|
||||||
|
return int(r.Int64()) + 1, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// RollWord rolls and aggregates dice to represent one word in the list. The
|
||||||
|
// result is the index of the word in the list.
|
||||||
|
func (g *Generator) RollWord(d int) (int, error) {
|
||||||
|
var final int
|
||||||
|
|
||||||
|
for i := d; i > 0; i-- {
|
||||||
|
res, err := g.RollDie()
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
final += res * int(math.Pow(10, float64(i-1)))
|
||||||
|
}
|
||||||
|
|
||||||
|
return final, nil
|
||||||
|
}
|
||||||
6
vendor/github.com/sethvargo/go-diceware/diceware/interface.go
generated
vendored
Normal file
6
vendor/github.com/sethvargo/go-diceware/diceware/interface.go
generated
vendored
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
package diceware
|
||||||
|
|
||||||
|
type DicewareGenerator interface {
|
||||||
|
Generate(int) ([]string, error)
|
||||||
|
MustGenerate(int) []string
|
||||||
|
}
|
||||||
38
vendor/github.com/sethvargo/go-diceware/diceware/mock.go
generated
vendored
Normal file
38
vendor/github.com/sethvargo/go-diceware/diceware/mock.go
generated
vendored
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
package diceware
|
||||||
|
|
||||||
|
var _ DicewareGenerator = (*mockGenerator)(nil)
|
||||||
|
|
||||||
|
type mockGenerator struct {
|
||||||
|
result []string
|
||||||
|
err error
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewMockGenerator creates a new generator that satisfies the DicewareGenerator
|
||||||
|
// interface. If an error is provided, the error is returned. If a result if
|
||||||
|
// provided, the result is always returned, regardless of what parameters are
|
||||||
|
// passed into the Generate or MustGenerate methods.
|
||||||
|
//
|
||||||
|
// This function is most useful for tests where you want to have predicable
|
||||||
|
// results for a transitive resource that depends on go-diceware.
|
||||||
|
func NewMockGenerator(result []string, err error) *mockGenerator {
|
||||||
|
return &mockGenerator{
|
||||||
|
result: result,
|
||||||
|
err: err,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generate returns the mocked result or error.
|
||||||
|
func (g *mockGenerator) Generate(int) ([]string, error) {
|
||||||
|
if g.err != nil {
|
||||||
|
return nil, g.err
|
||||||
|
}
|
||||||
|
return g.result, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// MustGenerate returns the mocked result or panics if an error was given.
|
||||||
|
func (g *mockGenerator) MustGenerate(int) []string {
|
||||||
|
if g.err != nil {
|
||||||
|
panic(g.err)
|
||||||
|
}
|
||||||
|
return g.result
|
||||||
|
}
|
||||||
42
vendor/github.com/sethvargo/go-diceware/diceware/word_list.go
generated
vendored
Normal file
42
vendor/github.com/sethvargo/go-diceware/diceware/word_list.go
generated
vendored
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
package diceware
|
||||||
|
|
||||||
|
// WordList is an interface that must be implemented to be considered a word
|
||||||
|
// list for use in the diceware algorithm. This interface can be implemented by
|
||||||
|
// other libraries.
|
||||||
|
type WordList interface {
|
||||||
|
// Digits is the number of digits for indexes in the word list. This
|
||||||
|
// determines the number of dice rolls.
|
||||||
|
Digits() int
|
||||||
|
|
||||||
|
// WordAt returns the word at the given integer in the word list.
|
||||||
|
WordAt(int) string
|
||||||
|
}
|
||||||
|
|
||||||
|
// WordListNumWordser is an auxiliary interface that returns the number of words
|
||||||
|
// in the list. This is a separate interface for backwards compatibility.
|
||||||
|
type WordListNumWordser interface {
|
||||||
|
// NumWords returns the total number of words in the list.
|
||||||
|
NumWords() int
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
_ WordList = (*wordListInternal)(nil)
|
||||||
|
_ WordListNumWordser = (*wordListInternal)(nil)
|
||||||
|
)
|
||||||
|
|
||||||
|
type wordListInternal struct {
|
||||||
|
digits int
|
||||||
|
words map[int]string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *wordListInternal) Digits() int {
|
||||||
|
return w.digits
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *wordListInternal) WordAt(i int) string {
|
||||||
|
return w.words[i]
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *wordListInternal) NumWords() int {
|
||||||
|
return len(w.words)
|
||||||
|
}
|
||||||
7794
vendor/github.com/sethvargo/go-diceware/diceware/word_list_eff_large.go
generated
vendored
Normal file
7794
vendor/github.com/sethvargo/go-diceware/diceware/word_list_eff_large.go
generated
vendored
Normal file
File diff suppressed because it is too large
Load Diff
1314
vendor/github.com/sethvargo/go-diceware/diceware/word_list_eff_small.go
generated
vendored
Normal file
1314
vendor/github.com/sethvargo/go-diceware/diceware/word_list_eff_small.go
generated
vendored
Normal file
File diff suppressed because it is too large
Load Diff
7789
vendor/github.com/sethvargo/go-diceware/diceware/word_list_original.go
generated
vendored
Normal file
7789
vendor/github.com/sethvargo/go-diceware/diceware/word_list_original.go
generated
vendored
Normal file
File diff suppressed because it is too large
Load Diff
20
vendor/github.com/sethvargo/go-password/LICENSE
generated
vendored
Normal file
20
vendor/github.com/sethvargo/go-password/LICENSE
generated
vendored
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
Copyright 2017 Seth Vargo <seth@sethvargo.com>
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining
|
||||||
|
a copy of this software and associated documentation files (the
|
||||||
|
"Software"), to deal in the Software without restriction, including
|
||||||
|
without limitation the rights to use, copy, modify, merge, publish,
|
||||||
|
distribute, sublicense, and/or sell copies of the Software, and to
|
||||||
|
permit persons to whom the Software is furnished to do so, subject to
|
||||||
|
the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be
|
||||||
|
included in all copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||||
|
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||||
|
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||||
|
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||||
|
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||||
|
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||||
|
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
262
vendor/github.com/sethvargo/go-password/password/generate.go
generated
vendored
Normal file
262
vendor/github.com/sethvargo/go-password/password/generate.go
generated
vendored
Normal file
@@ -0,0 +1,262 @@
|
|||||||
|
// Package password provides a library for generating high-entropy random
|
||||||
|
// password strings via the crypto/rand package.
|
||||||
|
//
|
||||||
|
// res, err := Generate(64, 10, 10, false, false)
|
||||||
|
// if err != nil {
|
||||||
|
// log.Fatal(err)
|
||||||
|
// }
|
||||||
|
// log.Printf(res)
|
||||||
|
//
|
||||||
|
// Most functions are safe for concurrent use.
|
||||||
|
package password
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/rand"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"math/big"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Built-time checks that the generators implement the interface.
|
||||||
|
var _ PasswordGenerator = (*Generator)(nil)
|
||||||
|
|
||||||
|
// PasswordGenerator is an interface that implements the Generate function. This
|
||||||
|
// is useful for testing where you can pass this interface instead of a real
|
||||||
|
// password generator to mock responses for predicability.
|
||||||
|
type PasswordGenerator interface {
|
||||||
|
Generate(int, int, int, bool, bool) (string, error)
|
||||||
|
MustGenerate(int, int, int, bool, bool) string
|
||||||
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
// LowerLetters is the list of lowercase letters.
|
||||||
|
LowerLetters = "abcdefghijklmnopqrstuvwxyz"
|
||||||
|
|
||||||
|
// UpperLetters is the list of uppercase letters.
|
||||||
|
UpperLetters = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
|
||||||
|
|
||||||
|
// Digits is the list of permitted digits.
|
||||||
|
Digits = "0123456789"
|
||||||
|
|
||||||
|
// Symbols is the list of symbols.
|
||||||
|
Symbols = "~!@#$%^&*()_+`-={}|[]\\:\"<>?,./"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
// ErrExceedsTotalLength is the error returned with the number of digits and
|
||||||
|
// symbols is greater than the total length.
|
||||||
|
ErrExceedsTotalLength = errors.New("number of digits and symbols must be less than total length")
|
||||||
|
|
||||||
|
// ErrLettersExceedsAvailable is the error returned with the number of letters
|
||||||
|
// exceeds the number of available letters and repeats are not allowed.
|
||||||
|
ErrLettersExceedsAvailable = errors.New("number of letters exceeds available letters and repeats are not allowed")
|
||||||
|
|
||||||
|
// ErrDigitsExceedsAvailable is the error returned with the number of digits
|
||||||
|
// exceeds the number of available digits and repeats are not allowed.
|
||||||
|
ErrDigitsExceedsAvailable = errors.New("number of digits exceeds available digits and repeats are not allowed")
|
||||||
|
|
||||||
|
// ErrSymbolsExceedsAvailable is the error returned with the number of symbols
|
||||||
|
// exceeds the number of available symbols and repeats are not allowed.
|
||||||
|
ErrSymbolsExceedsAvailable = errors.New("number of symbols exceeds available symbols and repeats are not allowed")
|
||||||
|
)
|
||||||
|
|
||||||
|
// Generator is the stateful generator which can be used to customize the list
|
||||||
|
// of letters, digits, and/or symbols.
|
||||||
|
type Generator struct {
|
||||||
|
lowerLetters string
|
||||||
|
upperLetters string
|
||||||
|
digits string
|
||||||
|
symbols string
|
||||||
|
reader io.Reader
|
||||||
|
}
|
||||||
|
|
||||||
|
// GeneratorInput is used as input to the NewGenerator function.
|
||||||
|
type GeneratorInput struct {
|
||||||
|
LowerLetters string
|
||||||
|
UpperLetters string
|
||||||
|
Digits string
|
||||||
|
Symbols string
|
||||||
|
Reader io.Reader // rand.Reader by default
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewGenerator creates a new Generator from the specified configuration. If no
|
||||||
|
// input is given, all the default values are used. This function is safe for
|
||||||
|
// concurrent use.
|
||||||
|
func NewGenerator(i *GeneratorInput) (*Generator, error) {
|
||||||
|
if i == nil {
|
||||||
|
i = new(GeneratorInput)
|
||||||
|
}
|
||||||
|
|
||||||
|
g := &Generator{
|
||||||
|
lowerLetters: i.LowerLetters,
|
||||||
|
upperLetters: i.UpperLetters,
|
||||||
|
digits: i.Digits,
|
||||||
|
symbols: i.Symbols,
|
||||||
|
reader: i.Reader,
|
||||||
|
}
|
||||||
|
|
||||||
|
if g.lowerLetters == "" {
|
||||||
|
g.lowerLetters = LowerLetters
|
||||||
|
}
|
||||||
|
|
||||||
|
if g.upperLetters == "" {
|
||||||
|
g.upperLetters = UpperLetters
|
||||||
|
}
|
||||||
|
|
||||||
|
if g.digits == "" {
|
||||||
|
g.digits = Digits
|
||||||
|
}
|
||||||
|
|
||||||
|
if g.symbols == "" {
|
||||||
|
g.symbols = Symbols
|
||||||
|
}
|
||||||
|
|
||||||
|
if g.reader == nil {
|
||||||
|
g.reader = rand.Reader
|
||||||
|
}
|
||||||
|
|
||||||
|
return g, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generate generates a password with the given requirements. length is the
|
||||||
|
// total number of characters in the password. numDigits is the number of digits
|
||||||
|
// to include in the result. numSymbols is the number of symbols to include in
|
||||||
|
// the result. noUpper excludes uppercase letters from the results. allowRepeat
|
||||||
|
// allows characters to repeat.
|
||||||
|
//
|
||||||
|
// The algorithm is fast, but it's not designed to be performant; it favors
|
||||||
|
// entropy over speed. This function is safe for concurrent use.
|
||||||
|
func (g *Generator) Generate(length, numDigits, numSymbols int, noUpper, allowRepeat bool) (string, error) {
|
||||||
|
letters := g.lowerLetters
|
||||||
|
if !noUpper {
|
||||||
|
letters += g.upperLetters
|
||||||
|
}
|
||||||
|
|
||||||
|
chars := length - numDigits - numSymbols
|
||||||
|
if chars < 0 {
|
||||||
|
return "", ErrExceedsTotalLength
|
||||||
|
}
|
||||||
|
|
||||||
|
if !allowRepeat && chars > len(letters) {
|
||||||
|
return "", ErrLettersExceedsAvailable
|
||||||
|
}
|
||||||
|
|
||||||
|
if !allowRepeat && numDigits > len(g.digits) {
|
||||||
|
return "", ErrDigitsExceedsAvailable
|
||||||
|
}
|
||||||
|
|
||||||
|
if !allowRepeat && numSymbols > len(g.symbols) {
|
||||||
|
return "", ErrSymbolsExceedsAvailable
|
||||||
|
}
|
||||||
|
|
||||||
|
var result string
|
||||||
|
|
||||||
|
// Characters
|
||||||
|
for i := 0; i < chars; i++ {
|
||||||
|
ch, err := randomElement(g.reader, letters)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
if !allowRepeat && strings.Contains(result, ch) {
|
||||||
|
i--
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
result, err = randomInsert(g.reader, result, ch)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Digits
|
||||||
|
for i := 0; i < numDigits; i++ {
|
||||||
|
d, err := randomElement(g.reader, g.digits)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
if !allowRepeat && strings.Contains(result, d) {
|
||||||
|
i--
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
result, err = randomInsert(g.reader, result, d)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Symbols
|
||||||
|
for i := 0; i < numSymbols; i++ {
|
||||||
|
sym, err := randomElement(g.reader, g.symbols)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
if !allowRepeat && strings.Contains(result, sym) {
|
||||||
|
i--
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
result, err = randomInsert(g.reader, result, sym)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// MustGenerate is the same as Generate, but panics on error.
|
||||||
|
func (g *Generator) MustGenerate(length, numDigits, numSymbols int, noUpper, allowRepeat bool) string {
|
||||||
|
res, err := g.Generate(length, numDigits, numSymbols, noUpper, allowRepeat)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generate is the package shortcut for Generator.Generate.
|
||||||
|
func Generate(length, numDigits, numSymbols int, noUpper, allowRepeat bool) (string, error) {
|
||||||
|
gen, err := NewGenerator(nil)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
return gen.Generate(length, numDigits, numSymbols, noUpper, allowRepeat)
|
||||||
|
}
|
||||||
|
|
||||||
|
// MustGenerate is the package shortcut for Generator.MustGenerate.
|
||||||
|
func MustGenerate(length, numDigits, numSymbols int, noUpper, allowRepeat bool) string {
|
||||||
|
res, err := Generate(length, numDigits, numSymbols, noUpper, allowRepeat)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
|
||||||
|
// randomInsert randomly inserts the given value into the given string.
|
||||||
|
func randomInsert(reader io.Reader, s, val string) (string, error) {
|
||||||
|
if s == "" {
|
||||||
|
return val, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
n, err := rand.Int(reader, big.NewInt(int64(len(s)+1)))
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("failed to generate random integer: %w", err)
|
||||||
|
}
|
||||||
|
i := n.Int64()
|
||||||
|
return s[0:i] + val + s[i:], nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// randomElement extracts a random element from the given string.
|
||||||
|
func randomElement(reader io.Reader, s string) (string, error) {
|
||||||
|
n, err := rand.Int(reader, big.NewInt(int64(len(s))))
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("failed to generate random integer: %w", err)
|
||||||
|
}
|
||||||
|
return string(s[n.Int64()]), nil
|
||||||
|
}
|
||||||
39
vendor/github.com/sethvargo/go-password/password/mock.go
generated
vendored
Normal file
39
vendor/github.com/sethvargo/go-password/password/mock.go
generated
vendored
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
package password
|
||||||
|
|
||||||
|
// Built-time checks that the generators implement the interface.
|
||||||
|
var _ PasswordGenerator = (*mockGenerator)(nil)
|
||||||
|
|
||||||
|
type mockGenerator struct {
|
||||||
|
result string
|
||||||
|
err error
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewMockGenerator creates a new generator that satisfies the PasswordGenerator
|
||||||
|
// interface. If an error is provided, the error is returned. If a result if
|
||||||
|
// provided, the result is always returned, regardless of what parameters are
|
||||||
|
// passed into the Generate or MustGenerate methods.
|
||||||
|
//
|
||||||
|
// This function is most useful for tests where you want to have predicable
|
||||||
|
// results for a transitive resource that depends on go-password.
|
||||||
|
func NewMockGenerator(result string, err error) *mockGenerator {
|
||||||
|
return &mockGenerator{
|
||||||
|
result: result,
|
||||||
|
err: err,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generate returns the mocked result or error.
|
||||||
|
func (g *mockGenerator) Generate(int, int, int, bool, bool) (string, error) {
|
||||||
|
if g.err != nil {
|
||||||
|
return "", g.err
|
||||||
|
}
|
||||||
|
return g.result, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// MustGenerate returns the mocked result or panics if an error was given.
|
||||||
|
func (g *mockGenerator) MustGenerate(int, int, int, bool, bool) string {
|
||||||
|
if g.err != nil {
|
||||||
|
panic(g.err)
|
||||||
|
}
|
||||||
|
return g.result
|
||||||
|
}
|
||||||
6
vendor/modules.txt
vendored
6
vendor/modules.txt
vendored
@@ -78,6 +78,12 @@ github.com/pmezard/go-difflib/difflib
|
|||||||
# github.com/robfig/cron/v3 v3.0.1
|
# github.com/robfig/cron/v3 v3.0.1
|
||||||
## explicit; go 1.12
|
## explicit; go 1.12
|
||||||
github.com/robfig/cron/v3
|
github.com/robfig/cron/v3
|
||||||
|
# github.com/sethvargo/go-diceware v0.5.0
|
||||||
|
## explicit; go 1.22
|
||||||
|
github.com/sethvargo/go-diceware/diceware
|
||||||
|
# github.com/sethvargo/go-password v0.3.1
|
||||||
|
## explicit; go 1.21
|
||||||
|
github.com/sethvargo/go-password/password
|
||||||
# github.com/stretchr/testify v1.10.0
|
# github.com/stretchr/testify v1.10.0
|
||||||
## explicit; go 1.17
|
## explicit; go 1.17
|
||||||
github.com/stretchr/testify/assert
|
github.com/stretchr/testify/assert
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user