revamped CSS - using Tailwind now, update layout and added home page
This commit is contained in:
+31
-13
@@ -3,6 +3,7 @@ package passwordgenerator
|
||||
import (
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"headeranalyzer/security"
|
||||
)
|
||||
@@ -17,17 +18,18 @@ func PasswordAPIHandler(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
|
||||
var requestData struct {
|
||||
Type string `json:"type"`
|
||||
Length int `json:"length"`
|
||||
IncludeUpper bool `json:"includeUpper"`
|
||||
IncludeLower bool `json:"includeLower"`
|
||||
NumberCount int `json:"numberCount"`
|
||||
SpecialChars string `json:"specialChars"`
|
||||
NoConsecutive bool `json:"noConsecutive"`
|
||||
WordCount int `json:"wordCount"`
|
||||
NumberPosition string `json:"numberPosition"`
|
||||
UseNumbers bool `json:"useNumbers"`
|
||||
UseSpecial bool `json:"useSpecial"`
|
||||
Type string `json:"type"`
|
||||
Length int `json:"length"`
|
||||
IncludeUpper bool `json:"includeUpper"`
|
||||
IncludeLower bool `json:"includeLower"`
|
||||
NumberCount int `json:"numberCount"`
|
||||
SpecialChars string `json:"specialChars"`
|
||||
MinSpecialChars int `json:"minSpecialChars"`
|
||||
NoConsecutive bool `json:"noConsecutive"`
|
||||
WordCount int `json:"wordCount"`
|
||||
NumberPosition string `json:"numberPosition"`
|
||||
UseNumbers bool `json:"useNumbers"`
|
||||
UseSpecial bool `json:"useSpecial"`
|
||||
}
|
||||
|
||||
if err := json.NewDecoder(r.Body).Decode(&requestData); err != nil {
|
||||
@@ -49,6 +51,12 @@ func PasswordAPIHandler(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
if requestData.MinSpecialChars < 0 || requestData.MinSpecialChars > 20 {
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
w.Write([]byte("Minimum special characters count must be between 0 and 20"))
|
||||
return
|
||||
}
|
||||
|
||||
if requestData.WordCount < 2 || requestData.WordCount > 10 {
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
w.Write([]byte("Word count must be between 2 and 10"))
|
||||
@@ -72,8 +80,17 @@ func PasswordAPIHandler(w http.ResponseWriter, r *http.Request) {
|
||||
requestData.NumberPosition = "end" // Default
|
||||
}
|
||||
|
||||
// Sanitize special characters to prevent potential issues
|
||||
requestData.SpecialChars = validator.SanitizeHTML(requestData.SpecialChars)
|
||||
// Validate special characters - only allow specific safe characters
|
||||
if len(requestData.SpecialChars) > 0 {
|
||||
allowedSpecialChars := "!@#$%&*-_=+."
|
||||
for _, char := range requestData.SpecialChars {
|
||||
if !strings.ContainsRune(allowedSpecialChars, char) {
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
w.Write([]byte("Special characters must only contain: !@#$%&*-_=+."))
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Convert to internal Config format
|
||||
config := Config{
|
||||
@@ -82,6 +99,7 @@ func PasswordAPIHandler(w http.ResponseWriter, r *http.Request) {
|
||||
IncludeLower: requestData.IncludeLower,
|
||||
NumberCount: requestData.NumberCount,
|
||||
SpecialChars: requestData.SpecialChars,
|
||||
MinSpecialChars: requestData.MinSpecialChars,
|
||||
NoConsecutive: requestData.NoConsecutive,
|
||||
UsePassphrase: requestData.Type == "passphrase",
|
||||
WordCount: requestData.WordCount,
|
||||
|
||||
@@ -18,6 +18,7 @@ type Config struct {
|
||||
IncludeLower bool `json:"includeLower"`
|
||||
NumberCount int `json:"numberCount"`
|
||||
SpecialChars string `json:"specialChars"`
|
||||
MinSpecialChars int `json:"minSpecialChars"`
|
||||
NoConsecutive bool `json:"noConsecutive"`
|
||||
UsePassphrase bool `json:"usePassphrase"`
|
||||
WordCount int `json:"wordCount"`
|
||||
@@ -232,8 +233,9 @@ func DefaultConfig() Config {
|
||||
IncludeUpper: true,
|
||||
IncludeLower: true,
|
||||
NumberCount: 1,
|
||||
SpecialChars: "!@#$%^&*-_=+",
|
||||
NoConsecutive: false,
|
||||
SpecialChars: "!@#$%&*-_=+.",
|
||||
MinSpecialChars: 3,
|
||||
NoConsecutive: true,
|
||||
UsePassphrase: true, // Default to passphrase
|
||||
WordCount: 3,
|
||||
NumberPosition: "end",
|
||||
@@ -279,6 +281,18 @@ func generateRandomPassword(config Config) (string, error) {
|
||||
return "", fmt.Errorf("no character types selected")
|
||||
}
|
||||
|
||||
// Validate that minimum special characters doesn't exceed password length
|
||||
totalRequired := config.NumberCount + config.MinSpecialChars
|
||||
if config.IncludeLower {
|
||||
totalRequired++
|
||||
}
|
||||
if config.IncludeUpper {
|
||||
totalRequired++
|
||||
}
|
||||
if totalRequired > config.Length {
|
||||
return "", fmt.Errorf("password length too short for required character counts")
|
||||
}
|
||||
|
||||
password := make([]byte, config.Length)
|
||||
|
||||
// Ensure at least one character from each required set
|
||||
@@ -304,10 +318,50 @@ func generateRandomPassword(config Config) (string, error) {
|
||||
}
|
||||
}
|
||||
|
||||
// Then place at least one from each other required character set
|
||||
// Place required special characters
|
||||
specialCharsPlaced := 0
|
||||
if config.MinSpecialChars > 0 && len(config.SpecialChars) > 0 {
|
||||
attempts := 0
|
||||
maxAttempts := config.Length * 10 // Prevent infinite loops
|
||||
for specialCharsPlaced < config.MinSpecialChars && attempts < maxAttempts {
|
||||
pos, err := rand.Int(rand.Reader, big.NewInt(int64(config.Length)))
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
posInt := int(pos.Int64())
|
||||
|
||||
if !usedPositions[posInt] {
|
||||
char, err := rand.Int(rand.Reader, big.NewInt(int64(len(config.SpecialChars))))
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
password[posInt] = config.SpecialChars[char.Int64()]
|
||||
usedPositions[posInt] = true
|
||||
specialCharsPlaced++
|
||||
}
|
||||
attempts++
|
||||
}
|
||||
|
||||
// If we couldn't place enough special characters due to bad luck, force placement
|
||||
if specialCharsPlaced < config.MinSpecialChars {
|
||||
for i := 0; i < config.Length && specialCharsPlaced < config.MinSpecialChars; i++ {
|
||||
if !usedPositions[i] {
|
||||
char, err := rand.Int(rand.Reader, big.NewInt(int64(len(config.SpecialChars))))
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
password[i] = config.SpecialChars[char.Int64()]
|
||||
usedPositions[i] = true
|
||||
specialCharsPlaced++
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Then place at least one from each other required character set (excluding numbers and special chars already handled)
|
||||
for _, reqSet := range required {
|
||||
if reqSet == "0123456789" {
|
||||
continue // Already handled numbers
|
||||
if reqSet == "0123456789" || reqSet == config.SpecialChars {
|
||||
continue // Already handled numbers and special chars
|
||||
}
|
||||
|
||||
placed := false
|
||||
@@ -344,11 +398,60 @@ func generateRandomPassword(config Config) (string, error) {
|
||||
}
|
||||
|
||||
// Handle no consecutive characters requirement
|
||||
finalPassword := string(password)
|
||||
if config.NoConsecutive {
|
||||
return ensureNoConsecutive(string(password), charset)
|
||||
}
|
||||
finalPassword, err := ensureNoConsecutive(string(password), charset)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return string(password), nil
|
||||
// After ensureNoConsecutive, we need to verify minimum requirements are still met
|
||||
// and fix them if necessary
|
||||
if config.MinSpecialChars > 0 && len(config.SpecialChars) > 0 {
|
||||
// Count current special characters
|
||||
currentSpecialCount := 0
|
||||
for _, char := range finalPassword {
|
||||
if strings.ContainsRune(config.SpecialChars, char) {
|
||||
currentSpecialCount++
|
||||
}
|
||||
}
|
||||
|
||||
// If we don't have enough special characters, replace some non-special characters
|
||||
if currentSpecialCount < config.MinSpecialChars {
|
||||
passwordRunes := []rune(finalPassword)
|
||||
needed := config.MinSpecialChars - currentSpecialCount
|
||||
|
||||
for i := 0; i < len(passwordRunes) && needed > 0; i++ {
|
||||
// Find non-special characters that can be replaced
|
||||
if !strings.ContainsRune(config.SpecialChars, passwordRunes[i]) {
|
||||
// Make sure this replacement won't create consecutive characters
|
||||
specialChar, err := rand.Int(rand.Reader, big.NewInt(int64(len(config.SpecialChars))))
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
newChar := rune(config.SpecialChars[specialChar.Int64()])
|
||||
|
||||
// Check if this would create consecutive characters
|
||||
wouldCreateConsecutive := false
|
||||
if i > 0 && passwordRunes[i-1] == newChar {
|
||||
wouldCreateConsecutive = true
|
||||
}
|
||||
if i < len(passwordRunes)-1 && passwordRunes[i+1] == newChar {
|
||||
wouldCreateConsecutive = true
|
||||
}
|
||||
|
||||
if !wouldCreateConsecutive {
|
||||
passwordRunes[i] = newChar
|
||||
needed--
|
||||
}
|
||||
}
|
||||
}
|
||||
finalPassword = string(passwordRunes)
|
||||
}
|
||||
}
|
||||
return finalPassword, nil
|
||||
}
|
||||
return finalPassword, nil
|
||||
}
|
||||
|
||||
func generatePassphrase(config Config) (string, error) {
|
||||
|
||||
@@ -19,18 +19,18 @@ type Handler struct {
|
||||
}
|
||||
|
||||
type PasswordConfig struct {
|
||||
Type string
|
||||
Length int
|
||||
IncludeUpper bool
|
||||
IncludeLower bool
|
||||
NumberCount int
|
||||
SpecialChars string
|
||||
NoConsecutive bool
|
||||
WordCount int
|
||||
UseNumbers bool
|
||||
UseSpecial bool
|
||||
NumberPosition string
|
||||
SavePasswords bool
|
||||
Type string
|
||||
Length int
|
||||
IncludeUpper bool
|
||||
IncludeLower bool
|
||||
NumberCount int
|
||||
SpecialChars string
|
||||
MinSpecialChars int
|
||||
NoConsecutive bool
|
||||
WordCount int
|
||||
UseNumbers bool
|
||||
UseSpecial bool
|
||||
NumberPosition string
|
||||
}
|
||||
|
||||
func NewHandler(embeddedFiles embed.FS) *Handler {
|
||||
@@ -59,7 +59,7 @@ func NewHandler(embeddedFiles embed.FS) *Handler {
|
||||
return 0
|
||||
}
|
||||
},
|
||||
}).ParseFS(embeddedFiles, "web/base.html", "web/password.html"))
|
||||
}).ParseFS(embeddedFiles, "web/base.html", "web/pwgenerator.html"))
|
||||
|
||||
return &Handler{
|
||||
templates: tmpl,
|
||||
@@ -72,24 +72,24 @@ func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
// Generate CSRF token
|
||||
csrfToken, err := h.csrf.GenerateToken()
|
||||
if err != nil {
|
||||
http.Redirect(w, r, "/password?error="+url.QueryEscape("Security token generation failed"), http.StatusSeeOther)
|
||||
http.Redirect(w, r, "/pwgenerator?error="+url.QueryEscape("Security token generation failed"), http.StatusSeeOther)
|
||||
return
|
||||
}
|
||||
|
||||
// Parse URL parameters to set default values
|
||||
config := PasswordConfig{
|
||||
Type: getStringParam(r, "type", "passphrase"),
|
||||
Length: getIntParam(r, "length", 12),
|
||||
IncludeUpper: getBoolParam(r, "includeUpper", true),
|
||||
IncludeLower: getBoolParam(r, "includeLower", true),
|
||||
NumberCount: getIntParam(r, "numberCount", 1),
|
||||
SpecialChars: getStringParam(r, "specialChars", "!@#$%^&*-_=+"),
|
||||
NoConsecutive: getBoolParam(r, "noConsecutive", false),
|
||||
WordCount: getIntParam(r, "wordCount", 3),
|
||||
UseNumbers: getBoolParam(r, "useNumbers", true),
|
||||
UseSpecial: getBoolParam(r, "useSpecial", false),
|
||||
NumberPosition: getStringParam(r, "numberPosition", "end"),
|
||||
SavePasswords: getBoolParam(r, "savePasswords", false),
|
||||
Type: getStringParam(r, "type", "passphrase"),
|
||||
Length: getIntParam(r, "length", 12),
|
||||
IncludeUpper: getBoolParam(r, "includeUpper", true),
|
||||
IncludeLower: getBoolParam(r, "includeLower", true),
|
||||
NumberCount: getIntParam(r, "numberCount", 2),
|
||||
SpecialChars: getStringParam(r, "specialChars", "!@#$%&*-_=+."),
|
||||
MinSpecialChars: getIntParam(r, "minSpecialChars", 3),
|
||||
NoConsecutive: getBoolParam(r, "noConsecutive", true),
|
||||
WordCount: getIntParam(r, "wordCount", 3),
|
||||
UseNumbers: getBoolParam(r, "useNumbers", true),
|
||||
UseSpecial: getBoolParam(r, "useSpecial", false),
|
||||
NumberPosition: getStringParam(r, "numberPosition", "end"),
|
||||
}
|
||||
|
||||
data := struct {
|
||||
@@ -101,7 +101,7 @@ func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
Config: config,
|
||||
CSRFToken: csrfToken,
|
||||
}
|
||||
h.templates.ExecuteTemplate(w, "password.html", data)
|
||||
h.templates.ExecuteTemplate(w, "pwgenerator.html", data)
|
||||
}
|
||||
|
||||
// Helper functions to parse URL parameters
|
||||
|
||||
Reference in New Issue
Block a user