Add option to set extra address for media

This commit is contained in:
Jan-Lukas Else 2021-08-02 20:43:24 +02:00
parent 55fa3421f9
commit ec9ef528c4
11 changed files with 104 additions and 25 deletions

3
app.go
View File

@ -71,5 +71,6 @@ type goBlog struct {
// Template strings
ts *ts.TemplateStrings
// Tor
torAddress string
torAddress string
torHostname string
}

View File

@ -32,6 +32,7 @@ type configServer struct {
Port int `mapstructure:"port"`
PublicAddress string `mapstructure:"publicAddress"`
ShortPublicAddress string `mapstructure:"shortPublicAddress"`
MediaAddress string `mapstructure:"mediaAddress"`
PublicHTTPS bool `mapstructure:"publicHttps"`
Tor bool `mapstructure:"tor"`
SecurityHeaders bool `mapstructure:"securityHeaders"`
@ -39,6 +40,7 @@ type configServer struct {
JWTSecret string `mapstructure:"jwtSecret"`
publicHostname string
shortPublicHostname string
mediaHostname string
}
type configDb struct {
@ -304,13 +306,20 @@ func (a *goBlog) initConfig() error {
return err
}
a.cfg.Server.publicHostname = publicURL.Hostname()
if a.cfg.Server.ShortPublicAddress != "" {
shortPublicURL, err := url.Parse(a.cfg.Server.ShortPublicAddress)
if sa := a.cfg.Server.ShortPublicAddress; sa != "" {
shortPublicURL, err := url.Parse(sa)
if err != nil {
return err
}
a.cfg.Server.shortPublicHostname = shortPublicURL.Hostname()
}
if ma := a.cfg.Server.MediaAddress; ma != "" {
mediaUrl, err := url.Parse(ma)
if err != nil {
return err
}
a.cfg.Server.mediaHostname = mediaUrl.Hostname()
}
if a.cfg.Server.JWTSecret == "" {
return errors.New("no JWT secret configured")
}

View File

@ -17,6 +17,7 @@ server:
port: 8080
publicAddress: https://example.com # Public address to use for the blog
shortPublicAddress: https://short.example.com # Optional short address, will redirect to main address
mediaAddress: https://media.example.com # Optional domain to use for serving media files
# Security
publicHttps: true # Use Let's Encrypt and serve site with HTTPS
securityHeaders: true # Set security HTTP headers (to always use HTTPS etc.)

33
http.go
View File

@ -15,6 +15,7 @@ import (
"github.com/go-chi/chi/v5"
"github.com/go-chi/chi/v5/middleware"
"github.com/justinas/alice"
"go.goblog.app/app/pkgs/maprouter"
"golang.org/x/crypto/acme"
"golang.org/x/crypto/acme/autocert"
"golang.org/x/net/context"
@ -78,8 +79,11 @@ func (a *goBlog) startServer() (err error) {
// Start HTTPS
s.Addr = ":https"
hosts := []string{a.cfg.Server.publicHostname}
if a.cfg.Server.shortPublicHostname != "" {
hosts = append(hosts, a.cfg.Server.shortPublicHostname)
if shn := a.cfg.Server.shortPublicHostname; shn != "" {
hosts = append(hosts, shn)
}
if mhn := a.cfg.Server.mediaHostname; mhn != "" {
hosts = append(hosts, mhn)
}
acmeDir := acme.LetsEncryptURL
// acmeDir := "https://acme-staging-v02.api.letsencrypt.org/directory"
@ -127,11 +131,29 @@ const (
)
func (a *goBlog) buildRouter() (http.Handler, error) {
mapRouter := &maprouter.MapRouter{
Handlers: map[string]http.Handler{},
}
if shn := a.cfg.Server.shortPublicHostname; shn != "" {
mapRouter.Handlers[shn] = http.HandlerFunc(a.redirectShortDomain)
}
if mhn := a.cfg.Server.mediaHostname; mhn != "" && !a.isPrivate() {
mr := chi.NewMux()
mr.Use(middleware.RedirectSlashes)
mr.Use(middleware.CleanPath)
mr.Use(middleware.GetHead)
mr.Group(a.mediaFilesRouter)
mapRouter.Handlers[mhn] = mr
}
// Default router
r := chi.NewMux()
// Basic middleware
r.Use(fixHTTPHandler)
r.Use(a.redirectShortDomain)
r.Use(middleware.RedirectSlashes)
r.Use(middleware.CleanPath)
r.Use(middleware.GetHead)
@ -173,7 +195,7 @@ func (a *goBlog) buildRouter() (http.Handler, error) {
r.Group(a.staticFilesRouter)
// Media files
r.With(a.privateModeHandler).Get(`/m/{file:[0-9a-fA-F]+(\.[0-9a-zA-Z]+)?}`, a.serveMediaFile)
r.Route("/m", a.mediaFilesRouter)
// Captcha
r.Handle("/captcha/*", captcha.Server(500, 250))
@ -196,7 +218,8 @@ func (a *goBlog) buildRouter() (http.Handler, error) {
r.MethodNotAllowed(a.serveNotAllowed)
return r, nil
mapRouter.DefaultHandler = r
return mapRouter, nil
}
func (a *goBlog) servePostsAliasesRedirects() http.HandlerFunc {

View File

@ -1,7 +1,6 @@
package main
import (
"fmt"
"net/http"
"net/url"
"strings"
@ -43,8 +42,8 @@ func (a *goBlog) securityHeaders(next http.Handler) http.Handler {
w.Header().Set("X-Frame-Options", "SAMEORIGIN")
w.Header().Set("X-Xss-Protection", "1; mode=block")
w.Header().Set("Content-Security-Policy", "default-src 'self'"+cspDomains)
if a.cfg.Server.Tor && a.torAddress != "" {
w.Header().Set("Onion-Location", fmt.Sprintf("http://%v%v", a.torAddress, r.RequestURI))
if a.torAddress != "" {
w.Header().Set("Onion-Location", a.torAddress+r.RequestURI)
}
next.ServeHTTP(w, r)
})

View File

@ -92,6 +92,12 @@ func (a *goBlog) staticFilesRouter(r chi.Router) {
}
}
// Media files
func (a *goBlog) mediaFilesRouter(r chi.Router) {
r.Use(a.privateModeHandler)
r.Get(mediaFileRoute, a.serveMediaFile)
}
// Blog
func (a *goBlog) blogRouter(blog string, conf *configBlog) func(r chi.Router) {
return func(r chi.Router) {

View File

@ -8,13 +8,17 @@ import (
"github.com/go-chi/chi/v5"
)
const mediaFilePath = "data/media"
const (
mediaFilePath = "data/media"
mediaFileRoute = `/{file:[0-9a-fA-F]+(\.[0-9a-zA-Z]+)?}`
)
func (a *goBlog) serveMediaFile(w http.ResponseWriter, r *http.Request) {
f := filepath.Join(mediaFilePath, chi.URLParam(r, "file"))
_, err := os.Stat(f)
if err != nil {
a.serve404(w, r)
// Serve 404, but don't use normal serve404 method because of media domain
http.NotFound(w, r)
return
}
w.Header().Add("Cache-Control", "public,max-age=31536000,immutable")

View File

@ -0,0 +1,40 @@
package maprouter
import (
"net/http"
)
// Make sure interface is satisfied
var _ http.Handler = &MapRouter{}
// Routes requests based on a map with routers
type MapRouter struct {
// Default http.Handler
DefaultHandler http.Handler
// Handlers mapped by prefix
Handlers map[string]http.Handler
// Optional function to find key for handler, default uses hostname
KeyFunc func(r *http.Request) string
}
// Serve the HTTP request
func (ar *MapRouter) ServeHTTP(rw http.ResponseWriter, r *http.Request) {
if len(ar.Handlers) > 0 {
var key string
if ar.KeyFunc != nil {
key = ar.KeyFunc(r)
} else {
key = defaultKey(r)
}
if h, ok := ar.Handlers[key]; ok {
h.ServeHTTP(rw, r)
return
}
}
ar.DefaultHandler.ServeHTTP(rw, r)
}
// Gets the default key for the router
func defaultKey(r *http.Request) string {
return r.Host
}

View File

@ -3,7 +3,6 @@ package main
import (
"bytes"
"errors"
"fmt"
"html/template"
"net/http"
"os"
@ -174,7 +173,7 @@ func (a *goBlog) checkRenderData(r *http.Request, data *renderData) {
}
// Tor
if a.cfg.Server.Tor && a.torAddress != "" {
data.TorAddress = fmt.Sprintf("http://%v%v", a.torAddress, r.RequestURI)
data.TorAddress = a.torAddress + r.RequestURI
}
if torUsed, ok := r.Context().Value(torUsedKey).(bool); ok && torUsed {
data.TorUsed = true

View File

@ -4,12 +4,6 @@ import (
"net/http"
)
func (a *goBlog) redirectShortDomain(next http.Handler) http.Handler {
return http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
if a.cfg.Server.shortPublicHostname != "" && r.Host == a.cfg.Server.shortPublicHostname {
http.Redirect(rw, r, a.getFullAddress(r.RequestURI), http.StatusMovedPermanently)
return
}
next.ServeHTTP(rw, r)
})
func (a *goBlog) redirectShortDomain(rw http.ResponseWriter, r *http.Request) {
http.Redirect(rw, r, a.getFullAddress(r.RequestURI), http.StatusMovedPermanently)
}

7
tor.go
View File

@ -8,6 +8,7 @@ import (
"encoding/pem"
"log"
"net/http"
"net/url"
"os"
"path/filepath"
"time"
@ -72,8 +73,10 @@ func (a *goBlog) startOnionService(h http.Handler) error {
return err
}
defer onion.Close()
a.torAddress = onion.String()
log.Println("Onion service published on http://" + a.torAddress)
a.torAddress = "http://" + onion.String()
torUrl, _ := url.Parse(a.torAddress)
a.torHostname = torUrl.Hostname()
log.Println("Onion service published on " + a.torAddress)
// Clear cache
a.cache.purge()
// Serve handler