diff --git a/pkg/login-app/cmd/dialog-forcelogin.go b/pkg/login-app/cmd/dialog-forcelogin.go new file mode 100644 index 0000000..66cbbc2 --- /dev/null +++ b/pkg/login-app/cmd/dialog-forcelogin.go @@ -0,0 +1,26 @@ +package main + +import ( + "github.com/rivo/tview" +) + +func CreateForceLoginDialog(app *tview.Application, username, password string, next func(string, string, *bool)) { + modal := tview.NewModal(). + SetText("You are already registered on a different machine.\n\nIf you continue, the other machine will no longer be able to use its dedicated IPs due to network safeties in place.\n\nIf the other machine doesn't work or you are Ok to lost your progression, just force this host as your new main host.\nIf you want a secondary machine to play without erasing your progression on the main one, continue without enforcing network safety."). + AddButtons([]string{"Cancel", "Force this host as main", "Boot without network safety"}). + SetDoneFunc(func(buttonIndex int, buttonLabel string) { + var force bool + if buttonLabel == "Force this host as main" { + force = true + next(username, password, &force) + } else if buttonLabel == "Boot without network safety" { + force = false + next(username, password, &force) + } else { + askLogin(app) + } + }) + + app.SetRoot(modal, true) + app.SetFocus(modal) +} diff --git a/pkg/login-app/cmd/login.go b/pkg/login-app/cmd/login.go index 15b71bf..068123c 100644 --- a/pkg/login-app/cmd/login.go +++ b/pkg/login-app/cmd/login.go @@ -8,21 +8,24 @@ import ( "net/http" ) -func checkLogin(username, password string) (bool, error) { - j, err := json.Marshal(map[string]string{ - "username": username, - "password": password, - }) +type loginForm struct { + Username string `json:"username"` + Password string `json:"password"` + Force *bool `json:"force,omitempty"` +} + +func checkLogin(username, password string, force *bool) (int, error) { + j, err := json.Marshal(loginForm{Username: username, Password: password, Force: force}) if err != nil { - return false, err + return 0, err } resp, err := http.Post(URLLogin, "application/json", bytes.NewReader(j)) if err != nil { - return false, err + return 0, err } defer resp.Body.Close() cnt, _ := ioutil.ReadAll(resp.Body) - return resp.StatusCode == http.StatusOK, errors.New(string(cnt)) + return resp.StatusCode, errors.New(string(cnt)) } diff --git a/pkg/login-app/cmd/main.go b/pkg/login-app/cmd/main.go index f00772f..73ed5af 100644 --- a/pkg/login-app/cmd/main.go +++ b/pkg/login-app/cmd/main.go @@ -2,6 +2,7 @@ package main import ( "math/rand" + "net/http" "os" "time" @@ -26,18 +27,25 @@ func modal(p tview.Primitive, width, height int) tview.Primitive { } func askLogin(app *tview.Application) { - CreateLoginDialog(app, func(username, password string) { + var afterLogin func(username, password string, force *bool) + afterLogin = func(username, password string, force *bool) { // Display check dialog CreateCheckDialog(app) go func() { - if ok, err := checkLogin(username, password); ok { + if status, err := checkLogin(username, password, force); status == http.StatusOK { loggedAs = username app.Stop() + } else if status == http.StatusPaymentRequired { + CreateForceLoginDialog(app, username, password, afterLogin) } else { CreateErrMsgDialog(app, err) } }() + } + + CreateLoginDialog(app, func(username, password string) { + afterLogin(username, password, nil) }) } diff --git a/pkg/login-validator/cmd/login.go b/pkg/login-validator/cmd/login.go index 298c337..43e2df4 100644 --- a/pkg/login-validator/cmd/login.go +++ b/pkg/login-validator/cmd/login.go @@ -28,6 +28,7 @@ type loginChecker struct { type loginUpload struct { Username string Password string + Force *bool } func (l loginChecker) ServeHTTP(w http.ResponseWriter, r *http.Request) { @@ -108,10 +109,26 @@ func (l loginChecker) ServeHTTP(w http.ResponseWriter, r *http.Request) { if mac == nil { log.Printf("Unable to find MAC address for given IP (%s)\n", ip) - http.Error(w, "Internal server error. Please retry in a few minutes", http.StatusInternalServerError) + http.Error(w, "Unable to find the MAC address of your host. Please retry in a few minutes", http.StatusInternalServerError) return } + // Check if the user is already registered + if exists, user, err := l.findUser(lu.Username); exists && (user == nil || mac.HWAddress.String() != user.MAC) { + log.Printf("Find the user in DB: %v", user) + if lu.Force == nil { + log.Println("Successful login of", lu.Username, "at", r.RemoteAddr) + http.Error(w, "You are already registered on a different machine. If you continue, the other machine will no longer be able to use its dedicated IP.", http.StatusPaymentRequired) + return + } else if !*lu.Force { + log.Println("Successful login of", lu.Username, "at", r.RemoteAddr) + http.Error(w, fmt.Sprintf("Use the following IP: %s", ip), http.StatusOK) + return + } + } else if err != nil { + log.Println("An error occurs when searching the user in current DB:", err.Error()) + } + // Register the user remotely if ip, err := l.registerUser(lu.Username, r.RemoteAddr, *mac); err != nil { log.Println("Error on remote registration for", lu.Username, ":", err) @@ -131,10 +148,37 @@ type myIP struct { Id int64 `json:"id"` Login string `json:"login"` IP string `json:"ip"` + MAC string `json:"mac"` +} + +func (l loginChecker) findUser(username string) (bool, *myIP, error) { + req, err := http.NewRequest("GET", "https://adlin.nemunai.re/api/students/"+username+"/", nil) + if err != nil { + return false, nil, err + } + + resp, err := http.DefaultClient.Do(req) + if err != nil { + return false, nil, err + } + defer resp.Body.Close() + + if resp.StatusCode == http.StatusNotFound { + return false, nil, nil + } else if resp.StatusCode != http.StatusOK { + return false, nil, errors.New(resp.Status) + } else { + dec := json.NewDecoder(resp.Body) + var myip myIP + if err := dec.Decode(&myip); err != nil { + return true, nil, err + } + return true, &myip, nil + } } func (l loginChecker) registerUser(username, remoteAddr string, ent ARPEntry) (net.IP, error) { - bts, err := json.Marshal(map[string]interface{}{"login": username, "ip": remoteAddr, "mac": fmt.Sprintf("%02x:%02x:%02x:%02x:%02x:%02x", ent.HWAddress[0], ent.HWAddress[1], ent.HWAddress[2], ent.HWAddress[3], ent.HWAddress[4], ent.HWAddress[5])}) + bts, err := json.Marshal(myIP{Login: username, IP: remoteAddr, MAC: ent.HWAddress.String()}) if err != nil { return nil, nil }