diff --git a/README.md b/README.md index e5df7f6..464379e 100644 --- a/README.md +++ b/README.md @@ -47,3 +47,6 @@ need to be up for this, making this ideal for some offline maintenance. - `purgeInvites`: Deletes all available invite codes. - `listAccounts`: Lists all active accounts. - `deleteAccount `: Deletes an account with a given `username`. +- `lockAccount `: Locks the account under `username`. +- `unlockAccount `: Unlocks the account under `username`. +- `logs`: Shows system logs. diff --git a/admin/accounthttp.go b/admin/accounthttp.go index 125abdc..945a507 100644 --- a/admin/accounthttp.go +++ b/admin/accounthttp.go @@ -1,17 +1,17 @@ package admin import ( - "database/sql" - "fmt" - "net/http" - "net/url" - "os" + "database/sql" + "fmt" + "net/http" + "net/url" + "os" - "arimelody-web/controller" - "arimelody-web/log" - "arimelody-web/model" + "arimelody-web/controller" + "arimelody-web/log" + "arimelody-web/model" - "golang.org/x/crypto/bcrypt" + "golang.org/x/crypto/bcrypt" ) func accountHandler(app *model.AppState) http.Handler { diff --git a/admin/artisthttp.go b/admin/artisthttp.go index 6dfbbfd..9fa6bb2 100644 --- a/admin/artisthttp.go +++ b/admin/artisthttp.go @@ -1,9 +1,9 @@ package admin import ( - "fmt" - "net/http" - "strings" + "fmt" + "net/http" + "strings" "arimelody-web/model" "arimelody-web/controller" diff --git a/admin/http.go b/admin/http.go index bdacad3..76eb5f2 100644 --- a/admin/http.go +++ b/admin/http.go @@ -1,20 +1,20 @@ package admin import ( - "context" - "database/sql" - "fmt" - "net/http" - "os" - "path/filepath" - "strings" - "time" + "context" + "database/sql" + "fmt" + "net/http" + "os" + "path/filepath" + "strings" + "time" - "arimelody-web/controller" - "arimelody-web/log" - "arimelody-web/model" + "arimelody-web/controller" + "arimelody-web/log" + "arimelody-web/model" - "golang.org/x/crypto/bcrypt" + "golang.org/x/crypto/bcrypt" ) func Handler(app *model.AppState) http.Handler { @@ -274,20 +274,20 @@ func loginHandler(app *model.AppState) http.Handler { render() return } - if account.Locked { - controller.SetSessionError(app.DB, session, "This account is locked.") - render() - return - } + if account.Locked { + controller.SetSessionError(app.DB, session, "This account is locked.") + render() + return + } err = bcrypt.CompareHashAndPassword([]byte(account.Password), []byte(password)) if err != nil { app.Log.Warn(log.TYPE_ACCOUNT, "\"%s\" attempted login with incorrect password. (%s)", account.Username, controller.ResolveIP(app, r)) - if locked := handleFailedLogin(app, account); locked { - controller.SetSessionError(app.DB, session, "Too many failed attempts. This account is now locked.") - } else { - controller.SetSessionError(app.DB, session, "Invalid username or password.") - } + if locked := handleFailedLogin(app, account); locked { + controller.SetSessionError(app.DB, session, "Too many failed attempts. This account is now locked.") + } else { + controller.SetSessionError(app.DB, session, "Invalid username or password.") + } render() return } @@ -308,8 +308,8 @@ func loginHandler(app *model.AppState) http.Handler { render() return } - controller.SetSessionMessage(app.DB, session, "") - controller.SetSessionError(app.DB, session, "") + controller.SetSessionMessage(app.DB, session, "") + controller.SetSessionError(app.DB, session, "") http.Redirect(w, r, "/admin/totp", http.StatusFound) return } @@ -389,13 +389,13 @@ func loginTOTPHandler(app *model.AppState) http.Handler { } if totpMethod == nil { app.Log.Warn(log.TYPE_ACCOUNT, "\"%s\" failed login (Incorrect TOTP). (%s)", session.AttemptAccount.Username, controller.ResolveIP(app, r)) - if locked := handleFailedLogin(app, session.AttemptAccount); locked { - controller.SetSessionError(app.DB, session, "Too many failed attempts. This account is now locked.") - controller.SetSessionAttemptAccount(app.DB, session, nil) - http.Redirect(w, r, "/admin", http.StatusFound) - } else { - controller.SetSessionError(app.DB, session, "Incorrect TOTP.") - } + if locked := handleFailedLogin(app, session.AttemptAccount); locked { + controller.SetSessionError(app.DB, session, "Too many failed attempts. This account is now locked.") + controller.SetSessionAttemptAccount(app.DB, session, nil) + http.Redirect(w, r, "/admin", http.StatusFound) + } else { + controller.SetSessionError(app.DB, session, "Incorrect TOTP.") + } render() return } @@ -515,27 +515,27 @@ func enforceSession(app *model.AppState, next http.Handler) http.Handler { } func handleFailedLogin(app *model.AppState, account *model.Account) bool { - locked, err := controller.IncrementAccountFails(app.DB, account.ID) - if err != nil { - fmt.Fprintf( - os.Stderr, - "WARN: Failed to increment login failures for \"%s\": %v\n", - account.Username, - err, - ) - app.Log.Warn( - log.TYPE_ACCOUNT, - "Failed to increment login failures for \"%s\"", - account.Username, - ) - } - if locked { - app.Log.Warn( - log.TYPE_ACCOUNT, - "Account \"%s\" was locked: %d failed login attempts", - account.Username, - model.MAX_LOGIN_FAIL_ATTEMPTS, - ) - } - return locked + locked, err := controller.IncrementAccountFails(app.DB, account.ID) + if err != nil { + fmt.Fprintf( + os.Stderr, + "WARN: Failed to increment login failures for \"%s\": %v\n", + account.Username, + err, + ) + app.Log.Warn( + log.TYPE_ACCOUNT, + "Failed to increment login failures for \"%s\"", + account.Username, + ) + } + if locked { + app.Log.Warn( + log.TYPE_ACCOUNT, + "Account \"%s\" was locked: %d failed login attempts", + account.Username, + model.MAX_LOGIN_FAIL_ATTEMPTS, + ) + } + return locked } diff --git a/admin/logshttp.go b/admin/logshttp.go index 93dc5b7..7249b16 100644 --- a/admin/logshttp.go +++ b/admin/logshttp.go @@ -1,12 +1,12 @@ package admin import ( - "arimelody-web/log" - "arimelody-web/model" - "fmt" - "net/http" - "os" - "strings" + "arimelody-web/log" + "arimelody-web/model" + "fmt" + "net/http" + "os" + "strings" ) func logsHandler(app *model.AppState) http.Handler { diff --git a/admin/releasehttp.go b/admin/releasehttp.go index 7ef4d37..c6b68ab 100644 --- a/admin/releasehttp.go +++ b/admin/releasehttp.go @@ -1,12 +1,12 @@ package admin import ( - "fmt" - "net/http" - "strings" + "fmt" + "net/http" + "strings" - "arimelody-web/controller" - "arimelody-web/model" + "arimelody-web/controller" + "arimelody-web/model" ) func serveRelease(app *model.AppState) http.Handler { diff --git a/admin/templates.go b/admin/templates.go index 12cdf08..606d569 100644 --- a/admin/templates.go +++ b/admin/templates.go @@ -1,12 +1,12 @@ package admin import ( - "arimelody-web/log" - "fmt" - "html/template" - "path/filepath" - "strings" - "time" + "arimelody-web/log" + "fmt" + "html/template" + "path/filepath" + "strings" + "time" ) var indexTemplate = template.Must(template.ParseFiles( diff --git a/admin/trackhttp.go b/admin/trackhttp.go index a92f81a..93eacdb 100644 --- a/admin/trackhttp.go +++ b/admin/trackhttp.go @@ -1,9 +1,9 @@ package admin import ( - "fmt" - "net/http" - "strings" + "fmt" + "net/http" + "strings" "arimelody-web/model" "arimelody-web/controller" diff --git a/api/api.go b/api/api.go index d3c83ce..398db4b 100644 --- a/api/api.go +++ b/api/api.go @@ -1,15 +1,15 @@ package api import ( - "context" - "errors" - "fmt" - "net/http" - "os" - "strings" + "context" + "errors" + "fmt" + "net/http" + "os" + "strings" - "arimelody-web/controller" - "arimelody-web/model" + "arimelody-web/controller" + "arimelody-web/model" ) func Handler(app *model.AppState) http.Handler { diff --git a/api/artist.go b/api/artist.go index 9006cc3..01899a6 100644 --- a/api/artist.go +++ b/api/artist.go @@ -1,66 +1,66 @@ package api import ( - "encoding/json" - "fmt" - "io/fs" - "net/http" - "os" - "path/filepath" - "strings" - "time" + "encoding/json" + "fmt" + "io/fs" + "net/http" + "os" + "path/filepath" + "strings" + "time" - "arimelody-web/controller" - "arimelody-web/log" - "arimelody-web/model" + "arimelody-web/controller" + "arimelody-web/log" + "arimelody-web/model" ) func ServeAllArtists(app *model.AppState) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { var artists = []*model.Artist{} artists, err := controller.GetAllArtists(app.DB) - if err != nil { + if err != nil { fmt.Printf("WARN: Failed to serve all artists: %s\n", err) - http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) - return - } + http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) + return + } - w.Header().Add("Content-Type", "application/json") + w.Header().Add("Content-Type", "application/json") encoder := json.NewEncoder(w) encoder.SetIndent("", "\t") err = encoder.Encode(artists) - if err != nil { - http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) - } + if err != nil { + http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) + } }) } func ServeArtist(app *model.AppState, artist *model.Artist) http.Handler { - return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - type ( - creditJSON struct { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + type ( + creditJSON struct { ID string `json:"id"` Title string `json:"title"` ReleaseDate time.Time `json:"releaseDate" db:"release_date"` Artwork string `json:"artwork"` - Role string `json:"role"` - Primary bool `json:"primary"` - } - artistJSON struct { - *model.Artist - Credits map[string]creditJSON `json:"credits"` - } - ) + Role string `json:"role"` + Primary bool `json:"primary"` + } + artistJSON struct { + *model.Artist + Credits map[string]creditJSON `json:"credits"` + } + ) session := r.Context().Value("session").(*model.Session) show_hidden_releases := session != nil && session.Account != nil dbCredits, err := controller.GetArtistCredits(app.DB, artist.ID, show_hidden_releases) - if err != nil { + if err != nil { fmt.Printf("WARN: Failed to retrieve artist credits for %s: %v\n", artist.ID, err) - http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) - return - } + http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) + return + } var credits = map[string]creditJSON{} for _, credit := range dbCredits { @@ -74,17 +74,17 @@ func ServeArtist(app *model.AppState, artist *model.Artist) http.Handler { } } - w.Header().Add("Content-Type", "application/json") + w.Header().Add("Content-Type", "application/json") encoder := json.NewEncoder(w) encoder.SetIndent("", "\t") err = encoder.Encode(artistJSON{ Artist: artist, Credits: credits, }) - if err != nil { - http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) - } - }) + if err != nil { + http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) + } + }) } func CreateArtist(app *model.AppState) http.Handler { diff --git a/api/release.go b/api/release.go index 5cb87b0..e07f0d7 100644 --- a/api/release.go +++ b/api/release.go @@ -1,22 +1,22 @@ package api import ( - "encoding/json" - "fmt" - "io/fs" - "net/http" - "os" - "path/filepath" - "strings" - "time" + "encoding/json" + "fmt" + "io/fs" + "net/http" + "os" + "path/filepath" + "strings" + "time" - "arimelody-web/controller" - "arimelody-web/log" - "arimelody-web/model" + "arimelody-web/controller" + "arimelody-web/log" + "arimelody-web/model" ) func ServeRelease(app *model.AppState, release *model.Release) http.Handler { - return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { // only allow authorised users to view hidden releases privileged := false if !release.Visible { @@ -116,15 +116,15 @@ func ServeRelease(app *model.AppState, release *model.Release) http.Handler { } } - w.Header().Add("Content-Type", "application/json") + w.Header().Add("Content-Type", "application/json") encoder := json.NewEncoder(w) encoder.SetIndent("", "\t") err := encoder.Encode(response) - if err != nil { - http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) - return - } - }) + if err != nil { + http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) + return + } + }) } func ServeCatalog(app *model.AppState) http.Handler { diff --git a/api/track.go b/api/track.go index e7d7c07..4e48418 100644 --- a/api/track.go +++ b/api/track.go @@ -1,13 +1,13 @@ package api import ( - "encoding/json" - "fmt" - "net/http" + "encoding/json" + "fmt" + "net/http" - "arimelody-web/controller" - "arimelody-web/log" - "arimelody-web/model" + "arimelody-web/controller" + "arimelody-web/log" + "arimelody-web/model" ) type ( @@ -29,7 +29,7 @@ func ServeAllTracks(app *model.AppState) http.Handler { dbTracks, err := controller.GetAllTracks(app.DB) if err != nil { fmt.Printf("WARN: Failed to pull tracks from DB: %s\n", err) - http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) + http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) } for _, track := range dbTracks { @@ -39,23 +39,23 @@ func ServeAllTracks(app *model.AppState) http.Handler { }) } - w.Header().Add("Content-Type", "application/json") + w.Header().Add("Content-Type", "application/json") encoder := json.NewEncoder(w) encoder.SetIndent("", "\t") err = encoder.Encode(tracks) - if err != nil { + if err != nil { fmt.Printf("WARN: Failed to serve all tracks: %s\n", err) - http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) - } + http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) + } }) } func ServeTrack(app *model.AppState, track *model.Track) http.Handler { - return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { dbReleases, err := controller.GetTrackReleases(app.DB, track.ID, false) if err != nil { fmt.Printf("WARN: Failed to pull track releases for %s from DB: %s\n", track.ID, err) - http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) + http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) } releases := []string{} @@ -63,15 +63,15 @@ func ServeTrack(app *model.AppState, track *model.Track) http.Handler { releases = append(releases, release.ID) } - w.Header().Add("Content-Type", "application/json") + w.Header().Add("Content-Type", "application/json") encoder := json.NewEncoder(w) encoder.SetIndent("", "\t") err = encoder.Encode(Track{ track, releases }) - if err != nil { + if err != nil { fmt.Printf("WARN: Failed to serve track %s: %s\n", track.ID, err) - http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) - } - }) + http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) + } + }) } func CreateTrack(app *model.AppState) http.Handler { diff --git a/api/uploads.go b/api/uploads.go index 60ab7dd..4678f22 100644 --- a/api/uploads.go +++ b/api/uploads.go @@ -1,56 +1,56 @@ package api import ( - "arimelody-web/log" - "arimelody-web/model" - "bufio" - "encoding/base64" - "errors" - "fmt" - "os" - "path/filepath" - "strings" + "arimelody-web/log" + "arimelody-web/model" + "bufio" + "encoding/base64" + "errors" + "fmt" + "os" + "path/filepath" + "strings" ) func HandleImageUpload(app *model.AppState, data *string, directory string, filename string) (string, error) { - split := strings.Split(*data, ";base64,") - header := split[0] - imageData, err := base64.StdEncoding.DecodeString(split[1]) - ext, _ := strings.CutPrefix(header, "data:image/") + split := strings.Split(*data, ";base64,") + header := split[0] + imageData, err := base64.StdEncoding.DecodeString(split[1]) + ext, _ := strings.CutPrefix(header, "data:image/") directory = filepath.Join(app.Config.DataDirectory, directory) - switch ext { - case "png": - case "jpg": - case "jpeg": - default: - return "", errors.New("Invalid image type. Allowed: .png, .jpg, .jpeg") - } + switch ext { + case "png": + case "jpg": + case "jpeg": + default: + return "", errors.New("Invalid image type. Allowed: .png, .jpg, .jpeg") + } filename = fmt.Sprintf("%s.%s", filename, ext) - // ensure directory exists - os.MkdirAll(directory, os.ModePerm) + // ensure directory exists + os.MkdirAll(directory, os.ModePerm) - imagePath := filepath.Join(directory, filename) - file, err := os.Create(imagePath) - if err != nil { - return "", err - } - defer file.Close() + imagePath := filepath.Join(directory, filename) + file, err := os.Create(imagePath) + if err != nil { + return "", err + } + defer file.Close() // TODO: generate compressed versions of image (512x512?) - buffer := bufio.NewWriter(file) - _, err = buffer.Write(imageData) - if err != nil { + buffer := bufio.NewWriter(file) + _, err = buffer.Write(imageData) + if err != nil { return "", nil - } + } - if err := buffer.Flush(); err != nil { + if err := buffer.Flush(); err != nil { return "", nil - } + } app.Log.Info(log.TYPE_FILES, "\"%s/%s.%s\" created.", directory, filename, ext) - return filename, nil + return filename, nil } diff --git a/controller/account.go b/controller/account.go index e4f5dc4..ab64ca5 100644 --- a/controller/account.go +++ b/controller/account.go @@ -1,10 +1,10 @@ package controller import ( - "arimelody-web/model" - "strings" + "arimelody-web/model" + "strings" - "github.com/jmoiron/sqlx" + "github.com/jmoiron/sqlx" ) func GetAllAccounts(db *sqlx.DB) ([]model.Account, error) { @@ -112,24 +112,24 @@ func DeleteAccount(db *sqlx.DB, accountID string) error { } func IncrementAccountFails(db *sqlx.DB, accountID string) (bool, error) { - failAttempts := 0 - err := db.Get(&failAttempts, "UPDATE account SET fail_attempts = fail_attempts + 1 WHERE id=$1 RETURNING fail_attempts", accountID) - if err != nil { return false, err } - locked := false - if failAttempts >= model.MAX_LOGIN_FAIL_ATTEMPTS { - err = LockAccount(db, accountID) - if err != nil { return false, err } - locked = true - } - return locked, err + failAttempts := 0 + err := db.Get(&failAttempts, "UPDATE account SET fail_attempts = fail_attempts + 1 WHERE id=$1 RETURNING fail_attempts", accountID) + if err != nil { return false, err } + locked := false + if failAttempts >= model.MAX_LOGIN_FAIL_ATTEMPTS { + err = LockAccount(db, accountID) + if err != nil { return false, err } + locked = true + } + return locked, err } func LockAccount(db *sqlx.DB, accountID string) error { - _, err := db.Exec("UPDATE account SET locked = true WHERE id=$1", accountID) - return err + _, err := db.Exec("UPDATE account SET locked = true WHERE id=$1", accountID) + return err } func UnlockAccount(db *sqlx.DB, accountID string) error { - _, err := db.Exec("UPDATE account SET locked = false, fail_attempts = 0 WHERE id=$1", accountID) - return err + _, err := db.Exec("UPDATE account SET locked = false, fail_attempts = 0 WHERE id=$1", accountID) + return err } diff --git a/controller/artist.go b/controller/artist.go index 1a613aa..f086778 100644 --- a/controller/artist.go +++ b/controller/artist.go @@ -1,48 +1,48 @@ package controller import ( - "arimelody-web/model" + "arimelody-web/model" - "github.com/jmoiron/sqlx" + "github.com/jmoiron/sqlx" ) // DATABASE func GetArtist(db *sqlx.DB, id string) (*model.Artist, error) { - var artist = model.Artist{} + var artist = model.Artist{} - err := db.Get(&artist, "SELECT * FROM artist WHERE id=$1", id) - if err != nil { - return nil, err - } + err := db.Get(&artist, "SELECT * FROM artist WHERE id=$1", id) + if err != nil { + return nil, err + } - return &artist, nil + return &artist, nil } func GetAllArtists(db *sqlx.DB) ([]*model.Artist, error) { - var artists = []*model.Artist{} + var artists = []*model.Artist{} - err := db.Select(&artists, "SELECT * FROM artist") - if err != nil { - return nil, err - } + err := db.Select(&artists, "SELECT * FROM artist") + if err != nil { + return nil, err + } - return artists, nil + return artists, nil } func GetArtistsNotOnRelease(db *sqlx.DB, releaseID string) ([]*model.Artist, error) { - var artists = []*model.Artist{} + var artists = []*model.Artist{} - err := db.Select(&artists, + err := db.Select(&artists, "SELECT * FROM artist "+ "WHERE id NOT IN "+ "(SELECT artist FROM musiccredit WHERE release=$1)", releaseID) - if err != nil { - return nil, err - } + if err != nil { + return nil, err + } - return artists, nil + return artists, nil } func GetArtistCredits(db *sqlx.DB, artistID string, show_hidden bool) ([]*model.Credit, error) { @@ -54,9 +54,9 @@ func GetArtistCredits(db *sqlx.DB, artistID string, show_hidden bool) ([]*model. if !show_hidden { query += "AND visible=true " } query += "ORDER BY release_date DESC" rows, err := db.Query(query, artistID) - if err != nil { - return nil, err - } + if err != nil { + return nil, err + } defer rows.Close() type NamePrimary struct { @@ -102,13 +102,13 @@ func GetArtistCredits(db *sqlx.DB, artistID string, show_hidden bool) ([]*model. func CreateArtist(db *sqlx.DB, artist *model.Artist) error { _, err := db.Exec( - "INSERT INTO artist (id, name, website, avatar) "+ + "INSERT INTO artist (id, name, website, avatar) "+ "VALUES ($1, $2, $3, $4)", - artist.ID, - artist.Name, - artist.Website, + artist.ID, + artist.Name, + artist.Website, artist.Avatar, - ) + ) if err != nil { return err } diff --git a/controller/config.go b/controller/config.go index 5a69d49..1d3cbbb 100644 --- a/controller/config.go +++ b/controller/config.go @@ -1,14 +1,14 @@ package controller import ( - "errors" - "fmt" - "os" - "strconv" + "errors" + "fmt" + "os" + "strconv" - "arimelody-web/model" + "arimelody-web/model" - "github.com/pelletier/go-toml/v2" + "github.com/pelletier/go-toml/v2" ) func GetConfig() model.Config { diff --git a/controller/invite.go b/controller/invite.go index f30db64..a7bde40 100644 --- a/controller/invite.go +++ b/controller/invite.go @@ -1,12 +1,12 @@ package controller import ( - "arimelody-web/model" - "math/rand" - "strings" - "time" + "arimelody-web/model" + "math/rand" + "strings" + "time" - "github.com/jmoiron/sqlx" + "github.com/jmoiron/sqlx" ) var inviteChars = []byte("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789") diff --git a/controller/ip.go b/controller/ip.go index 233d76a..cbc3054 100644 --- a/controller/ip.go +++ b/controller/ip.go @@ -1,10 +1,10 @@ package controller import ( - "arimelody-web/model" - "net/http" - "slices" - "strings" + "arimelody-web/model" + "net/http" + "slices" + "strings" ) // Returns the request's original IP address, resolving the `x-forwarded-for` diff --git a/controller/migrator.go b/controller/migrator.go index b970a1b..4b99b9c 100644 --- a/controller/migrator.go +++ b/controller/migrator.go @@ -1,11 +1,11 @@ package controller import ( - "fmt" - "os" - "time" + "fmt" + "os" + "time" - "github.com/jmoiron/sqlx" + "github.com/jmoiron/sqlx" ) const DB_VERSION int = 4 diff --git a/controller/qr.go b/controller/qr.go index 7ada0f8..6b04e69 100644 --- a/controller/qr.go +++ b/controller/qr.go @@ -1,13 +1,13 @@ package controller import ( - "bytes" - "encoding/base64" - "errors" - "fmt" - "image" - "image/color" - "image/png" + "bytes" + "encoding/base64" + "errors" + "fmt" + "image" + "image/color" + "image/png" "github.com/skip2/go-qrcode" ) diff --git a/controller/release.go b/controller/release.go index 362669a..3dcad26 100644 --- a/controller/release.go +++ b/controller/release.go @@ -1,12 +1,12 @@ package controller import ( - "errors" - "fmt" + "errors" + "fmt" - "arimelody-web/model" + "arimelody-web/model" - "github.com/jmoiron/sqlx" + "github.com/jmoiron/sqlx" ) func GetRelease(db *sqlx.DB, id string, full bool) (*model.Release, error) { diff --git a/controller/session.go b/controller/session.go index dce7ad0..b037575 100644 --- a/controller/session.go +++ b/controller/session.go @@ -1,17 +1,17 @@ package controller import ( - "database/sql" - "errors" - "fmt" - "net/http" - "strings" - "time" + "database/sql" + "errors" + "fmt" + "net/http" + "strings" + "time" - "arimelody-web/log" - "arimelody-web/model" + "arimelody-web/log" + "arimelody-web/model" - "github.com/jmoiron/sqlx" + "github.com/jmoiron/sqlx" ) const TOKEN_LEN = 64 @@ -33,19 +33,19 @@ func GetSessionFromRequest(app *model.AppState, r *http.Request) (*model.Session } if session != nil { - if session.UserAgent != r.UserAgent() { - msg := "Session user agent mismatch. A cookie may have been hijacked!" - if session.Account != nil { - account, _ := GetAccountByID(app.DB, session.Account.ID) - msg += " (Account \"" + account.Username + "\")" - } - app.Log.Warn(log.TYPE_ACCOUNT, msg) - err = DeleteSession(app.DB, session.Token) - if err != nil { - app.Log.Warn(log.TYPE_ACCOUNT, "Failed to delete affected session") - } - return nil, nil - } + if session.UserAgent != r.UserAgent() { + msg := "Session user agent mismatch. A cookie may have been hijacked!" + if session.Account != nil { + account, _ := GetAccountByID(app.DB, session.Account.ID) + msg += " (Account \"" + account.Username + "\")" + } + app.Log.Warn(log.TYPE_ACCOUNT, msg) + err = DeleteSession(app.DB, session.Token) + if err != nil { + app.Log.Warn(log.TYPE_ACCOUNT, "Failed to delete affected session") + } + return nil, nil + } } } diff --git a/controller/totp.go b/controller/totp.go index 88f6bc3..076d3a1 100644 --- a/controller/totp.go +++ b/controller/totp.go @@ -1,20 +1,20 @@ package controller import ( - "arimelody-web/model" - "crypto/hmac" - "crypto/rand" - "crypto/sha1" - "encoding/base32" - "encoding/binary" - "fmt" - "math" - "net/url" - "os" - "strings" - "time" + "arimelody-web/model" + "crypto/hmac" + "crypto/rand" + "crypto/sha1" + "encoding/base32" + "encoding/binary" + "fmt" + "math" + "net/url" + "os" + "strings" + "time" - "github.com/jmoiron/sqlx" + "github.com/jmoiron/sqlx" ) const TOTP_SECRET_LENGTH = 32 diff --git a/controller/track.go b/controller/track.go index fa4efc1..ee7581c 100644 --- a/controller/track.go +++ b/controller/track.go @@ -1,9 +1,9 @@ package controller import ( - "arimelody-web/model" + "arimelody-web/model" - "github.com/jmoiron/sqlx" + "github.com/jmoiron/sqlx" ) // DATABASE @@ -13,19 +13,19 @@ func GetTrack(db *sqlx.DB, id string) (*model.Track, error) { stmt, _ := db.Preparex("SELECT * FROM musictrack WHERE id=$1") err := stmt.Get(&track, id) - if err != nil { + if err != nil { return nil, err - } + } return &track, nil } func GetAllTracks(db *sqlx.DB) ([]*model.Track, error) { var tracks = []*model.Track{} - err := db.Select(&tracks, "SELECT * FROM musictrack") - if err != nil { + err := db.Select(&tracks, "SELECT * FROM musictrack") + if err != nil { return nil, err - } + } return tracks, nil } @@ -33,33 +33,33 @@ func GetAllTracks(db *sqlx.DB) ([]*model.Track, error) { func GetOrphanTracks(db *sqlx.DB) ([]*model.Track, error) { var tracks = []*model.Track{} - err := db.Select(&tracks, "SELECT * FROM musictrack WHERE id NOT IN (SELECT track FROM musicreleasetrack)") - if err != nil { + err := db.Select(&tracks, "SELECT * FROM musictrack WHERE id NOT IN (SELECT track FROM musicreleasetrack)") + if err != nil { return nil, err - } + } return tracks, nil } func GetTracksNotOnRelease(db *sqlx.DB, releaseID string) ([]*model.Track, error) { - var tracks = []*model.Track{} + var tracks = []*model.Track{} - err := db.Select(&tracks, + err := db.Select(&tracks, "SELECT * FROM musictrack "+ "WHERE id NOT IN "+ "(SELECT track FROM musicreleasetrack WHERE release=$1)", releaseID) - if err != nil { - return nil, err - } + if err != nil { + return nil, err + } - return tracks, nil + return tracks, nil } func GetTrackReleases(db *sqlx.DB, trackID string, full bool) ([]*model.Release, error) { var releases = []*model.Release{} - err := db.Select(&releases, + err := db.Select(&releases, "SELECT id,title,type,release_date,artwork,buylink "+ "FROM musicrelease "+ "JOIN musicreleasetrack ON release=id "+ @@ -67,9 +67,9 @@ func GetTrackReleases(db *sqlx.DB, trackID string, full bool) ([]*model.Release, "ORDER BY release_date", trackID, ) - if err != nil { + if err != nil { return nil, err - } + } type NamePrimary struct { Name string `json:"name"` @@ -114,14 +114,14 @@ func GetTrackReleases(db *sqlx.DB, trackID string, full bool) ([]*model.Release, func PullOrphanTracks(db *sqlx.DB) ([]*model.Track, error) { var tracks = []*model.Track{} - err := db.Select(&tracks, + err := db.Select(&tracks, "SELECT id, title, description, lyrics, preview_url FROM musictrack "+ "WHERE id NOT IN "+ "(SELECT track FROM musicreleasetrack)", ) - if err != nil { + if err != nil { return nil, err - } + } return tracks, nil } diff --git a/cursor/cursor.go b/cursor/cursor.go index 4ed59e3..56edb56 100644 --- a/cursor/cursor.go +++ b/cursor/cursor.go @@ -1,16 +1,16 @@ package cursor import ( - "arimelody-web/model" - "fmt" - "math/rand" - "net/http" - "strconv" - "strings" - "sync" - "time" + "arimelody-web/model" + "fmt" + "math/rand" + "net/http" + "strconv" + "strings" + "sync" + "time" - "github.com/gorilla/websocket" + "github.com/gorilla/websocket" ) type CursorClient struct { diff --git a/discord/discord.go b/discord/discord.go index d46f32d..0eb9b97 100644 --- a/discord/discord.go +++ b/discord/discord.go @@ -1,13 +1,13 @@ package discord import ( - "arimelody-web/model" - "encoding/json" - "errors" - "fmt" - "net/http" - "net/url" - "strings" + "arimelody-web/model" + "encoding/json" + "errors" + "fmt" + "net/http" + "net/url" + "strings" ) const API_ENDPOINT = "https://discord.com/api/v10" diff --git a/log/log.go b/log/log.go index 2d1c0c2..88d328b 100644 --- a/log/log.go +++ b/log/log.go @@ -1,11 +1,11 @@ package log import ( - "fmt" - "os" - "time" + "fmt" + "os" + "time" - "github.com/jmoiron/sqlx" + "github.com/jmoiron/sqlx" ) type ( diff --git a/main.go b/main.go index 360f7ac..23c7dc4 100644 --- a/main.go +++ b/main.go @@ -1,33 +1,33 @@ package main import ( - "bufio" - "errors" - "fmt" - stdLog "log" - "math" - "math/rand" - "net" - "net/http" - "os" - "path/filepath" - "strconv" - "strings" - "time" + "bufio" + "errors" + "fmt" + stdLog "log" + "math" + "math/rand" + "net" + "net/http" + "os" + "path/filepath" + "strconv" + "strings" + "time" - "arimelody-web/admin" - "arimelody-web/api" - "arimelody-web/colour" - "arimelody-web/controller" - "arimelody-web/cursor" - "arimelody-web/log" - "arimelody-web/model" - "arimelody-web/templates" - "arimelody-web/view" + "arimelody-web/admin" + "arimelody-web/api" + "arimelody-web/colour" + "arimelody-web/controller" + "arimelody-web/cursor" + "arimelody-web/log" + "arimelody-web/model" + "arimelody-web/templates" + "arimelody-web/view" - "github.com/jmoiron/sqlx" - _ "github.com/lib/pq" - "golang.org/x/crypto/bcrypt" + "github.com/jmoiron/sqlx" + _ "github.com/lib/pq" + "golang.org/x/crypto/bcrypt" ) // used for database migrations @@ -282,7 +282,7 @@ func main() { account.ID, email, account.CreatedAt, - account.Locked, + account.Locked, ) } return @@ -450,8 +450,9 @@ func main() { "purgeInvites:\n\tDeletes all available invite codes.\n" + "listAccounts:\n\tLists all active accounts.\n", "deleteAccount :\n\tDeletes the account under `username`.\n", + "lockAccount :\n\tLocks the account under `username`.\n", "unlockAccount :\n\tUnlocks the account under `username`.\n", - "logs:\n\tShows system logs.\n", + "logs:\n\tShows system logs.\n", ) return } diff --git a/model/account.go b/model/account.go index ad65b82..67424b7 100644 --- a/model/account.go +++ b/model/account.go @@ -1,23 +1,23 @@ package model import ( - "database/sql" - "time" + "database/sql" + "time" ) const COOKIE_TOKEN string = "AM_SESSION" const MAX_LOGIN_FAIL_ATTEMPTS int = 3 type ( - Account struct { - ID string `json:"id" db:"id"` - Username string `json:"username" db:"username"` - Password string `json:"password" db:"password"` - Email sql.NullString `json:"email" db:"email"` - AvatarURL sql.NullString `json:"avatar_url" db:"avatar_url"` - CreatedAt time.Time `json:"created_at" db:"created_at"` - FailAttempts int `json:"fail_attempts" db:"fail_attempts"` - Locked bool `json:"locked" db:"locked"` + Account struct { + ID string `json:"id" db:"id"` + Username string `json:"username" db:"username"` + Password string `json:"password" db:"password"` + Email sql.NullString `json:"email" db:"email"` + AvatarURL sql.NullString `json:"avatar_url" db:"avatar_url"` + CreatedAt time.Time `json:"created_at" db:"created_at"` + FailAttempts int `json:"fail_attempts" db:"fail_attempts"` + Locked bool `json:"locked" db:"locked"` Privileges []AccountPrivilege `json:"privileges"` } diff --git a/model/appstate.go b/model/appstate.go index 233e0db..a910a29 100644 --- a/model/appstate.go +++ b/model/appstate.go @@ -1,7 +1,7 @@ package model import ( - "github.com/jmoiron/sqlx" + "github.com/jmoiron/sqlx" "arimelody-web/log" ) diff --git a/model/artist.go b/model/artist.go index 63871c7..746a7dd 100644 --- a/model/artist.go +++ b/model/artist.go @@ -1,17 +1,17 @@ package model type ( - Artist struct { - ID string `json:"id"` - Name string `json:"name"` - Website string `json:"website"` - Avatar string `json:"avatar"` - } + Artist struct { + ID string `json:"id"` + Name string `json:"name"` + Website string `json:"website"` + Avatar string `json:"avatar"` + } ) func (artist Artist) GetAvatar() string { - if artist.Avatar == "" { - return "/img/default-avatar.png" - } - return artist.Avatar + if artist.Avatar == "" { + return "/img/default-avatar.png" + } + return artist.Avatar } diff --git a/model/link.go b/model/link.go index 1a5bb8f..ba83e22 100644 --- a/model/link.go +++ b/model/link.go @@ -1,16 +1,16 @@ package model import ( - "regexp" - "strings" + "regexp" + "strings" ) type Link struct { - Name string `json:"name"` - URL string `json:"url"` + Name string `json:"name"` + URL string `json:"url"` } func (link Link) NormaliseName() string { - rgx := regexp.MustCompile(`[^a-z0-9\-]`) - return rgx.ReplaceAllString(strings.ToLower(link.Name), "") + rgx := regexp.MustCompile(`[^a-z0-9\-]`) + return rgx.ReplaceAllString(strings.ToLower(link.Name), "") } diff --git a/model/release.go b/model/release.go index afaacca..e64317b 100644 --- a/model/release.go +++ b/model/release.go @@ -1,9 +1,9 @@ package model import ( - "html/template" - "strings" - "time" + "html/template" + "strings" + "time" ) type ( @@ -73,23 +73,23 @@ func (release Release) GetUniqueArtistNames(only_primary bool) []string { names = append(names, credit.Artist.Name) } - return names + return names } func (release Release) PrintArtists(only_primary bool, ampersand bool) string { names := release.GetUniqueArtistNames(only_primary) - if len(names) == 0 { - return "Unknown Artist" - } else if len(names) == 1 { - return names[0] - } + if len(names) == 0 { + return "Unknown Artist" + } else if len(names) == 1 { + return names[0] + } - if ampersand { - res := strings.Join(names[:len(names)-1], ", ") - res += " & " + names[len(names)-1] - return res - } else { - return strings.Join(names[:], ", ") - } + if ampersand { + res := strings.Join(names[:len(names)-1], ", ") + res += " & " + names[len(names)-1] + return res + } else { + return strings.Join(names[:], ", ") + } } diff --git a/model/release_test.go b/model/release_test.go index 11a58a1..b0ddaf5 100644 --- a/model/release_test.go +++ b/model/release_test.go @@ -1,8 +1,8 @@ package model import ( - "testing" - "time" + "testing" + "time" ) func Test_Release_DescriptionHTML(t *testing.T) { diff --git a/model/session.go b/model/session.go index de016e1..7382de3 100644 --- a/model/session.go +++ b/model/session.go @@ -1,8 +1,8 @@ package model import ( - "database/sql" - "time" + "database/sql" + "time" ) type Session struct { diff --git a/model/totp.go b/model/totp.go index cfad10a..108dae5 100644 --- a/model/totp.go +++ b/model/totp.go @@ -1,7 +1,7 @@ package model import ( - "time" + "time" ) type TOTP struct { diff --git a/model/track.go b/model/track.go index ca54ddd..deaf086 100644 --- a/model/track.go +++ b/model/track.go @@ -1,20 +1,20 @@ package model import ( - "html/template" - "strings" + "html/template" + "strings" ) type ( - Track struct { - ID string `json:"id"` - Title string `json:"title"` - Description string `json:"description"` + Track struct { + ID string `json:"id"` + Title string `json:"title"` + Description string `json:"description"` Lyrics string `json:"lyrics" db:"lyrics"` - PreviewURL string `json:"previewURL" db:"preview_url"` + PreviewURL string `json:"previewURL" db:"preview_url"` Number int - } + } ) func (track Track) GetDescriptionHTML() template.HTML { diff --git a/templates/templates.go b/templates/templates.go index 8d1a5ca..752c78d 100644 --- a/templates/templates.go +++ b/templates/templates.go @@ -1,8 +1,8 @@ package templates import ( - "html/template" - "path/filepath" + "html/template" + "path/filepath" ) var IndexTemplate = template.Must(template.ParseFiles( diff --git a/view/music.go b/view/music.go index dfe884e..8ed5279 100644 --- a/view/music.go +++ b/view/music.go @@ -1,13 +1,13 @@ package view import ( - "fmt" - "net/http" - "os" + "fmt" + "net/http" + "os" - "arimelody-web/controller" - "arimelody-web/model" - "arimelody-web/templates" + "arimelody-web/controller" + "arimelody-web/model" + "arimelody-web/templates" ) // HTTP HANDLER METHODS