Skip to content

Commit

Permalink
Matrix: Add token generation wizard
Browse files Browse the repository at this point in the history
Pressing the "+" next to matrix in settings allows you to enter a
homeserver, username and password to enable matrix and generate an
access token.
  • Loading branch information
hrfee committed May 30, 2021
1 parent 75fdf6e commit 375022b
Show file tree
Hide file tree
Showing 9 changed files with 144 additions and 4 deletions.
43 changes: 42 additions & 1 deletion api.go
Expand Up @@ -1550,6 +1550,7 @@ func (app *appContext) GetConfig(gc *gin.Context) {
// @Produce json
// @Param appConfig body configDTO true "Config split into sections as in config.ini, all values as strings."
// @Success 200 {object} boolResponse
// @Failure 500 {object} boolResponse
// @Router /config [post]
// @Security Bearer
// @tags Configuration
Expand All @@ -1575,7 +1576,11 @@ func (app *appContext) ModifyConfig(gc *gin.Context) {
}
}
}
tempConfig.SaveTo(app.configPath)
if err := tempConfig.SaveTo(app.configPath); err != nil {
app.err.Printf("Failed to save config to \"%s\": %v", app.configPath, err)
respondBool(500, false, gc)
return
}
app.debug.Println("Config saved")
gc.JSON(200, map[string]bool{"success": true})
if req["restart-program"] != nil && req["restart-program"].(bool) {
Expand Down Expand Up @@ -2367,6 +2372,42 @@ func (app *appContext) MatrixCheckPIN(gc *gin.Context) {
respondBool(200, true, gc)
}

// @Summary Generates a Matrix access token from a username and password.
// @Produce json
// @Success 200 {object} boolResponse
// @Failure 400 {object} stringResponse
// @Failure 401 {object} boolResponse
// @Failure 500 {object} boolResponse
// @Param MatrixLoginDTO body MatrixLoginDTO true "Username & password."
// @Router /matrix/login [post]
// @tags Other
func (app *appContext) MatrixLogin(gc *gin.Context) {
var req MatrixLoginDTO
gc.BindJSON(&req)
if req.Username == "" || req.Password == "" {
respond(400, "errorLoginBlank", gc)
return
}
token, err := app.matrix.generateAccessToken(req.Homeserver, req.Username, req.Password)
if err != nil {
app.err.Printf("Matrix: Failed to generate token: %v", err)
respond(401, "Unauthorized", gc)
return
}
tempConfig, _ := ini.Load(app.configPath)
matrix := tempConfig.Section("matrix")
matrix.Key("enabled").SetValue("true")
matrix.Key("homeserver").SetValue(req.Homeserver)
matrix.Key("token").SetValue(token)
matrix.Key("user_id").SetValue(req.Username)
if err := tempConfig.SaveTo(app.configPath); err != nil {
app.err.Printf("Failed to save config to \"%s\": %v", app.configPath, err)
respondBool(500, false, gc)
return
}
respondBool(200, true, gc)
}

// @Summary Links a Matrix user to a Jellyfin account via user IDs. Notifications are turned on by default.
// @Produce json
// @Success 200 {object} boolResponse
Expand Down
13 changes: 13 additions & 0 deletions html/admin.html
Expand Up @@ -341,6 +341,19 @@ <h1 class="ac" id="telegram-pin"></h1>
</div>
</div>
{{ end }}
<div id="modal-matrix" class="modal">
<form class="modal-content card" id="form-matrix" href="">
<span class="heading">{{ .strings.linkMatrix }}</span>
<p class="content">{{ .strings.linkMatrixDescription }}</p>
<input type="text" class="field input ~neutral !high mt-half mb-1" placeholder="{{ .strings.matrixHomeServer }}" id="matrix-homeserver">
<input type="text" class="field input ~neutral !high mt-half mb-1" placeholder="{{ .strings.username }}" id="matrix-user">
<input type="password" class="field input ~neutral !high mt-half mb-1" placeholder="{{ .strings.password }}" id="matrix-password">
<label>
<input type="submit" class="unfocused">
<span class="button ~urge !normal full-width center supra submit">{{ .strings.submit }}</span>
</label>
</form>
</div>
<div id="notification-box"></div>
<span class="dropdown" tabindex="0" id="lang-dropdown">
<span class="button ~urge dropdown-button">
Expand Down
4 changes: 3 additions & 1 deletion lang/admin/en-us.json
Expand Up @@ -98,7 +98,9 @@
"notifyUserCreation": "On user creation",
"sendPIN": "Ask the user to send the PIN below to the bot.",
"searchDiscordUser": "Start typing the Discord username to find the user.",
"findDiscordUser": "Find Discord user"
"findDiscordUser": "Find Discord user",
"linkMatrixDescription": "Enter the username and password of the user to use as a bot. Once submitted, the app will restart.",
"matrixHomeServer": "Home server address"
},
"notifications": {
"changedEmailAddress": "Changed email address of {n}.",
Expand Down
28 changes: 28 additions & 0 deletions matrix.go
Expand Up @@ -31,6 +31,13 @@ type MatrixUser struct {
Contact bool
}

type MatrixIdentifier struct {
User string `json:"user"`
IdentType string `json:"type"`
}

func (m MatrixIdentifier) Type() string { return m.IdentType }

var matrixFilter = gomatrix.Filter{
Room: gomatrix.RoomFilter{
Timeline: gomatrix.FilterPart{
Expand Down Expand Up @@ -80,6 +87,27 @@ func newMatrixDaemon(app *appContext) (d *MatrixDaemon, err error) {
return
}

func (d *MatrixDaemon) generateAccessToken(homeserver, username, password string) (string, error) {
req := &gomatrix.ReqLogin{
Type: "m.login.password",
Identifier: MatrixIdentifier{
User: username,
IdentType: "m.id.user",
},
Password: password,
DeviceID: "jfa-go-" + commit,
}
bot, err := gomatrix.NewClient(homeserver, username, "")
if err != nil {
return "", err
}
resp, err := bot.Login(req)
if err != nil {
return "", err
}
return resp.AccessToken, nil
}

func (d *MatrixDaemon) run() {
d.app.info.Println("Starting Matrix bot daemon")
syncer := d.bot.Syncer.(*gomatrix.DefaultSyncer)
Expand Down
6 changes: 6 additions & 0 deletions models.go
Expand Up @@ -299,3 +299,9 @@ type MatrixConnectUserDTO struct {
JellyfinID string `json:"jf_id"`
UserID string `json:"user_id"`
}

type MatrixLoginDTO struct {
Homeserver string `json:"homeserver"`
Username string `json:"username"`
Password string `json:"password"`
}
4 changes: 3 additions & 1 deletion router.go
Expand Up @@ -169,7 +169,7 @@ func (app *appContext) loadRoutes(router *gin.Engine) {
api.GET(p+"/config", app.GetConfig)
api.POST(p+"/config", app.ModifyConfig)
api.POST(p+"/restart", app.restart)
if telegramEnabled || discordEnabled {
if telegramEnabled || discordEnabled || matrixEnabled {
api.GET(p+"/telegram/pin", app.TelegramGetPin)
api.GET(p+"/telegram/verified/:pin", app.TelegramVerified)
api.POST(p+"/users/telegram", app.TelegramAddUser)
Expand All @@ -183,6 +183,8 @@ func (app *appContext) loadRoutes(router *gin.Engine) {
api.GET(p+"/ombi/users", app.OmbiUsers)
api.POST(p+"/ombi/defaults", app.SetOmbiDefaults)
}
api.POST(p+"/matrix/login", app.MatrixLogin)

}
}

Expand Down
2 changes: 2 additions & 0 deletions ts/admin.ts
Expand Up @@ -63,6 +63,8 @@ window.availableProfiles = window.availableProfiles || [];

window.modals.updateInfo = new Modal(document.getElementById("modal-update"));

window.modals.matrix = new Modal(document.getElementById("modal-matrix"));

if (window.telegramEnabled) {
window.modals.telegram = new Modal(document.getElementById("modal-telegram"));
}
Expand Down
47 changes: 46 additions & 1 deletion ts/modules/settings.ts
@@ -1,4 +1,4 @@
import { _get, _post, toggleLoader } from "../modules/common.js";
import { _get, _post, toggleLoader, addLoader, removeLoader } from "../modules/common.js";
import { Marked } from "@ts-stack/markdown";
import { stripMarkdown } from "../modules/stripmd.js";

Expand Down Expand Up @@ -666,6 +666,40 @@ export class settingsList {
}
}

private _addMatrix = () => {
// Modify the login modal, why not
const modal = document.getElementById("form-matrix") as HTMLFormElement;
modal.onsubmit = (event: Event) => {
event.preventDefault();
const button = modal.querySelector("span.submit") as HTMLSpanElement;
addLoader(button);
let send = {
homeserver: (document.getElementById("matrix-homeserver") as HTMLInputElement).value,
username: (document.getElementById("matrix-user") as HTMLInputElement).value,
password: (document.getElementById("matrix-password") as HTMLInputElement).value
}
_post("/matrix/login", send, (req: XMLHttpRequest) => {
if (req.readyState == 4) {
removeLoader(button);
if (req.status == 400) {
window.notifications.customError("errorUnknown", window.lang.notif(req.response["error"] as string));
return;
} else if (req.status == 401) {
window.notifications.customError("errorUnauthorized", req.response["error"] as string);
return;
} else if (req.status == 500) {
window.notifications.customError("errorAddMatrix", window.lang.notif("errorFailureCheckLogs"));
return;
}
window.modals.matrix.close();
_post("/restart", null, () => {});
window.location.reload();
}
}, true);
};
window.modals.matrix.show();
}

reload = () => _get("/config", null, (req: XMLHttpRequest) => {
if (req.readyState == 4) {
if (req.status != 200) {
Expand Down Expand Up @@ -698,6 +732,17 @@ export class settingsList {
icon.onclick = () => window.updater.checkForUpdates(window.modals.updateInfo.show);
}
this.addSection(name, settings.sections[name], icon);
} else if (name == "matrix" && !window.matrixEnabled) {
const addButton = document.createElement("div");
addButton.classList.add("tooltip", "left");
addButton.innerHTML = `
<span class="button ~neutral !normal">+</span>
<span class="content sm">
${window.lang.strings("linkMatrix")}
</span>
`;
(addButton.querySelector("span.button") as HTMLSpanElement).onclick = this._addMatrix;
this.addSection(name, settings.sections[name], addButton);
} else {
this.addSection(name, settings.sections[name]);
}
Expand Down
1 change: 1 addition & 0 deletions ts/typings/d.ts
Expand Up @@ -104,6 +104,7 @@ declare interface Modals {
updateInfo: Modal;
telegram: Modal;
discord: Modal;
matrix: Modal;
}

interface Invite {
Expand Down

0 comments on commit 375022b

Please sign in to comment.