Implement account configuration save/load from db
This commit is contained in:
parent
d1b66d3088
commit
f675ba57e4
6 changed files with 141 additions and 18 deletions
|
@ -14,6 +14,7 @@ type Account struct {
|
||||||
MatrixUser string
|
MatrixUser string
|
||||||
AccountName string
|
AccountName string
|
||||||
Protocol string
|
Protocol string
|
||||||
|
Config map[string]string
|
||||||
Conn Connector
|
Conn Connector
|
||||||
|
|
||||||
JoinedRooms map[RoomID]bool
|
JoinedRooms map[RoomID]bool
|
||||||
|
@ -77,7 +78,7 @@ func (a *Account) ezbrMessagef(format string, args ...interface{}) {
|
||||||
ezbrSystemSend(a.MatrixUser, msg)
|
ezbrSystemSend(a.MatrixUser, msg)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *Account) connect(config map[string]string, join_rooms []string) {
|
func (a *Account) connect(config map[string]string) {
|
||||||
ezbrSystemSendf(a.MatrixUser, "Connecting to account %s (%s)", a.AccountName, a.Protocol)
|
ezbrSystemSendf(a.MatrixUser, "Connecting to account %s (%s)", a.AccountName, a.Protocol)
|
||||||
|
|
||||||
err := a.Conn.Configure(config)
|
err := a.Conn.Configure(config)
|
||||||
|
@ -86,10 +87,6 @@ func (a *Account) connect(config map[string]string, join_rooms []string) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, room := range join_rooms {
|
|
||||||
a.addAutojoin(RoomID(room))
|
|
||||||
}
|
|
||||||
|
|
||||||
var autojoin []DbJoinedRoom
|
var autojoin []DbJoinedRoom
|
||||||
db.Where(&DbJoinedRoom{
|
db.Where(&DbJoinedRoom{
|
||||||
MxUserID: a.MatrixUser,
|
MxUserID: a.MatrixUser,
|
||||||
|
|
14
db.go
14
db.go
|
@ -25,6 +25,8 @@ func InitDb() error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
db.AutoMigrate(&DbAccountConfig{})
|
||||||
|
|
||||||
db.AutoMigrate(&DbCache{})
|
db.AutoMigrate(&DbCache{})
|
||||||
|
|
||||||
db.AutoMigrate(&DbUserMap{})
|
db.AutoMigrate(&DbUserMap{})
|
||||||
|
@ -42,6 +44,16 @@ func InitDb() error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Account configuration
|
||||||
|
type DbAccountConfig struct {
|
||||||
|
gorm.Model
|
||||||
|
|
||||||
|
MxUserID string `gorm:"index:account_mxuserid"`
|
||||||
|
Name string
|
||||||
|
Protocol string
|
||||||
|
Config string
|
||||||
|
}
|
||||||
|
|
||||||
// Long-term cache entries
|
// Long-term cache entries
|
||||||
type DbCache struct {
|
type DbCache struct {
|
||||||
gorm.Model
|
gorm.Model
|
||||||
|
@ -56,7 +68,7 @@ type DbUserMap struct {
|
||||||
|
|
||||||
Protocol string
|
Protocol string
|
||||||
UserID connector.UserID
|
UserID connector.UserID
|
||||||
MxUserID string `gorm:"index:mxuserid"`
|
MxUserID string `gorm:"index:usermap_mxuserid"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// Room mapping between Matrix rooms and outside rooms
|
// Room mapping between Matrix rooms and outside rooms
|
||||||
|
|
2
go.sum
2
go.sum
|
@ -106,6 +106,7 @@ github.com/keybase/go-keybase-chat-bot v0.0.0-20190816161829-561f10822eb2/go.mod
|
||||||
github.com/keybase/go-ps v0.0.0-20161005175911-668c8856d999/go.mod h1:hY+WOq6m2FpbvyrI93sMaypsttvaIL5nhVR92dTMUcQ=
|
github.com/keybase/go-ps v0.0.0-20161005175911-668c8856d999/go.mod h1:hY+WOq6m2FpbvyrI93sMaypsttvaIL5nhVR92dTMUcQ=
|
||||||
github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q=
|
github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q=
|
||||||
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
||||||
|
github.com/konsorten/go-windows-terminal-sequences v1.0.1 h1:mweAR1A6xJ3oS2pRaGiHgQ4OO8tzTaLawm8vnODuwDk=
|
||||||
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
||||||
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
|
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
|
||||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||||
|
@ -274,6 +275,7 @@ golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGm
|
||||||
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||||
golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||||
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
|
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
|
||||||
|
google.golang.org/appengine v1.4.0 h1:/wp5JvzpHIxhs/dumFmF7BXTf3Z+dd4uXta4kVyO508=
|
||||||
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||||
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
|
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
|
||||||
google.golang.org/genproto v0.0.0-20180831171423-11092d34479b/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
|
google.golang.org/genproto v0.0.0-20180831171423-11092d34479b/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
|
||||||
|
|
30
main.go
30
main.go
|
@ -24,7 +24,6 @@ import (
|
||||||
|
|
||||||
type ConfigAccount struct {
|
type ConfigAccount struct {
|
||||||
Protocol string `json:"protocol"`
|
Protocol string `json:"protocol"`
|
||||||
Rooms []string `json:"rooms"`
|
|
||||||
Config map[string]string `json:"config"`
|
Config map[string]string `json:"config"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -176,27 +175,21 @@ func main() {
|
||||||
|
|
||||||
for user, accounts := range config.Accounts {
|
for user, accounts := range config.Accounts {
|
||||||
for name, params := range accounts {
|
for name, params := range accounts {
|
||||||
var conn connector.Connector
|
conn := createConnector(params.Protocol)
|
||||||
switch params.Protocol {
|
if conn == nil {
|
||||||
case "irc":
|
log.Fatalf("Could not create connector for protocol %s", params.Protocol)
|
||||||
conn = &irc.IRC{}
|
|
||||||
case "xmpp":
|
|
||||||
conn = &xmpp.XMPP{}
|
|
||||||
case "mattermost":
|
|
||||||
conn = &mattermost.Mattermost{}
|
|
||||||
default:
|
|
||||||
log.Fatalf("Invalid protocol %s", params.Protocol)
|
|
||||||
}
|
}
|
||||||
account := &Account{
|
account := &Account{
|
||||||
MatrixUser: fmt.Sprintf("@%s:%s", user, config.MatrixDomain),
|
MatrixUser: fmt.Sprintf("@%s:%s", user, config.MatrixDomain),
|
||||||
AccountName: name,
|
AccountName: name,
|
||||||
Protocol: params.Protocol,
|
Protocol: params.Protocol,
|
||||||
|
Config: params.Config,
|
||||||
Conn: conn,
|
Conn: conn,
|
||||||
JoinedRooms: map[connector.RoomID]bool{},
|
JoinedRooms: map[connector.RoomID]bool{},
|
||||||
}
|
}
|
||||||
conn.SetHandler(account)
|
conn.SetHandler(account)
|
||||||
AddAccount(account)
|
AddAccount(account)
|
||||||
go account.connect(params.Config, params.Rooms)
|
go account.connect(params.Config)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -205,3 +198,16 @@ func main() {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func createConnector(protocol string) connector.Connector {
|
||||||
|
switch protocol {
|
||||||
|
case "irc":
|
||||||
|
return &irc.IRC{}
|
||||||
|
case "xmpp":
|
||||||
|
return &xmpp.XMPP{}
|
||||||
|
case "mattermost":
|
||||||
|
return &mattermost.Mattermost{}
|
||||||
|
default:
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
42
util.go
42
util.go
|
@ -1,10 +1,14 @@
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"crypto/rand"
|
||||||
|
"encoding/base64"
|
||||||
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"unicode"
|
"unicode"
|
||||||
|
|
||||||
log "github.com/sirupsen/logrus"
|
log "github.com/sirupsen/logrus"
|
||||||
|
"golang.org/x/crypto/nacl/secretbox"
|
||||||
|
|
||||||
. "git.deuxfleurs.fr/Deuxfleurs/easybridge/connector"
|
. "git.deuxfleurs.fr/Deuxfleurs/easybridge/connector"
|
||||||
)
|
)
|
||||||
|
@ -56,3 +60,41 @@ func safeStringForId(in string) string {
|
||||||
}
|
}
|
||||||
return id2
|
return id2
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ---- Encoding and encryption of account config
|
||||||
|
|
||||||
|
func encryptAccountConfig(config map[string]string, key *[32]byte) string {
|
||||||
|
bytes, err := json.Marshal(config)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var nonce [24]byte
|
||||||
|
_, err = rand.Read(nonce[:])
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
crypto := secretbox.Seal([]byte{}, bytes, &nonce, key)
|
||||||
|
all := append(nonce[:], crypto...)
|
||||||
|
return base64.StdEncoding.EncodeToString(all)
|
||||||
|
}
|
||||||
|
|
||||||
|
func decryptAccountConfig(data string, key *[32]byte) (map[string]string, error) {
|
||||||
|
bytes, err := base64.StdEncoding.DecodeString(data)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var nonce [24]byte
|
||||||
|
copy(nonce[:], bytes[:24])
|
||||||
|
|
||||||
|
decoded, ok := secretbox.Open([]byte{}, bytes[24:], &nonce, key)
|
||||||
|
if !ok {
|
||||||
|
return nil, fmt.Errorf("Invalid key")
|
||||||
|
}
|
||||||
|
|
||||||
|
var config map[string]string
|
||||||
|
err = json.Unmarshal(decoded, &config)
|
||||||
|
return config, err
|
||||||
|
}
|
||||||
|
|
64
web.go
64
web.go
|
@ -9,13 +9,16 @@ import (
|
||||||
|
|
||||||
"github.com/gorilla/mux"
|
"github.com/gorilla/mux"
|
||||||
"github.com/gorilla/sessions"
|
"github.com/gorilla/sessions"
|
||||||
|
"golang.org/x/crypto/argon2"
|
||||||
|
|
||||||
|
"git.deuxfleurs.fr/Deuxfleurs/easybridge/connector"
|
||||||
"git.deuxfleurs.fr/Deuxfleurs/easybridge/mxlib"
|
"git.deuxfleurs.fr/Deuxfleurs/easybridge/mxlib"
|
||||||
)
|
)
|
||||||
|
|
||||||
const SESSION_NAME = "easybridge_session"
|
const SESSION_NAME = "easybridge_session"
|
||||||
|
|
||||||
var sessionsStore sessions.Store = nil
|
var sessionsStore sessions.Store = nil
|
||||||
|
var userKeys = map[string]*[32]byte{}
|
||||||
|
|
||||||
func StartWeb() {
|
func StartWeb() {
|
||||||
session_key := make([]byte, 32)
|
session_key := make([]byte, 32)
|
||||||
|
@ -147,6 +150,12 @@ func handleLogin(w http.ResponseWriter, r *http.Request) *LoginInfo {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
key := new([32]byte)
|
||||||
|
key_slice := argon2.IDKey([]byte(password), []byte("EZBRIDGE account store"), 3, 64*1024, 4, 32)
|
||||||
|
copy(key[:], key_slice[:])
|
||||||
|
userKeys[mxid] = key
|
||||||
|
syncDbAccounts(mxid, key)
|
||||||
|
|
||||||
// Successfully logged in, save it to session
|
// Successfully logged in, save it to session
|
||||||
session, err := sessionsStore.Get(r, SESSION_NAME)
|
session, err := sessionsStore.Get(r, SESSION_NAME)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -169,3 +178,58 @@ func handleLogin(w http.ResponseWriter, r *http.Request) *LoginInfo {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func syncDbAccounts(mxid string, key *[32]byte) {
|
||||||
|
accountsLock.Lock()
|
||||||
|
defer accountsLock.Unlock()
|
||||||
|
|
||||||
|
// 1. Save all accounts that we have
|
||||||
|
var accounts map[string]*Account
|
||||||
|
if accts, ok := registeredAccounts[mxid]; ok {
|
||||||
|
accounts = accts
|
||||||
|
for name, acct := range accts {
|
||||||
|
var entry DbAccountConfig
|
||||||
|
db.Where(&DbAccountConfig{
|
||||||
|
MxUserID: mxid,
|
||||||
|
Name: name,
|
||||||
|
}).Assign(&DbAccountConfig{
|
||||||
|
Protocol: acct.Protocol,
|
||||||
|
Config: encryptAccountConfig(acct.Config, key),
|
||||||
|
}).FirstOrCreate(&entry)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
accounts = make(map[string]*Account)
|
||||||
|
registeredAccounts[mxid] = accounts
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. Load and start missing accounts
|
||||||
|
var allAccounts []DbAccountConfig
|
||||||
|
db.Where(&DbAccountConfig{MxUserID: mxid}).Find(&allAccounts)
|
||||||
|
for _, acct := range allAccounts {
|
||||||
|
if _, ok := accounts[acct.Name]; !ok {
|
||||||
|
config, err := decryptAccountConfig(acct.Config, key)
|
||||||
|
if err != nil {
|
||||||
|
ezbrSystemSendf("Could not decrypt stored configuration for account %s", acct.Name)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
conn := createConnector(acct.Protocol)
|
||||||
|
if conn == nil {
|
||||||
|
ezbrSystemSendf("Could not create connector for protocol %s", acct.Protocol)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
account := &Account{
|
||||||
|
MatrixUser: mxid,
|
||||||
|
AccountName: acct.Name,
|
||||||
|
Protocol: acct.Protocol,
|
||||||
|
Config: config,
|
||||||
|
Conn: conn,
|
||||||
|
JoinedRooms: map[connector.RoomID]bool{},
|
||||||
|
}
|
||||||
|
conn.SetHandler(account)
|
||||||
|
|
||||||
|
accounts[acct.Name] = account
|
||||||
|
|
||||||
|
go account.connect(config)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in a new issue