Stub web interface
This commit is contained in:
parent
4270202972
commit
d1b66d3088
10 changed files with 308 additions and 1 deletions
2
go.mod
2
go.mod
|
@ -7,7 +7,9 @@ require (
|
|||
github.com/blang/semver v3.5.1+incompatible // indirect
|
||||
github.com/dyatlov/go-opengraph v0.0.0-20180429202543-816b6608b3c8 // indirect
|
||||
github.com/go-ldap/ldap v3.0.3+incompatible // indirect
|
||||
github.com/go-ldap/ldap/v3 v3.1.7
|
||||
github.com/gorilla/mux v1.7.4
|
||||
github.com/gorilla/sessions v1.2.0
|
||||
github.com/jinzhu/gorm v1.9.12
|
||||
github.com/lrstanley/girc v0.0.0-20190801035559-4fc93959e1a7
|
||||
github.com/matterbridge/go-xmpp v0.0.0-20180529212104-cd19799fba91
|
||||
|
|
8
go.sum
8
go.sum
|
@ -42,9 +42,13 @@ github.com/dyatlov/go-opengraph v0.0.0-20180429202543-816b6608b3c8/go.mod h1:nYi
|
|||
github.com/erikstmartin/go-testdb v0.0.0-20160219214506-8d10e4a1bae5/go.mod h1:a2zkGnVExMxdzMo3M0Hi/3sEU+cWnZpSni0O6/Yb/P0=
|
||||
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
|
||||
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
|
||||
github.com/go-asn1-ber/asn1-ber v1.3.1 h1:gvPdv/Hr++TRFCl0UbPFHC54P9N9jgsRPnmnr419Uck=
|
||||
github.com/go-asn1-ber/asn1-ber v1.3.1/go.mod h1:hEBeB/ic+5LoWskz+yKT7vGhhPYkProFKoKdwZRWMe0=
|
||||
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
|
||||
github.com/go-ldap/ldap v3.0.3+incompatible h1:HTeSZO8hWMS1Rgb2Ziku6b8a7qRIZZMHjsvuZyatzwk=
|
||||
github.com/go-ldap/ldap v3.0.3+incompatible/go.mod h1:qfd9rJvER9Q0/D/Sqn1DfHRoBp40uXYvFoEVrNEPqRc=
|
||||
github.com/go-ldap/ldap/v3 v3.1.7 h1:aHjuWTgZsnxjMgqzx0JHwNqz4jBYZTcNarbPFkW1Oww=
|
||||
github.com/go-ldap/ldap/v3 v3.1.7/go.mod h1:5Zun81jBTabRaI8lzN7E1JjyEl1g6zI6u9pd8luAK4Q=
|
||||
github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
|
||||
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
|
||||
github.com/go-ole/go-ole v1.2.1/go.mod h1:7FAglXiTm7HKlQRDeOQ6ZNUHidzCWXuZWq/1dTyBNF8=
|
||||
|
@ -70,6 +74,10 @@ github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORR
|
|||
github.com/gorilla/mux v1.7.4 h1:VuZ8uybHlWmqV03+zRzdwKL4tUnIp1MAQtp1mIFE1bc=
|
||||
github.com/gorilla/mux v1.7.4/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So=
|
||||
github.com/gorilla/schema v1.1.0/go.mod h1:kgLaKoK1FELgZqMAVxx/5cbj0kT+57qxUrAlIO2eleU=
|
||||
github.com/gorilla/securecookie v1.1.1 h1:miw7JPhV+b/lAHSXz4qd/nN9jRiAFV5FwjeKyCS8BvQ=
|
||||
github.com/gorilla/securecookie v1.1.1/go.mod h1:ra0sb63/xPlUeL+yeDciTfxMRAA+MP+HVt/4epWDjd4=
|
||||
github.com/gorilla/sessions v1.2.0 h1:S7P+1Hm5V/AT9cjEcUD5uDaQSX0OE577aCXgoaKpYbQ=
|
||||
github.com/gorilla/sessions v1.2.0/go.mod h1:dk2InVEVJ0sfLlnXv9EAgkf6ecYs/i80K/zI+bUmuGM=
|
||||
github.com/gorilla/websocket v1.2.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=
|
||||
github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=
|
||||
github.com/gorilla/websocket v1.4.1 h1:q7AeDBpnBk8AogcD4DSag/Ukw/KV+YhzLj2bP5HvKCM=
|
||||
|
|
4
main.go
4
main.go
|
@ -30,6 +30,7 @@ type ConfigAccount struct {
|
|||
|
||||
type ConfigFile struct {
|
||||
ASBindAddr string `json:"appservice_bind_addr"`
|
||||
WebBindAddr string `json:"web_bind_addr"`
|
||||
Registration string `json:"registration"`
|
||||
Server string `json:"homeserver_url"`
|
||||
DbType string `json:"db_type"`
|
||||
|
@ -46,6 +47,7 @@ var registration *mxlib.Registration
|
|||
func readConfig() ConfigFile {
|
||||
config_file := ConfigFile{
|
||||
ASBindAddr: "0.0.0.0:8321",
|
||||
WebBindAddr: "0.0.0.0:8281",
|
||||
Registration: "./registration.yaml",
|
||||
Server: "http://localhost:8008",
|
||||
DbType: "sqlite3",
|
||||
|
@ -170,6 +172,8 @@ func main() {
|
|||
log.Fatal(err)
|
||||
}
|
||||
|
||||
StartWeb()
|
||||
|
||||
for user, accounts := range config.Accounts {
|
||||
for name, params := range accounts {
|
||||
var conn connector.Connector
|
||||
|
|
13
mxlib/api.go
13
mxlib/api.go
|
@ -26,6 +26,19 @@ type Event struct {
|
|||
OriginServerTs int `json:"origin_server_ts"`
|
||||
}
|
||||
|
||||
type PasswordLoginRequest struct {
|
||||
Type string `json:"type"`
|
||||
Identifier map[string]string `json:"identifier"`
|
||||
Password string `json:"password"`
|
||||
DeviceID string `json:"device_id"`
|
||||
InitialDeviceDisplayNAme string `json:"initial_device_display_name"`
|
||||
}
|
||||
|
||||
type LoginResponse struct {
|
||||
UserID string `json:"user_id"`
|
||||
AccessToken string `json:"access_token"`
|
||||
}
|
||||
|
||||
type RegisterRequest struct {
|
||||
Username string `json:"username"`
|
||||
}
|
||||
|
|
|
@ -79,7 +79,9 @@ func (mx *Client) PostApiCall(endpoint string, data interface{}, response interf
|
|||
}
|
||||
|
||||
func (mx *Client) DoAndParse(req *http.Request, response interface{}) error {
|
||||
req.Header.Add("Authorization", "Bearer "+mx.Token)
|
||||
if mx.Token != "" {
|
||||
req.Header.Add("Authorization", "Bearer "+mx.Token)
|
||||
}
|
||||
|
||||
resp, err := mx.httpClient.Do(req)
|
||||
if err != nil {
|
||||
|
@ -107,6 +109,28 @@ func (mx *Client) DoAndParse(req *http.Request, response interface{}) error {
|
|||
|
||||
// ----
|
||||
|
||||
func (mx *Client) PasswordLogin(username string, password string, device_id string, device_name string) (string, error) {
|
||||
req := PasswordLoginRequest{
|
||||
Type: "m.login.password",
|
||||
Identifier: map[string]string{
|
||||
"type": "m.id.user",
|
||||
"user": username,
|
||||
},
|
||||
Password: password,
|
||||
DeviceID: device_id,
|
||||
InitialDeviceDisplayNAme: device_name,
|
||||
}
|
||||
var rep LoginResponse
|
||||
err := mx.PostApiCall("/_matrix/client/r0/login", &req, &rep)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if mx.Token == "" {
|
||||
mx.Token = rep.AccessToken
|
||||
}
|
||||
return rep.UserID, nil
|
||||
}
|
||||
|
||||
func (mx *Client) RegisterUser(username string) error {
|
||||
req := RegisterRequest{
|
||||
Username: username,
|
||||
|
|
7
static/css/bootstrap.min.css
vendored
Normal file
7
static/css/bootstrap.min.css
vendored
Normal file
File diff suppressed because one or more lines are too long
30
templates/home.html
Normal file
30
templates/home.html
Normal file
|
@ -0,0 +1,30 @@
|
|||
{{define "title"}}{{end}}
|
||||
|
||||
{{define "body"}}
|
||||
<div class="alert alert-info">
|
||||
Logged in as <strong>{{ .Login.MxId }}</strong>
|
||||
</div>
|
||||
<div class="d-flex">
|
||||
<a class="ml-auto btn btn-sm btn-dark" href="/logout">Log out</a>
|
||||
</div>
|
||||
|
||||
<table class="table mt-4">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Account name</th>
|
||||
<th>Protocol</th>
|
||||
<th></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{{range $name, $acc := .Accounts}}
|
||||
<tr>
|
||||
<td>{{ $name }}</td>
|
||||
<td>{{ $acc.Protocol }}</td>
|
||||
<td>Modifier etc</td>
|
||||
</tr>
|
||||
{{end}}
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
{{end}}
|
18
templates/layout.html
Normal file
18
templates/layout.html
Normal file
|
@ -0,0 +1,18 @@
|
|||
<!doctype html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
|
||||
|
||||
<link rel="stylesheet" href="/static/css/bootstrap.min.css">
|
||||
|
||||
<title>{{template "title"}} Easybridge</title>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<h1>Easybridge manager</h1>
|
||||
<hr />
|
||||
{{template "body" .}}
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
30
templates/login.html
Normal file
30
templates/login.html
Normal file
|
@ -0,0 +1,30 @@
|
|||
{{define "title"}}{{end}}
|
||||
|
||||
{{define "body"}}
|
||||
<h4>Log in</h4>
|
||||
|
||||
<div class="alert alert-info">
|
||||
Log in using your Matrix credentials on {{ .MatrixDomain }}
|
||||
</div>
|
||||
|
||||
<form method="POST">
|
||||
{{if .WrongPass}}
|
||||
<div class="alert alert-danger">Wrong password.</div>
|
||||
{{end}}
|
||||
{{if .ErrorMessage}}
|
||||
<div class="alert alert-danger">Unable to log in.
|
||||
<div style="font-size: 0.8em">{{ .ErrorMessage }}</div>
|
||||
</div>
|
||||
{{end}}
|
||||
<div class="form-group">
|
||||
<label for="username">Username:</label>
|
||||
<input type="text" name="username" id="username" class="form-control" value="{{ .Username }}" />
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="password">Password:</label>
|
||||
<input type="password" name="password" id="password" class="form-control" />
|
||||
</div>
|
||||
<button type="submit" class="btn btn-primary">Log in</button>
|
||||
</form>
|
||||
|
||||
{{end}}
|
171
web.go
Normal file
171
web.go
Normal file
|
@ -0,0 +1,171 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"html/template"
|
||||
"log"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/gorilla/mux"
|
||||
"github.com/gorilla/sessions"
|
||||
|
||||
"git.deuxfleurs.fr/Deuxfleurs/easybridge/mxlib"
|
||||
)
|
||||
|
||||
const SESSION_NAME = "easybridge_session"
|
||||
|
||||
var sessionsStore sessions.Store = nil
|
||||
|
||||
func StartWeb() {
|
||||
session_key := make([]byte, 32)
|
||||
n, err := rand.Read(session_key)
|
||||
if err != nil || n != 32 {
|
||||
log.Fatal(err)
|
||||
}
|
||||
sessionsStore = sessions.NewCookieStore(session_key)
|
||||
|
||||
r := mux.NewRouter()
|
||||
r.HandleFunc("/", handleHome)
|
||||
r.HandleFunc("/logout", handleLogout)
|
||||
|
||||
staticfiles := http.FileServer(http.Dir("static"))
|
||||
r.Handle("/static/{file:.*}", http.StripPrefix("/static/", staticfiles))
|
||||
|
||||
log.Printf("Starting web UI HTTP server on %s", config.WebBindAddr)
|
||||
go func() {
|
||||
err = http.ListenAndServe(config.WebBindAddr, logRequest(r))
|
||||
if err != nil {
|
||||
log.Fatal("Cannot start http server: ", err)
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
func logRequest(handler http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
log.Printf("%s %s %s\n", r.RemoteAddr, r.Method, r.URL)
|
||||
handler.ServeHTTP(w, r)
|
||||
})
|
||||
}
|
||||
|
||||
// ----
|
||||
|
||||
type LoginInfo struct {
|
||||
MxId string
|
||||
}
|
||||
|
||||
func checkLogin(w http.ResponseWriter, r *http.Request) *LoginInfo {
|
||||
var login_info *LoginInfo
|
||||
|
||||
session, err := sessionsStore.Get(r, SESSION_NAME)
|
||||
if err == nil {
|
||||
mxid, ok := session.Values["login_mxid"]
|
||||
if ok {
|
||||
login_info = &LoginInfo{
|
||||
MxId: mxid.(string),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if login_info == nil {
|
||||
login_info = handleLogin(w, r)
|
||||
}
|
||||
|
||||
return login_info
|
||||
}
|
||||
|
||||
// ----
|
||||
|
||||
type HomeData struct {
|
||||
Login *LoginInfo
|
||||
Accounts map[string]*Account
|
||||
}
|
||||
|
||||
func handleHome(w http.ResponseWriter, r *http.Request) {
|
||||
templateHome := template.Must(template.ParseFiles("templates/layout.html", "templates/home.html"))
|
||||
|
||||
login := checkLogin(w, r)
|
||||
if login == nil {
|
||||
return
|
||||
}
|
||||
|
||||
accountsLock.Lock()
|
||||
defer accountsLock.Unlock()
|
||||
templateHome.Execute(w, &HomeData{
|
||||
Login: login,
|
||||
Accounts: registeredAccounts[login.MxId],
|
||||
})
|
||||
}
|
||||
|
||||
func handleLogout(w http.ResponseWriter, r *http.Request) {
|
||||
session, err := sessionsStore.Get(r, SESSION_NAME)
|
||||
if err != nil {
|
||||
session, _ = sessionsStore.New(r, SESSION_NAME)
|
||||
}
|
||||
|
||||
delete(session.Values, "login_mxid")
|
||||
|
||||
err = session.Save(r, w)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
http.Redirect(w, r, "/", http.StatusFound)
|
||||
}
|
||||
|
||||
type LoginFormData struct {
|
||||
Username string
|
||||
WrongPass bool
|
||||
ErrorMessage string
|
||||
MatrixDomain string
|
||||
}
|
||||
|
||||
func handleLogin(w http.ResponseWriter, r *http.Request) *LoginInfo {
|
||||
templateLogin := template.Must(template.ParseFiles("templates/layout.html", "templates/login.html"))
|
||||
|
||||
data := &LoginFormData{
|
||||
MatrixDomain: config.MatrixDomain,
|
||||
}
|
||||
|
||||
if r.Method == "GET" {
|
||||
templateLogin.Execute(w, data)
|
||||
return nil
|
||||
} else if r.Method == "POST" {
|
||||
r.ParseForm()
|
||||
|
||||
username := strings.Join(r.Form["username"], "")
|
||||
password := strings.Join(r.Form["password"], "")
|
||||
|
||||
cli := mxlib.NewClient(config.Server, "")
|
||||
mxid, err := cli.PasswordLogin(username, password, "EZBRIDGE", "Easybridge")
|
||||
|
||||
if err != nil {
|
||||
data.Username = username
|
||||
data.ErrorMessage = err.Error()
|
||||
templateLogin.Execute(w, data)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Successfully logged in, save it to session
|
||||
session, err := sessionsStore.Get(r, SESSION_NAME)
|
||||
if err != nil {
|
||||
session, _ = sessionsStore.New(r, SESSION_NAME)
|
||||
}
|
||||
|
||||
session.Values["login_mxid"] = mxid
|
||||
|
||||
err = session.Save(r, w)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return nil
|
||||
}
|
||||
|
||||
return &LoginInfo{
|
||||
MxId: mxid,
|
||||
}
|
||||
} else {
|
||||
http.Error(w, "Unsupported method", http.StatusBadRequest)
|
||||
return nil
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue