mirror of
https://github.com/gmemstr/pogo.git
synced 2024-09-20 01:31:09 +01:00
db24981641
Now that config no longer handles sensitive account info it's fine to pass this to client. Debating whether to allow anyone to fetch config, but for now restricted to level 2 user accounts.
241 lines
6 KiB
Go
241 lines
6 KiB
Go
package router
|
|
|
|
import (
|
|
"database/sql"
|
|
"fmt"
|
|
"golang.org/x/crypto/bcrypt"
|
|
"log"
|
|
"net/http"
|
|
|
|
_ "github.com/mattn/go-sqlite3"
|
|
|
|
"github.com/gmemstr/pogo/admin"
|
|
"github.com/gmemstr/pogo/auth"
|
|
"github.com/gmemstr/pogo/common"
|
|
"github.com/gorilla/mux"
|
|
)
|
|
|
|
type NewConfig struct {
|
|
Name string
|
|
Host string
|
|
Email string
|
|
Description string
|
|
Image string
|
|
PodcastURL string
|
|
}
|
|
|
|
// Handle takes multiple Handler and executes them in a serial order starting from first to last.
|
|
// In case, Any middle ware returns an error, The error is logged to console and sent to the user, Middlewares further up in chain are not executed.
|
|
func Handle(handlers ...common.Handler) http.Handler {
|
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
|
|
rc := &common.RouterContext{}
|
|
for _, handler := range handlers {
|
|
err := handler(rc, w, r)
|
|
if err != nil {
|
|
log.Printf("%v", err)
|
|
|
|
w.Write([]byte(http.StatusText(err.StatusCode)))
|
|
|
|
return
|
|
}
|
|
}
|
|
})
|
|
}
|
|
|
|
func Init() *mux.Router {
|
|
|
|
r := mux.NewRouter()
|
|
|
|
// "Static" paths
|
|
r.PathPrefix("/assets/").Handler(http.StripPrefix("/assets/", http.FileServer(http.Dir("assets/web/static"))))
|
|
r.PathPrefix("/static/").Handler(http.StripPrefix("/static/", http.FileServer(http.Dir("assets/web/static"))))
|
|
r.PathPrefix("/download/").Handler(http.StripPrefix("/download/", http.FileServer(http.Dir("podcasts"))))
|
|
|
|
// Paths that require specific handlers
|
|
r.Handle("/", Handle(
|
|
rootHandler(),
|
|
)).Methods("GET")
|
|
|
|
r.Handle("/rss", Handle(
|
|
rootHandler(),
|
|
)).Methods("GET")
|
|
|
|
r.Handle("/json", Handle(
|
|
rootHandler(),
|
|
)).Methods("GET")
|
|
|
|
// RequireAuthorization() handles authentication
|
|
// and takes a single argument for permission level.
|
|
// 0 any user, 1 most users, 2 only admin users
|
|
r.Handle("/admin", Handle(
|
|
auth.RequireAuthorization(0),
|
|
adminHandler(),
|
|
)).Methods("GET", "POST")
|
|
|
|
r.Handle("/login", Handle(
|
|
loginHandler(),
|
|
)).Methods("POST")
|
|
|
|
r.Handle("/admin/publish", Handle(
|
|
auth.RequireAuthorization(0),
|
|
admin.CreateEpisode(),
|
|
)).Methods("POST")
|
|
|
|
r.Handle("/admin/edituser", Handle(
|
|
auth.RequireAuthorization(2),
|
|
admin.EditUser(),
|
|
)).Methods("POST")
|
|
|
|
r.Handle("/admin/newuser", Handle(
|
|
auth.RequireAuthorization(2),
|
|
admin.AddUser(),
|
|
)).Methods("POST")
|
|
r.Handle("/admin/deleteuser/{id}", Handle(
|
|
auth.RequireAuthorization(2),
|
|
admin.DeleteUser(),
|
|
)).Methods("GET")
|
|
r.Handle("/admin/edit", Handle(
|
|
auth.RequireAuthorization(1),
|
|
admin.EditEpisode(),
|
|
)).Methods("POST")
|
|
|
|
r.Handle("/admin/delete", Handle(
|
|
auth.RequireAuthorization(1),
|
|
admin.RemoveEpisode(),
|
|
)).Methods("GET")
|
|
|
|
r.Handle("/admin/css", Handle(
|
|
auth.RequireAuthorization(1),
|
|
admin.CustomCss(),
|
|
)).Methods("GET", "POST")
|
|
|
|
r.Handle("/admin/adduser", Handle(
|
|
auth.RequireAuthorization(2),
|
|
admin.AddUser(),
|
|
)).Methods("POST")
|
|
|
|
r.Handle("/admin/listusers", Handle(
|
|
auth.RequireAuthorization(1),
|
|
admin.ListUsers(),
|
|
)).Methods("GET")
|
|
|
|
r.Handle("/admin/settings", Handle(
|
|
auth.RequireAuthorization(2),
|
|
admin.ConfigurationManager(),
|
|
)).Methods("GET", "POST")
|
|
|
|
return r
|
|
}
|
|
|
|
func loginHandler() common.Handler {
|
|
return func(rc *common.RouterContext, w http.ResponseWriter, r *http.Request) *common.HTTPError {
|
|
db, err := sql.Open("sqlite3", "assets/config/users.db")
|
|
|
|
if err != nil {
|
|
return &common.HTTPError{
|
|
Message: fmt.Sprintf("error in reading user database: %v", err),
|
|
StatusCode: http.StatusInternalServerError,
|
|
}
|
|
}
|
|
|
|
statement, err := db.Prepare("SELECT * FROM users WHERE username=?")
|
|
|
|
if _, err := auth.DecryptCookie(r); err == nil {
|
|
http.Redirect(w, r, "/admin", http.StatusTemporaryRedirect)
|
|
return nil
|
|
}
|
|
|
|
err = r.ParseForm()
|
|
if err != nil {
|
|
return &common.HTTPError{
|
|
Message: fmt.Sprintf("error in parsing form: %v", err),
|
|
StatusCode: http.StatusBadRequest,
|
|
}
|
|
}
|
|
|
|
username := r.Form.Get("username")
|
|
password := r.Form.Get("password")
|
|
rows, err := statement.Query(username)
|
|
|
|
if username == "" || password == "" || err != nil {
|
|
return &common.HTTPError{
|
|
Message: "username or password is invalid",
|
|
StatusCode: http.StatusBadRequest,
|
|
}
|
|
}
|
|
var id int
|
|
var dbun string
|
|
var dbhsh string
|
|
var dbrn string
|
|
var dbem string
|
|
var dbperm int
|
|
for rows.Next() {
|
|
err := rows.Scan(&id, &dbun, &dbhsh, &dbrn, &dbem, &dbperm)
|
|
if err != nil {
|
|
return &common.HTTPError{
|
|
Message: fmt.Sprintf("error in decoding sql data", err),
|
|
StatusCode: http.StatusBadRequest,
|
|
}
|
|
}
|
|
|
|
}
|
|
// Create a cookie here because the credentials are correct
|
|
if bcrypt.CompareHashAndPassword([]byte(dbhsh), []byte(password)) == nil {
|
|
c, err := auth.CreateSession(&common.User{
|
|
Username: username,
|
|
})
|
|
if err != nil {
|
|
return &common.HTTPError{
|
|
Message: err.Error(),
|
|
StatusCode: http.StatusInternalServerError,
|
|
}
|
|
}
|
|
|
|
// r.AddCookie(c)
|
|
w.Header().Add("Set-Cookie", c.String())
|
|
// And now redirect the user to admin page
|
|
http.Redirect(w, r, "/admin", http.StatusTemporaryRedirect)
|
|
db.Close()
|
|
return nil
|
|
}
|
|
|
|
return &common.HTTPError{
|
|
Message: "Invalid credentials!",
|
|
StatusCode: http.StatusUnauthorized,
|
|
}
|
|
}
|
|
}
|
|
|
|
// Handles /, /feed and /json endpoints
|
|
func rootHandler() common.Handler {
|
|
return func(rc *common.RouterContext, w http.ResponseWriter, r *http.Request) *common.HTTPError {
|
|
|
|
var file string
|
|
switch r.URL.Path {
|
|
case "/rss":
|
|
w.Header().Set("Content-Type", "application/rss+xml")
|
|
file = "assets/web/feed.rss"
|
|
case "/json":
|
|
w.Header().Set("Content-Type", "application/json")
|
|
file = "assets/web/feed.json"
|
|
case "/":
|
|
w.Header().Set("Content-Type", "text/html")
|
|
file = "assets/web/index.html"
|
|
default:
|
|
return &common.HTTPError{
|
|
Message: fmt.Sprintf("%s: Not Found", r.URL.Path),
|
|
StatusCode: http.StatusNotFound,
|
|
}
|
|
}
|
|
|
|
return common.ReadAndServeFile(file, w)
|
|
}
|
|
}
|
|
|
|
func adminHandler() common.Handler {
|
|
return func(rc *common.RouterContext, w http.ResponseWriter, r *http.Request) *common.HTTPError {
|
|
return common.ReadAndServeFile("assets/web/admin.html", w)
|
|
}
|
|
}
|