diff options
-rw-r--r-- | README.md | 11 | ||||
-rw-r--r-- | go.mod | 2 | ||||
-rw-r--r-- | go.sum | 1 | ||||
-rw-r--r-- | matrix/matrix.go | 88 | ||||
-rw-r--r-- | ui/view-login.go | 25 |
5 files changed, 110 insertions, 17 deletions
@@ -1,11 +1,9 @@ # gomuks ![Languages](https://img.shields.io/github/languages/top/tulir/gomuks.svg) -[![License](https://img.shields.io/github/license/tulir/gomuks.svg)](LICENSE) -[![Release](https://img.shields.io/github/release/tulir/gomuks/all.svg)](https://github.com/tulir/gomuks/releases) -[![Build Status](https://travis-ci.org/tulir/gomuks.svg?branch=master)](https://travis-ci.org/tulir/gomuks) +[![License](https://img.shields.io/github/license/tulir/gomuks.svg)](LICENSE)<!-- +[![Release](https://img.shields.io/github/release/tulir/gomuks/all.svg)](https://github.com/tulir/gomuks/releases)--> [![GitLab CI](https://mau.dev/tulir/gomuks/badges/master/pipeline.svg)](https://mau.dev/tulir/gomuks/pipelines) [![Maintainability](https://img.shields.io/codeclimate/maintainability/tulir/gomuks.svg)](https://codeclimate.com/github/tulir/gomuks) -[![Coverage](https://img.shields.io/codeclimate/coverage/tulir/gomuks.svg)](https://codeclimate.com/github/tulir/gomuks) ![Chat Preview](chat-preview.png) @@ -18,9 +16,8 @@ Matrix room: [#gomuks:maunium.net](https://matrix.to/#/#gomuks:maunium.net) ## Installation Once the client becomes actually usable, I'll start making GitHub releases with -precompiled executables. For now, you can either download -a CI build from [GitLab CI](https://mau.dev/tulir/gomuks/pipelines) -or compile from source: +precompiled executables. For now, you can either download a CI build from +[GitLab CI](https://mau.dev/tulir/gomuks/pipelines) or compile from source: 0. Install [Go](https://golang.org/) 1.12 or higher 1. Clone the repo: `git clone https://github.com/tulir/gomuks.git && cd gomuks` @@ -19,7 +19,7 @@ require ( golang.org/x/net v0.0.0-20200202094626-16171245cfb2 gopkg.in/toast.v1 v1.0.0-20180812000517-0a84660828b2 gopkg.in/yaml.v2 v2.2.8 - maunium.net/go/mautrix v0.1.0-alpha.3.0.20200218183645-fea33ed88d03 + maunium.net/go/mautrix v0.1.0-alpha.3.0.20200218191514-cb8e637f1c62 maunium.net/go/mauview v0.0.0-20200218183549-88ecb1321176 maunium.net/go/tcell v1.1.2-0.20200218183045-87c4a25c5b09 ) @@ -71,6 +71,7 @@ gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10= gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= maunium.net/go/mautrix v0.1.0-alpha.3.0.20200218183645-fea33ed88d03 h1:0fvOe9KeB/JAkMAzJTmj6mg1P9xGPAgFhJcCSxNe1Rk= maunium.net/go/mautrix v0.1.0-alpha.3.0.20200218183645-fea33ed88d03/go.mod h1:g10T1fh2Q2HkJWycVs93eBXdWpqD67f1YVQhNxdIDr4= +maunium.net/go/mautrix v0.1.0-alpha.3.0.20200218191514-cb8e637f1c62/go.mod h1:g10T1fh2Q2HkJWycVs93eBXdWpqD67f1YVQhNxdIDr4= maunium.net/go/mauview v0.0.0-20200218183549-88ecb1321176 h1:KoTm7ASEzFIZ1SvPWuWYzpkeA+wiR1fuUu4l7TCHcE0= maunium.net/go/mauview v0.0.0-20200218183549-88ecb1321176/go.mod h1:jwg3Ow7akzsCX3q38pZAfmEC5gGN8gXwMyyjy/yZVMg= maunium.net/go/tcell v1.1.2-0.20200218183045-87c4a25c5b09 h1:hu+R+0nodoZPS19WGyYiw/d63+/NQS/R3Duw3d9HqAU= diff --git a/matrix/matrix.go b/matrix/matrix.go index ffd4d0c..ed50f95 100644 --- a/matrix/matrix.go +++ b/matrix/matrix.go @@ -18,6 +18,7 @@ package matrix import ( "bytes" + "context" "crypto/tls" "encoding/json" "fmt" @@ -33,6 +34,7 @@ import ( dbg "runtime/debug" "time" + "maunium.net/go/gomuks/lib/open" "maunium.net/go/gomuks/matrix/event" "maunium.net/go/mautrix" "maunium.net/go/mautrix/format" @@ -131,8 +133,7 @@ func (c *Container) Initialized() bool { return c.client != nil } -// Login sends a password login request with the given username and password. -func (c *Container) Login(user, password string) error { +func (c *Container) PasswordLogin(user, password string) error { resp, err := c.client.Login(&mautrix.ReqLogin{ Type: "m.login.password", Identifier: mautrix.UserIdentifier{ @@ -145,14 +146,95 @@ func (c *Container) Login(user, password string) error { if err != nil { return err } + c.finishLogin(resp) + return nil +} + +func (c *Container) finishLogin(resp *mautrix.RespLogin) { c.client.SetCredentials(resp.UserID, resp.AccessToken) c.config.UserID = resp.UserID c.config.AccessToken = resp.AccessToken c.config.Save() go c.Start() +} - return nil +func respondHTML(w http.ResponseWriter, status int, message string) { + w.Header().Add("Content-Type", "text/html") + w.WriteHeader(status) + _, _ = w.Write([]byte(fmt.Sprintf(`<!DOCTYPE html> +<html> +<head> + <title>gomuks single-sign on</title> + <meta charset="utf-8"/> +</head> +<body> + <center> + <h2>%s</h2> + </center> +</body> +</html>`, message))) +} + +func (c *Container) SingleSignOn() error { + loginURL := c.client.BuildURLWithQuery([]string{"login", "sso", "redirect"}, map[string]string{ + "redirectUrl": "http://localhost:29325", + }) + err := open.Open(loginURL) + if err != nil { + return err + } + errChan := make(chan error, 1) + server := &http.Server{Addr: ":29325"} + server.Handler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + loginToken := r.URL.Query().Get("loginToken") + if len(loginToken) == 0 { + respondHTML(w, http.StatusBadRequest, "Missing loginToken parameter") + return + } + resp, err := c.client.Login(&mautrix.ReqLogin{ + Type: "m.login.token", + Token: loginToken, + InitialDeviceDisplayName: "gomuks", + }) + if err != nil { + respondHTML(w, http.StatusForbidden, err.Error()) + errChan <- err + return + } + respondHTML(w, http.StatusOK, fmt.Sprintf("Successfully logged in as %s", resp.UserID)) + c.finishLogin(resp) + go func() { + ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) + defer cancel() + err = server.Shutdown(ctx) + if err != nil { + debug.Printf("Failed to shut down SSO server: %v\n", err) + } + errChan <- err + }() + }) + err = server.ListenAndServe() + if err != nil { + return err + } + err = <- errChan + return err +} + +// Login sends a password login request with the given username and password. +func (c *Container) Login(user, password string) error { + resp, err := c.client.GetLoginFlows() + if err != nil { + return err + } + if len(resp.Flows) == 1 && resp.Flows[0].Type == "m.login.password" { + return c.PasswordLogin(user, password) + } else if len(resp.Flows) == 2 && resp.Flows[0].Type == "m.login.sso" && resp.Flows[1].Type == "m.login.token" { + return c.SingleSignOn() + } else { + return fmt.Errorf("no supported login flows") + } } // Logout revokes the access token, stops the syncer and calls the OnLogout() method of the UI. diff --git a/ui/view-login.go b/ui/view-login.go index a65a77c..023aaad 100644 --- a/ui/view-login.go +++ b/ui/view-login.go @@ -44,6 +44,8 @@ type LoginView struct { loginButton *mauview.Button quitButton *mauview.Button + loading bool + matrix ifc.MatrixContainer config *config.Config parent *GomuksUI @@ -112,11 +114,7 @@ func (view *LoginView) Error(err string) { view.parent.Render() } -func (view *LoginView) Login() { - hs := view.homeserver.GetText() - mxid := view.username.GetText() - password := view.password.GetText() - +func (view *LoginView) actuallyLogin(hs, mxid, password string) { debug.Printf("Logging into %s as %s...", hs, mxid) view.config.HS = hs err := view.matrix.InitClient() @@ -130,8 +128,23 @@ func (view *LoginView) Login() { view.Error(httpErr.Message) } } else { - view.Error("Failed to connect to server.") + view.Error(err.Error()) } debug.Print("Login error:", err) } + view.loading = false + view.loginButton.SetText("Login") +} + +func (view *LoginView) Login() { + if view.loading { + return + } + hs := view.homeserver.GetText() + mxid := view.username.GetText() + password := view.password.GetText() + + view.loading = true + view.loginButton.SetText("Logging in...") + go view.actuallyLogin(hs, mxid, password) } |