1
0
mirror of https://github.com/hrfee/jfa-go.git synced 2024-12-29 12:30:11 +00:00

Compare commits

..

No commits in common. "6e3d5dac19d929d296f2ad673885759509a791af" and "a5a721b07cd7d8a5fd326de3d5bfd5ca3e311820" have entirely different histories.

6 changed files with 58 additions and 97 deletions

View File

@ -1,8 +1,3 @@
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';
@ -104,34 +99,16 @@ 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');
var jfData = {};
jfData['jfHost'] = document.getElementById('jfHost').value;
jfData['jfUser'] = document.getElementById('jfUser').value;
jfData['jfPassword'] = document.getElementById('jfPassword').value;
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.disabled = true;
testButton.innerHTML = testButton.innerHTML =
'<span class="spinner-border spinner-border-sm" role="status" aria-hidden="true" style="margin-right: 0.5rem;"></span>' + '<span class="spinner-border spinner-border-sm" role="status" aria-hidden="true" style="margin-right: 0.5rem;"></span>' +
'Testing...'; 'Testing...';
nextButton.classList.add('disabled'); nextButton.classList.add('disabled');
nextButton.setAttribute('aria-disabled', 'true'); 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(); var req = new XMLHttpRequest();
req.open("POST", "/testJF", true); req.open("POST", "/testJF", true);
req.setRequestHeader('Content-Type', 'application/json; charset=UTF-8'); req.setRequestHeader('Content-Type', 'application/json; charset=UTF-8');
@ -152,7 +129,6 @@ document.getElementById('jfTestButton').onclick = function() {
}; };
}; };
req.send(JSON.stringify(jfData)); req.send(JSON.stringify(jfData));
}
}; };
document.getElementById('submitButton').onclick = function() { document.getElementById('submitButton').onclick = function() {
@ -186,10 +162,6 @@ 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-sm"> <div class="col">
<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"> <div class="col-md-auto">
<div class="form-group"> <div class="form-group">
<label for="multiUseCount"> <label for="multiUseCount">
Multiple uses Multiple uses

View File

@ -3,10 +3,12 @@
<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="bs5-jf.css"> <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css"> <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/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://cdnjs.cloudflare.com/ajax/libs/jquery/3.4.1/jquery.min.js" integrity="sha256-CSXorXvZcTkaix6Yvo6HppcZGetbYMGWSFlBw8HfCJo=" 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> <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>
<style> <style>
.card-body { .card-body {
width: 100%; width: 100%;
@ -43,7 +45,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 jfa-go. Click below to get started, or quit and edit the config file manually. 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.
</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>
@ -57,7 +59,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">
@ -104,25 +106,20 @@
<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">
jfa-go needs admin access so that it can create users, as this is currently not permitted via API tokens. jellyfin-accounts needs admin access so that it can create users.
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 (For internal use)</label> <label for="jfHost">Host</label>
<input type="url" class="form-control" id="jfHost" placeholder="http://jellyf.in:443" required> <input type="url" class="form-control" id="jfHost" placeholder="http://jellyf.in:443">
</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" required> <input type="text" class="form-control" id="jfUser" placeholder="Username">
</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" required> <input type="password" class="form-control" id="jfPassword" placeholder="Password">
</div> </div>
<div style="margin-top: 1rem;">
<button class="btn btn-secondary" id="jfTestButton">Test</button> <button class="btn btn-secondary" id="jfTestButton">Test</button>
<div class="btn-group float-right" role="group" aria-label="Back/Next buttons"> <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-secondary backButton" href="#page-2">Back</a>
@ -130,11 +127,10 @@
</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">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. <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.
<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">
@ -156,9 +152,9 @@
</div> </div>
</div> </div>
<div id="emailSMTPArea"> <div id="emailSMTPArea">
<div class="form-group form-check form-switch"> <div class="form-group form-check">
<input type="checkbox" class="form-check-input" id="emailSSL_TLS" checked> <input type="checkbox" class="custom-control-input" id="emailSSL_TLS" checked>
<label for="emailSSL_TLS" class="form-check-label" id="emailSSL_TLSLabel">Use SSL/TLS</label> <label for="emailSSL_TLS" class="custom-control-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">
@ -240,7 +236,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. 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. 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.
</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,22 +40,15 @@ 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 {
out := fmt.Sprintf("Failed to authenticate with Jellyfin @ %s: Timed out", jf.server) log.Fatalf("Failed to authenticate with Jellyfin @ %s: Timed out", jf.server)
if jf.noFail {
log.Printf(out)
} else {
log.Fatalf(out)
}
} }
} }
func newJellyfin(server, client, version, device, deviceId string) (*Jellyfin, error) { func (jf *Jellyfin) init(server, client, version, device, deviceId string) error {
jf := &Jellyfin{}
jf.server = server jf.server = server
jf.client = client jf.client = client
jf.version = version jf.version = version
@ -85,7 +78,7 @@ func newJellyfin(server, client, version, device, deviceId string) (*Jellyfin, e
} }
jf.cacheLength = 30 jf.cacheLength = 30
jf.cacheExpiry = time.Now() jf.cacheExpiry = time.Now()
return jf, nil return 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, _ = newJellyfin(server, "jfa-go", app.version, "hrfee-arch", "hrfee-arch") app.jf.init(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, _ = newJellyfin(server, "jfa-go", app.version, "auth", "auth") app.authJf.init(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, _ := newJellyfin(req.Host, "jfa-go-setup", app.version, "auth", "auth") tempjf := Jellyfin{}
tempjf.noFail = true tempjf.init(req.Host, "jfa-go-setup", app.version, "auth", "auth")
_, 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)