1
0
mirror of https://github.com/hrfee/jfa-go.git synced 2024-12-22 17:10:10 +00:00

Matrix: Add token generation wizard

Pressing the "+" next to matrix in settings allows you to enter a
homeserver, username and password to enable matrix and generate an
access token.
This commit is contained in:
Harvey Tindall 2021-05-30 22:35:34 +01:00
parent 75fdf6ec3d
commit 375022ba95
Signed by: hrfee
GPG Key ID: BBC65952848FB1A2
9 changed files with 144 additions and 4 deletions

43
api.go
View File

@ -1550,6 +1550,7 @@ func (app *appContext) GetConfig(gc *gin.Context) {
// @Produce json // @Produce json
// @Param appConfig body configDTO true "Config split into sections as in config.ini, all values as strings." // @Param appConfig body configDTO true "Config split into sections as in config.ini, all values as strings."
// @Success 200 {object} boolResponse // @Success 200 {object} boolResponse
// @Failure 500 {object} boolResponse
// @Router /config [post] // @Router /config [post]
// @Security Bearer // @Security Bearer
// @tags Configuration // @tags Configuration
@ -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") app.debug.Println("Config saved")
gc.JSON(200, map[string]bool{"success": true}) gc.JSON(200, map[string]bool{"success": true})
if req["restart-program"] != nil && req["restart-program"].(bool) { if req["restart-program"] != nil && req["restart-program"].(bool) {
@ -2367,6 +2372,42 @@ func (app *appContext) MatrixCheckPIN(gc *gin.Context) {
respondBool(200, true, gc) 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. // @Summary Links a Matrix user to a Jellyfin account via user IDs. Notifications are turned on by default.
// @Produce json // @Produce json
// @Success 200 {object} boolResponse // @Success 200 {object} boolResponse

View File

@ -341,6 +341,19 @@
</div> </div>
</div> </div>
{{ end }} {{ 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> <div id="notification-box"></div>
<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">

View File

@ -98,7 +98,9 @@
"notifyUserCreation": "On user creation", "notifyUserCreation": "On user creation",
"sendPIN": "Ask the user to send the PIN below to the bot.", "sendPIN": "Ask the user to send the PIN below to the bot.",
"searchDiscordUser": "Start typing the Discord username to find the user.", "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": { "notifications": {
"changedEmailAddress": "Changed email address of {n}.", "changedEmailAddress": "Changed email address of {n}.",

View File

@ -31,6 +31,13 @@ type MatrixUser struct {
Contact bool 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{ var matrixFilter = gomatrix.Filter{
Room: gomatrix.RoomFilter{ Room: gomatrix.RoomFilter{
Timeline: gomatrix.FilterPart{ Timeline: gomatrix.FilterPart{
@ -80,6 +87,27 @@ func newMatrixDaemon(app *appContext) (d *MatrixDaemon, err error) {
return 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() { func (d *MatrixDaemon) run() {
d.app.info.Println("Starting Matrix bot daemon") d.app.info.Println("Starting Matrix bot daemon")
syncer := d.bot.Syncer.(*gomatrix.DefaultSyncer) syncer := d.bot.Syncer.(*gomatrix.DefaultSyncer)

View File

@ -299,3 +299,9 @@ type MatrixConnectUserDTO struct {
JellyfinID string `json:"jf_id"` JellyfinID string `json:"jf_id"`
UserID string `json:"user_id"` UserID string `json:"user_id"`
} }
type MatrixLoginDTO struct {
Homeserver string `json:"homeserver"`
Username string `json:"username"`
Password string `json:"password"`
}

View File

@ -169,7 +169,7 @@ func (app *appContext) loadRoutes(router *gin.Engine) {
api.GET(p+"/config", app.GetConfig) api.GET(p+"/config", app.GetConfig)
api.POST(p+"/config", app.ModifyConfig) api.POST(p+"/config", app.ModifyConfig)
api.POST(p+"/restart", app.restart) api.POST(p+"/restart", app.restart)
if telegramEnabled || discordEnabled { if telegramEnabled || discordEnabled || matrixEnabled {
api.GET(p+"/telegram/pin", app.TelegramGetPin) api.GET(p+"/telegram/pin", app.TelegramGetPin)
api.GET(p+"/telegram/verified/:pin", app.TelegramVerified) api.GET(p+"/telegram/verified/:pin", app.TelegramVerified)
api.POST(p+"/users/telegram", app.TelegramAddUser) api.POST(p+"/users/telegram", app.TelegramAddUser)
@ -183,6 +183,8 @@ func (app *appContext) loadRoutes(router *gin.Engine) {
api.GET(p+"/ombi/users", app.OmbiUsers) api.GET(p+"/ombi/users", app.OmbiUsers)
api.POST(p+"/ombi/defaults", app.SetOmbiDefaults) api.POST(p+"/ombi/defaults", app.SetOmbiDefaults)
} }
api.POST(p+"/matrix/login", app.MatrixLogin)
} }
} }

View File

@ -63,6 +63,8 @@ window.availableProfiles = window.availableProfiles || [];
window.modals.updateInfo = new Modal(document.getElementById("modal-update")); window.modals.updateInfo = new Modal(document.getElementById("modal-update"));
window.modals.matrix = new Modal(document.getElementById("modal-matrix"));
if (window.telegramEnabled) { if (window.telegramEnabled) {
window.modals.telegram = new Modal(document.getElementById("modal-telegram")); window.modals.telegram = new Modal(document.getElementById("modal-telegram"));
} }

View File

@ -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 { Marked } from "@ts-stack/markdown";
import { stripMarkdown } from "../modules/stripmd.js"; import { stripMarkdown } from "../modules/stripmd.js";
@ -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) => { reload = () => _get("/config", null, (req: XMLHttpRequest) => {
if (req.readyState == 4) { if (req.readyState == 4) {
if (req.status != 200) { if (req.status != 200) {
@ -698,6 +732,17 @@ export class settingsList {
icon.onclick = () => window.updater.checkForUpdates(window.modals.updateInfo.show); icon.onclick = () => window.updater.checkForUpdates(window.modals.updateInfo.show);
} }
this.addSection(name, settings.sections[name], icon); 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 { } else {
this.addSection(name, settings.sections[name]); this.addSection(name, settings.sections[name]);
} }

View File

@ -104,6 +104,7 @@ declare interface Modals {
updateInfo: Modal; updateInfo: Modal;
telegram: Modal; telegram: Modal;
discord: Modal; discord: Modal;
matrix: Modal;
} }
interface Invite { interface Invite {