2024-04-16 21:58:39 +00:00
|
|
|
package admin
|
|
|
|
|
|
|
|
import (
|
2024-07-31 03:09:22 +00:00
|
|
|
"context"
|
2024-04-16 21:58:39 +00:00
|
|
|
"fmt"
|
2024-08-01 23:53:19 +00:00
|
|
|
"html/template"
|
2024-04-16 21:58:39 +00:00
|
|
|
"net/http"
|
2024-07-31 03:09:22 +00:00
|
|
|
"os"
|
2024-08-01 23:53:19 +00:00
|
|
|
"path/filepath"
|
|
|
|
"strings"
|
2024-07-31 03:09:22 +00:00
|
|
|
"time"
|
|
|
|
|
|
|
|
"arimelody.me/arimelody.me/discord"
|
2024-07-31 12:45:34 +00:00
|
|
|
"arimelody.me/arimelody.me/global"
|
2024-08-02 21:48:26 +00:00
|
|
|
musicModel "arimelody.me/arimelody.me/music/model"
|
2024-04-16 21:58:39 +00:00
|
|
|
)
|
|
|
|
|
2024-08-05 00:23:17 +00:00
|
|
|
type loginData struct {
|
|
|
|
DiscordURI string
|
|
|
|
Token string
|
|
|
|
}
|
|
|
|
|
2024-07-31 03:09:22 +00:00
|
|
|
func Handler() http.Handler {
|
2024-07-31 12:45:34 +00:00
|
|
|
mux := http.NewServeMux()
|
2024-07-31 03:09:22 +00:00
|
|
|
|
2024-08-02 21:48:26 +00:00
|
|
|
mux.Handle("/login", LoginHandler())
|
|
|
|
mux.Handle("/logout", MustAuthorise(LogoutHandler()))
|
|
|
|
mux.Handle("/static/", http.StripPrefix("/static", staticHandler()))
|
2024-08-05 00:23:17 +00:00
|
|
|
mux.Handle("/release/", MustAuthorise(http.StripPrefix("/release", serveRelease())))
|
2024-07-31 12:45:34 +00:00
|
|
|
mux.Handle("/", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
2024-08-02 21:48:26 +00:00
|
|
|
if r.URL.Path != "/" {
|
|
|
|
http.NotFound(w, r)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
session := GetSession(r)
|
|
|
|
if session == nil {
|
|
|
|
http.Redirect(w, r, "/admin/login", http.StatusFound)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2024-08-03 23:23:55 +00:00
|
|
|
type (
|
|
|
|
Track struct {
|
|
|
|
*musicModel.Track
|
|
|
|
Lyrics template.HTML
|
|
|
|
// Number int
|
|
|
|
}
|
|
|
|
IndexData struct {
|
|
|
|
Releases []*musicModel.Release
|
|
|
|
Artists []*musicModel.Artist
|
|
|
|
Tracks []Track
|
|
|
|
}
|
|
|
|
)
|
2024-08-02 21:48:26 +00:00
|
|
|
|
2024-08-03 23:23:55 +00:00
|
|
|
var tracks = []Track{}
|
2024-08-03 22:24:15 +00:00
|
|
|
for _, track := range global.Tracks {
|
2024-08-05 00:23:17 +00:00
|
|
|
if track.Release != nil { continue }
|
2024-08-03 23:23:55 +00:00
|
|
|
tracks = append(tracks, Track{
|
|
|
|
Track: track,
|
|
|
|
Lyrics: template.HTML(strings.Replace(track.Lyrics, "\n", "<br>", -1)),
|
|
|
|
})
|
2024-08-03 22:24:15 +00:00
|
|
|
}
|
|
|
|
|
2024-08-02 21:48:26 +00:00
|
|
|
serveTemplate("index.html", IndexData{
|
|
|
|
Releases: global.Releases,
|
|
|
|
Artists: global.Artists,
|
2024-08-03 23:23:55 +00:00
|
|
|
Tracks: tracks,
|
2024-08-02 21:48:26 +00:00
|
|
|
}).ServeHTTP(w, r)
|
2024-07-31 12:45:34 +00:00
|
|
|
}))
|
|
|
|
|
|
|
|
return mux
|
2024-07-31 03:09:22 +00:00
|
|
|
}
|
|
|
|
|
2024-08-01 00:39:18 +00:00
|
|
|
func MustAuthorise(next http.Handler) http.Handler {
|
2024-07-31 03:09:22 +00:00
|
|
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
2024-08-02 21:48:26 +00:00
|
|
|
session := GetSession(r)
|
2024-07-31 03:09:22 +00:00
|
|
|
if session == nil {
|
2024-08-01 00:39:18 +00:00
|
|
|
http.Error(w, http.StatusText(http.StatusUnauthorized), http.StatusUnauthorized)
|
2024-07-31 03:09:22 +00:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2024-08-02 21:48:26 +00:00
|
|
|
ctx := context.WithValue(r.Context(), "session", session)
|
2024-07-31 03:09:22 +00:00
|
|
|
next.ServeHTTP(w, r.WithContext(ctx))
|
|
|
|
})
|
2024-04-16 21:58:39 +00:00
|
|
|
}
|
|
|
|
|
2024-08-02 21:48:26 +00:00
|
|
|
func GetSession(r *http.Request) *Session {
|
2024-08-23 22:08:28 +00:00
|
|
|
if ADMIN_BYPASS {
|
|
|
|
return &Session{}
|
|
|
|
}
|
2024-08-02 21:48:26 +00:00
|
|
|
|
|
|
|
var token = ""
|
|
|
|
// is the session token in context?
|
|
|
|
var ctx_session = r.Context().Value("session")
|
|
|
|
if ctx_session != nil {
|
|
|
|
token = ctx_session.(string)
|
|
|
|
}
|
|
|
|
// okay, is it in the auth header?
|
|
|
|
if token == "" {
|
|
|
|
if strings.HasPrefix(r.Header.Get("Authorization"), "Bearer ") {
|
|
|
|
token = r.Header.Get("Authorization")[7:]
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// finally, is it in the cookie?
|
|
|
|
if token == "" {
|
|
|
|
cookie, err := r.Cookie("token")
|
|
|
|
if err != nil {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
token = cookie.Value
|
|
|
|
}
|
|
|
|
|
|
|
|
var session *Session = nil
|
|
|
|
for _, s := range sessions {
|
|
|
|
if s.Expires.Before(time.Now()) {
|
|
|
|
// expired session. remove it from the list!
|
|
|
|
new_sessions := []*Session{}
|
|
|
|
for _, ns := range sessions {
|
|
|
|
if ns.Token == s.Token {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
new_sessions = append(new_sessions, ns)
|
|
|
|
}
|
|
|
|
sessions = new_sessions
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
if s.Token == token {
|
|
|
|
session = s
|
|
|
|
break
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return session
|
|
|
|
}
|
|
|
|
|
2024-07-31 03:09:22 +00:00
|
|
|
func LoginHandler() http.Handler {
|
|
|
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
2024-08-02 21:48:26 +00:00
|
|
|
if discord.CREDENTIALS_PROVIDED && ADMIN_ID_DISCORD == "" {
|
2024-08-01 00:39:18 +00:00
|
|
|
http.Error(w, http.StatusText(http.StatusServiceUnavailable), http.StatusServiceUnavailable)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2024-07-31 03:09:22 +00:00
|
|
|
code := r.URL.Query().Get("code")
|
|
|
|
|
|
|
|
if code == "" {
|
2024-08-05 00:23:17 +00:00
|
|
|
serveTemplate("login.html", loginData{DiscordURI: discord.REDIRECT_URI}).ServeHTTP(w, r)
|
2024-07-31 03:09:22 +00:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2024-07-31 03:21:52 +00:00
|
|
|
auth_token, err := discord.GetOAuthTokenFromCode(code)
|
2024-07-31 03:09:22 +00:00
|
|
|
if err != nil {
|
2024-07-31 03:21:52 +00:00
|
|
|
fmt.Printf("Failed to retrieve discord access token: %s\n", err)
|
2024-08-01 00:39:18 +00:00
|
|
|
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
2024-07-31 03:09:22 +00:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2024-07-31 03:21:52 +00:00
|
|
|
discord_user, err := discord.GetDiscordUserFromAuth(auth_token)
|
2024-07-31 03:09:22 +00:00
|
|
|
if err != nil {
|
2024-07-31 03:21:52 +00:00
|
|
|
fmt.Printf("Failed to retrieve discord user information: %s\n", err)
|
2024-08-01 00:39:18 +00:00
|
|
|
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
2024-07-31 03:09:22 +00:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2024-08-05 00:23:17 +00:00
|
|
|
if discord_user.ID != ADMIN_ID_DISCORD {
|
2024-08-01 00:39:18 +00:00
|
|
|
// TODO: unauthorized user; revoke the token
|
2024-08-05 00:23:17 +00:00
|
|
|
fmt.Printf("Unauthorized login attempted: %s\n", discord_user.ID)
|
2024-08-01 00:39:18 +00:00
|
|
|
http.Error(w, http.StatusText(http.StatusUnauthorized), http.StatusUnauthorized)
|
2024-07-31 03:09:22 +00:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
// login success!
|
2024-08-02 21:48:26 +00:00
|
|
|
session := createSession(discord_user.Username, time.Now().Add(24 * time.Hour))
|
2024-07-31 03:09:22 +00:00
|
|
|
sessions = append(sessions, &session)
|
|
|
|
|
|
|
|
cookie := http.Cookie{}
|
|
|
|
cookie.Name = "token"
|
|
|
|
cookie.Value = session.Token
|
|
|
|
cookie.Expires = time.Now().Add(24 * time.Hour)
|
2024-08-23 22:08:28 +00:00
|
|
|
// TODO: uncomment this probably that might be nice i think
|
2024-07-31 03:09:22 +00:00
|
|
|
// cookie.Secure = true
|
|
|
|
cookie.HttpOnly = true
|
|
|
|
cookie.Path = "/"
|
|
|
|
http.SetCookie(w, &cookie)
|
|
|
|
|
2024-08-05 00:23:17 +00:00
|
|
|
serveTemplate("login.html", loginData{Token: session.Token}).ServeHTTP(w, r)
|
2024-07-31 03:09:22 +00:00
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
func LogoutHandler() http.Handler {
|
|
|
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
2024-08-02 21:48:26 +00:00
|
|
|
if r.Method != http.MethodGet {
|
|
|
|
http.NotFound(w, r)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2024-08-05 00:23:17 +00:00
|
|
|
session := GetSession(r)
|
2024-04-16 21:58:39 +00:00
|
|
|
|
2024-07-31 03:09:22 +00:00
|
|
|
// remove this session from the list
|
|
|
|
sessions = func (token string) []*Session {
|
|
|
|
new_sessions := []*Session{}
|
|
|
|
for _, session := range sessions {
|
2024-08-05 00:23:17 +00:00
|
|
|
if session.Token != token {
|
|
|
|
new_sessions = append(new_sessions, session)
|
|
|
|
}
|
2024-07-31 03:09:22 +00:00
|
|
|
}
|
|
|
|
return new_sessions
|
2024-08-05 00:23:17 +00:00
|
|
|
}(session.Token)
|
2024-07-31 03:09:22 +00:00
|
|
|
|
2024-08-05 00:23:17 +00:00
|
|
|
serveTemplate("logout.html", nil).ServeHTTP(w, r)
|
2024-07-31 03:09:22 +00:00
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2024-08-02 21:48:26 +00:00
|
|
|
func serveTemplate(page string, data any) http.Handler {
|
2024-08-01 23:53:19 +00:00
|
|
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
2024-08-05 00:23:17 +00:00
|
|
|
lp_layout := filepath.Join("admin", "views", "layout.html")
|
2024-08-01 23:53:19 +00:00
|
|
|
lp_prideflag := filepath.Join("views", "prideflag.html")
|
2024-08-05 00:23:17 +00:00
|
|
|
fp := filepath.Join("admin", "views", filepath.Clean(page))
|
2024-08-01 23:53:19 +00:00
|
|
|
|
|
|
|
info, err := os.Stat(fp)
|
|
|
|
if err != nil {
|
|
|
|
if os.IsNotExist(err) {
|
|
|
|
http.NotFound(w, r)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if info.IsDir() {
|
|
|
|
http.NotFound(w, r)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2024-08-02 21:48:26 +00:00
|
|
|
template, err := template.ParseFiles(lp_layout, lp_prideflag, fp)
|
2024-08-01 23:53:19 +00:00
|
|
|
if err != nil {
|
|
|
|
fmt.Printf("Error parsing template files: %s\n", err)
|
|
|
|
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
err = template.ExecuteTemplate(w, "layout.html", data)
|
|
|
|
if err != nil {
|
|
|
|
fmt.Printf("Error executing template: %s\n", err)
|
|
|
|
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
})
|
|
|
|
}
|
2024-04-16 21:58:39 +00:00
|
|
|
|
2024-08-23 22:08:28 +00:00
|
|
|
func serveComponent(page string, data any) http.Handler {
|
|
|
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
|
|
fp := filepath.Join("admin", "components", filepath.Clean(page))
|
|
|
|
|
|
|
|
info, err := os.Stat(fp)
|
|
|
|
if err != nil {
|
|
|
|
if os.IsNotExist(err) {
|
|
|
|
http.NotFound(w, r)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if info.IsDir() {
|
|
|
|
http.NotFound(w, r)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
template, err := template.ParseFiles(fp)
|
|
|
|
if err != nil {
|
|
|
|
fmt.Printf("Error parsing template files: %s\n", err)
|
|
|
|
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
err = template.Execute(w, data);
|
|
|
|
if err != nil {
|
|
|
|
fmt.Printf("Error executing template: %s\n", err)
|
|
|
|
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2024-08-01 23:53:19 +00:00
|
|
|
func staticHandler() http.Handler {
|
|
|
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
|
|
info, err := os.Stat(filepath.Join("admin", "static", filepath.Clean(r.URL.Path)))
|
|
|
|
// does the file exist?
|
|
|
|
if err != nil {
|
|
|
|
if os.IsNotExist(err) {
|
|
|
|
http.NotFound(w, r)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
2024-04-16 21:58:39 +00:00
|
|
|
|
2024-08-01 23:53:19 +00:00
|
|
|
// is thjs a directory? (forbidden)
|
|
|
|
if info.IsDir() {
|
|
|
|
http.NotFound(w, r)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
http.FileServer(http.Dir(filepath.Join("admin", "static"))).ServeHTTP(w, r)
|
|
|
|
})
|
2024-04-16 21:58:39 +00:00
|
|
|
}
|