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

Compare commits

...

3 Commits

Author SHA1 Message Date
d7fcfe9416
mention unstable docker branch 2020-09-15 12:23:43 +01:00
258656fbf9
version based on current tag for makefile
if there isnt a tag, version is 'git'. this fixes versioning for aur package and docker.
2020-09-15 12:12:51 +01:00
500ecac95d
add issue template 2020-09-15 12:00:20 +01:00
7 changed files with 77 additions and 25 deletions

33
.github/ISSUE_TEMPLATE/bug_report.md vendored Normal file
View File

@ -0,0 +1,33 @@
---
name: Bug report
about: Template for bug reports.
title: ''
labels: ''
assignees: ''
---
#### Read the [FAQ](https://github.com/hrfee/jfa-go/wiki/FAQ) first!
**Describe the bug**
Describe the problem, and what you would expect if it isn't clear already.
**To Reproduce**
What to do to reproduce the problem.
**Logs**
When you notice the problem, check the output of `jfa-go`. If the problem is not obvious (e.g a panic (red text) or 'ERROR' log), re-run jfa-go with the `-debug` argument and reproduce the problem. You should then take a screenshot of the output, or paste it here, preferably between \`\`\` tags (e.g \`\`\``Log here`\`\`\`). Remember to censor any personal information.
If nothing catches your eye in the log, access the admin page via your browser, go into the console (Right click > Inspect Element > Console), refresh, reproduce the problem then paste the output here in the same way as above.
**Configuration**
If you see it as necessary, include relevant sections of your `config.ini`, for example, include `[email]` and `[smtp]|[mailgun]` if you have an email issue.
**Platform/Version**
Include the platform jfa-go is running on (e.g Windows, Linux, Docker), the version (first line of output by `jfa-go` or Settings>About in web UI), and if necessary the browser version and platform.

View File

@ -29,7 +29,7 @@ mail:
python3 mail/generate.py python3 mail/generate.py
version: version:
python3 version.py git version.go python3 version.py auto version.go
compile: compile:
echo "Downloading deps" echo "Downloading deps"

View File

@ -50,7 +50,7 @@ docker create \
-v /path/to/.config/jfa-go:/data \ # Path to wherever you want to store the config file and other data -v /path/to/.config/jfa-go:/data \ # Path to wherever you want to store the config file and other data
-v /path/to/jellyfin:/jf \ # Path to jellyfin config directory -v /path/to/jellyfin:/jf \ # Path to jellyfin config directory
-v /etc/localtime:/etc/localtime:ro \ # Makes sure time is correct -v /etc/localtime:/etc/localtime:ro \ # Makes sure time is correct
hrfee/jfa-go hrfee/jfa-go # hrfee/jfa-go:unstable for latest build from git
``` ```
#### Build from source #### Build from source

View File

@ -20,6 +20,7 @@ type emailClient interface {
send(address, fromName, fromAddr string, email *Email) error send(address, fromName, fromAddr string, email *Email) error
} }
// Mailgun client implements emailClient.
type Mailgun struct { type Mailgun struct {
client *mailgun.MailgunImpl client *mailgun.MailgunImpl
} }
@ -38,14 +39,15 @@ func (mg *Mailgun) send(address, fromName, fromAddr string, email *Email) error
return err return err
} }
type Smtp struct { // SMTP supports SSL/TLS and STARTTLS; implements emailClient.
sslTls bool type SMTP struct {
sslTLS bool
host, server string host, server string
port int port int
auth smtp.Auth auth smtp.Auth
} }
func (sm *Smtp) send(address, fromName, fromAddr string, email *Email) error { func (sm *SMTP) send(address, fromName, fromAddr string, email *Email) error {
e := jEmail.NewEmail() e := jEmail.NewEmail()
e.Subject = email.subject e.Subject = email.subject
e.From = fmt.Sprintf("%s <%s>", fromName, fromAddr) e.From = fmt.Sprintf("%s <%s>", fromName, fromAddr)
@ -58,7 +60,7 @@ func (sm *Smtp) send(address, fromName, fromAddr string, email *Email) error {
} }
server := fmt.Sprintf("%s:%d", sm.server, sm.port) server := fmt.Sprintf("%s:%d", sm.server, sm.port)
var err error var err error
if sm.sslTls { if sm.sslTLS {
err = e.SendWithTLS(server, sm.auth, tlsConfig) err = e.SendWithTLS(server, sm.auth, tlsConfig)
} else { } else {
err = e.SendWithStartTLS(server, sm.auth, tlsConfig) err = e.SendWithStartTLS(server, sm.auth, tlsConfig)
@ -78,27 +80,28 @@ type Email struct {
html, text string html, text string
} }
func (email *Emailer) formatExpiry(expiry time.Time, tzaware bool, datePattern, timePattern string) (d, t, expires_in string) { func (emailer *Emailer) formatExpiry(expiry time.Time, tzaware bool, datePattern, timePattern string) (d, t, expiresIn string) {
d, _ = strtime.Strftime(expiry, datePattern) d, _ = strtime.Strftime(expiry, datePattern)
t, _ = strtime.Strftime(expiry, timePattern) t, _ = strtime.Strftime(expiry, timePattern)
current_time := time.Now() currentTime := time.Now()
if tzaware { if tzaware {
current_time = current_time.UTC() currentTime = currentTime.UTC()
} }
_, _, days, hours, minutes, _ := timeDiff(expiry, current_time) _, _, days, hours, minutes, _ := timeDiff(expiry, currentTime)
if days != 0 { if days != 0 {
expires_in += fmt.Sprintf("%dd ", days) expiresIn += fmt.Sprintf("%dd ", days)
} }
if hours != 0 { if hours != 0 {
expires_in += fmt.Sprintf("%dh ", hours) expiresIn += fmt.Sprintf("%dh ", hours)
} }
if minutes != 0 { if minutes != 0 {
expires_in += fmt.Sprintf("%dm ", minutes) expiresIn += fmt.Sprintf("%dm ", minutes)
} }
expires_in = strings.TrimSuffix(expires_in, " ") expiresIn = strings.TrimSuffix(expiresIn, " ")
return return
} }
// NewEmailer configures and returns a new emailer.
func NewEmailer(app *appContext) *Emailer { func NewEmailer(app *appContext) *Emailer {
emailer := &Emailer{ emailer := &Emailer{
fromAddr: app.config.Section("email").Key("address").String(), fromAddr: app.config.Section("email").Key("address").String(),
@ -117,6 +120,7 @@ func NewEmailer(app *appContext) *Emailer {
return emailer return emailer
} }
// NewMailgun returns a Mailgun emailClient.
func (emailer *Emailer) NewMailgun(url, key string) { func (emailer *Emailer) NewMailgun(url, key string) {
sender := &Mailgun{ sender := &Mailgun{
client: mailgun.NewMailgun(strings.Split(emailer.fromAddr, "@")[1], key), client: mailgun.NewMailgun(strings.Split(emailer.fromAddr, "@")[1], key),
@ -130,13 +134,14 @@ func (emailer *Emailer) NewMailgun(url, key string) {
emailer.sender = sender emailer.sender = sender
} }
func (emailer *Emailer) NewSMTP(server string, port int, password, host string, sslTls bool) { // NewSMTP returns an SMTP emailClient.
emailer.sender = &Smtp{ func (emailer *Emailer) NewSMTP(server string, port int, password, host string, sslTLS bool) {
emailer.sender = &SMTP{
auth: smtp.PlainAuth("", emailer.fromAddr, password, host), auth: smtp.PlainAuth("", emailer.fromAddr, password, host),
server: server, server: server,
host: host, host: host,
port: port, port: port,
sslTls: sslTls, sslTLS: sslTLS,
} }
} }
@ -145,10 +150,10 @@ func (emailer *Emailer) constructInvite(code string, invite Invite, app *appCont
subject: app.config.Section("invite_emails").Key("subject").String(), subject: app.config.Section("invite_emails").Key("subject").String(),
} }
expiry := invite.ValidTill expiry := invite.ValidTill
d, t, expires_in := emailer.formatExpiry(expiry, false, app.datePattern, app.timePattern) d, t, expiresIn := emailer.formatExpiry(expiry, false, app.datePattern, app.timePattern)
message := app.config.Section("email").Key("message").String() message := app.config.Section("email").Key("message").String()
invite_link := app.config.Section("invite_emails").Key("url_base").String() inviteLink := app.config.Section("invite_emails").Key("url_base").String()
invite_link = fmt.Sprintf("%s/%s", invite_link, code) inviteLink = fmt.Sprintf("%s/%s", inviteLink, code)
for _, key := range []string{"html", "text"} { for _, key := range []string{"html", "text"} {
fpath := app.config.Section("invite_emails").Key("email_" + key).String() fpath := app.config.Section("invite_emails").Key("email_" + key).String()
@ -160,8 +165,8 @@ func (emailer *Emailer) constructInvite(code string, invite Invite, app *appCont
err = tpl.Execute(&tplData, map[string]string{ err = tpl.Execute(&tplData, map[string]string{
"expiry_date": d, "expiry_date": d,
"expiry_time": t, "expiry_time": t,
"expires_in": expires_in, "expires_in": expiresIn,
"invite_link": invite_link, "invite_link": inviteLink,
"message": message, "message": message,
}) })
if err != nil { if err != nil {
@ -244,7 +249,7 @@ func (emailer *Emailer) constructReset(pwr Pwr, app *appContext) (*Email, error)
email := &Email{ email := &Email{
subject: app.config.Section("password_resets").Key("subject").MustString("Password reset - Jellyfin"), subject: app.config.Section("password_resets").Key("subject").MustString("Password reset - Jellyfin"),
} }
d, t, expires_in := emailer.formatExpiry(pwr.Expiry, true, app.datePattern, app.timePattern) d, t, expiresIn := emailer.formatExpiry(pwr.Expiry, true, app.datePattern, app.timePattern)
message := app.config.Section("email").Key("message").String() message := app.config.Section("email").Key("message").String()
for _, key := range []string{"html", "text"} { for _, key := range []string{"html", "text"} {
fpath := app.config.Section("password_resets").Key("email_" + key).String() fpath := app.config.Section("password_resets").Key("email_" + key).String()
@ -257,7 +262,7 @@ func (emailer *Emailer) constructReset(pwr Pwr, app *appContext) (*Email, error)
"username": pwr.Username, "username": pwr.Username,
"expiry_date": d, "expiry_date": d,
"expiry_time": t, "expiry_time": t,
"expires_in": expires_in, "expires_in": expiresIn,
"pin": pwr.Pin, "pin": pwr.Pin,
"message": message, "message": message,
}) })
@ -273,6 +278,7 @@ func (emailer *Emailer) constructReset(pwr Pwr, app *appContext) (*Email, error)
return email, nil return email, nil
} }
// calls the send method in the underlying emailClient.
func (emailer *Emailer) send(address string, email *Email) error { func (emailer *Emailer) send(address string, email *Email) error {
return emailer.sender.send(address, emailer.fromName, emailer.fromAddr, email) return emailer.sender.send(address, emailer.fromName, emailer.fromAddr, email)
} }

View File

@ -32,6 +32,7 @@ func newOmbi(server, key string, noFail bool) *Ombi {
} }
} }
// does a GET and returns the response as an io.reader.
func (ombi *Ombi) _getReader(url string, params map[string]string) (string, int, error) { func (ombi *Ombi) _getReader(url string, params map[string]string) (string, int, error) {
if ombi.key == "" { if ombi.key == "" {
return "", 401, fmt.Errorf("No API key provided") return "", 401, fmt.Errorf("No API key provided")
@ -70,6 +71,7 @@ func (ombi *Ombi) _getReader(url string, params map[string]string) (string, int,
return buf.String(), resp.StatusCode, nil return buf.String(), resp.StatusCode, nil
} }
// does a POST and optionally returns response as string. Returns a string instead of an io.reader bcs i couldn't get it working otherwise.
func (ombi *Ombi) _post(url string, data map[string]interface{}, response bool) (string, int, error) { func (ombi *Ombi) _post(url string, data map[string]interface{}, response bool) (string, int, error) {
responseText := "" responseText := ""
params, _ := json.Marshal(data) params, _ := json.Marshal(data)
@ -105,12 +107,14 @@ func (ombi *Ombi) _post(url string, data map[string]interface{}, response bool)
return responseText, resp.StatusCode, nil return responseText, resp.StatusCode, nil
} }
// gets an ombi user by their ID.
func (ombi *Ombi) userByID(id string) (result map[string]interface{}, code int, err error) { func (ombi *Ombi) userByID(id string) (result map[string]interface{}, code int, err error) {
resp, code, err := ombi._getReader(fmt.Sprintf("%s/api/v1/Identity/User/%s", ombi.server, id), nil) resp, code, err := ombi._getReader(fmt.Sprintf("%s/api/v1/Identity/User/%s", ombi.server, id), nil)
json.Unmarshal([]byte(resp), &result) json.Unmarshal([]byte(resp), &result)
return return
} }
// gets a list of all users.
func (ombi *Ombi) getUsers() (result []map[string]interface{}, code int, err error) { func (ombi *Ombi) getUsers() (result []map[string]interface{}, code int, err error) {
resp, code, err := ombi._getReader(fmt.Sprintf("%s/api/v1/Identity/Users", ombi.server), nil) resp, code, err := ombi._getReader(fmt.Sprintf("%s/api/v1/Identity/Users", ombi.server), nil)
json.Unmarshal([]byte(resp), &result) json.Unmarshal([]byte(resp), &result)
@ -129,6 +133,7 @@ var stripFromOmbi = []string{
"userName", "userName",
} }
// returns a template based on the user corresponding to the provided ID's settings.
func (ombi *Ombi) templateByID(id string) (result map[string]interface{}, code int, err error) { func (ombi *Ombi) templateByID(id string) (result map[string]interface{}, code int, err error) {
result, code, err = ombi.userByID(id) result, code, err = ombi.userByID(id)
if err != nil || code != 200 { if err != nil || code != 200 {
@ -147,6 +152,7 @@ func (ombi *Ombi) templateByID(id string) (result map[string]interface{}, code i
return return
} }
// creates a new user.
func (ombi *Ombi) newUser(username, password, email string, template map[string]interface{}) ([]string, int, error) { func (ombi *Ombi) newUser(username, password, email string, template map[string]interface{}) ([]string, int, error) {
url := fmt.Sprintf("%s/api/v1/Identity", ombi.server) url := fmt.Sprintf("%s/api/v1/Identity", ombi.server)
user := template user := template

View File

@ -58,7 +58,7 @@ func pwrMonitor(app *appContext, watcher *fsnotify.Watcher) {
return return
} }
app.info.Printf("New password reset for user \"%s\"", pwr.Username) app.info.Printf("New password reset for user \"%s\"", pwr.Username)
if ct := time.Now(); pwr.Expiry.After(ct) { if currentTime := time.Now(); pwr.Expiry.After(currentTime) {
user, status, err := app.jf.userByName(pwr.Username, false) user, status, err := app.jf.userByName(pwr.Username, false)
if !(status == 200 || status == 204) || err != nil { if !(status == 200 || status == 204) || err != nil {
app.err.Printf("Failed to get users from Jellyfin: Code %d", status) app.err.Printf("Failed to get users from Jellyfin: Code %d", status)

View File

@ -6,6 +6,13 @@ try:
except IndexError: except IndexError:
version = "git" version = "git"
if version == "auto":
try:
version = subprocess.check_output("git describe --exact-match HEAD".split()).decode("utf-8").rstrip().replace('v', '')
except subprocess.CalledProcessError as e:
if e.returncode == 128:
version = "git"
commit = subprocess.check_output("git rev-parse --short HEAD".split()).decode("utf-8").rstrip() commit = subprocess.check_output("git rev-parse --short HEAD".split()).decode("utf-8").rstrip()
file = f'package main; const VERSION = "{version}"; const COMMIT = "{commit}";' file = f'package main; const VERSION = "{version}"; const COMMIT = "{commit}";'