tulir/gomuks

View on GitHub
matrix/uia-fallback.go

Summary

Maintainability
A
0 mins
Test Coverage
// gomuks - A terminal Matrix client written in Go.
// Copyright (C) 2020 Tulir Asokan
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program.  If not, see <https://www.gnu.org/licenses/>.

package matrix

import (
    "context"
    "errors"
    "net/http"
    "net/url"
    "time"

    "maunium.net/go/mautrix"

    "maunium.net/go/gomuks/debug"
    "maunium.net/go/gomuks/lib/open"
)

const uiaFallbackPage = `<!DOCTYPE html>
<html lang="en">
<head>
    <title>gomuks user-interactive auth</title>
    <meta charset="utf-8"/>
    <style>
        body {
            text-align: center;
        }
    </style>
</head>
<body>
    <h2>Please complete the login in the popup window</h2>
    <p>Keep this page open while logging in, it will close automatically after the login finishes.</p>
    <button onclick="openPopup()">Open popup</button>
    <button onclick="finish(false)">Cancel</button>
    <script>
        const url = location.hash.substr(1)
        let popupWindow

        function finish(success) {
            if (popupWindow) {
                popupWindow.close()
            }
            fetch("", {method: success ? "POST" : "DELETE"}).then(() => window.close())
        }

        function openPopup() {
            popupWindow = window.open(url)
        }

        window.addEventListener("message", evt => evt.data === "authDone" && finish(true))
    </script>
</body>
</html>
`

func (c *Container) UIAFallback(loginType mautrix.AuthType, sessionID string) error {
    errChan := make(chan error, 1)
    server := &http.Server{Addr: ":29325"}
    server.Handler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        if r.Method == "GET" {
            w.Header().Add("Content-Type", "text/html")
            w.WriteHeader(http.StatusOK)
            _, _ = w.Write([]byte(uiaFallbackPage))
        } else if r.Method == "POST" || r.Method == "DELETE" {
            w.Header().Add("Content-Type", "text/html")
            w.WriteHeader(http.StatusOK)

            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)
                }
                if r.Method == "DELETE" {
                    errChan <- errors.New("login cancelled")
                } else {
                    errChan <- nil
                }
            }()
        } else {
            w.WriteHeader(http.StatusMethodNotAllowed)
        }
    })
    go server.ListenAndServe()
    defer server.Close()
    authURL := c.client.BuildURLWithQuery(mautrix.ClientURLPath{"v3", "auth", loginType, "fallback", "web"}, map[string]string{
        "session": sessionID,
    })
    link := url.URL{
        Scheme:   "http",
        Host:     "localhost:29325",
        Path:     "/",
        Fragment: authURL,
    }
    err := open.Open(link.String())
    if err != nil {
        return err
    }
    err = <-errChan
    return err
}