1
0
mirror of https://github.com/hrfee/jfa-go.git synced 2024-12-26 19:10: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() {
if (document.getElementById('manualAuthRadio').checked) {
document.getElementById('adminOnlyArea').style.display = 'none';
@ -99,36 +104,55 @@ var jfValid = false
document.getElementById('jfTestButton').onclick = function() {
var testButton = document.getElementById('jfTestButton');
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 = {};
jfData['jfHost'] = document.getElementById('jfHost').value;
jfData['jfUser'] = document.getElementById('jfUser').value;
jfData['jfPassword'] = document.getElementById('jfPassword').value;
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';
let valid = true;
for (val in jfData) {
if (jfData[val] == "") {
valid = false;
}
}
if (!valid) {
if (!testButton.classList.contains('btn-danger')) {
testButton.classList.add('btn-danger');
testButton.textContent = 'Fill out fields above.';
setTimeout(function() {
if (testButton.classList.contains('btn-danger')) {
testButton.classList.remove('btn-danger');
testButton.textContent = 'Test';
}
}, 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() {
@ -162,6 +186,10 @@ document.getElementById('submitButton').onclick = function() {
};
// Page 3: Connect to jellyfin
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']['password'] = document.getElementById('jfPassword').value;
// Page 4: Email (Page 5, 6, 7 are only used if this is enabled)

View File

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

View File

@ -3,12 +3,10 @@
<head>
<meta charset="utf-8">
<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="https://maxcdn.bootstrapcdn.com/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://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.7/umd/popper.min.js"></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>
<link rel="stylesheet" href="bs5-jf.css">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css">
<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://stackpath.bootstrapcdn.com/bootstrap/5.0.0-alpha1/js/bootstrap.min.js" integrity="sha384-oesi62hOLfzrys4LxRF63OJCXdXDipiYWBnvTl9Y9/TRlw5xlKIEHpNyvvDShgf/" crossorigin="anonymous"></script>
<style>
.card-body {
width: 100%;
@ -45,7 +43,7 @@
<div class="card-body">
<h5 class="card-title">Welcome!</h5>
<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>
<a class="btn btn-primary nextButton" href="#page-2">Get Started</a>
</div>
@ -59,7 +57,7 @@
<p class="card-text">
To access the admin page, you'll need to login. Choose how below.
<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>
</ul>
<div class="form-check" id="jfAuthFormGroup">
@ -106,31 +104,37 @@
<div class="card-body">
<h5 class="card-title">Jellyfin</h5>
<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.
<div class="form-group">
<label for="jfHost">Host</label>
<input type="url" class="form-control" id="jfHost" placeholder="http://jellyf.in:443">
<label for="jfHost">Host (For internal use)</label>
<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 class="form-group">
<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 class="form-group">
<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>
<button class="btn btn-secondary" id="jfTestButton">Test</button>
<div class="btn-group float-right" role="group" aria-label="Back/Next buttons">
<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 style="margin-top: 1rem;">
<button class="btn btn-secondary" id="jfTestButton">Test</button>
<div class="btn-group float-right" role="group" aria-label="Back/Next buttons">
<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 class="slide card" id="page-4">
<div class="card-body">
<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-check" id="emailDisabled">
<input class="form-check-input" type="radio" name="email" id="emailDisabledRadio" value="emailDisabled">
@ -152,9 +156,9 @@
</div>
</div>
<div id="emailSMTPArea">
<div class="form-group form-check">
<input type="checkbox" class="custom-control-input" id="emailSSL_TLS" checked>
<label for="emailSSL_TLS" class="custom-control-label" id="emailSSL_TLSLabel">Use SSL/TLS</label>
<div class="form-group form-check form-switch">
<input type="checkbox" class="form-check-input" id="emailSSL_TLS" checked>
<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>
</div>
<div class="form-group form-row">
@ -236,7 +240,7 @@
<div class="card-body">
<h5 class="card-title">Password Resets</h5>
<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>
<div class="form-group form-check">
<input type="checkbox" class="form-check-input" id="pwrEnabled" value="enabled">

View File

@ -40,15 +40,22 @@ type Jellyfin struct {
userCache []map[string]interface{}
cacheExpiry time.Time
cacheLength int
noFail bool
}
func (jf *Jellyfin) timeoutHandler() {
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.client = client
jf.version = version
@ -78,7 +85,7 @@ func (jf *Jellyfin) init(server, client, version, device, deviceId string) error
}
jf.cacheLength = 30
jf.cacheExpiry = time.Now()
return nil
return jf, nil
}
func (jf *Jellyfin) authenticate(username, password string) (map[string]interface{}, int, error) {

View File

@ -43,8 +43,8 @@ type appContext struct {
jellyfinLogin bool
users []User
invalidTokens []string
jf Jellyfin
authJf Jellyfin
jf *Jellyfin
authJf *Jellyfin
datePattern string
timePattern string
storage Storage
@ -266,14 +266,14 @@ func main() {
}
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
_, status, err = app.jf.authenticate(app.config.Section("jellyfin").Key("username").String(), app.config.Section("jellyfin").Key("password").String())
if status != 200 || err != nil {
app.err.Fatalf("Failed to authenticate with Jellyfin @ %s: Code %d", server, status)
}
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()

View File

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