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

Compare commits

...

4 Commits

Author SHA1 Message Date
6e3d5dac19
use newJellyfin instead of constructor method 2020-08-30 20:44:10 +01:00
072776c15f
add public_server input to setup 2020-08-30 18:21:53 +01:00
1c980cf7cd
Use bs5-jf on setup, fix bugs
No longer quits if the program times out connecting to the given
jellyfin host.
2020-08-30 18:09:06 +01:00
c6f845296a
fix alignment on setup page, change invite generator column widths 2020-08-30 17:40:18 +01:00
6 changed files with 97 additions and 58 deletions

View File

@ -1,3 +1,8 @@
document.getElementById('page-1').scrollIntoView({
behavior: 'auto',
block: 'center',
inline: 'center' });
function checkAuthRadio() { function checkAuthRadio() {
if (document.getElementById('manualAuthRadio').checked) { if (document.getElementById('manualAuthRadio').checked) {
document.getElementById('adminOnlyArea').style.display = 'none'; document.getElementById('adminOnlyArea').style.display = 'none';
@ -99,36 +104,55 @@ var jfValid = false
document.getElementById('jfTestButton').onclick = function() { document.getElementById('jfTestButton').onclick = function() {
var testButton = document.getElementById('jfTestButton'); var testButton = document.getElementById('jfTestButton');
var nextButton = document.getElementById('jfNextButton'); var nextButton = document.getElementById('jfNextButton');
testButton.disabled = true;
testButton.innerHTML =
'<span class="spinner-border spinner-border-sm" role="status" aria-hidden="true" style="margin-right: 0.5rem;"></span>' +
'Testing...';
nextButton.classList.add('disabled');
nextButton.setAttribute('aria-disabled', 'true');
var jfData = {}; var jfData = {};
jfData['jfHost'] = document.getElementById('jfHost').value; jfData['jfHost'] = document.getElementById('jfHost').value;
jfData['jfUser'] = document.getElementById('jfUser').value; jfData['jfUser'] = document.getElementById('jfUser').value;
jfData['jfPassword'] = document.getElementById('jfPassword').value; jfData['jfPassword'] = document.getElementById('jfPassword').value;
var req = new XMLHttpRequest(); let valid = true;
req.open("POST", "/testJF", true); for (val in jfData) {
req.setRequestHeader('Content-Type', 'application/json; charset=UTF-8'); if (jfData[val] == "") {
req.responseType = 'json'; valid = false;
req.onreadystatechange = function() { }
if (this.readyState == 4) { }
testButton.disabled = false; if (!valid) {
testButton.className = ''; if (!testButton.classList.contains('btn-danger')) {
if (this.response['success'] == true) { testButton.classList.add('btn-danger');
testButton.classList.add('btn', 'btn-success'); testButton.textContent = 'Fill out fields above.';
testButton.textContent = 'Success'; setTimeout(function() {
nextButton.classList.remove('disabled'); if (testButton.classList.contains('btn-danger')) {
nextButton.setAttribute('aria-disabled', 'false'); testButton.classList.remove('btn-danger');
} else { testButton.textContent = 'Test';
testButton.classList.add('btn', 'btn-danger'); }
testButton.textContent = 'Failed'; }, 2000);
}
} else {
testButton.disabled = true;
testButton.innerHTML =
'<span class="spinner-border spinner-border-sm" role="status" aria-hidden="true" style="margin-right: 0.5rem;"></span>' +
'Testing...';
nextButton.classList.add('disabled');
nextButton.setAttribute('aria-disabled', 'true');
var req = new XMLHttpRequest();
req.open("POST", "/testJF", true);
req.setRequestHeader('Content-Type', 'application/json; charset=UTF-8');
req.responseType = 'json';
req.onreadystatechange = function() {
if (this.readyState == 4) {
testButton.disabled = false;
testButton.className = '';
if (this.response['success'] == true) {
testButton.classList.add('btn', 'btn-success');
testButton.textContent = 'Success';
nextButton.classList.remove('disabled');
nextButton.setAttribute('aria-disabled', 'false');
} else {
testButton.classList.add('btn', 'btn-danger');
testButton.textContent = 'Failed';
};
}; };
}; };
}; req.send(JSON.stringify(jfData));
req.send(JSON.stringify(jfData)); }
}; };
document.getElementById('submitButton').onclick = function() { document.getElementById('submitButton').onclick = function() {
@ -162,6 +186,10 @@ document.getElementById('submitButton').onclick = function() {
}; };
// Page 3: Connect to jellyfin // Page 3: Connect to jellyfin
config['jellyfin']['server'] = document.getElementById('jfHost').value; config['jellyfin']['server'] = document.getElementById('jfHost').value;
let publicAddress = document.getElementById('jfPublicHost').value;
if (publicAddress != "") {
config['jellyfin']['public_server'] = publicAddress;
}
config['jellyfin']['username'] = document.getElementById('jfUser').value; config['jellyfin']['username'] = document.getElementById('jfUser').value;
config['jellyfin']['password'] = document.getElementById('jfPassword').value; config['jellyfin']['password'] = document.getElementById('jfPassword').value;
// Page 4: Email (Page 5, 6, 7 are only used if this is enabled) // Page 4: Email (Page 5, 6, 7 are only used if this is enabled)

View File

@ -279,7 +279,7 @@
<div class="card-body"> <div class="card-body">
<form action="#" method="POST" id="inviteForm" class="container"> <form action="#" method="POST" id="inviteForm" class="container">
<div class="row align-items-start"> <div class="row align-items-start">
<div class="col"> <div class="col-sm">
<div class="form-group"> <div class="form-group">
<label for="days">Days</label> <label for="days">Days</label>
<select class="form-control form-select" id="days" name="days"> <select class="form-control form-select" id="days" name="days">
@ -296,7 +296,7 @@
</select> </select>
</div> </div>
</div> </div>
<div class="col-md-auto"> <div class="col">
<div class="form-group"> <div class="form-group">
<label for="multiUseCount"> <label for="multiUseCount">
Multiple uses Multiple uses

View File

@ -3,12 +3,10 @@
<head> <head>
<meta charset="utf-8"> <meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no"> <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css"> <link rel="stylesheet" href="bs5-jf.css">
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/font-awesome/4.7.0/css/font-awesome.min.css"> <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css">
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.4.1/jquery.min.js" integrity="sha256-CSXorXvZcTkaix6Yvo6HppcZGetbYMGWSFlBw8HfCJo=" crossorigin="anonymous"></script> <script src="https://cdn.jsdelivr.net/npm/popper.js@1.16.0/dist/umd/popper.min.js" integrity="sha384-Q6E9RHvbIyZFJoft+2mJbHaEWldlvI9IOYy5n3zV9zzTtmI3UksdQRVvoxMfooAo" crossorigin="anonymous"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.7/umd/popper.min.js"></script> <script src="https://stackpath.bootstrapcdn.com/bootstrap/5.0.0-alpha1/js/bootstrap.min.js" integrity="sha384-oesi62hOLfzrys4LxRF63OJCXdXDipiYWBnvTl9Y9/TRlw5xlKIEHpNyvvDShgf/" crossorigin="anonymous"></script>
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/js/bootstrap.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery-serialize-object/2.5.0/jquery.serialize-object.min.js" integrity="sha256-E8KRdFk/LTaaCBoQIV/rFNc0s3ICQQiOHFT4Cioifa8=" crossorigin="anonymous"></script>
<style> <style>
.card-body { .card-body {
width: 100%; width: 100%;
@ -45,7 +43,7 @@
<div class="card-body"> <div class="card-body">
<h5 class="card-title">Welcome!</h5> <h5 class="card-title">Welcome!</h5>
<p class="card-text"> <p class="card-text">
You'll need to do a few things to start using jellyfin-accounts. Click below to get started, or quit and edit the config file manually. You'll need to do a few things to start using jfa-go. Click below to get started, or quit and edit the config file manually.
</p> </p>
<a class="btn btn-primary nextButton" href="#page-2">Get Started</a> <a class="btn btn-primary nextButton" href="#page-2">Get Started</a>
</div> </div>
@ -59,7 +57,7 @@
<p class="card-text"> <p class="card-text">
To access the admin page, you'll need to login. Choose how below. To access the admin page, you'll need to login. Choose how below.
<ul> <ul>
<li><b>Authorize through Jellyfin: </b>Checks credentials with jellyfin, allowing you to share login details and grant multiple users access.</li> <li><b>Authorize through Jellyfin: </b>Checks credentials with Jellyfin, allowing you to share login details and grant multiple users access.</li>
<li><b>Username & Password: </b>Set your own username and password manually.</li> <li><b>Username & Password: </b>Set your own username and password manually.</li>
</ul> </ul>
<div class="form-check" id="jfAuthFormGroup"> <div class="form-check" id="jfAuthFormGroup">
@ -106,31 +104,37 @@
<div class="card-body"> <div class="card-body">
<h5 class="card-title">Jellyfin</h5> <h5 class="card-title">Jellyfin</h5>
<p class="card-text"> <p class="card-text">
jellyfin-accounts needs admin access so that it can create users. jfa-go needs admin access so that it can create users, as this is currently not permitted via API tokens.
You should create a separate account for it, checking 'Allow this user to manage the server'. You can disable everything else. Once done, enter the credentials here. You should create a separate account for it, checking 'Allow this user to manage the server'. You can disable everything else. Once done, enter the credentials here.
<div class="form-group"> <div class="form-group">
<label for="jfHost">Host</label> <label for="jfHost">Host (For internal use)</label>
<input type="url" class="form-control" id="jfHost" placeholder="http://jellyf.in:443"> <input type="url" class="form-control" id="jfHost" placeholder="http://jellyf.in:443" required>
</div>
<div class="form-group">
<label for="jfPublicHost">Public Host (For access by users)</label>
<input type="url" class="form-control" id="jfPublicHost" placeholder="Leave blank to use the above address.">
</div> </div>
<div class="form-group"> <div class="form-group">
<label for="jfUser">Username</label> <label for="jfUser">Username</label>
<input type="text" class="form-control" id="jfUser" placeholder="Username"> <input type="text" class="form-control" id="jfUser" placeholder="Username" required>
</div> </div>
<div class="form-group"> <div class="form-group">
<label for="jfPassword">Password</label> <label for="jfPassword">Password</label>
<input type="password" class="form-control" id="jfPassword" placeholder="Password"> <input type="password" class="form-control" id="jfPassword" placeholder="Password" required>
</div> </div>
<button class="btn btn-secondary" id="jfTestButton">Test</button> <div style="margin-top: 1rem;">
<div class="btn-group float-right" role="group" aria-label="Back/Next buttons"> <button class="btn btn-secondary" id="jfTestButton">Test</button>
<a class="btn btn-secondary backButton" href="#page-2">Back</a> <div class="btn-group float-right" role="group" aria-label="Back/Next buttons">
<a class="btn btn-primary nextButton disabled" id="jfNextButton" aria-disabled="true" href="#page-4">Next</a> <a class="btn btn-secondary backButton" href="#page-2">Back</a>
<a class="btn btn-primary nextButton disabled" id="jfNextButton" aria-disabled="true" href="#page-4">Next</a>
</div>
</div> </div>
</div> </div>
</div> </div>
<div class="slide card" id="page-4"> <div class="slide card" id="page-4">
<div class="card-body"> <div class="card-body">
<h5 class="card-title">Email</h5> <h5 class="card-title">Email</h5>
<p class="card-text">jellyfin-accounts is capable of sending a PIN code when a user tries to reset their password on Jellyfin. One can also choose to send an invite code directly to an email address. This can be done through SMTP or through <a href="https://www.mailgun.com/">Mailgun's</a> API. <p class="card-text">jfa-go is capable of sending a PIN code when a user tries to reset their password on Jellyfin. One can also choose to send an invite code directly to an email address. This can be done through SMTP or through <a href="https://www.mailgun.com/">Mailgun's</a> API.
<div class="form-group"> <div class="form-group">
<div class="form-check" id="emailDisabled"> <div class="form-check" id="emailDisabled">
<input class="form-check-input" type="radio" name="email" id="emailDisabledRadio" value="emailDisabled"> <input class="form-check-input" type="radio" name="email" id="emailDisabledRadio" value="emailDisabled">
@ -152,9 +156,9 @@
</div> </div>
</div> </div>
<div id="emailSMTPArea"> <div id="emailSMTPArea">
<div class="form-group form-check"> <div class="form-group form-check form-switch">
<input type="checkbox" class="custom-control-input" id="emailSSL_TLS" checked> <input type="checkbox" class="form-check-input" id="emailSSL_TLS" checked>
<label for="emailSSL_TLS" class="custom-control-label" id="emailSSL_TLSLabel">Use SSL/TLS</label> <label for="emailSSL_TLS" class="form-check-label" id="emailSSL_TLSLabel">Use SSL/TLS</label>
<small class="form-text text-muted">Note: SSL/TLS usually uses port 465, whereas STARTTLS usually uses 587.</small> <small class="form-text text-muted">Note: SSL/TLS usually uses port 465, whereas STARTTLS usually uses 587.</small>
</div> </div>
<div class="form-group form-row"> <div class="form-group form-row">
@ -236,7 +240,7 @@
<div class="card-body"> <div class="card-body">
<h5 class="card-title">Password Resets</h5> <h5 class="card-title">Password Resets</h5>
<p class="card-text"> <p class="card-text">
When a user tries to reset their password in jellyfin, it informs them that a file has been created, named "passwordreset*.json" where * is a number. jellyfin-accounts will then read this file, and send the PIN to the user's email. Try it now, and put the folder that it informs you it put the file in below. Also, if enter a custom email subject if you don't like the default one. When a user tries to reset their password in jellyfin, it informs them that a file has been created, named "passwordreset*.json" where * is a number. jfa-go will then read this file, and send the PIN to the user's email. Try it now, and put the folder that it informs you it put the file in below. Also, if enter a custom email subject if you don't like the default one.
</p> </p>
<div class="form-group form-check"> <div class="form-group form-check">
<input type="checkbox" class="form-check-input" id="pwrEnabled" value="enabled"> <input type="checkbox" class="form-check-input" id="pwrEnabled" value="enabled">

View File

@ -40,15 +40,22 @@ type Jellyfin struct {
userCache []map[string]interface{} userCache []map[string]interface{}
cacheExpiry time.Time cacheExpiry time.Time
cacheLength int cacheLength int
noFail bool
} }
func (jf *Jellyfin) timeoutHandler() { func (jf *Jellyfin) timeoutHandler() {
if r := recover(); r != nil { if r := recover(); r != nil {
log.Fatalf("Failed to authenticate with Jellyfin @ %s: Timed out", jf.server) out := fmt.Sprintf("Failed to authenticate with Jellyfin @ %s: Timed out", jf.server)
if jf.noFail {
log.Printf(out)
} else {
log.Fatalf(out)
}
} }
} }
func (jf *Jellyfin) init(server, client, version, device, deviceId string) error { func newJellyfin(server, client, version, device, deviceId string) (*Jellyfin, error) {
jf := &Jellyfin{}
jf.server = server jf.server = server
jf.client = client jf.client = client
jf.version = version jf.version = version
@ -78,7 +85,7 @@ func (jf *Jellyfin) init(server, client, version, device, deviceId string) error
} }
jf.cacheLength = 30 jf.cacheLength = 30
jf.cacheExpiry = time.Now() jf.cacheExpiry = time.Now()
return nil return jf, nil
} }
func (jf *Jellyfin) authenticate(username, password string) (map[string]interface{}, int, error) { func (jf *Jellyfin) authenticate(username, password string) (map[string]interface{}, int, error) {

View File

@ -43,8 +43,8 @@ type appContext struct {
jellyfinLogin bool jellyfinLogin bool
users []User users []User
invalidTokens []string invalidTokens []string
jf Jellyfin jf *Jellyfin
authJf Jellyfin authJf *Jellyfin
datePattern string datePattern string
timePattern string timePattern string
storage Storage storage Storage
@ -266,14 +266,14 @@ func main() {
} }
server := app.config.Section("jellyfin").Key("server").String() server := app.config.Section("jellyfin").Key("server").String()
app.jf.init(server, "jfa-go", app.version, "hrfee-arch", "hrfee-arch") app.jf, _ = newJellyfin(server, "jfa-go", app.version, "hrfee-arch", "hrfee-arch")
var status int var status int
_, status, err = app.jf.authenticate(app.config.Section("jellyfin").Key("username").String(), app.config.Section("jellyfin").Key("password").String()) _, status, err = app.jf.authenticate(app.config.Section("jellyfin").Key("username").String(), app.config.Section("jellyfin").Key("password").String())
if status != 200 || err != nil { if status != 200 || err != nil {
app.err.Fatalf("Failed to authenticate with Jellyfin @ %s: Code %d", server, status) app.err.Fatalf("Failed to authenticate with Jellyfin @ %s: Code %d", server, status)
} }
app.info.Printf("Authenticated with %s", server) app.info.Printf("Authenticated with %s", server)
app.authJf.init(server, "jfa-go", app.version, "auth", "auth") app.authJf, _ = newJellyfin(server, "jfa-go", app.version, "auth", "auth")
app.loadStrftime() app.loadStrftime()

View File

@ -13,8 +13,8 @@ type testReq struct {
func (app *appContext) TestJF(gc *gin.Context) { func (app *appContext) TestJF(gc *gin.Context) {
var req testReq var req testReq
gc.BindJSON(&req) gc.BindJSON(&req)
tempjf := Jellyfin{} tempjf, _ := newJellyfin(req.Host, "jfa-go-setup", app.version, "auth", "auth")
tempjf.init(req.Host, "jfa-go-setup", app.version, "auth", "auth") tempjf.noFail = true
_, status, err := tempjf.authenticate(req.Username, req.Password) _, status, err := tempjf.authenticate(req.Username, req.Password)
if !(status == 200 || status == 204) || err != nil { if !(status == 200 || status == 204) || err != nil {
app.info.Printf("Auth failed with code %d (%s)", status, err) app.info.Printf("Auth failed with code %d (%s)", status, err)