mirror of
https://github.com/hrfee/jfa-go.git
synced 2024-12-22 09:00:10 +00:00
accounts: allow giving individual users jfa-go access
New "Access jfa-go" column allows you to select users for jfa-go access. New "Allow All" setting allows all Jellyfin users access, as disabling "Admin Only" no longer does this.
This commit is contained in:
parent
46d1da7cd3
commit
6448a7db9e
58
api.go
58
api.go
@ -1453,6 +1453,8 @@ func (app *appContext) GetUsers(gc *gin.Context) {
|
||||
respond(500, "Couldn't get users", gc)
|
||||
return
|
||||
}
|
||||
adminOnly := app.config.Section("ui").Key("admin_only").MustBool(true)
|
||||
allowAll := app.config.Section("ui").Key("allow_all").MustBool(false)
|
||||
i := 0
|
||||
app.storage.usersLock.Lock()
|
||||
defer app.storage.usersLock.Unlock()
|
||||
@ -1470,6 +1472,7 @@ func (app *appContext) GetUsers(gc *gin.Context) {
|
||||
user.Email = email.Addr
|
||||
user.NotifyThroughEmail = email.Contact
|
||||
user.Label = email.Label
|
||||
user.AccountsAdmin = (app.jellyfinLogin) && (email.Admin || (adminOnly && jfUser.Policy.IsAdministrator) || allowAll)
|
||||
}
|
||||
expiry, ok := app.storage.users[jfUser.ID]
|
||||
if ok {
|
||||
@ -1580,6 +1583,43 @@ func (app *appContext) DeleteOmbiProfile(gc *gin.Context) {
|
||||
respondBool(204, true, gc)
|
||||
}
|
||||
|
||||
// @Summary Set whether or not a user can access jfa-go. Redundant if the user is a Jellyfin admin.
|
||||
// @Produce json
|
||||
// @Param setAccountsAdminDTO body setAccountsAdminDTO true "Map of userIDs whether or not they have access."
|
||||
// @Success 204 {object} boolResponse
|
||||
// @Failure 500 {object} boolResponse
|
||||
// @Router /users/accounts-admin [post]
|
||||
// @Security Bearer
|
||||
// @tags Users
|
||||
func (app *appContext) SetAccountsAdmin(gc *gin.Context) {
|
||||
var req setAccountsAdminDTO
|
||||
gc.BindJSON(&req)
|
||||
app.debug.Println("Admin modification requested")
|
||||
users, status, err := app.jf.GetUsers(false)
|
||||
if !(status == 200 || status == 204) || err != nil {
|
||||
app.err.Printf("Failed to get users from Jellyfin (%d): %v", status, err)
|
||||
respond(500, "Couldn't get users", gc)
|
||||
return
|
||||
}
|
||||
for _, jfUser := range users {
|
||||
id := jfUser.ID
|
||||
if admin, ok := req[id]; ok {
|
||||
var emailStore = EmailAddress{}
|
||||
if oldEmail, ok := app.storage.emails[id]; ok {
|
||||
emailStore = oldEmail
|
||||
}
|
||||
emailStore.Admin = admin
|
||||
app.storage.emails[id] = emailStore
|
||||
}
|
||||
}
|
||||
if err := app.storage.storeEmails(); err != nil {
|
||||
app.err.Printf("Failed to store email list: %v", err)
|
||||
respondBool(500, false, gc)
|
||||
}
|
||||
app.info.Println("Email list modified")
|
||||
respondBool(204, true, gc)
|
||||
}
|
||||
|
||||
// @Summary Modify user's labels, which show next to their name in the accounts tab.
|
||||
// @Produce json
|
||||
// @Param modifyEmailsDTO body modifyEmailsDTO true "Map of userIDs to labels"
|
||||
@ -1601,13 +1641,12 @@ func (app *appContext) ModifyLabels(gc *gin.Context) {
|
||||
for _, jfUser := range users {
|
||||
id := jfUser.ID
|
||||
if label, ok := req[id]; ok {
|
||||
addr := ""
|
||||
contact := true
|
||||
var emailStore = EmailAddress{}
|
||||
if oldEmail, ok := app.storage.emails[id]; ok {
|
||||
addr = oldEmail.Addr
|
||||
contact = oldEmail.Contact
|
||||
emailStore = oldEmail
|
||||
}
|
||||
app.storage.emails[id] = EmailAddress{Addr: addr, Contact: contact, Label: label}
|
||||
emailStore.Label = label
|
||||
app.storage.emails[id] = emailStore
|
||||
}
|
||||
}
|
||||
if err := app.storage.storeEmails(); err != nil {
|
||||
@ -1640,11 +1679,12 @@ func (app *appContext) ModifyEmails(gc *gin.Context) {
|
||||
for _, jfUser := range users {
|
||||
id := jfUser.ID
|
||||
if address, ok := req[id]; ok {
|
||||
contact := true
|
||||
if oldAddr, ok := app.storage.emails[id]; ok {
|
||||
contact = oldAddr.Contact
|
||||
var emailStore = EmailAddress{}
|
||||
if oldEmail, ok := app.storage.emails[id]; ok {
|
||||
emailStore = oldEmail
|
||||
}
|
||||
app.storage.emails[id] = EmailAddress{Addr: address, Contact: contact}
|
||||
emailStore.Addr = address
|
||||
app.storage.emails[id] = emailStore
|
||||
if ombiEnabled {
|
||||
ombiUser, code, err := app.getOmbiUser(id)
|
||||
if code == 200 && err == nil {
|
||||
|
10
auth.go
10
auth.go
@ -145,8 +145,14 @@ func (app *appContext) getTokenLogin(gc *gin.Context) {
|
||||
return
|
||||
}
|
||||
jfID = user.ID
|
||||
if app.config.Section("ui").Key("admin_only").MustBool(true) {
|
||||
if !user.Policy.IsAdministrator {
|
||||
if !app.config.Section("ui").Key("allow_all").MustBool(false) {
|
||||
accountsAdmin := false
|
||||
adminOnly := app.config.Section("ui").Key("admin_only").MustBool(true)
|
||||
if emailStore, ok := app.storage.emails[jfID]; ok {
|
||||
accountsAdmin = emailStore.Admin
|
||||
}
|
||||
accountsAdmin = accountsAdmin || (adminOnly && user.Policy.IsAdministrator)
|
||||
if !accountsAdmin {
|
||||
app.debug.Printf("Auth denied: Users \"%s\" isn't admin", creds[0])
|
||||
respond(401, "Unauthorized", gc)
|
||||
return
|
||||
|
@ -181,6 +181,15 @@
|
||||
"value": true,
|
||||
"description": "Allows only admin users on Jellyfin to access the admin page."
|
||||
},
|
||||
"allow_all": {
|
||||
"name": "Allow all users to login",
|
||||
"required": false,
|
||||
"requires_restart": true,
|
||||
"depends_true": "jellyfin_login",
|
||||
"type": "bool",
|
||||
"value": false,
|
||||
"description": "Allow all Jellyfin users to access jfa-go. Not recommended, add individual users in the Accounts tab instead."
|
||||
},
|
||||
"username": {
|
||||
"name": "Web Username",
|
||||
"required": true,
|
||||
|
@ -14,6 +14,9 @@
|
||||
window.langFile = JSON.parse({{ .language }});
|
||||
window.linkResetEnabled = {{ .linkResetEnabled }};
|
||||
window.language = "{{ .langName }}";
|
||||
window.jellyfinLogin = {{ .jellyfinLogin }};
|
||||
window.jfAdminOnly = {{ .jfAdminOnly }};
|
||||
window.jfAllowAll = {{ .jfAllowAll }};
|
||||
</script>
|
||||
<title>Admin - jfa-go</title>
|
||||
{{ template "header.html" . }}
|
||||
@ -613,6 +616,9 @@
|
||||
<tr>
|
||||
<th><input type="checkbox" value="" id="accounts-select-all"></th>
|
||||
<th class="table-inline my-2">{{ .strings.username }}</th>
|
||||
{{ if .jellyfinLogin }}
|
||||
<th class="text-center-i">{{ .strings.accessJFA }}</th>
|
||||
{{ end }}
|
||||
<th>{{ .strings.emailAddress }}</th>
|
||||
{{ if .telegramEnabled }}
|
||||
<th class="text-center-i">Telegram</th>
|
||||
|
@ -139,6 +139,10 @@
|
||||
<label class="row switch pl-4 pb-4">
|
||||
<input type="checkbox" class="mr-2" id="ui-admin_only"><span>{{ .lang.Login.adminOnly }}</span>
|
||||
</label>
|
||||
<label class="row switch pl-4 pb-2">
|
||||
<input type="checkbox" class="mr-2" id="ui-allow_all"><span>{{ .lang.Login.allowAll }}</span>
|
||||
</label>
|
||||
<p class="support pb-4 pl-4 mt-1" id="description-ui-allow_all">{{ .lang.Login.allowAllDescription }}</p>
|
||||
<label class="row switch pb-4">
|
||||
<input type="radio" class="mr-2" name="ui-jellyfin_login" value="false"><span>{{ .lang.Login.authorizeManual }}</span>
|
||||
</label>
|
||||
|
@ -111,7 +111,9 @@
|
||||
"matrixHomeServer": "Home server address",
|
||||
"saveAsTemplate": "Save as template",
|
||||
"deleteTemplate": "Delete template",
|
||||
"templateEnterName": "Enter a name to save this template."
|
||||
"templateEnterName": "Enter a name to save this template.",
|
||||
"accessJFA": "Access jfa-go",
|
||||
"accessJFASettings": "Cannot be changed as this either \"Admin Only\" or \"Allow All\" has been set in Settings > General."
|
||||
},
|
||||
"notifications": {
|
||||
"changedEmailAddress": "Changed email address of {n}.",
|
||||
|
@ -65,6 +65,8 @@
|
||||
"authorizeWithJellyfin": "Authorize with Jellyfin/Emby: Login details are shared with Jellyfin, which allows for multiple users.",
|
||||
"authorizeManual": "Username and Password: Manually set the username and password.",
|
||||
"adminOnly": "Admin users only (recommended)",
|
||||
"allowAll": "Allow all Jellyfin users to login",
|
||||
"allowAllDescription": "Not recommended, you should allow individual users to login once setup.",
|
||||
"emailNotice": "Your email address can be used to receive notifications."
|
||||
},
|
||||
"jellyfinEmby": {
|
||||
|
@ -145,7 +145,8 @@ type respUser struct {
|
||||
NotifyThroughDiscord bool `json:"notify_discord"`
|
||||
Matrix string `json:"matrix"` // Matrix ID (if known)
|
||||
NotifyThroughMatrix bool `json:"notify_matrix"`
|
||||
Label string `json:"label"` // Label of user, shown next to their name.
|
||||
Label string `json:"label"` // Label of user, shown next to their name.
|
||||
AccountsAdmin bool `json:"accounts_admin"` // Whether or not the user is a jfa-go admin.
|
||||
}
|
||||
|
||||
type getUsersDTO struct {
|
||||
@ -346,3 +347,5 @@ type InternalPWR struct {
|
||||
type LogDTO struct {
|
||||
Log string `json:"log"`
|
||||
}
|
||||
|
||||
type setAccountsAdminDTO map[string]bool
|
||||
|
@ -161,6 +161,7 @@ func (app *appContext) loadRoutes(router *gin.Engine) {
|
||||
api.POST(p+"/invites/notify", app.SetNotify)
|
||||
api.POST(p+"/users/emails", app.ModifyEmails)
|
||||
api.POST(p+"/users/labels", app.ModifyLabels)
|
||||
api.POST(p+"/users/accounts-admin", app.SetAccountsAdmin)
|
||||
// api.POST(p + "/setDefaults", app.SetDefaults)
|
||||
api.POST(p+"/users/settings", app.ApplySettings)
|
||||
api.POST(p+"/users/announce", app.Announce)
|
||||
|
9
setup.go
9
setup.go
@ -38,10 +38,11 @@ func (app *appContext) ServeSetup(gc *gin.Context) {
|
||||
return
|
||||
}
|
||||
gc.HTML(200, "setup.html", gin.H{
|
||||
"lang": app.storage.lang.Setup[lang],
|
||||
"emailLang": app.storage.lang.Email[emailLang],
|
||||
"language": app.storage.lang.Setup[lang].JSON,
|
||||
"messages": string(msg),
|
||||
"cssVersion": cssVersion,
|
||||
"lang": app.storage.lang.Setup[lang],
|
||||
"emailLang": app.storage.lang.Email[emailLang],
|
||||
"language": app.storage.lang.Setup[lang].JSON,
|
||||
"messages": string(msg),
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -54,6 +54,7 @@ type EmailAddress struct {
|
||||
Addr string
|
||||
Label string // User Label.
|
||||
Contact bool
|
||||
Admin bool // Whether or not user is jfa-go admin.
|
||||
}
|
||||
|
||||
type customEmails struct {
|
||||
|
@ -21,6 +21,7 @@ interface User {
|
||||
matrix: string;
|
||||
notify_matrix: boolean;
|
||||
label: string;
|
||||
accounts_admin: boolean;
|
||||
}
|
||||
|
||||
interface getPinResponse {
|
||||
@ -64,6 +65,7 @@ class user implements User {
|
||||
private _label: HTMLInputElement;
|
||||
private _userLabel: string;
|
||||
private _labelEditButton: HTMLElement;
|
||||
private _accounts_admin: HTMLInputElement
|
||||
id = "";
|
||||
private _selected: boolean;
|
||||
|
||||
@ -98,6 +100,18 @@ class user implements User {
|
||||
}
|
||||
}
|
||||
|
||||
get accounts_admin(): boolean { return this._accounts_admin.checked; }
|
||||
set accounts_admin(a: boolean) {
|
||||
if (!window.jellyfinLogin) return;
|
||||
this._accounts_admin.checked = a;
|
||||
this._accounts_admin.disabled = (window.jfAllowAll || (a && this.admin && window.jfAdminOnly));
|
||||
if (this._accounts_admin.disabled) {
|
||||
this._accounts_admin.title = window.lang.strings("accessJFASettings");
|
||||
} else {
|
||||
this._accounts_admin.title = "";
|
||||
}
|
||||
}
|
||||
|
||||
get disabled(): boolean { return this._disabled.classList.contains("chip"); }
|
||||
set disabled(state: boolean) {
|
||||
if (state) {
|
||||
@ -386,7 +400,6 @@ class user implements User {
|
||||
|
||||
get label(): string { return this._userLabel; }
|
||||
set label(l: string) {
|
||||
console.log(l);
|
||||
this._userLabel = l ? l : "";
|
||||
this._label.innerHTML = l ? l : "";
|
||||
this._labelEditButton.classList.add("ri-edit-line");
|
||||
@ -403,8 +416,15 @@ class user implements User {
|
||||
constructor(user: User) {
|
||||
this._row = document.createElement("tr") as HTMLTableRowElement;
|
||||
let innerHTML = `
|
||||
<td><input type="checkbox" value=""></td>
|
||||
<td><input type="checkbox" class="accounts-select-user" value=""></td>
|
||||
<td><div class="table-inline"><span class="accounts-username py-2 mr-2"></span><span class="accounts-label-container ml-2"></span> <i class="icon ri-edit-line accounts-label-edit"></i> <span class="accounts-admin"></span> <span class="accounts-disabled"></span></span></div></td>
|
||||
`;
|
||||
if (window.jellyfinLogin) {
|
||||
innerHTML += `
|
||||
<td><div class="table-inline justify-center"><input type="checkbox" class="accounts-access-jfa" value=""></div></td>
|
||||
`;
|
||||
}
|
||||
innerHTML += `
|
||||
<td><div class="table-inline"><i class="icon ri-edit-line accounts-email-edit"></i><span class="accounts-email-container ml-2"></span></div></td>
|
||||
`;
|
||||
if (window.telegramEnabled) {
|
||||
@ -429,7 +449,8 @@ class user implements User {
|
||||
this._row.innerHTML = innerHTML;
|
||||
const emailEditor = `<input type="email" class="input ~neutral @low stealth-input">`;
|
||||
const labelEditor = `<input type="text" class="field ~neutral @low stealth-input">`;
|
||||
this._check = this._row.querySelector("input[type=checkbox]") as HTMLInputElement;
|
||||
this._check = this._row.querySelector("input[type=checkbox].accounts-select-user") as HTMLInputElement;
|
||||
this._accounts_admin = this._row.querySelector("input[type=checkbox].accounts-access-jfa") as HTMLInputElement;
|
||||
this._username = this._row.querySelector(".accounts-username") as HTMLSpanElement;
|
||||
this._admin = this._row.querySelector(".accounts-admin") as HTMLSpanElement;
|
||||
this._disabled = this._row.querySelector(".accounts-disabled") as HTMLSpanElement;
|
||||
@ -444,6 +465,22 @@ class user implements User {
|
||||
this._labelEditButton = this._row.querySelector(".accounts-label-edit") as HTMLElement;
|
||||
this._check.onchange = () => { this.selected = this._check.checked; }
|
||||
|
||||
if (window.jellyfinLogin) {
|
||||
this._accounts_admin.onchange = () => {
|
||||
this.accounts_admin = this._accounts_admin.checked;
|
||||
let send = {};
|
||||
send[this.id] = this.accounts_admin;
|
||||
_post("/users/accounts-admin", send, (req: XMLHttpRequest) => {
|
||||
if (req.readyState == 4) {
|
||||
if (req.status != 204) {
|
||||
this.accounts_admin = !this.accounts_admin;
|
||||
window.notifications.customError("accountsAdminChanged", window.lang.notif("errorUnknown"));
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
this._notifyDropdown = this._constructDropdown();
|
||||
|
||||
const toggleEmailInput = () => {
|
||||
@ -611,6 +648,7 @@ class user implements User {
|
||||
this.notify_email = user.notify_email;
|
||||
this.discord_id = user.discord_id;
|
||||
this.label = user.label;
|
||||
this.accounts_admin = user.accounts_admin;
|
||||
}
|
||||
|
||||
asElement = (): HTMLTableRowElement => { return this._row; }
|
||||
@ -1145,7 +1183,6 @@ export class accountsList {
|
||||
let manualUser: user;
|
||||
for (let id of list) {
|
||||
let user = this._users[id];
|
||||
console.log(user, user.notify_email, user.notify_matrix, user.notify_discord, user.notify_telegram);
|
||||
if (!user.lastNotifyMethod() && !user.email) {
|
||||
manualUser = user;
|
||||
break;
|
||||
|
26
ts/setup.ts
26
ts/setup.ts
@ -239,6 +239,7 @@ const settings = {
|
||||
"language-admin": new LangSelect("admin", get("ui-language-admin")),
|
||||
"jellyfin_login": new BoolRadios("ui-jellyfin_login", "", false, "ui", "jellyfin_login"),
|
||||
"admin_only": new Checkbox(get("ui-admin_only"), "jellyfin_login", true, "ui"),
|
||||
"allow_all": new Checkbox(get("ui-allow_all"), "jellyfin_login", true, "ui"),
|
||||
"username": new Input(get("ui-username"), "", "", "jellyfin_login", false, "ui"),
|
||||
"password": new Input(get("ui-password"), "", "", "jellyfin_login", false, "ui"),
|
||||
"email": new Input(get("ui-email"), "", "", "jellyfin_login", false, "ui"),
|
||||
@ -389,6 +390,31 @@ settings["email"]["method"].onchange = emailMethodChange;
|
||||
settings["messages"]["enabled"].onchange = emailMethodChange;
|
||||
emailMethodChange();
|
||||
|
||||
const jellyfinLoginAccessChange = () => {
|
||||
const adminOnly = settings["ui"]["admin_only"].value == "true";
|
||||
const allowAll = settings["ui"]["allow_all"].value == "true";
|
||||
const adminOnlyEl = document.getElementById("ui-admin_only") as HTMLInputElement;
|
||||
const allowAllEls = [document.getElementById("ui-allow_all"), document.getElementById("description-ui-allow_all")];
|
||||
const nextButton = adminOnlyEl.parentElement.parentElement.parentElement.querySelector("span.next") as HTMLSpanElement;
|
||||
if (adminOnly && !allowAll) {
|
||||
(allowAllEls[0] as HTMLInputElement).disabled = true;
|
||||
adminOnlyEl.disabled = false;
|
||||
nextButton.removeAttribute("disabled");
|
||||
} else if (!adminOnly && allowAll) {
|
||||
adminOnlyEl.disabled = true;
|
||||
(allowAllEls[0] as HTMLInputElement).disabled = false;
|
||||
nextButton.removeAttribute("disabled");
|
||||
} else {
|
||||
adminOnlyEl.disabled = false;
|
||||
(allowAllEls[0] as HTMLInputElement).disabled = false;
|
||||
nextButton.setAttribute("disabled", "true")
|
||||
}
|
||||
};
|
||||
|
||||
settings["ui"]["admin_only"].onchange = jellyfinLoginAccessChange;
|
||||
settings["ui"]["allow_all"].onchange = jellyfinLoginAccessChange;
|
||||
jellyfinLoginAccessChange();
|
||||
|
||||
const embyHidePWR = () => {
|
||||
const pwr = document.getElementById("password-resets");
|
||||
const val = settings["jellyfin"]["type"].value;
|
||||
|
@ -37,6 +37,9 @@ declare interface Window {
|
||||
lang: Lang;
|
||||
langFile: {};
|
||||
updater: updater;
|
||||
jellyfinLogin: boolean;
|
||||
jfAdminOnly: boolean;
|
||||
jfAllowAll: boolean;
|
||||
}
|
||||
|
||||
declare interface Update {
|
||||
|
5
views.go
5
views.go
@ -110,6 +110,8 @@ func (app *appContext) AdminPage(gc *gin.Context) {
|
||||
emailEnabled, _ := app.config.Section("invite_emails").Key("enabled").Bool()
|
||||
notificationsEnabled, _ := app.config.Section("notifications").Key("enabled").Bool()
|
||||
ombiEnabled := app.config.Section("ombi").Key("enabled").MustBool(false)
|
||||
jfAdminOnly := app.config.Section("ui").Key("admin_only").MustBool(true)
|
||||
jfAllowAll := app.config.Section("ui").Key("allow_all").MustBool(false)
|
||||
var license string
|
||||
l, err := fs.ReadFile(localFS, "LICENSE")
|
||||
if err != nil {
|
||||
@ -137,6 +139,9 @@ func (app *appContext) AdminPage(gc *gin.Context) {
|
||||
"language": app.storage.lang.Admin[lang].JSON,
|
||||
"langName": lang,
|
||||
"license": license,
|
||||
"jellyfinLogin": app.jellyfinLogin,
|
||||
"jfAdminOnly": jfAdminOnly,
|
||||
"jfAllowAll": jfAllowAll,
|
||||
})
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user