Matrix: Setup bot, add PIN verification

PIN is verified but not used currently. Works a little different than
the others, you input your matrix user ID and then the PIN is sent to
you. The bot doesn't support E2EE, so the bot being the first one to
message ensures the chat is unencrypted.
This commit is contained in:
Harvey Tindall 2021-05-29 17:43:11 +01:00
parent fb6256d1ed
commit e97b90d4d7
Signed by: hrfee
GPG Key ID: BBC65952848FB1A2
20 changed files with 446 additions and 22 deletions

62
api.go
View File

@ -2233,6 +2233,68 @@ func (app *appContext) DiscordServerInvite(gc *gin.Context) {
gc.JSON(200, DiscordInviteDTO{invURL, iconURL}) gc.JSON(200, DiscordInviteDTO{invURL, iconURL})
} }
// @Summary Generate and send a new PIN to a specified Matrix user.
// @Produce json
// @Success 200 {object} boolResponse
// @Failure 400 {object} boolResponse
// @Failure 401 {object} boolResponse
// @Failure 500 {object} boolResponse
// @Param invCode path string true "invite Code"
// @Param MatrixSendPINDTO body MatrixSendPINDTO true "User's Matrix ID."
// @Router /invite/{invCode}/matrix/user [post]
// @tags Other
func (app *appContext) MatrixSendPIN(gc *gin.Context) {
code := gc.Param("invCode")
if _, ok := app.storage.invites[code]; !ok {
respondBool(401, false, gc)
return
}
var req MatrixSendPINDTO
gc.BindJSON(&req)
if req.UserID == "" {
respondBool(400, false, gc)
return
}
ok := app.matrix.SendStart(req.UserID)
if !ok {
respondBool(500, false, gc)
return
}
respondBool(200, true, gc)
}
// @Summary Check whether a matrix PIN is valid. Requires invite code.
// @Produce json
// @Success 200 {object} boolResponse
// @Failure 401 {object} boolResponse
// @Param pin path string true "PIN code to check"
// @Param invCode path string true "invite Code"
// @Param userID path string true "Matrix User ID"
// @Router /invite/{invCode}/matrix/verified/{userID}/{pin} [get]
// @tags Other
func (app *appContext) MatrixCheckPIN(gc *gin.Context) {
code := gc.Param("invCode")
if _, ok := app.storage.invites[code]; !ok {
app.debug.Println("Matrix: Invite code was invalid")
respondBool(401, false, gc)
return
}
userID := gc.Param("userID")
pin := gc.Param("pin")
user, ok := app.matrix.tokens[pin]
if !ok {
app.debug.Println("Matrix: PIN not found")
respondBool(200, false, gc)
return
}
if user.User.UserID != userID {
app.debug.Println("Matrix: User ID of PIN didn't match")
respondBool(200, false, gc)
return
}
respondBool(200, true, gc)
}
// @Summary Returns a list of matching users from a Discord guild, given a username (discriminator optional). // @Summary Returns a list of matching users from a Discord guild, given a username (discriminator optional).
// @Produce json // @Produce json
// @Success 200 {object} DiscordUsersDTO // @Success 200 {object} DiscordUsersDTO

View File

@ -15,6 +15,7 @@ var emailEnabled = false
var messagesEnabled = false var messagesEnabled = false
var telegramEnabled = false var telegramEnabled = false
var discordEnabled = false var discordEnabled = false
var matrixEnabled = false
func (app *appContext) GetPath(sect, key string) (fs.FS, string) { func (app *appContext) GetPath(sect, key string) (fs.FS, string) {
val := app.config.Section(sect).Key(key).MustString("") val := app.config.Section(sect).Key(key).MustString("")
@ -43,7 +44,7 @@ func (app *appContext) loadConfig() error {
key.SetValue(key.MustString(filepath.Join(app.dataPath, (key.Name() + ".json")))) key.SetValue(key.MustString(filepath.Join(app.dataPath, (key.Name() + ".json"))))
} }
} }
for _, key := range []string{"user_configuration", "user_displayprefs", "user_profiles", "ombi_template", "invites", "emails", "user_template", "custom_emails", "users", "telegram_users", "discord_users"} { for _, key := range []string{"user_configuration", "user_displayprefs", "user_profiles", "ombi_template", "invites", "emails", "user_template", "custom_emails", "users", "telegram_users", "discord_users", "matrix_users"} {
app.config.Section("files").Key(key).SetValue(app.config.Section("files").Key(key).MustString(filepath.Join(app.dataPath, (key + ".json")))) app.config.Section("files").Key(key).SetValue(app.config.Section("files").Key(key).MustString(filepath.Join(app.dataPath, (key + ".json"))))
} }
app.URLBase = strings.TrimSuffix(app.config.Section("ui").Key("url_base").MustString(""), "/") app.URLBase = strings.TrimSuffix(app.config.Section("ui").Key("url_base").MustString(""), "/")
@ -89,16 +90,18 @@ func (app *appContext) loadConfig() error {
messagesEnabled = app.config.Section("messages").Key("enabled").MustBool(false) messagesEnabled = app.config.Section("messages").Key("enabled").MustBool(false)
telegramEnabled = app.config.Section("telegram").Key("enabled").MustBool(false) telegramEnabled = app.config.Section("telegram").Key("enabled").MustBool(false)
discordEnabled = app.config.Section("discord").Key("enabled").MustBool(false) discordEnabled = app.config.Section("discord").Key("enabled").MustBool(false)
matrixEnabled = app.config.Section("matrix").Key("enabled").MustBool(false)
if !messagesEnabled { if !messagesEnabled {
emailEnabled = false emailEnabled = false
telegramEnabled = false telegramEnabled = false
discordEnabled = false discordEnabled = false
matrixEnabled = false
} else if app.config.Section("email").Key("method").MustString("") == "" { } else if app.config.Section("email").Key("method").MustString("") == "" {
emailEnabled = false emailEnabled = false
} else { } else {
emailEnabled = true emailEnabled = true
} }
if !emailEnabled && !telegramEnabled && !discordEnabled { if !emailEnabled && !telegramEnabled && !discordEnabled && !matrixEnabled {
messagesEnabled = false messagesEnabled = false
} }

View File

@ -676,6 +676,71 @@
} }
} }
}, },
"matrix": {
"order": [],
"meta": {
"name": "Matrix",
"description": "Settings for Matrix invites/signup/notifications"
},
"settings": {
"enabled": {
"name": "Enabled",
"required": false,
"requires_restart": true,
"type": "bool",
"value": false,
"description": "Enable signup verification through Matrix and the sending of notifications through it.\nSee the jfa-go wiki for setting up a bot."
},
"required": {
"name": "Require on sign-up",
"required": false,
"required_restart": true,
"depends_true": "enabled",
"type": "bool",
"value": false,
"description": "Require Matrix connection on sign-up."
},
"homeserver": {
"name": "Home Server URL",
"required": false,
"requires_restart": true,
"depends_true": "enabled",
"type": "text",
"value": "",
"description": "Matrix Home server URL."
},
"token": {
"name": "Access Token",
"required": false,
"requires_restart": true,
"depends_true": "enabled",
"type": "text",
"value": "",
"description": "Matrix Bot API Token."
},
"user_id": {
"name": "Bot User ID",
"required": false,
"requires_restart": true,
"depends_true": "enabled",
"type": "text",
"value": "",
"description": "User ID of bot account (Example: @jfa-bot:riot.im)"
},
"language": {
"name": "Language",
"required": false,
"requires_restart": false,
"depends_true": "enabled",
"type": "select",
"options": [
["en-us", "English (US)"]
],
"value": "en-us",
"description": "Default Matrix message language. Visit weblate if you'd like to translate."
}
}
},
"password_resets": { "password_resets": {
"order": [], "order": [],
"meta": { "meta": {
@ -1225,6 +1290,14 @@
"value": "", "value": "",
"description": "Stores telegram user IDs and language preferences." "description": "Stores telegram user IDs and language preferences."
}, },
"matrix_users": {
"name": "Matrix users",
"required": false,
"requires_restart": false,
"type": "text",
"value": "",
"description": "Stores matrix user IDs and language preferences."
},
"discord_users": { "discord_users": {
"name": "Discord users", "name": "Discord users",
"required": false, "required": false,

1
go.mod
View File

@ -38,6 +38,7 @@ require (
github.com/lithammer/shortuuid/v3 v3.0.4 github.com/lithammer/shortuuid/v3 v3.0.4
github.com/mailgun/mailgun-go/v4 v4.5.1 github.com/mailgun/mailgun-go/v4 v4.5.1
github.com/mailru/easyjson v0.7.7 // indirect github.com/mailru/easyjson v0.7.7 // indirect
github.com/matrix-org/gomatrix v0.0.0-20210324163249-be2af5ef2e16 // indirect
github.com/russross/blackfriday/v2 v2.1.0 // indirect github.com/russross/blackfriday/v2 v2.1.0 // indirect
github.com/skratchdot/open-golang v0.0.0-20200116055534-eef842397966 // indirect github.com/skratchdot/open-golang v0.0.0-20200116055534-eef842397966 // indirect
github.com/smartystreets/goconvey v1.6.4 // indirect github.com/smartystreets/goconvey v1.6.4 // indirect

2
go.sum
View File

@ -189,6 +189,8 @@ github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN
github.com/mailru/easyjson v0.7.6/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= github.com/mailru/easyjson v0.7.6/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0=
github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
github.com/matrix-org/gomatrix v0.0.0-20210324163249-be2af5ef2e16 h1:ZtO5uywdd5dLDCud4r0r55eP4j9FuUNpl60Gmntcop4=
github.com/matrix-org/gomatrix v0.0.0-20210324163249-be2af5ef2e16/go.mod h1:/gBX06Kw0exX1HrwmoBibFA98yBk/jxKpGVeyQbff+s=
github.com/mattn/go-colorable v0.1.8 h1:c1ghPdyEDarC70ftn0y+A/Ee++9zz8ljHG1b13eJ0s8= github.com/mattn/go-colorable v0.1.8 h1:c1ghPdyEDarC70ftn0y+A/Ee++9zz8ljHG1b13eJ0s8=
github.com/mattn/go-colorable v0.1.8/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= github.com/mattn/go-colorable v0.1.8/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=

View File

@ -22,6 +22,9 @@
window.discordPIN = "{{ .discordPIN }}"; window.discordPIN = "{{ .discordPIN }}";
window.discordInviteLink = {{ .discordInviteLink }}; window.discordInviteLink = {{ .discordInviteLink }};
window.discordServerName = "{{ .discordServerName }}"; window.discordServerName = "{{ .discordServerName }}";
window.matrixEnabled = {{ .matrixEnabled }};
window.matrixRequired = {{ .matrixRequired }};
window.matrixUserID = "{{ .matrixUser }}";
</script> </script>
<script src="js/form.js" type="module"></script> <script src="js/form.js" type="module"></script>
{{ end }} {{ end }}

View File

@ -48,6 +48,24 @@
</div> </div>
</div> </div>
{{ end }} {{ end }}
{{ if .matrixEnabled }}
<div id="modal-matrix" class="modal">
<div class="modal-content card">
<span class="heading mb-1">{{ .strings.linkMatrix }}</span>
<p class="content mb-1"> {{ .strings.matrixEnterUser }}</p>
<input type="text" class="input ~neutral !high" placeholder="@user:riot.im" id="matrix-userid">
<div class="subheading link-center mt-1">
<span class="shield ~info mr-1">
<span class="icon">
<i class="ri-chat-3-line"></i>
</span>
</span>
{{ .matrixUser }}
</div>
<span class="button ~info !normal full-width center mt-1" id="matrix-send">{{ .strings.submit }}</span>
</div>
</div>
{{ end }}
<span class="dropdown" tabindex="0" id="lang-dropdown"> <span class="dropdown" tabindex="0" id="lang-dropdown">
<span class="button ~urge dropdown-button"> <span class="button ~urge dropdown-button">
<i class="ri-global-line"></i> <i class="ri-global-line"></i>
@ -84,7 +102,10 @@
{{ if .discordEnabled }} {{ if .discordEnabled }}
<span class="button ~info !normal full-width center mb-1" id="link-discord">{{ .strings.linkDiscord }}</span> <span class="button ~info !normal full-width center mb-1" id="link-discord">{{ .strings.linkDiscord }}</span>
{{ end }} {{ end }}
{{ if or (.telegramEnabled) (.discordEnabled) }} {{ if .matrixEnabled }}
<span class="button ~info !normal full-width center mb-1" id="link-matrix">{{ .strings.linkMatrix }}</span>
{{ end }}
{{ if or (.telegramEnabled) (or .discordEnabled .matrixEnabled) }}
<div id="contact-via" class="unfocused"> <div id="contact-via" class="unfocused">
<label class="row switch pb-1"> <label class="row switch pb-1">
<input type="radio" name="contact-via" value="email"><span>Contact through Email</span> <input type="radio" name="contact-via" value="email"><span>Contact through Email</span>
@ -99,6 +120,11 @@
<input type="radio" name="contact-via" value="discord" id="contact-via-discord"><span>Contact through Discord</span> <input type="radio" name="contact-via" value="discord" id="contact-via-discord"><span>Contact through Discord</span>
</label> </label>
{{ end }} {{ end }}
{{ if .matrixEnabled }}
<label class="row switch pb-1">
<input type="radio" name="contact-via" value="matrix" id="contact-via-matrix"><span>Contact through Matrix</span>
</label>
{{ end }}
</div> </div>
{{ end }} {{ end }}
<label class="label supra" for="create-password">{{ .strings.password }}</label> <label class="label supra" for="create-password">{{ .strings.password }}</label>

View File

@ -8,6 +8,7 @@
"emailAddress": "Email Address", "emailAddress": "Email Address",
"name": "Name", "name": "Name",
"submit": "Submit", "submit": "Submit",
"send": "Send",
"success": "Success", "success": "Success",
"error": "Error", "error": "Error",
"copy": "Copy", "copy": "Copy",
@ -18,6 +19,7 @@
"contactEmail": "Contact through Email", "contactEmail": "Contact through Email",
"contactTelegram": "Contact through Telegram", "contactTelegram": "Contact through Telegram",
"linkDiscord": "Link Discord", "linkDiscord": "Link Discord",
"linkMatrix": "Link Matrix",
"contactDiscord": "Contact through Discord", "contactDiscord": "Contact through Discord",
"theme": "Theme" "theme": "Theme"
} }

View File

@ -19,7 +19,8 @@
"confirmationRequiredMessage": "Please check your email inbox to verify your address.", "confirmationRequiredMessage": "Please check your email inbox to verify your address.",
"yourAccountIsValidUntil": "Your account will be valid until {date}.", "yourAccountIsValidUntil": "Your account will be valid until {date}.",
"sendPIN": "Send the PIN below to the bot, then come back here to link your account.", "sendPIN": "Send the PIN below to the bot, then come back here to link your account.",
"sendPINDiscord": "Type {command} in {server_channel} on Discord, then send the PIN below via DM to the bot." "sendPINDiscord": "Type {command} in {server_channel} on Discord, then send the PIN below via DM to the bot.",
"matrixEnterUser": "Enter your User ID, press submit, and a PIN will be sent to you. Enter it here to continue."
}, },
"notifications": { "notifications": {
"errorUserExists": "User already exists.", "errorUserExists": "User already exists.",
@ -27,6 +28,7 @@
"errorTelegramVerification": "Telegram verification required.", "errorTelegramVerification": "Telegram verification required.",
"errorDiscordVerification": "Discord verification required.", "errorDiscordVerification": "Discord verification required.",
"errorInvalidPIN": "PIN is invalid.", "errorInvalidPIN": "PIN is invalid.",
"errorUnknown": "Unknown error.",
"verified": "Account verified." "verified": "Account verified."
}, },
"validationStrings": { "validationStrings": {

View File

@ -4,6 +4,7 @@
}, },
"strings": { "strings": {
"startMessage": "Hi!\nEnter your Jellyfin PIN code here to verify your account.", "startMessage": "Hi!\nEnter your Jellyfin PIN code here to verify your account.",
"matrixStartMessage": "Hi\nEnter the below PIN in the Jellyfin sign-up page to verify your account.",
"invalidPIN": "That PIN was invalid, try again.", "invalidPIN": "That PIN was invalid, try again.",
"pinSuccess": "Success! You can now return to the sign-up page.", "pinSuccess": "Success! You can now return to the sign-up page.",
"languageMessage": "Note: See available languages with {command}, and set language with {command} <language code>." "languageMessage": "Note: See available languages with {command}, and set language with {command} <language code>."

11
main.go
View File

@ -100,6 +100,7 @@ type appContext struct {
email *Emailer email *Emailer
telegram *TelegramDaemon telegram *TelegramDaemon
discord *DiscordDaemon discord *DiscordDaemon
matrix *MatrixDaemon
info, debug, err logger.Logger info, debug, err logger.Logger
host string host string
port int port int
@ -590,6 +591,16 @@ func start(asDaemon, firstCall bool) {
defer app.discord.Shutdown() defer app.discord.Shutdown()
} }
} }
if matrixEnabled {
app.matrix, err = newMatrixDaemon(app)
if err != nil {
app.err.Printf("Failed to initialize Matrix daemon: %v", err)
matrixEnabled = false
} else {
go app.matrix.run()
defer app.matrix.Shutdown()
}
}
} else { } else {
debugMode = false debugMode = false
address = "0.0.0.0:8056" address = "0.0.0.0:8056"

133
matrix.go Normal file
View File

@ -0,0 +1,133 @@
package main
import (
"encoding/json"
"github.com/matrix-org/gomatrix"
)
type MatrixDaemon struct {
Stopped bool
ShutdownChannel chan string
bot *gomatrix.Client
userID string
tokens map[string]UnverifiedUser // Map of tokens to users
languages map[string]string // Map of roomIDs to language codes
app *appContext
}
type UnverifiedUser struct {
Verified bool
User *MatrixUser
}
type MatrixUser struct {
RoomID string
UserID string
Lang string
Contact bool
}
var matrixFilter = gomatrix.Filter{
Room: gomatrix.RoomFilter{
Timeline: gomatrix.FilterPart{
Types: []string{
"m.room.message",
"m.room.member",
},
},
},
EventFields: []string{
"type",
"event_id",
"room_id",
"state_key",
"sender",
"content.body",
"content.membership",
},
}
func newMatrixDaemon(app *appContext) (d *MatrixDaemon, err error) {
matrix := app.config.Section("matrix")
homeserver := matrix.Key("homeserver").String()
token := matrix.Key("token").String()
d = &MatrixDaemon{
ShutdownChannel: make(chan string),
userID: matrix.Key("user_id").String(),
tokens: map[string]UnverifiedUser{},
languages: map[string]string{},
app: app,
}
d.bot, err = gomatrix.NewClient(homeserver, d.userID, token)
if err != nil {
return
}
filter, err := json.Marshal(matrixFilter)
if err != nil {
return
}
resp, err := d.bot.CreateFilter(filter)
d.bot.Store.SaveFilterID(d.userID, resp.FilterID)
for _, user := range app.storage.matrix {
if user.Lang != "" {
d.languages[user.RoomID] = user.Lang
}
}
return
}
func (d *MatrixDaemon) run() {
d.app.info.Println("Starting Matrix bot daemon")
syncer := d.bot.Syncer.(*gomatrix.DefaultSyncer)
syncer.OnEventType("m.room.message", d.handleMessage)
// syncer.OnEventType("m.room.member", d.handleMembership)
if err := d.bot.Sync(); err != nil {
d.app.err.Printf("Matrix sync failed: %v", err)
}
}
func (d *MatrixDaemon) Shutdown() {
d.bot.StopSync()
d.Stopped = true
close(d.ShutdownChannel)
}
func (d *MatrixDaemon) handleMessage(event *gomatrix.Event) { return }
func (d *MatrixDaemon) SendStart(userID string) (ok bool) {
room, err := d.bot.CreateRoom(&gomatrix.ReqCreateRoom{
Visibility: "private",
Invite: []string{userID},
Topic: "jfa-go",
})
if err != nil {
d.app.err.Printf("Failed to create room for user \"%s\": %v", userID, err)
return
}
lang := "en-us"
pin := genAuthToken()
d.tokens[pin] = UnverifiedUser{
false,
&MatrixUser{
RoomID: room.RoomID,
UserID: userID,
Lang: lang,
},
}
_, err = d.bot.SendText(
room.RoomID,
d.app.storage.lang.Telegram[lang].Strings.get("matrixStartMessage")+"\n\n"+pin+"\n\n"+
d.app.storage.lang.Telegram[lang].Strings.template("languageMessage", tmpl{"command": "!lang"}),
)
if err != nil {
d.app.err.Printf("Matrix: Failed to send welcome message to \"%s\": %v", userID, err)
return
}
ok = true
return
}
// User enters ID on sign-up, a PIN is sent to them. They enter it on sign-up.
// Message the user first, to avoid E2EE by default

View File

@ -281,3 +281,10 @@ type DiscordInviteDTO struct {
InviteURL string `json:"invite"` InviteURL string `json:"invite"`
IconURL string `json:"icon"` IconURL string `json:"icon"`
} }
type MatrixSendPINDTO struct {
UserID string `json:"user_id"`
}
type MatrixCheckPINDTO struct {
PIN string `json:"pin"`
}

View File

@ -127,6 +127,10 @@ func (app *appContext) loadRoutes(router *gin.Engine) {
router.GET(p+"/invite/:invCode/discord/invite", app.DiscordServerInvite) router.GET(p+"/invite/:invCode/discord/invite", app.DiscordServerInvite)
} }
} }
if matrixEnabled {
router.GET(p+"/invite/:invCode/matrix/verified/:userID/:pin", app.MatrixCheckPIN)
router.POST(p+"/invite/:invCode/matrix/user", app.MatrixSendPIN)
}
} }
if *SWAGGER { if *SWAGGER {
app.info.Print(warning("\n\nWARNING: Swagger should not be used on a public instance.\n\n")) app.info.Print(warning("\n\nWARNING: Swagger should not be used on a public instance.\n\n"))

View File

@ -15,21 +15,22 @@ import (
) )
type Storage struct { type Storage struct {
timePattern string timePattern string
invite_path, emails_path, policy_path, configuration_path, displayprefs_path, ombi_path, profiles_path, customEmails_path, users_path, telegram_path, discord_path string invite_path, emails_path, policy_path, configuration_path, displayprefs_path, ombi_path, profiles_path, customEmails_path, users_path, telegram_path, discord_path, matrix_path string
users map[string]time.Time users map[string]time.Time
invites Invites invites Invites
profiles map[string]Profile profiles map[string]Profile
defaultProfile string defaultProfile string
displayprefs, ombi_template map[string]interface{} displayprefs, ombi_template map[string]interface{}
emails map[string]EmailAddress emails map[string]EmailAddress
telegram map[string]TelegramUser // Map of Jellyfin User IDs to telegram users. telegram map[string]TelegramUser // Map of Jellyfin User IDs to telegram users.
discord map[string]DiscordUser // Map of Jellyfin user IDs to discord users. discord map[string]DiscordUser // Map of Jellyfin user IDs to discord users.
customEmails customEmails matrix map[string]MatrixUser // Map of Jellyfin user IDs to Matrix users.
policy mediabrowser.Policy customEmails customEmails
configuration mediabrowser.Configuration policy mediabrowser.Policy
lang Lang configuration mediabrowser.Configuration
invitesLock, usersLock sync.Mutex lang Lang
invitesLock, usersLock sync.Mutex
} }
type TelegramUser struct { type TelegramUser struct {
@ -790,6 +791,14 @@ func (st *Storage) storeDiscordUsers() error {
return storeJSON(st.discord_path, st.discord) return storeJSON(st.discord_path, st.discord)
} }
func (st *Storage) loadMatrixUsers() error {
return loadJSON(st.matrix_path, &st.matrix)
}
func (st *Storage) storeMatrixUsers() error {
return storeJSON(st.matrix_path, st.matrix)
}
func (st *Storage) loadCustomEmails() error { func (st *Storage) loadCustomEmails() error {
return loadJSON(st.customEmails_path, &st.customEmails) return loadJSON(st.customEmails_path, &st.customEmails)
} }

View File

@ -58,7 +58,7 @@ func genAuthToken() string {
rand.Seed(time.Now().UnixNano()) rand.Seed(time.Now().UnixNano())
pin := make([]rune, 8) pin := make([]rune, 8)
for i := range pin { for i := range pin {
if i == 2 || i == 5 { if (i+1)%3 == 0 {
pin[i] = '-' pin[i] = '-'
} else { } else {
pin[i] = runes[rand.Intn(len(runes))] pin[i] = runes[rand.Intn(len(runes))]

View File

@ -1,6 +1,6 @@
import { Modal } from "./modules/modal.js"; import { Modal } from "./modules/modal.js";
import { notificationBox, whichAnimationEvent } from "./modules/common.js"; import { notificationBox, whichAnimationEvent } from "./modules/common.js";
import { _get, _post, toggleLoader, toDateString } from "./modules/common.js"; import { _get, _post, toggleLoader, addLoader, removeLoader, toDateString } from "./modules/common.js";
import { loadLangSelector } from "./modules/lang.js"; import { loadLangSelector } from "./modules/lang.js";
interface formWindow extends Window { interface formWindow extends Window {
@ -9,6 +9,7 @@ interface formWindow extends Window {
successModal: Modal; successModal: Modal;
telegramModal: Modal; telegramModal: Modal;
discordModal: Modal; discordModal: Modal;
matrixModal: Modal;
confirmationModal: Modal confirmationModal: Modal
code: string; code: string;
messages: { [key: string]: string }; messages: { [key: string]: string };
@ -20,6 +21,8 @@ interface formWindow extends Window {
discordStartCommand: string; discordStartCommand: string;
discordInviteLink: boolean; discordInviteLink: boolean;
discordServerName: string; discordServerName: string;
matrixRequired: boolean;
matrixUserID: string;
userExpiryEnabled: boolean; userExpiryEnabled: boolean;
userExpiryMonths: number; userExpiryMonths: number;
userExpiryDays: number; userExpiryDays: number;
@ -150,6 +153,69 @@ if (window.discordEnabled) {
}; };
} }
var matrixVerified = false;
var matrixPIN = "";
if (window.matrixEnabled) {
window.matrixModal = new Modal(document.getElementById("modal-matrix"), window.matrixRequired);
const matrixButton = document.getElementById("link-matrix") as HTMLSpanElement;
matrixButton.onclick = window.matrixModal.show;
const submitButton = document.getElementById("matrix-send") as HTMLSpanElement;
const input = document.getElementById("matrix-userid") as HTMLInputElement;
let userID = "";
submitButton.onclick = () => {
addLoader(submitButton);
if (userID == "") {
const send = {
user_id: input.value
};
_post("/invite/" + window.code + "/matrix/user", send, (req: XMLHttpRequest) => {
if (req.readyState == 4) {
removeLoader(submitButton);
userID = input.value;
if (req.status != 200) {
window.notifications.customError("errorUnknown", window.messages["errorUnknown"]);
window.matrixModal.close();
return;
}
submitButton.classList.add("~positive");
submitButton.classList.remove("~info");
setTimeout(() => {
submitButton.classList.add("~info");
submitButton.classList.remove("~positive");
}, 2000);
input.placeholder = "PIN";
input.value = "";
}
});
} else {
_get("/invite/" + window.code + "/matrix/verified/" + userID + "/" + input.value, null, (req: XMLHttpRequest) => {
if (req.readyState == 4) {
removeLoader(submitButton)
const valid = req.response["success"] as boolean;
if (valid) {
window.matrixModal.close();
window.notifications.customPositive("successVerified", "", window.messages["verified"]);
matrixVerified = true;
matrixPIN = input.value;
matrixButton.classList.add("unfocused");
document.getElementById("contact-via").classList.remove("unfocused");
const radio = document.getElementById("contact-via-discord") as HTMLInputElement;
radio.checked = true;
} else {
window.notifications.customError("errorInvalidPIN", window.messages["errorInvalidPIN"]);
submitButton.classList.add("~critical");
submitButton.classList.remove("~info");
setTimeout(() => {
submitButton.classList.add("~info");
submitButton.classList.remove("~critical");
}, 800);
}
}
},);
}
};
}
if (window.confirmation) { if (window.confirmation) {
window.confirmationModal = new Modal(document.getElementById("modal-confirmation"), true); window.confirmationModal = new Modal(document.getElementById("modal-confirmation"), true);
} }
@ -229,6 +295,8 @@ interface sendDTO {
telegram_contact?: boolean; telegram_contact?: boolean;
discord_pin?: string; discord_pin?: string;
discord_contact?: boolean; discord_contact?: boolean;
matrix_pin?: string;
matrix_contact?: boolean;
} }
const create = (event: SubmitEvent) => { const create = (event: SubmitEvent) => {
@ -254,6 +322,13 @@ const create = (event: SubmitEvent) => {
send.discord_contact = true; send.discord_contact = true;
} }
} }
if (matrixVerified) {
send.matrix_pin = matrixPIN;
const radio = document.getElementById("contact-via-matrix") as HTMLInputElement;
if (radio.checked) {
send.matrix_contact = true;
}
}
_post("/newUser", send, (req: XMLHttpRequest) => { _post("/newUser", send, (req: XMLHttpRequest) => {
if (req.readyState == 4) { if (req.readyState == 4) {
let vals = req.response as respDTO; let vals = req.response as respDTO;

View File

@ -105,7 +105,11 @@ export class notificationBox implements NotificationBox {
private _error = (message: string): HTMLElement => { private _error = (message: string): HTMLElement => {
const noti = document.createElement('aside'); const noti = document.createElement('aside');
noti.classList.add("aside", "~critical", "!normal", "mt-half", "notification-error"); noti.classList.add("aside", "~critical", "!normal", "mt-half", "notification-error");
noti.innerHTML = `<strong>${window.lang.strings("error")}:</strong> ${message}`; let error = "";
if (window.lang) {
error = window.lang.strings("error") + ":"
}
noti.innerHTML = `<strong>${error}</strong> ${message}`;
const closeButton = document.createElement('span') as HTMLSpanElement; const closeButton = document.createElement('span') as HTMLSpanElement;
closeButton.classList.add("button", "~critical", "!low", "ml-1"); closeButton.classList.add("button", "~critical", "!low", "ml-1");
closeButton.innerHTML = `<i class="icon ri-close-line"></i>`; closeButton.innerHTML = `<i class="icon ri-close-line"></i>`;

View File

@ -22,6 +22,7 @@ declare interface Window {
emailEnabled: boolean; emailEnabled: boolean;
telegramEnabled: boolean; telegramEnabled: boolean;
discordEnabled: boolean; discordEnabled: boolean;
matrixEnabled: boolean;
ombiEnabled: boolean; ombiEnabled: boolean;
usernameEnabled: boolean; usernameEnabled: boolean;
token: string; token: string;

View File

@ -287,6 +287,7 @@ func (app *appContext) InviteProxy(gc *gin.Context) {
"langName": lang, "langName": lang,
"telegramEnabled": telegramEnabled, "telegramEnabled": telegramEnabled,
"discordEnabled": discordEnabled, "discordEnabled": discordEnabled,
"matrixEnabled": matrixEnabled,
} }
if telegramEnabled { if telegramEnabled {
data["telegramPIN"] = app.telegram.NewAuthToken() data["telegramPIN"] = app.telegram.NewAuthToken()
@ -294,6 +295,10 @@ func (app *appContext) InviteProxy(gc *gin.Context) {
data["telegramURL"] = app.telegram.link data["telegramURL"] = app.telegram.link
data["telegramRequired"] = app.config.Section("telegram").Key("required").MustBool(false) data["telegramRequired"] = app.config.Section("telegram").Key("required").MustBool(false)
} }
if matrixEnabled {
data["matrixRequired"] = app.config.Section("matrix").Key("required").MustBool(false)
data["matrixUser"] = app.matrix.userID
}
if discordEnabled { if discordEnabled {
data["discordPIN"] = app.discord.NewAuthToken() data["discordPIN"] = app.discord.NewAuthToken()
data["discordUsername"] = app.discord.username data["discordUsername"] = app.discord.username