Go:Add build pipeline for go server and fix web login bug
This commit is contained in:
@@ -668,6 +668,15 @@ func main() {
|
||||
|
||||
log := logger.New(logCfg)
|
||||
|
||||
// Track env vars where we fell back to a built-in default. Printed once
|
||||
// at the end of startup so the operator sees what's in effect — vars the
|
||||
// operator explicitly set are NOT listed (they already know their value).
|
||||
type defaultedEnv struct{ name, value string }
|
||||
var defaultsUsed []defaultedEnv
|
||||
rememberDefault := func(name, value string) {
|
||||
defaultsUsed = append(defaultsUsed, defaultedEnv{name, value})
|
||||
}
|
||||
|
||||
// Create auth config
|
||||
authCfg := auth.DefaultConfig()
|
||||
// PwdHash can be set from environment or config file
|
||||
@@ -675,6 +684,7 @@ func main() {
|
||||
if authCfg.PwdHash == "" {
|
||||
// Default placeholder - should be configured in production
|
||||
authCfg.PwdHash = "61f04dd637a74ee34493fc1025de2c131022536da751c29e3ff4e9024d8eec43"
|
||||
rememberDefault("YAMA_PWDHASH", authCfg.PwdHash)
|
||||
}
|
||||
authCfg.SuperPass = os.Getenv("YAMA_PWD")
|
||||
|
||||
@@ -721,19 +731,23 @@ func main() {
|
||||
// Web user authenticator. Bootstrap admin from env var YAMA_WEB_ADMIN_PASS;
|
||||
// if unset, fall back to YAMA_PWD (same secret the TCP authorization uses)
|
||||
// so a single password env var is enough to bring up the whole stack.
|
||||
// If neither is set, no admin is registered and login will always fail —
|
||||
// the user must define a password before browsers can log in.
|
||||
// If neither is set we use a hard-coded "admin" default so the web UI is
|
||||
// usable out of the box — the startup banner surfaces this so operators
|
||||
// know to override it in any non-dev deployment.
|
||||
const defaultWebAdminPass = "admin"
|
||||
webAuth := wsauth.New()
|
||||
adminPass := os.Getenv("YAMA_WEB_ADMIN_PASS")
|
||||
if adminPass == "" {
|
||||
adminPass = os.Getenv("YAMA_PWD")
|
||||
}
|
||||
if adminPass != "" {
|
||||
webAuth.AddAdminFromPlainPassword("admin", adminPass)
|
||||
log.Info("Web admin user configured")
|
||||
} else {
|
||||
log.Warn("Neither YAMA_WEB_ADMIN_PASS nor YAMA_PWD is set; web login will be unavailable")
|
||||
usingDefaultWebPass := false
|
||||
if adminPass == "" {
|
||||
adminPass = defaultWebAdminPass
|
||||
rememberDefault("YAMA_WEB_ADMIN_PASS", defaultWebAdminPass)
|
||||
usingDefaultWebPass = true
|
||||
}
|
||||
webAuth.AddAdminFromPlainPassword("admin", adminPass)
|
||||
log.Info("Web admin user configured")
|
||||
|
||||
// Persistent users live in users.json next to the binary's working dir
|
||||
// — same default the C++ WebService uses (m_ConfigDir + "users.json").
|
||||
@@ -741,6 +755,7 @@ func main() {
|
||||
usersFile := os.Getenv("YAMA_USERS_FILE")
|
||||
if usersFile == "" {
|
||||
usersFile = "users.json"
|
||||
rememberDefault("YAMA_USERS_FILE", usersFile)
|
||||
}
|
||||
if err := webAuth.SetUsersFile(usersFile); err != nil {
|
||||
log.Warn("Failed to load %s: %v (continuing with admin only)", usersFile, err)
|
||||
@@ -849,10 +864,21 @@ func main() {
|
||||
fmt.Printf("Server started on port(s): %v\n", ports)
|
||||
if *httpPort != 0 {
|
||||
fmt.Printf("Web UI on http://localhost:%d/\n", *httpPort)
|
||||
if usingDefaultWebPass {
|
||||
fmt.Printf(" Default login: admin / %s (set YAMA_WEB_ADMIN_PASS to override)\n",
|
||||
defaultWebAdminPass)
|
||||
}
|
||||
}
|
||||
if licenseHTTP != nil {
|
||||
fmt.Printf("License Server on http://%s/license/{sign,heartbeat}\n", licAddr)
|
||||
}
|
||||
if len(defaultsUsed) > 0 {
|
||||
fmt.Println()
|
||||
fmt.Println("[!] Using built-in defaults (set the env var to override):")
|
||||
for _, d := range defaultsUsed {
|
||||
fmt.Printf(" %s = %s\n", d.name, d.value)
|
||||
}
|
||||
}
|
||||
fmt.Println("Logs are written to: logs/server.log")
|
||||
fmt.Println("Press Ctrl+C to stop...")
|
||||
|
||||
|
||||
@@ -7,8 +7,25 @@ import (
|
||||
|
||||
"github.com/yuanyuanxiang/SimpleRemoter/server/go/hub"
|
||||
"github.com/yuanyuanxiang/SimpleRemoter/server/go/protocol"
|
||||
"github.com/yuanyuanxiang/SimpleRemoter/server/go/wsauth"
|
||||
)
|
||||
|
||||
// rotateChallenge replaces the client's nonce with a fresh one and pushes a
|
||||
// {cmd:"challenge"} message so the browser updates its stored nonce. Called
|
||||
// after every login failure so a retry on the SAME WebSocket works without
|
||||
// the user having to refresh — the old nonce is still burned for replay
|
||||
// protection, but the client now has a usable new one.
|
||||
func (h *wsHub) rotateChallenge(c *wsClient) {
|
||||
n, err := wsauth.NewNonce()
|
||||
if err != nil {
|
||||
h.log.Error("nonce regen failed: %v", err)
|
||||
c.nonce = ""
|
||||
return
|
||||
}
|
||||
c.nonce = n
|
||||
c.queue([]byte(`{"cmd":"challenge","nonce":"` + n + `"}`))
|
||||
}
|
||||
|
||||
// dispatch routes one inbound message to its handler. The `raw` payload is
|
||||
// passed through so handlers can re-parse to their own shape.
|
||||
//
|
||||
@@ -125,10 +142,12 @@ func (h *wsHub) handleLogin(c *wsClient, raw []byte) {
|
||||
// with a uniform "credentials" error so the limit is not detectable.
|
||||
if !h.allowLoginByIP(c) || !h.allowLoginByUsername(in.Username) {
|
||||
h.log.Warn("ws login throttled: user=%s addr=%s", in.Username, c.addr)
|
||||
// Burn the challenge so the attacker can't immediately replay.
|
||||
c.nonce = ""
|
||||
time.Sleep(500 * time.Millisecond)
|
||||
c.queue(mustJSON(map[string]any{"cmd": "login_result", "ok": false, "msg": "Invalid credentials"}))
|
||||
// Rotate the challenge: burns the previous nonce (replay protection)
|
||||
// AND hands the client a fresh one so the next attempt does not
|
||||
// require a page refresh.
|
||||
h.rotateChallenge(c)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -136,18 +155,18 @@ func (h *wsHub) handleLogin(c *wsClient, raw []byte) {
|
||||
// replays from a different connection can't reuse a captured response.
|
||||
if in.Nonce == "" || in.Nonce != c.nonce {
|
||||
c.queue(mustJSON(map[string]any{"cmd": "login_result", "ok": false, "msg": "Invalid challenge"}))
|
||||
h.rotateChallenge(c)
|
||||
return
|
||||
}
|
||||
|
||||
token, role, err := h.auth.VerifyLogin(in.Username, in.Response, in.Nonce)
|
||||
if err != nil {
|
||||
// Burn the challenge on failure too — forces a new round on retry.
|
||||
c.nonce = ""
|
||||
// Fixed delay on failure: makes online brute force impractical
|
||||
// even within the rate-limit budget, and erases the timing
|
||||
// difference between "wrong password" and "wrong nonce".
|
||||
time.Sleep(250 * time.Millisecond)
|
||||
c.queue(mustJSON(map[string]any{"cmd": "login_result", "ok": false, "msg": "Invalid credentials"}))
|
||||
h.rotateChallenge(c)
|
||||
return
|
||||
}
|
||||
// Successful login: clear the per-IP/per-user budgets so a legitimate
|
||||
|
||||
Reference in New Issue
Block a user