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

Setup: add messages, set password via link

Also changed some section names to use "messages" instead of "emails".
Since I haven't added Discord/Telegram/Matrix bot setup, I mentioned you
can do these later and linked to the setup guides. Sections related to
email mostly now depend on [messages]/enabled now too. Set password via
link has been added to password resets.
This commit is contained in:
Harvey Tindall 2021-06-23 15:44:48 +01:00
parent 2d83e9ff7e
commit 729fc7baf7
Signed by: hrfee
GPG Key ID: BBC65952848FB1A2
5 changed files with 140 additions and 97 deletions

View File

@ -233,85 +233,92 @@
</section> </section>
</div> </div>
<div class="card ~neutral !low mb-1 unfocused"> <div class="card ~neutral !low mb-1 unfocused">
<span class="heading">{{ .lang.Email.title }}</span> <span class="heading">{{ .lang.Messages.title }}</span>
<p class="content" id="email-description"></p> <p class="content" id="messages-description"></p>
<div class="row"> <label class="row switch pb-1">
<div class="col"> <input type="checkbox" id="messages-enabled" checked><span>{{ .lang.Strings.enabled }}</span>
<label class="label"> </label>
<span>{{ .lang.Email.method }}</span> <label class="label">
<div class="select ~neutral !normal mt-half mb-1"> <span class="mt-half">{{ .lang.Email.dateFormat }}</span>
<select id="email-method"> <input type="text" class="input ~neutral !normal mt-half" id="email-date_format" value="%d/%m/%y">
<option value="">{{ .lang.Strings.disabled }}</option> <p class="support mb-1" id="email-dateformat-notice"></p>
<option value="smtp">SMTP</option> </label>
<option value="mailgun">Mailgun</option> <div>
</select> <label class="row switch pb-1">
</div> <input type="radio" name="email-24h" value="true" checked><span>{{ .lang.Strings.time24h }}</span>
</label> </label>
<label class="row switch"> <label class="row switch pb-1">
<input type="checkbox" id="email-no_username"><span>{{ .lang.Email.useEmailAsUsername }}</span> <input type="radio" name="email-24h" value="false"><span>{{ .lang.Strings.time12h }}</span>
<p class="support mb-1">{{ .lang.Email.useEmailAsUsernameNotice }}</p> </label>
</label> </div>
<label class="label"> <div id="email-sect">
<span class="mt-half">{{ .lang.Email.fromAddress }}</span> <span class="heading">{{ .lang.Email.title }}</span>
<input type="email" class="input ~neutral !normal mt-half mb-1" id="email-address" placeholder="mail@jellyf.in"> <p class="content" id="email-description"></p>
</label> <div class="row">
<label class="label"> <div class="col">
<span class="mt-half">{{ .lang.Email.senderName }}</span>
<input type="text" class="input ~neutral !normal mt-half mb-1" id="email-from" value="Jellyfin">
</label>
<label class="label">
<span class="mt-half">{{ .lang.Email.dateFormat }}</span>
<input type="text" class="input ~neutral !normal mt-half" id="email-date_format" value="%d/%m/%y">
<p class="support mb-1" id="email-dateformat-notice"></p>
</label>
<div>
<label class="row switch pb-1">
<input type="radio" name="email-24h" value="true" checked><span>{{ .lang.Strings.time24h }}</span>
</label>
<label class="row switch pb-1">
<input type="radio" name="email-24h" value="false"><span>{{ .lang.Strings.time12h }}</span>
</label>
</div>
</div>
<div class="col">
<div id="email-smtp">
<p class="subheading">SMTP</p>
<label class="label"> <label class="label">
<span>{{ .lang.Email.encryption }}</span> <span>{{ .lang.Email.method }}</span>
<div class="select ~neutral !normal mt-half mb-1"> <div class="select ~neutral !normal mt-half mb-1">
<select id="smtp-encryption"> <select id="email-method">
<option value="starttls">STARTTLS ({{ .lang.Strings.port }} 587)</option> <option value="">{{ .lang.Strings.disabled }}</option>
<option value="ssl_tls">SSL/TLS ({{ .lang.Strings.port }} 465)</option> <option value="smtp">SMTP</option>
<option value="mailgun">Mailgun</option>
</select> </select>
</div> </div>
</label> </label>
<label class="label"> <label class="row switch">
<span class="mt-half">{{ .lang.Strings.serverAddress }}</span> <input type="checkbox" id="email-no_username"><span>{{ .lang.Email.useEmailAsUsername }}</span>
<input type="url" class="input ~neutral !normal mt-half mb-1" id="smtp-server" placeholder="smtp.jellyf.in"> <p class="support mb-1">{{ .lang.Email.useEmailAsUsernameNotice }}</p>
</label> </label>
<label class="label"> <label class="label">
<span class="mt-half">{{ .lang.Strings.port }}</span> <span class="mt-half">{{ .lang.Email.fromAddress }}</span>
<input type="number" class="input ~neutral !normal mt-half mb-1" id="smtp-port" placeholder="587"> <input type="email" class="input ~neutral !normal mt-half mb-1" id="email-address" placeholder="mail@jellyf.in">
</label> </label>
<label class="label"> <label class="label">
<span class="mt-half">{{ .lang.Strings.username }}</span> <span class="mt-half">{{ .lang.Email.senderName }}</span>
<input type="text" class="input ~neutral !normal mt-half mb-1" id="smtp-username"> <input type="text" class="input ~neutral !normal mt-half mb-1" id="email-from" value="Jellyfin">
</label>
<label class="label">
<span class="mt-half">{{ .lang.Strings.password }}</span>
<input type="password" class="input ~neutral !normal mt-half mb-1" id="smtp-password">
</label> </label>
</div> </div>
<div id="email-mailgun"> <div class="col">
<p class="subheading">Mailgun</p> <div id="email-smtp">
<label class="label"> <p class="subheading">SMTP</p>
<span class="mt-half">{{ .lang.Email.mailgunApiURL }}</span> <label class="label">
<input type="url" class="input ~neutral !normal mt-half mb-1" id="mailgun-api_url" placeholder="https://api.eu.mailgun.net/v3/mail.jellyf.in/messages"> <span>{{ .lang.Email.encryption }}</span>
</label> <div class="select ~neutral !normal mt-half mb-1">
<label class="label"> <select id="smtp-encryption">
<span class="mt-half">{{ .lang.Strings.apiKey }}</span> <option value="starttls">STARTTLS ({{ .lang.Strings.port }} 587)</option>
<input type="text" class="input ~neutral !normal mt-half mb-1" id="mailgun-api_key"> <option value="ssl_tls">SSL/TLS ({{ .lang.Strings.port }} 465)</option>
</label> </select>
</div>
</label>
<label class="label">
<span class="mt-half">{{ .lang.Strings.serverAddress }}</span>
<input type="url" class="input ~neutral !normal mt-half mb-1" id="smtp-server" placeholder="smtp.jellyf.in">
</label>
<label class="label">
<span class="mt-half">{{ .lang.Strings.port }}</span>
<input type="number" class="input ~neutral !normal mt-half mb-1" id="smtp-port" placeholder="587">
</label>
<label class="label">
<span class="mt-half">{{ .lang.Strings.username }}</span>
<input type="text" class="input ~neutral !normal mt-half mb-1" id="smtp-username">
</label>
<label class="label">
<span class="mt-half">{{ .lang.Strings.password }}</span>
<input type="password" class="input ~neutral !normal mt-half mb-1" id="smtp-password">
</label>
</div>
<div id="email-mailgun">
<p class="subheading">Mailgun</p>
<label class="label">
<span class="mt-half">{{ .lang.Email.mailgunApiURL }}</span>
<input type="url" class="input ~neutral !normal mt-half mb-1" id="mailgun-api_url" placeholder="https://api.eu.mailgun.net/v3/mail.jellyf.in/messages">
</label>
<label class="label">
<span class="mt-half">{{ .lang.Strings.apiKey }}</span>
<input type="text" class="input ~neutral !normal mt-half mb-1" id="mailgun-api_key">
</label>
</div>
</div> </div>
</div> </div>
</div> </div>
@ -380,7 +387,11 @@
<input type="checkbox" id="password_resets-link_reset"><span>{{ .lang.PasswordResets.resetLinks }}</span> <input type="checkbox" id="password_resets-link_reset"><span>{{ .lang.PasswordResets.resetLinks }}</span>
<p class="support mb-1">{{ .lang.PasswordResets.resetLinksNotice }}</p> <p class="support mb-1">{{ .lang.PasswordResets.resetLinksNotice }}</p>
</label> </label>
<label class="row label"> <label class="switch">
<input type="checkbox" id="password_resets-set_password"><span>{{ .lang.PasswordResets.setPassword }}</span>
<p class="support mb-1">{{ .lang.PasswordResets.setPasswordNotice }}</p>
</label>
<label class="label">
<p class="mt-half">{{ .lang.PasswordResets.resetLinksLanguage }}</p> <p class="mt-half">{{ .lang.PasswordResets.resetLinksLanguage }}</p>
<div class="select ~neutral !normal mt-half mb-1"> <div class="select ~neutral !normal mt-half mb-1">
<select id="password_resets-language"> <select id="password_resets-language">

View File

@ -117,6 +117,7 @@ type setupLang struct {
JellyfinEmby langSection `json:"jellyfinEmby"` JellyfinEmby langSection `json:"jellyfinEmby"`
Ombi langSection `json:"ombi"` Ombi langSection `json:"ombi"`
Email langSection `json:"email"` Email langSection `json:"email"`
Messages langSection `json:"messages"`
Notifications langSection `json:"notifications"` Notifications langSection `json:"notifications"`
WelcomeEmails langSection `json:"welcomeEmails"` WelcomeEmails langSection `json:"welcomeEmails"`
PasswordResets langSection `json:"passwordResets"` PasswordResets langSection `json:"passwordResets"`

View File

@ -25,7 +25,7 @@
}, },
"endPage": { "endPage": {
"finished": "Finished!", "finished": "Finished!",
"restartMessage": "There are more settings you can configure on the admin page. Click below to restart, then refresh the page.", "restartMessage": "You can configure Discord/Telegram/Matrix bots, customize your messages and more in Settings. Click below to restart, then refresh the page.",
"refreshPage": "Refresh" "refreshPage": "Refresh"
}, },
"language": { "language": {
@ -79,6 +79,10 @@
"description": "By connecting to Ombi, both a Jellyfin and Ombi account will be created when a user joins through jfa-go. After setup is finished, go to Settings to set a default profile for new ombi users.", "description": "By connecting to Ombi, both a Jellyfin and Ombi account will be created when a user joins through jfa-go. After setup is finished, go to Settings to set a default profile for new ombi users.",
"apiKeyNotice": "Find this in the first tab of Ombi settings." "apiKeyNotice": "Find this in the first tab of Ombi settings."
}, },
"messages": {
"title": "Messages",
"description": "jfa-go can send password resets and various messages through Email, Discord, Telegram, and/or Matrix. You can set up email below, and the others can be configured in Settings later. Instructions can be found on the {n}. If you don't need this, you can disable these features here."
},
"email": { "email": {
"title": "Email", "title": "Email",
"description": "jfa-go can send password reset PINs and various notifications through email. You can connect to an SMTP server, or use the {n} API.", "description": "jfa-go can send password reset PINs and various notifications through email. You can connect to an SMTP server, or use the {n} API.",
@ -93,16 +97,16 @@
"mailgunApiURL": "API URL" "mailgunApiURL": "API URL"
}, },
"notifications": { "notifications": {
"title": "Notifications", "title": "Admin Notifications",
"description": "If enabled, you can choose (per invite) to receive an email when an invite expires, or a user is created. If you didn't choose the Jellyfin login method, make sure you provided your email address." "description": "If enabled, you can choose (per invite) to receive an message when an invite expires, or a user is created. If you didn't choose the Jellyfin login method, make sure you provided your email address, or add another contact method later."
}, },
"welcomeEmails": { "welcomeEmails": {
"title": "Welcome emails", "title": "Welcome messages",
"description": "If enabled, an email will be sent to new users with the Jellyfin/Emby URL and their username." "description": "If enabled, an message will be sent to new users with the Jellyfin/Emby URL and their username."
}, },
"inviteEmails": { "inviteEmails": {
"title": "Invite Emails", "title": "Invite Messages",
"description": "If enabled, you can send invites directly to a user's email address. Because you might be using a reverse proxy, you need to provide the URL invites are accessed from. Write your URL Base, and append '/invite'." "description": "If enabled, you can send invites directly to a user's email address, Discord or Matrix user. Because you might be using a reverse proxy, you need to provide the URL invites are accessed from. Write your URL Base, and append '/invite'."
}, },
"passwordResets": { "passwordResets": {
"title": "Password Resets", "title": "Password Resets",
@ -111,7 +115,9 @@
"pathToJellyfinNotice": "If you don't know where this is, try resetting your password in Jellyfin. A popup with '<path to jellyfin>/passwordreset-*.json' will appear.", "pathToJellyfinNotice": "If you don't know where this is, try resetting your password in Jellyfin. A popup with '<path to jellyfin>/passwordreset-*.json' will appear.",
"resetLinks": "Send a link instead of a PIN", "resetLinks": "Send a link instead of a PIN",
"resetLinksNotice": "If Ombi integration is enabled, use this to sync Jellyfin password resets with Ombi.", "resetLinksNotice": "If Ombi integration is enabled, use this to sync Jellyfin password resets with Ombi.",
"resetLinksLanguage": "Default reset link language" "resetLinksLanguage": "Default reset link language",
"setPassword": "Set password through link",
"setPasswordNotice": "Enabling this means the user doesn't have to change their password from the PIN after the reset. Password validation will also be enforced."
}, },
"passwordValidation": { "passwordValidation": {
"title": "Password Validation", "title": "Password Validation",

View File

@ -28,7 +28,7 @@ func (app *appContext) ServeSetup(gc *gin.Context) {
"help_message": app.config.Section("ui").Key("help_message").String(), "help_message": app.config.Section("ui").Key("help_message").String(),
"success_message": app.config.Section("ui").Key("success_message").String(), "success_message": app.config.Section("ui").Key("success_message").String(),
}, },
"email": { "messages": {
"message": app.config.Section("messages").Key("message").String(), "message": app.config.Section("messages").Key("message").String(),
}, },
} }
@ -106,6 +106,7 @@ func (st *Storage) loadLangSetup(filesystems ...fs.FS) error {
patchLang(&lang.Login, &fallback.Login, &english.Login) patchLang(&lang.Login, &fallback.Login, &english.Login)
patchLang(&lang.JellyfinEmby, &fallback.JellyfinEmby, &english.JellyfinEmby) patchLang(&lang.JellyfinEmby, &fallback.JellyfinEmby, &english.JellyfinEmby)
patchLang(&lang.Email, &fallback.Email, &english.Email) patchLang(&lang.Email, &fallback.Email, &english.Email)
patchLang(&lang.Messages, &fallback.Messages, &english.Messages)
patchLang(&lang.Notifications, &fallback.Notifications, &english.Notifications) patchLang(&lang.Notifications, &fallback.Notifications, &english.Notifications)
patchLang(&lang.PasswordResets, &fallback.PasswordResets, &english.PasswordResets) patchLang(&lang.PasswordResets, &fallback.PasswordResets, &english.PasswordResets)
patchLang(&lang.InviteEmails, &fallback.InviteEmails, &english.InviteEmails) patchLang(&lang.InviteEmails, &fallback.InviteEmails, &english.InviteEmails)

View File

@ -42,19 +42,29 @@ class Input {
class Checkbox { class Checkbox {
private _el: HTMLInputElement; private _el: HTMLInputElement;
private _hideEl: HTMLElement;
get value(): string { return this._el.checked ? "true" : "false"; } get value(): string { return this._el.checked ? "true" : "false"; }
set value(v: string) { this._el.checked = (v == "true") ? true : false; } set value(v: string) { this._el.checked = (v == "true") ? true : false; }
private _section: string; private _section: string;
private _setting: string; private _setting: string;
broadcast = () => { broadcast = () => {
let state = this._el.checked;
if (this._hideEl.classList.contains("unfocused")) {
state = false;
}
if (this._section && this._setting) { if (this._section && this._setting) {
const ev = new CustomEvent(`settings-${this._section}-${this._setting}`, { "detail": this._el.checked }) const ev = new CustomEvent(`settings-${this._section}-${this._setting}`, { "detail": state })
document.dispatchEvent(ev); document.dispatchEvent(ev);
} }
} }
set onchange(f: () => void) {
this._el.addEventListener("change", f);
}
constructor(el: HTMLElement, depends?: string, dependsTrue?: boolean, section?: string, setting?: string) { constructor(el: HTMLElement, depends?: string, dependsTrue?: boolean, section?: string, setting?: string) {
this._el = el as HTMLInputElement; this._el = el as HTMLInputElement;
this._hideEl = this._el as HTMLElement;
if (this._hideEl.parentElement.tagName == "LABEL") { this._hideEl = this._hideEl.parentElement; }
if (section && setting) { if (section && setting) {
this._section = section; this._section = section;
this._setting = setting; this._setting = setting;
@ -62,12 +72,12 @@ class Checkbox {
} }
if (depends) { if (depends) {
document.addEventListener(`settings-${section}-${depends}`, (event: boolEvent) => { document.addEventListener(`settings-${section}-${depends}`, (event: boolEvent) => {
let el = this._el as HTMLElement;
if (el.parentElement.tagName == "LABEL") { el = el.parentElement; }
if (event.detail !== dependsTrue) { if (event.detail !== dependsTrue) {
el.classList.add("unfocused"); this._hideEl.classList.add("unfocused");
this.broadcast();
} else { } else {
el.classList.remove("unfocused"); this._hideEl.classList.remove("unfocused");
this.broadcast();
} }
}); });
} }
@ -201,10 +211,11 @@ class LangSelect extends Select {
} }
window.lang = new lang(window.langFile as LangFile); window.lang = new lang(window.langFile as LangFile);
html("language-description", window.lang.var("language", "description", `<a href="https://weblate.hrfee.pw">Weblate</a>`)); html("language-description", window.lang.var("language", "description", `<a target="_blank" href="https://weblate.hrfee.pw">Weblate</a>`));
html("email-description", window.lang.var("email", "description", `<a href="https://mailgun.com">Mailgun</a>`)); html("email-description", window.lang.var("email", "description", `<a target="_blank" href="https://mailgun.com">Mailgun</a>`));
html("email-dateformat-notice", window.lang.var("email", "dateFormatNotice", `<a href="https://strftime.ninja/">strftime.ninja</a>`)); html("email-dateformat-notice", window.lang.var("email", "dateFormatNotice", `<a target="_blank" href="https://strftime.ninja/">strftime.ninja</a>`));
html("updates-description", window.lang.var("updates", "description", `<a href="https://builds.hrfee.dev/view/hrfee/jfa-go">buildrone</a>`)); html("updates-description", window.lang.var("updates", "description", `<a target="_blank" href="https://builds.hrfee.dev/view/hrfee/jfa-go">buildrone</a>`));
html("messages-description", window.lang.var("messages", "description", `<a target="_blank" href="https://github.com/hrfee/jfa-go/wiki">Wiki</a>`));
const settings = { const settings = {
"jellyfin": { "jellyfin": {
@ -243,12 +254,15 @@ const settings = {
"number": new Input(get("password_validation-number"), "", 1, "enabled", true, "password_validation"), "number": new Input(get("password_validation-number"), "", 1, "enabled", true, "password_validation"),
"special": new Input(get("password_validation-special"), "", 0, "enabled", true, "password_validation") "special": new Input(get("password_validation-special"), "", 0, "enabled", true, "password_validation")
}, },
"messages": {
"enabled": new Checkbox(get("messages-enabled"), "", false, "messages", "enabled"),
"use_24h": new BoolRadios("email-24h", "enabled", true, "messages"),
"date_format": new Input(get("email-date_format"), "", "%d/%m/%y", "enabled", true, "messages"),
"message": new Input(get("email-message"), window.messages["messages"]["message"], "", "enabled", true, "messages")
},
"email": { "email": {
"language": new LangSelect("email", get("email-language")), "language": new LangSelect("email", get("email-language")),
"no_username": new Checkbox(get("email-no_username"), "method", true, "email"), "no_username": new Checkbox(get("email-no_username"), "method", true, "email"),
"use_24h": new BoolRadios("email-24h", "method", true, "email"),
"date_format": new Input(get("email-date_format"), "", "%d/%m/%y", "method", true, "email"),
"message": new Input(get("email-message"), window.messages["email"]["message"], "", "method", true, "email"),
"method": new Select(get("email-method"), "", false, "email", "method"), "method": new Select(get("email-method"), "", false, "email", "method"),
"address": new Input(get("email-address"), "jellyfin@jellyf.in", "", "method", true, "email"), "address": new Input(get("email-address"), "jellyfin@jellyf.in", "", "method", true, "email"),
"from": new Input(get("email-from"), "", "Jellyfin", "method", true, "email") "from": new Input(get("email-from"), "", "Jellyfin", "method", true, "email")
@ -258,7 +272,8 @@ const settings = {
"watch_directory": new Input(get("password_resets-watch_directory"), "", "", "enabled", true, "password_resets"), "watch_directory": new Input(get("password_resets-watch_directory"), "", "", "enabled", true, "password_resets"),
"subject": new Input(get("password_resets-subject"), "", "", "enabled", true, "password_resets"), "subject": new Input(get("password_resets-subject"), "", "", "enabled", true, "password_resets"),
"link_reset": new Checkbox(get("password_resets-link_reset"), "enabled", true, "password_resets", "link_reset"), "link_reset": new Checkbox(get("password_resets-link_reset"), "enabled", true, "password_resets", "link_reset"),
"language": new LangSelect("pwr", get("password_resets-language"), "link_reset", true, "password_resets", "language") "language": new LangSelect("pwr", get("password_resets-language"), "link_reset", true, "password_resets", "language"),
"set_password": new Checkbox(get("password_resets-set_password"), "link_reset", true, "password_resets", "set_password")
}, },
"notifications": { "notifications": {
"enabled": new Checkbox(get("notifications-enabled")) "enabled": new Checkbox(get("notifications-enabled"))
@ -342,12 +357,23 @@ const emailMethodChange = () => {
const val = settings["email"]["method"].value; const val = settings["email"]["method"].value;
const smtp = document.getElementById("email-smtp"); const smtp = document.getElementById("email-smtp");
const mailgun = document.getElementById("email-mailgun"); const mailgun = document.getElementById("email-mailgun");
if (val == "smtp") { const emailSect = document.getElementById("email-sect");
smtp.classList.remove("unfocused"); const enabled = settings["messages"]["enabled"].value;
mailgun.classList.add("unfocused"); if (enabled == "false") {
for (let el of relatedToEmail) {
el.classList.add("hidden");
}
emailSect.classList.add("unfocused");
return;
} else {
for (let el of relatedToEmail) { for (let el of relatedToEmail) {
el.classList.remove("hidden"); el.classList.remove("hidden");
} }
emailSect.classList.remove("unfocused");
}
if (val == "smtp") {
smtp.classList.remove("unfocused");
mailgun.classList.add("unfocused");
} else if (val == "mailgun") { } else if (val == "mailgun") {
mailgun.classList.remove("unfocused"); mailgun.classList.remove("unfocused");
smtp.classList.add("unfocused"); smtp.classList.add("unfocused");
@ -357,12 +383,10 @@ const emailMethodChange = () => {
} else { } else {
mailgun.classList.add("unfocused"); mailgun.classList.add("unfocused");
smtp.classList.add("unfocused"); smtp.classList.add("unfocused");
for (let el of relatedToEmail) {
el.classList.add("hidden");
}
} }
}; };
settings["email"]["method"].onchange = emailMethodChange; settings["email"]["method"].onchange = emailMethodChange;
settings["messages"]["enabled"].onchange = emailMethodChange;
emailMethodChange(); emailMethodChange();
const embyHidePWR = () => { const embyHidePWR = () => {