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.
6e3d5dac19
...
a5a721b07c
@ -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,55 +99,36 @@ 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;
|
||||||
let valid = true;
|
var req = new XMLHttpRequest();
|
||||||
for (val in jfData) {
|
req.open("POST", "/testJF", true);
|
||||||
if (jfData[val] == "") {
|
req.setRequestHeader('Content-Type', 'application/json; charset=UTF-8');
|
||||||
valid = false;
|
req.responseType = 'json';
|
||||||
}
|
req.onreadystatechange = function() {
|
||||||
}
|
if (this.readyState == 4) {
|
||||||
if (!valid) {
|
testButton.disabled = false;
|
||||||
if (!testButton.classList.contains('btn-danger')) {
|
testButton.className = '';
|
||||||
testButton.classList.add('btn-danger');
|
if (this.response['success'] == true) {
|
||||||
testButton.textContent = 'Fill out fields above.';
|
testButton.classList.add('btn', 'btn-success');
|
||||||
setTimeout(function() {
|
testButton.textContent = 'Success';
|
||||||
if (testButton.classList.contains('btn-danger')) {
|
nextButton.classList.remove('disabled');
|
||||||
testButton.classList.remove('btn-danger');
|
nextButton.setAttribute('aria-disabled', 'false');
|
||||||
testButton.textContent = 'Test';
|
} else {
|
||||||
}
|
testButton.classList.add('btn', 'btn-danger');
|
||||||
}, 2000);
|
testButton.textContent = 'Failed';
|
||||||
}
|
|
||||||
} 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() {
|
||||||
@ -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)
|
||||||
|
@ -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
|
||||||
|
@ -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,37 +106,31 @@
|
|||||||
<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>
|
<a class="btn btn-primary nextButton disabled" id="jfNextButton" aria-disabled="true" href="#page-4">Next</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">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">
|
||||||
|
13
jfapi.go
13
jfapi.go
@ -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) {
|
||||||
|
8
main.go
8
main.go
@ -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()
|
||||||
|
|
||||||
|
4
setup.go
4
setup.go
@ -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)
|
||||||
|
Loading…
Reference in New Issue
Block a user