Add reverse proxy for local debugging

This commit is contained in:
2021-03-04 17:10:59 +01:00
parent b481db7b45
commit f3f101d804
4 changed files with 89 additions and 53 deletions

View File

@@ -19,18 +19,32 @@ type PixelWebConfig struct {
SessionCookieDomain string `toml:"session_cookie_domain"` SessionCookieDomain string `toml:"session_cookie_domain"`
ResourceDir string `toml:"resource_dir"` ResourceDir string `toml:"resource_dir"`
DebugMode bool `toml:"debug_mode"` DebugMode bool `toml:"debug_mode"`
ProxyAPIRequests bool `toml:"proxy_api_requests"`
MaintenanceMode bool `toml:"maintenance_mode"` MaintenanceMode bool `toml:"maintenance_mode"`
} }
// DefaultConfig is the default configuration for Pixeldrain Web // DefaultConfig is the default configuration for Pixeldrain Web
const DefaultConfig = `# Pixeldrain Web UI server configuration const DefaultConfig = `## Pixeldrain Web UI server configuration
# Address used in the browser for making requests directly to the API. Can be
# relative to the current domain name
api_url_external = "/api"
# Address used to make internal API requests to the backend
api_url_internal = "https://pixeldrain.com/api"
api_url_external = "/api" # Used in the web browser
api_url_internal = "http://127.0.0.1:8080" # Used for internal API requests to the pixeldrain server, not visible to users
website_address = "https://pixeldrain.com" website_address = "https://pixeldrain.com"
session_cookie_domain = ".pixeldrain.com" session_cookie_domain = ""
resource_dir = "res" resource_dir = "res"
debug_mode = false
# Parse all the templates every time a request comes in
debug_mode = true
# Create proxy listeners to forward all requests made to /api to
# api_url_internal
proxy_api_requests = true
# When this is true every request will return a maintainance HTML page
maintenance_mode = false maintenance_mode = false
` `
@@ -68,5 +82,6 @@ func Init(r *httprouter.Router, prefix string, setLogLevel bool) {
webconf.SessionCookieDomain, webconf.SessionCookieDomain,
webconf.MaintenanceMode, webconf.MaintenanceMode,
webconf.DebugMode, webconf.DebugMode,
webconf.ProxyAPIRequests,
) )
} }

View File

@@ -1,42 +1,42 @@
function Toolbar(viewer) { function Toolbar(viewer) {
this.viewer = viewer this.viewer = viewer
this.visible = false this.visible = false
this.sharebarVisible = false this.sharebarVisible = false
this.currentFile = null this.currentFile = null
this.editWindow = null this.editWindow = null
this.views = 0 this.views = 0
this.downloads = 0 this.downloads = 0
this.statsWebsocket = null this.statsWebsocket = null
this.divToolbar = document.getElementById("toolbar") this.divToolbar = document.getElementById("toolbar")
this.divFilePreview = document.getElementById("filepreview") this.divFilePreview = document.getElementById("filepreview")
this.downloadFrame = document.getElementById("download_frame") this.downloadFrame = document.getElementById("download_frame")
this.spanViews = document.getElementById("stat_views") this.spanViews = document.getElementById("stat_views")
this.spanDownloads = document.getElementById("stat_downloads") this.spanDownloads = document.getElementById("stat_downloads")
this.spanSize = document.getElementById("stat_size") this.spanSize = document.getElementById("stat_size")
this.btnToggleToolbar = document.getElementById("btn_toggle_toolbar") this.btnToggleToolbar = document.getElementById("btn_toggle_toolbar")
this.btnDownload = document.getElementById("btn_download") this.btnDownload = document.getElementById("btn_download")
this.btnCopyLink = document.getElementById("btn_copy") this.btnCopyLink = document.getElementById("btn_copy")
this.spanCopyLink = document.querySelector("#btn_copy > span") this.spanCopyLink = document.querySelector("#btn_copy > span")
this.btnShare = document.getElementById("btn_share") this.btnShare = document.getElementById("btn_share")
this.divSharebar = document.getElementById("sharebar") this.divSharebar = document.getElementById("sharebar")
this.btnToggleToolbar.addEventListener("click", () => { this.toggle() }) this.btnToggleToolbar.addEventListener("click", () => { this.toggle() })
this.btnDownload.addEventListener("click", () => { this.download() }) this.btnDownload.addEventListener("click", () => { this.download() })
this.btnCopyLink.addEventListener("click", () => { this.copyUrl() }) this.btnCopyLink.addEventListener("click", () => { this.copyUrl() })
this.btnShare.addEventListener("click", () => { this.toggleSharebar() }) this.btnShare.addEventListener("click", () => { this.toggleSharebar() })
} }
Toolbar.prototype.setFile = function(file) { Toolbar.prototype.setFile = function (file) {
this.currentFile = file this.currentFile = file
this.spanSize.innerText = formatDataVolume(file.size, 3) this.spanSize.innerText = formatDataVolume(file.size, 3)
this.setStats() this.setStats()
} }
Toolbar.prototype.setStats = function() { Toolbar.prototype.setStats = function () {
let size = this.currentFile.size let size = this.currentFile.size
this.spanViews.innerText = "loading..." this.spanViews.innerText = "loading..."
@@ -47,19 +47,19 @@ Toolbar.prototype.setStats = function() {
} }
this.statsWebsocket = new WebSocket( this.statsWebsocket = new WebSocket(
location.origin.replace(/^http/, 'ws')+"/api/file/"+this.currentFile.id+"/stats" location.origin.replace(/^http/, 'ws') + "/api/file/" + this.currentFile.id + "/stats"
) )
this.statsWebsocket.onmessage = (msg) => { this.statsWebsocket.onmessage = (msg) => {
let j = JSON.parse(msg.data) let j = JSON.parse(msg.data)
console.debug("WS update", j) console.debug("WS update", j)
this.views = j.views this.views = j.views
this.downloads = Math.round(j.bandwidth/size) this.downloads = Math.round(j.bandwidth / size)
this.spanViews.innerText = formatThousands(this.views) this.spanViews.innerText = formatThousands(this.views)
this.spanDownloads.innerText = formatThousands(this.downloads) this.spanDownloads.innerText = formatThousands(this.downloads)
} }
this.statsWebsocket.onerror = (err) => { this.statsWebsocket.onerror = (err) => {
log.error("WS error", err) console.error("WS error", err)
this.statsWebsocket.close() this.statsWebsocket.close()
this.statsWebsocket = null this.statsWebsocket = null
@@ -74,7 +74,7 @@ Toolbar.prototype.setStats = function() {
} }
} }
Toolbar.prototype.toggle = function() { Toolbar.prototype.toggle = function () {
if (this.visible) { if (this.visible) {
if (this.sharebarVisible) { this.toggleSharebar() } if (this.sharebarVisible) { this.toggleSharebar() }
@@ -90,7 +90,7 @@ Toolbar.prototype.toggle = function() {
} }
} }
Toolbar.prototype.toggleSharebar = function() { Toolbar.prototype.toggleSharebar = function () {
if (navigator.share) { if (navigator.share) {
navigator.share({ navigator.share({
title: this.viewer.title, title: this.viewer.title,
@@ -100,19 +100,19 @@ Toolbar.prototype.toggleSharebar = function() {
return return
} }
if(this.sharebarVisible){ if (this.sharebarVisible) {
this.divSharebar.style.left = "-8em" this.divSharebar.style.left = "-8em"
this.btnShare.classList.remove("button_highlight") this.btnShare.classList.remove("button_highlight")
this.sharebarVisible = false this.sharebarVisible = false
}else{ } else {
this.divSharebar.style.left = "8em" this.divSharebar.style.left = "8em"
this.btnShare.classList.add("button_highlight") this.btnShare.classList.add("button_highlight")
this.sharebarVisible = true this.sharebarVisible = true
} }
} }
Toolbar.prototype.download = function() { Toolbar.prototype.download = function () {
if (captchaKey === "none" || captchaKey === ""){ if (captchaKey === "none" || captchaKey === "") {
console.debug("Server doesn't support captcha, starting download") console.debug("Server doesn't support captcha, starting download")
this.downloadFrame.src = this.currentFile.download_href this.downloadFrame.src = this.currentFile.download_href
return return
@@ -140,7 +140,7 @@ Toolbar.prototype.download = function() {
// Set the callback function // Set the callback function
recaptchaCallback = token => { recaptchaCallback = token => {
// Download the file using the recaptcha token // Download the file using the recaptcha token
this.downloadFrame.src = this.currentFile.download_href+"&recaptcha_response="+token this.downloadFrame.src = this.currentFile.download_href + "&recaptcha_response=" + token
this.captchaModal.close() this.captchaModal.close()
} }
@@ -158,25 +158,25 @@ Toolbar.prototype.download = function() {
console.debug("Showing rate limiting captcha") console.debug("Showing rate limiting captcha")
showCaptcha( showCaptcha(
"Rate limiting enabled!", "Rate limiting enabled!",
"This file is using a suspicious amount of bandwidth relative "+ "This file is using a suspicious amount of bandwidth relative " +
"to its popularity. To continue downloading this file you "+ "to its popularity. To continue downloading this file you " +
"will have to prove that you're a human first.", "will have to prove that you're a human first.",
) )
} else if (this.currentFile.availability === "virus_detected_captcha_required") { } else if (this.currentFile.availability === "virus_detected_captcha_required") {
console.debug("Showing virus captcha") console.debug("Showing virus captcha")
showCaptcha( showCaptcha(
"Malware warning!", "Malware warning!",
"According to our scanning systems this file may contain a "+ "According to our scanning systems this file may contain a " +
"virus of type '"+this.currentFile.availability_name+"'. You "+ "virus of type '" + this.currentFile.availability_name + "'. You " +
"can continue downloading this file at your own risk, but you "+ "can continue downloading this file at your own risk, but you " +
"will have to prove that you're a human first.", "will have to prove that you're a human first.",
) )
} }
} }
} }
Toolbar.prototype.copyUrl = function() { Toolbar.prototype.copyUrl = function () {
if(copyText(window.location.href)) { if (copyText(window.location.href)) {
console.log('Text copied') console.log('Text copied')
this.spanCopyLink.innerText = "Copied!" this.spanCopyLink.innerText = "Copied!"
this.btnCopyLink.classList.add("button_highlight") this.btnCopyLink.classList.add("button_highlight")
@@ -194,9 +194,9 @@ Toolbar.prototype.copyUrl = function() {
} }
// Called by the google recaptcha script // Called by the google recaptcha script
let recaptchaElement = null let recaptchaElement = null
let recaptchaCallback = null let recaptchaCallback = null
function loadCaptcha(){ function loadCaptcha() {
grecaptcha.render(recaptchaElement, { grecaptcha.render(recaptchaElement, {
sitekey: captchaKey, sitekey: captchaKey,
theme: "dark", theme: "dark",

View File

@@ -19,7 +19,7 @@ import (
func (wc *WebController) viewTokenOrBust() (t string) { func (wc *WebController) viewTokenOrBust() (t string) {
var err error var err error
if t, err = wc.api.GetMiscViewToken(); err != nil { if t, err = wc.api.GetMiscViewToken(); err != nil && !wc.proxyAPIRequests {
log.Error("Could not get viewtoken: %s", err) log.Error("Could not get viewtoken: %s", err)
} }
return t return t

View File

@@ -6,6 +6,8 @@ import (
"fmt" "fmt"
"html/template" "html/template"
"net/http" "net/http"
"net/http/httputil"
"net/url"
"os" "os"
"strings" "strings"
"time" "time"
@@ -26,11 +28,11 @@ type WebController struct {
hostname string hostname string
apiURLInternal string apiURLInternal string
apiURLExternal string apiURLExternal string
websiteAddress string websiteAddress string
sessionCookieDomain string sessionCookieDomain string
proxyAPIRequests bool
// page-specific variables // page-specific variables
captchaSiteKey string captchaSiteKey string
@@ -55,6 +57,7 @@ func New(
sessionCookieDomain string, sessionCookieDomain string,
maintenanceMode bool, maintenanceMode bool,
debugMode bool, debugMode bool,
proxyAPIRequests bool,
) (wc *WebController) { ) (wc *WebController) {
var err error var err error
wc = &WebController{ wc = &WebController{
@@ -63,6 +66,7 @@ func New(
apiURLExternal: apiURLExternal, apiURLExternal: apiURLExternal,
websiteAddress: websiteAddress, websiteAddress: websiteAddress,
sessionCookieDomain: sessionCookieDomain, sessionCookieDomain: sessionCookieDomain,
proxyAPIRequests: proxyAPIRequests,
httpClient: &http.Client{Timeout: time.Minute * 10}, httpClient: &http.Client{Timeout: time.Minute * 10},
api: apiclient.New(apiURLInternal), api: apiclient.New(apiURLInternal),
} }
@@ -96,6 +100,23 @@ func New(
return wc return wc
} }
if proxyAPIRequests {
proxPath := strings.TrimSuffix(apiURLInternal, "/api")
proxURL, err := url.Parse(proxPath)
if err != nil {
panic(fmt.Errorf("failed to parse reverse proxy URL '%s': %w", proxPath, err))
}
var prox = httputil.NewSingleHostReverseProxy(proxURL)
prox.Transport = wc.httpClient.Transport
r.Handler("OPTIONS", "/api/*p", prox)
r.Handler("POST", "/api/*p", prox)
r.Handler("GET", "/api/*p", prox)
r.Handler("PUT", "/api/*p", prox)
r.Handler("PATCH", "/api/*p", prox)
r.Handler("DELETE", "/api/*p", prox)
}
r.NotFound = http.HandlerFunc(wc.serveNotFound) r.NotFound = http.HandlerFunc(wc.serveNotFound)
// Request method shorthands. These help keep the array of handlers aligned // Request method shorthands. These help keep the array of handlers aligned