From af59a520e4d8eb1058304f84f801b336c73f9ede Mon Sep 17 00:00:00 2001 From: ELF-Nigel Date: Sat, 28 Feb 2026 18:14:51 -0500 Subject: [PATCH 1/5] ELF-Nigel --- KeyAuth/KeyAuth.go | 165 +++++++++++++++++++++++++-------------------- main.go | 138 ++++++++++++++++++++----------------- 2 files changed, 166 insertions(+), 137 deletions(-) diff --git a/KeyAuth/KeyAuth.go b/KeyAuth/KeyAuth.go index c5c368e..fe023b7 100644 --- a/KeyAuth/KeyAuth.go +++ b/KeyAuth/KeyAuth.go @@ -13,29 +13,30 @@ import ( "crypto/md5" "crypto/sha256" - "encoding/hex" - "encoding/json" - "io/ioutil" - "net/http" - "net/url" - "os/exec" - "path/filepath" -) - -var ( - APIUrl string = "https://keyauth.win/api/1.3/" - NumUsers string - NumOnlineUsers string - NumKeys string + "encoding/hex" + "encoding/json" + "net/http" + "net/url" + "os/exec" + "path/filepath" +) + +var ( + APIUrl string = "https://keyauth.win/api/1.3/" + NumUsers string + NumOnlineUsers string + NumKeys string CustomerPanelURL string - SessionID string = "lol" -) - -var ( - Name string - OwnerID string - Secret string - Version string + SessionID string = "lol" +) + +var httpClient = &http.Client{Timeout: 10 * time.Second} + +var ( + Name string + OwnerID string + Secret string + Version string TokenPath string Username string IP string @@ -82,13 +83,13 @@ func Init() { "ownerid": OwnerID, } - if TokenPath != "" { - token, err := ioutil.ReadFile(TokenPath) - if err != nil { - fmt.Println("Error reading token file: " + err.Error()) - } - postData["token"] = string(token) - postData["thash"] = tokenHash(TokenPath) + if TokenPath != "" { + token, err := os.ReadFile(TokenPath) + if err != nil { + fmt.Println("Error reading token file: " + err.Error()) + } + postData["token"] = string(token) + postData["thash"] = tokenHash(TokenPath) } response := doRequest(postData) @@ -741,13 +742,12 @@ func doRequest(postData map[string]string) string { } req.Header.Set("Content-Type", "application/x-www-form-urlencoded") - client := http.Client{Timeout: 10 * time.Second} - response, err := client.Do(req) - if err != nil { - fmt.Println("Error sending request:", err) - return "" - } - defer response.Body.Close() + response, err := httpClient.Do(req) + if err != nil { + fmt.Println("Error sending request:", err) + return "" + } + defer response.Body.Close() responseBody, err := io.ReadAll(response.Body) if err != nil { @@ -783,21 +783,22 @@ func doRequest(postData map[string]string) string { os.Exit(1) } - exeName := filepath.Base(os.Args[0]) - debugPath := filepath.Join("C:\\ProgramData\\KeyAuth\\Debug", exeName) - - if _, err := os.Stat(debugPath); os.IsNotExist(err) { - if err := os.MkdirAll(debugPath, 0755); err != nil { - fmt.Println("Error creating debug directory:", err) - } - } - - if len(string(responseBody)) <= 200 { - tampered := false - executionTime := time.Now().Format("03:04:05 PM | 01/02/2006") - - redactedResponse := redactFields(responseBody) - + exeName := filepath.Base(os.Args[0]) + debugPath := debugDir(exeName) + + if _, err := os.Stat(debugPath); os.IsNotExist(err) { + if err := os.MkdirAll(debugPath, 0755); err != nil { + fmt.Println("Error creating debug directory:", err) + } + } + + responseText := string(responseBody) + if len(responseText) <= 200 { + tampered := false + executionTime := time.Now().Format("03:04:05 PM | 01/02/2006") + + redactedResponse := redactFields(responseBody) + debugLog := fmt.Sprintf("\n%s | %s \nResponse: %s\nWas response tampered with? %v\n", executionTime, postData["type"], redactedResponse, tampered) if err := writeDebugLogToFile(filepath.Join(debugPath, "log.txt"), debugLog); err != nil { @@ -805,8 +806,8 @@ func doRequest(postData map[string]string) string { } } - return string(responseBody) -} + return responseText +} func verifySignature(responseBody []byte, signature, timestamp, publicKey string) bool { message := append([]byte(timestamp), responseBody...) @@ -858,15 +859,19 @@ func GetHWID() string { return strings.TrimSpace(strings.TrimPrefix(stdout.String(), "SID")) - case "darwin": - out, err := exec.Command("ioreg", "-l", "|", "grep", "IOPlatformSerialNumber").Output() - if err != nil { - fmt.Println("Error reading IOPlatformSerialNumber: " + err.Error()) - return "" - } - serial := strings.Split(string(out), "=")[1] - hwid := strings.TrimSpace(strings.ReplaceAll(serial, " ", "")) - return hwid + case "darwin": + out, err := exec.Command("/bin/sh", "-c", "ioreg -l | grep IOPlatformSerialNumber").Output() + if err != nil { + fmt.Println("Error reading IOPlatformSerialNumber: " + err.Error()) + return "" + } + parts := strings.SplitN(string(out), "=", 2) + if len(parts) != 2 { + return "" + } + serial := strings.TrimSpace(parts[1]) + hwid := strings.Trim(serial, "\"") + return hwid default: fmt.Println("Unfortunatly you are on an unsupported OS.") @@ -987,11 +992,11 @@ func openUrl(url string) error { return exec.Command(cmd, args...).Start() } -func writeDebugLogToFile(filePath, debugLog string) error { - file, err := os.OpenFile(filePath, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0666) - if err != nil { - return err - } +func writeDebugLogToFile(filePath, debugLog string) error { + file, err := os.OpenFile(filePath, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0600) + if err != nil { + return err + } defer file.Close() _, err = file.WriteString(debugLog) @@ -1002,11 +1007,11 @@ func writeDebugLogToFile(filePath, debugLog string) error { return nil } -func tokenHash(tokenPath string) string { - data, err := ioutil.ReadFile(tokenPath) - if err != nil { - panic(err) - } +func tokenHash(tokenPath string) string { + data, err := os.ReadFile(tokenPath) + if err != nil { + panic(err) + } hash := sha256.Sum256(data) return hex.EncodeToString(hash[:]) @@ -1033,5 +1038,17 @@ func redactFields(responseBody []byte) string { return string(responseBody) } - return string(redactedResponse) -} + return string(redactedResponse) +} + +func debugDir(exeName string) string { + switch runtime.GOOS { + case "windows": + return filepath.Join("C:\\ProgramData\\KeyAuth\\Debug", exeName) + default: + if dir, err := os.UserCacheDir(); err == nil && dir != "" { + return filepath.Join(dir, "keyauth", "debug", exeName) + } + return filepath.Join(os.TempDir(), "keyauth", "debug", exeName) + } +} diff --git a/main.go b/main.go index 455aeef..8f5ac95 100644 --- a/main.go +++ b/main.go @@ -16,66 +16,78 @@ func Input(message string) string { return input } -func UnixToReadable(unixTimestamp string) string { - timestamp, err := strconv.ParseInt(unixTimestamp, 10, 64) - if err != nil { - return unixTimestamp // Return original if parsing fails - } - t := time.Unix(timestamp, 0).UTC() - return t.Format("2006-01-02 15:04:05") -} - -func main() { - KeyAuthApp.Api( - "", // -- Application Name - "", // -- Owner ID - "1.0", // -- Application Version - "", // -- Token Path (PUT NULL OR LEAVE BLANK IF YOU DON'T WANT TO USE TOKEN SYSTEM) - ) - - fmt.Println("[1] Login") - fmt.Println("[2] Register") - fmt.Println("[3] Upgrade") - fmt.Println("[4] License Only Login") - - ans := Input("\nChoose your option: ") - - if ans == "1" { - username := Input("Input username: ") - password := Input("Input password: ") - - KeyAuthApp.Login(username, password) - } else if ans == "2" { - username := Input("Input username: ") - password := Input("Input password: ") - license := Input("Input license: ") - - KeyAuthApp.Register(username, password, license) - } else if ans == "3" { - username := Input("Input username: ") - license := Input("Input license: ") - - KeyAuthApp.Upgrade(username, license) - } else if ans == "4" { - license := Input("Input license: ") - - KeyAuthApp.License(license) - } else { - fmt.Println("Invalid option") - time.Sleep(2 * time.Second) - main() - } - - fmt.Println("\nUser Data:") - fmt.Println(" Username: ", KeyAuthApp.Username) - fmt.Println(" IP Address: ", KeyAuthApp.IP) - fmt.Println(" HWID: ", KeyAuthApp.HWID) - fmt.Println(" Created At: ", UnixToReadable(KeyAuthApp.CreatedDate)) - fmt.Println(" Last Login At: ", UnixToReadable(KeyAuthApp.LastLogin)) - fmt.Println(" Subscription Expiry: ", UnixToReadable(KeyAuthApp.Expires)) - fmt.Println(" Subscription: ", KeyAuthApp.Subscription) - - fmt.Println("\nExiting application in 10 seconds...") - time.Sleep(10 * time.Second) - os.Exit(0) -} +func UnixToReadable(unixTimestamp string) string { + timestamp, err := strconv.ParseInt(unixTimestamp, 10, 64) + if err != nil { + return unixTimestamp // Return original if parsing fails + } + t := time.Unix(timestamp, 0).UTC() + return t.Format("2006-01-02 15:04:05") +} + +func printUserData() { + fmt.Println("\nUser Data:") + fmt.Println(" Username: ", KeyAuthApp.Username) + fmt.Println(" IP Address: ", KeyAuthApp.IP) + fmt.Println(" HWID: ", KeyAuthApp.HWID) + fmt.Println(" Created At: ", UnixToReadable(KeyAuthApp.CreatedDate)) + fmt.Println(" Last Login At: ", UnixToReadable(KeyAuthApp.LastLogin)) + fmt.Println(" Subscription Expiry: ", UnixToReadable(KeyAuthApp.Expires)) + fmt.Println(" Subscription: ", KeyAuthApp.Subscription) +} + +func showMenu() { + fmt.Println("[1] Login") + fmt.Println("[2] Register") + fmt.Println("[3] Upgrade") + fmt.Println("[4] License Only Login") +} + +func main() { + KeyAuthApp.Api( + "", // -- Application Name + "", // -- Owner ID + "1.0", // -- Application Version + "", // -- Token Path (PUT NULL OR LEAVE BLANK IF YOU DON'T WANT TO USE TOKEN SYSTEM) + ) + + done := false + for !done { + showMenu() + ans := Input("\nChoose your option: ") + + switch ans { + case "1": + username := Input("Input username: ") + password := Input("Input password: ") + KeyAuthApp.Login(username, password) + printUserData() + done = true + case "2": + username := Input("Input username: ") + password := Input("Input password: ") + license := Input("Input license: ") + KeyAuthApp.Register(username, password, license) + printUserData() + done = true + case "3": + username := Input("Input username: ") + license := Input("Input license: ") + KeyAuthApp.Upgrade(username, license) + printUserData() + done = true + case "4": + license := Input("Input license: ") + KeyAuthApp.License(license) + printUserData() + done = true + default: + fmt.Println("Invalid option") + time.Sleep(2 * time.Second) + } + } + + fmt.Println("\nExiting application in 10 seconds...") + time.Sleep(10 * time.Second) + os.Exit(0) +} From 81adabd4587f0deda5dac78c770b747f19ebf309 Mon Sep 17 00:00:00 2001 From: ELF-Nigel Date: Sat, 28 Feb 2026 18:17:40 -0500 Subject: [PATCH 2/5] ELF-Nigel --- KeyAuth/KeyAuth.go | 144 +++++++++++++++++++++++++++++---------------- 1 file changed, 93 insertions(+), 51 deletions(-) diff --git a/KeyAuth/KeyAuth.go b/KeyAuth/KeyAuth.go index fe023b7..5dfa184 100644 --- a/KeyAuth/KeyAuth.go +++ b/KeyAuth/KeyAuth.go @@ -46,9 +46,11 @@ var ( LastLogin string Subscription string Subscriptions string - Initialized bool - PublicKey string = "5586b4bc69c7a4b487e4563a4cd96afd39140f919bd31cea7d1c6a1e8439422b" -) + Initialized bool + PublicKey string = "5586b4bc69c7a4b487e4563a4cd96afd39140f919bd31cea7d1c6a1e8439422b" + serverTime int64 + serverTimeAt time.Time +) func Api(name, ownerid, version, path string) { if name == "" || ownerid == "" || version == "" || len(ownerid) != 10 { @@ -160,14 +162,15 @@ func Register(user, password, license string) { os.Exit(1) } - if jsonResponse["success"].(bool) { - fmt.Println(jsonResponse["message"].(string)) - LoadUserData(jsonResponse["info"]) - } else { - fmt.Println(jsonResponse["message"].(string)) - time.Sleep(3 * time.Second) - os.Exit(1) - } + if jsonResponse["success"].(bool) { + fmt.Println(jsonResponse["message"].(string)) + LoadUserData(jsonResponse["info"]) + EnforceNotExpired() + } else { + fmt.Println(jsonResponse["message"].(string)) + time.Sleep(3 * time.Second) + os.Exit(1) + } } func Login(user, password string) { @@ -194,14 +197,15 @@ func Login(user, password string) { os.Exit(1) } - if jsonResponse["success"].(bool) { - fmt.Println(jsonResponse["message"].(string)) - LoadUserData(jsonResponse["info"]) - } else { - fmt.Println(jsonResponse["message"].(string)) - time.Sleep(3 * time.Second) - os.Exit(1) - } + if jsonResponse["success"].(bool) { + fmt.Println(jsonResponse["message"].(string)) + LoadUserData(jsonResponse["info"]) + EnforceNotExpired() + } else { + fmt.Println(jsonResponse["message"].(string)) + time.Sleep(3 * time.Second) + os.Exit(1) + } } func Forgot(user, email string) { @@ -225,14 +229,15 @@ func Forgot(user, email string) { os.Exit(1) } - if jsonResponse["success"].(bool) { - fmt.Println(jsonResponse["message"].(string)) - LoadUserData(jsonResponse["info"]) - } else { - fmt.Println(jsonResponse["message"].(string)) - time.Sleep(3 * time.Second) - os.Exit(1) - } + if jsonResponse["success"].(bool) { + fmt.Println(jsonResponse["message"].(string)) + LoadUserData(jsonResponse["info"]) + EnforceNotExpired() + } else { + fmt.Println(jsonResponse["message"].(string)) + time.Sleep(3 * time.Second) + os.Exit(1) + } } func Upgrade(user, license string) { @@ -291,13 +296,14 @@ func License(key string) { os.Exit(1) } - if jsonResponse["success"].(bool) { - LoadUserData(jsonResponse["info"]) - fmt.Println(jsonResponse["message"].(string)) - } else { - fmt.Println(jsonResponse["message"].(string)) - time.Sleep(3 * time.Second) - os.Exit(1) + if jsonResponse["success"].(bool) { + LoadUserData(jsonResponse["info"]) + EnforceNotExpired() + fmt.Println(jsonResponse["message"].(string)) + } else { + fmt.Println(jsonResponse["message"].(string)) + time.Sleep(3 * time.Second) + os.Exit(1) } } @@ -763,13 +769,14 @@ func doRequest(postData map[string]string) string { os.Exit(1) } - serverTime, err := strconv.ParseInt(timestamp, 10, 64) - if err != nil { - fmt.Println("Invalid timestamp format:", err) - time.Sleep(5 * time.Second) - os.Exit(1) - } - currentTime := time.Now().Unix() + serverTime, err := strconv.ParseInt(timestamp, 10, 64) + if err != nil { + fmt.Println("Invalid timestamp format:", err) + time.Sleep(5 * time.Second) + os.Exit(1) + } + setServerTime(serverTime) + currentTime := time.Now().Unix() bufferSeconds := int64(5) if abs(currentTime-serverTime) > bufferSeconds+20 { fmt.Printf("Time difference is too large: %d seconds, try syncing your date and time settings.\n", abs(currentTime-serverTime)) @@ -828,16 +835,51 @@ func verifySignature(responseBody []byte, signature, timestamp, publicKey string return verified } -func abs(x int64) int64 { - if x < 0 { - return -x - } - return x -} - -func GetHWID() string { - switch runtime.GOOS { - case "linux": +func abs(x int64) int64 { + if x < 0 { + return -x + } + return x +} + +func setServerTime(ts int64) { + serverTime = ts + serverTimeAt = time.Now() +} + +func serverUnix() (int64, bool) { + if serverTime == 0 || serverTimeAt.IsZero() { + return 0, false + } + elapsed := time.Since(serverTimeAt) + if elapsed < 0 { + elapsed = 0 + } + return serverTime + int64(elapsed.Seconds()), true +} + +func EnforceNotExpired() { + if Expires == "" { + return + } + expiry, err := strconv.ParseInt(Expires, 10, 64) + if err != nil || expiry == 0 { + return + } + now, ok := serverUnix() + if !ok { + return + } + if now >= expiry { + fmt.Println("Subscription expired.") + time.Sleep(3 * time.Second) + os.Exit(1) + } +} + +func GetHWID() string { + switch runtime.GOOS { + case "linux": out, err := exec.Command("cat", "/etc/machine-id").Output() if err != nil { fmt.Println("Error reading /etc/machine-id: " + err.Error()) From 52aaf0a78b70a95137310ba17d2dce2b0643b4e6 Mon Sep 17 00:00:00 2001 From: ELF-Nigel Date: Mon, 2 Mar 2026 09:31:06 -0500 Subject: [PATCH 3/5] Add CI and update credits --- .github/workflows/ci.yml | 21 +++++++++++++++++++++ README.md | 4 +++- 2 files changed, 24 insertions(+), 1 deletion(-) create mode 100644 .github/workflows/ci.yml diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..76d6da5 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,21 @@ +name: ci + +on: + push: + branches: [ "main" ] + pull_request: + branches: [ "main" ] + +jobs: + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Set up Go + uses: actions/setup-go@v5 + with: + go-version: "1.21.x" + - name: Build + run: go build ./... + - name: Test + run: go test ./... diff --git a/README.md b/README.md index 6e89cd5..17d5464 100644 --- a/README.md +++ b/README.md @@ -2,6 +2,8 @@ KeyAuth Go example SDK for https://keyauth.cc license key API auth. +Credits: Nigel (Discord: chefendpoint, Telegram: ELF_Nigel) + ## **Bugs** If you are using our example with no significant changes, and you are having problems, please Report Bug here https://keyauth.cc/app/?page=forms @@ -31,7 +33,7 @@ Thank you for your compliance, we work hard on the development of KeyAuth and do KeyAuth is a powerful cloud-based authentication system designed to protect your software from piracy and unauthorized access. With KeyAuth, you can implement secure licensing, user management, and subscription systems in minutes. Client SDKs available for [C#](https://github.com/KeyAuth/KeyAuth-CSHARP-Example), [C++](https://github.com/KeyAuth/KeyAuth-CPP-Example), [Python](https://github.com/KeyAuth/KeyAuth-Python-Example), [Java](https://github.com/KeyAuth-Archive/KeyAuth-JAVA-api), [JavaScript](https://github.com/mazkdevf/KeyAuth-JS-Example), [VB.NET](https://github.com/KeyAuth/KeyAuth-VB-Example), [PHP](https://github.com/KeyAuth/KeyAuth-PHP-Example), [Rust](https://github.com/KeyAuth/KeyAuth-Rust-Example), [Go](https://github.com/KeyAuth/KeyAuth-Go-Example), [Lua](https://github.com/mazkdevf/KeyAuth-Lua-Examples), [Ruby](https://github.com/mazkdevf/KeyAuth-Ruby-Example), and [Perl](https://github.com/mazkdevf/KeyAuth-Perl-Example). KeyAuth has several unique features such as memory streaming, webhook function where you can send requests to API without leaking the API, discord webhook notifications, ban the user securely through the application at your discretion. Feel free to join https://t.me/keyauth if you have questions or suggestions. > [!TIP] -> https://vaultcord.com FREE Discord bot to Backup server, members, channels, messages & more. Custom verify page, block alt accounts, VPNs & more. +> Use the official KeyAuth examples and docs when troubleshooting to ensure your local code matches the latest API expectations. ## **Customer connection issues?** From e63f572fc897bade795747ae13a07924a672ab26 Mon Sep 17 00:00:00 2001 From: ELF-Nigel Date: Mon, 2 Mar 2026 09:33:22 -0500 Subject: [PATCH 4/5] Add lockout helpers and update docs --- KeyAuth/KeyAuth.go | 112 +++++++++++++++++++++++++++++++++++++++++---- README.md | 23 ++++++++++ 2 files changed, 126 insertions(+), 9 deletions(-) diff --git a/KeyAuth/KeyAuth.go b/KeyAuth/KeyAuth.go index 5dfa184..af591b8 100644 --- a/KeyAuth/KeyAuth.go +++ b/KeyAuth/KeyAuth.go @@ -1,15 +1,16 @@ package keyauth import ( - "bytes" - "crypto/ed25519" - "fmt" - "io" - "os" - "runtime" - "strconv" - "strings" - "time" + "bytes" + "crypto/ed25519" + "fmt" + "io" + "os" + "runtime" + "strconv" + "strings" + "sync" + "time" "crypto/md5" "crypto/sha256" @@ -51,6 +52,34 @@ var ( serverTime int64 serverTimeAt time.Time ) + +type LockoutState struct { + FailCount int + FirstFailAt time.Time + LockedUntil time.Time + LastFailAt time.Time +} + +type LockoutConfig struct { + MaxAttempts int + Window time.Duration + Lockout time.Duration +} + +var lockoutMu sync.Mutex +var lockoutState LockoutState + +var LockoutSettings = LockoutConfig{ + MaxAttempts: 5, + Window: 2 * time.Minute, + Lockout: 5 * time.Minute, +} + +const ( + initFailDelayMs = 1500 + badInputDelayMs = 3000 + closeDelayMs = 5000 +) func Api(name, ownerid, version, path string) { if name == "" || ownerid == "" || version == "" || len(ownerid) != 10 { @@ -877,6 +906,71 @@ func EnforceNotExpired() { } } +func InitFailDelay() { + time.Sleep(initFailDelayMs * time.Millisecond) +} + +func BadInputDelay() { + time.Sleep(badInputDelayMs * time.Millisecond) +} + +func CloseDelay() { + time.Sleep(closeDelayMs * time.Millisecond) +} + +func LockoutStateSnapshot() LockoutState { + lockoutMu.Lock() + defer lockoutMu.Unlock() + return lockoutState +} + +func LockoutActive() bool { + lockoutMu.Lock() + defer lockoutMu.Unlock() + if lockoutState.LockedUntil.IsZero() { + return false + } + return time.Now().Before(lockoutState.LockedUntil) +} + +func LockoutRemainingMs() int64 { + lockoutMu.Lock() + defer lockoutMu.Unlock() + if lockoutState.LockedUntil.IsZero() { + return 0 + } + remaining := time.Until(lockoutState.LockedUntil) + if remaining < 0 { + return 0 + } + return remaining.Milliseconds() +} + +func RecordLoginFail() { + lockoutMu.Lock() + defer lockoutMu.Unlock() + + now := time.Now() + lockoutState.LastFailAt = now + + if lockoutState.FirstFailAt.IsZero() || now.Sub(lockoutState.FirstFailAt) > LockoutSettings.Window { + lockoutState.FirstFailAt = now + lockoutState.FailCount = 1 + return + } + + lockoutState.FailCount++ + if lockoutState.FailCount >= LockoutSettings.MaxAttempts { + lockoutState.LockedUntil = now.Add(LockoutSettings.Lockout) + } +} + +func ResetLockout() { + lockoutMu.Lock() + defer lockoutMu.Unlock() + lockoutState = LockoutState{} +} + func GetHWID() string { switch runtime.GOOS { case "linux": diff --git a/README.md b/README.md index 17d5464..511fd3a 100644 --- a/README.md +++ b/README.md @@ -66,6 +66,29 @@ You don't need to add any code to initalize. KeyAuth will initalize when the ins If you edit the example, you can init it by running `KeyAuthApp.Init()` +## **Client-side delays & lockout helpers (optional)** + +These helpers are client-side only and meant to slow down abuse. They do not replace server-side enforcement. + +```go +// Delay helpers +KeyAuthApp.InitFailDelay() +KeyAuthApp.BadInputDelay() +KeyAuthApp.CloseDelay() + +// Lockout helpers +if KeyAuthApp.LockoutActive() { + fmt.Printf("Locked out, try again in %d ms\n", KeyAuthApp.LockoutRemainingMs()) + return +} + +// On failed login attempt: +KeyAuthApp.RecordLoginFail() + +// On successful login: +KeyAuthApp.ResetLockout() +``` + ## **Display application information** ```go From b1de600202b94361abffbf4cb7999138b7ec92dc Mon Sep 17 00:00:00 2001 From: ELF-Nigel Date: Mon, 2 Mar 2026 09:52:11 -0500 Subject: [PATCH 5/5] Use lockout helpers in example --- main.go | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/main.go b/main.go index 8f5ac95..2e5628c 100644 --- a/main.go +++ b/main.go @@ -53,6 +53,12 @@ func main() { done := false for !done { + if KeyAuthApp.LockoutActive() { + fmt.Printf("Locked out. Try again in %d ms\n", KeyAuthApp.LockoutRemainingMs()) + KeyAuthApp.CloseDelay() + os.Exit(1) + } + showMenu() ans := Input("\nChoose your option: ") @@ -62,6 +68,7 @@ func main() { password := Input("Input password: ") KeyAuthApp.Login(username, password) printUserData() + KeyAuthApp.ResetLockout() done = true case "2": username := Input("Input username: ") @@ -69,21 +76,24 @@ func main() { license := Input("Input license: ") KeyAuthApp.Register(username, password, license) printUserData() + KeyAuthApp.ResetLockout() done = true case "3": username := Input("Input username: ") license := Input("Input license: ") KeyAuthApp.Upgrade(username, license) printUserData() + KeyAuthApp.ResetLockout() done = true case "4": license := Input("Input license: ") KeyAuthApp.License(license) printUserData() + KeyAuthApp.ResetLockout() done = true default: fmt.Println("Invalid option") - time.Sleep(2 * time.Second) + KeyAuthApp.BadInputDelay() } }