web: support http basic auth in dashboard

This commit is contained in:
fatedier 2017-05-15 21:18:06 +08:00
parent fd268b5082
commit 3f17837a2c
13 changed files with 71 additions and 40 deletions

View File

@ -7,9 +7,11 @@ build: frps frpc
# compile assets into binary file # compile assets into binary file
file: file:
rm -rf ./assets/static/*
cp -rf ./web/frps/dist/* ./assets/static
go get -d github.com/rakyll/statik go get -d github.com/rakyll/statik
@go install github.com/rakyll/statik go install github.com/rakyll/statik
@rm -rf ./assets/statik rm -rf ./assets/statik
go generate ./assets/... go generate ./assets/...
fmt: fmt:

View File

@ -1 +1 @@
<!DOCTYPE html> <html lang=en> <head> <meta charset=utf-8> <title>frps dashboard</title> <link rel="shortcut icon" href="favicon.ico"></head> <body> <div id=app></div> <script type="text/javascript" src="manifest.js?b99c1346247a42f912f8"></script><script type="text/javascript" src="vendor.js?66dfcf2d1c500e900413"></script><script type="text/javascript" src="index.js?ba80138d6369555bbd4e"></script></body> </html> <!DOCTYPE html> <html lang=en> <head> <meta charset=utf-8> <title>frps dashboard</title> <link rel="shortcut icon" href="favicon.ico"></head> <body> <div id=app></div> <script type="text/javascript" src="manifest.js?b52826060da73c6b5a10"></script><script type="text/javascript" src="vendor.js?66dfcf2d1c500e900413"></script><script type="text/javascript" src="index.js?ceb589f1be7a87112dbd"></script></body> </html>

File diff suppressed because one or more lines are too long

View File

@ -1 +1 @@
!function(e){function r(n){if(t[n])return t[n].exports;var o=t[n]={i:n,l:!1,exports:{}};return e[n].call(o.exports,o,o.exports,r),o.l=!0,o.exports}var n=window.webpackJsonp;window.webpackJsonp=function(t,c,u){for(var i,a,f,l=0,s=[];l<t.length;l++)a=t[l],o[a]&&s.push(o[a][0]),o[a]=0;for(i in c)Object.prototype.hasOwnProperty.call(c,i)&&(e[i]=c[i]);for(n&&n(t,c,u);s.length;)s.shift()();if(u)for(l=0;l<u.length;l++)f=r(r.s=u[l]);return f};var t={},o={2:0};r.e=function(e){function n(){u.onerror=u.onload=null,clearTimeout(i);var r=o[e];0!==r&&(r&&r[1](new Error("Loading chunk "+e+" failed.")),o[e]=void 0)}if(0===o[e])return Promise.resolve();if(o[e])return o[e][2];var t=new Promise(function(r,n){o[e]=[r,n]});o[e][2]=t;var c=document.getElementsByTagName("head")[0],u=document.createElement("script");u.type="text/javascript",u.charset="utf-8",u.async=!0,u.timeout=12e4,r.nc&&u.setAttribute("nonce",r.nc),u.src=r.p+""+e+".js?"+{0:"ba80138d6369555bbd4e",1:"66dfcf2d1c500e900413"}[e];var i=setTimeout(n,12e4);return u.onerror=u.onload=n,c.appendChild(u),t},r.m=e,r.c=t,r.i=function(e){return e},r.d=function(e,n,t){r.o(e,n)||Object.defineProperty(e,n,{configurable:!1,enumerable:!0,get:t})},r.n=function(e){var n=e&&e.__esModule?function(){return e.default}:function(){return e};return r.d(n,"a",n),n},r.o=function(e,r){return Object.prototype.hasOwnProperty.call(e,r)},r.p="",r.oe=function(e){throw console.error(e),e}}([]); !function(e){function r(n){if(t[n])return t[n].exports;var o=t[n]={i:n,l:!1,exports:{}};return e[n].call(o.exports,o,o.exports,r),o.l=!0,o.exports}var n=window.webpackJsonp;window.webpackJsonp=function(t,c,u){for(var i,a,f,l=0,s=[];l<t.length;l++)a=t[l],o[a]&&s.push(o[a][0]),o[a]=0;for(i in c)Object.prototype.hasOwnProperty.call(c,i)&&(e[i]=c[i]);for(n&&n(t,c,u);s.length;)s.shift()();if(u)for(l=0;l<u.length;l++)f=r(r.s=u[l]);return f};var t={},o={2:0};r.e=function(e){function n(){u.onerror=u.onload=null,clearTimeout(i);var r=o[e];0!==r&&(r&&r[1](new Error("Loading chunk "+e+" failed.")),o[e]=void 0)}if(0===o[e])return Promise.resolve();if(o[e])return o[e][2];var t=new Promise(function(r,n){o[e]=[r,n]});o[e][2]=t;var c=document.getElementsByTagName("head")[0],u=document.createElement("script");u.type="text/javascript",u.charset="utf-8",u.async=!0,u.timeout=12e4,r.nc&&u.setAttribute("nonce",r.nc),u.src=r.p+""+e+".js?"+{0:"ceb589f1be7a87112dbd",1:"66dfcf2d1c500e900413"}[e];var i=setTimeout(n,12e4);return u.onerror=u.onload=n,c.appendChild(u),t},r.m=e,r.c=t,r.i=function(e){return e},r.d=function(e,n,t){r.o(e,n)||Object.defineProperty(e,n,{configurable:!1,enumerable:!0,get:t})},r.n=function(e){var n=e&&e.__esModule?function(){return e.default}:function(){return e};return r.d(n,"a",n),n},r.o=function(e,r){return Object.prototype.hasOwnProperty.call(e,r)},r.p="",r.oe=function(e){throw console.error(e),e}}([]);

File diff suppressed because one or more lines are too long

View File

@ -36,20 +36,19 @@ func RunDashboardServer(addr string, port int64) (err error) {
router := httprouter.New() router := httprouter.New()
// api, see dashboard_api.go // api, see dashboard_api.go
//mux.HandleFunc("/api/reload", use(apiReload, basicAuth)) router.GET("/api/serverinfo", httprouterBasicAuth(apiServerInfo))
router.GET("/api/serverinfo", apiServerInfo) router.GET("/api/proxy/tcp", httprouterBasicAuth(apiProxyTcp))
router.GET("/api/proxy/tcp", apiProxyTcp) router.GET("/api/proxy/udp", httprouterBasicAuth(apiProxyUdp))
router.GET("/api/proxy/udp", apiProxyUdp) router.GET("/api/proxy/http", httprouterBasicAuth(apiProxyHttp))
router.GET("/api/proxy/http", apiProxyHttp) router.GET("/api/proxy/https", httprouterBasicAuth(apiProxyHttps))
router.GET("/api/proxy/https", apiProxyHttps) router.GET("/api/proxy/traffic/:name", httprouterBasicAuth(apiProxyTraffic))
router.GET("/api/proxy/traffic/:name", apiProxyTraffic)
// view // view
router.Handler("GET", "/favicon.ico", http.FileServer(assets.FileSystem)) router.Handler("GET", "/favicon.ico", http.FileServer(assets.FileSystem))
router.Handler("GET", "/static/*filepath", http.StripPrefix("/static/", http.FileServer(assets.FileSystem))) router.Handler("GET", "/static/*filepath", basicAuthWraper(http.StripPrefix("/static/", http.FileServer(assets.FileSystem))))
router.HandlerFunc("GET", "/", func(w http.ResponseWriter, r *http.Request) { router.HandlerFunc("GET", "/", basicAuth(func(w http.ResponseWriter, r *http.Request) {
http.Redirect(w, r, "/static/", http.StatusMovedPermanently) http.Redirect(w, r, "/static/", http.StatusMovedPermanently)
}) }))
address := fmt.Sprintf("%s:%d", addr, port) address := fmt.Sprintf("%s:%d", addr, port)
server := &http.Server{ server := &http.Server{
@ -77,22 +76,50 @@ func use(h http.HandlerFunc, middleware ...func(http.HandlerFunc) http.HandlerFu
return h return h
} }
func basicAuth(h http.HandlerFunc) http.HandlerFunc { type AuthWraper struct {
return func(w http.ResponseWriter, r *http.Request) { h http.Handler
user string
passwd string
}
func (aw *AuthWraper) ServeHTTP(w http.ResponseWriter, r *http.Request) {
user, passwd, hasAuth := r.BasicAuth()
if hasAuth && user == aw.user || passwd == aw.passwd {
aw.h.ServeHTTP(w, r)
} else {
w.Header().Set("WWW-Authenticate", `Basic realm="Restricted"`) w.Header().Set("WWW-Authenticate", `Basic realm="Restricted"`)
http.Error(w, http.StatusText(http.StatusUnauthorized), http.StatusUnauthorized)
username, passwd, ok := r.BasicAuth() }
if !ok { }
http.Error(w, "Not authorized", 401)
return func basicAuthWraper(h http.Handler) http.Handler {
} return &AuthWraper{
h: h,
if username != config.ServerCommonCfg.DashboardUser || passwd != config.ServerCommonCfg.DashboardPwd { user: config.ServerCommonCfg.DashboardUser,
http.Error(w, "Not authorized", 401) passwd: config.ServerCommonCfg.DashboardPwd,
return }
} }
h.ServeHTTP(w, r) func basicAuth(h http.HandlerFunc) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
user, passwd, hasAuth := r.BasicAuth()
if hasAuth && user == config.ServerCommonCfg.DashboardUser || passwd == config.ServerCommonCfg.DashboardPwd {
h.ServeHTTP(w, r)
} else {
w.Header().Set("WWW-Authenticate", `Basic realm="Restricted"`)
http.Error(w, http.StatusText(http.StatusUnauthorized), http.StatusUnauthorized)
}
}
}
func httprouterBasicAuth(h httprouter.Handle) httprouter.Handle {
return func(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
user, passwd, hasAuth := r.BasicAuth()
if hasAuth && user == config.ServerCommonCfg.DashboardUser || passwd == config.ServerCommonCfg.DashboardPwd {
h(w, r, ps)
} else {
w.Header().Set("WWW-Authenticate", `Basic realm="Restricted"`)
http.Error(w, http.StatusText(http.StatusUnauthorized), http.StatusUnauthorized)
}
} }
} }

View File

@ -434,7 +434,9 @@ func (pxy *UdpProxy) Close() {
pxy.isClosed = true pxy.isClosed = true
pxy.BaseProxy.Close() pxy.BaseProxy.Close()
pxy.workConn.Close() if pxy.workConn != nil {
pxy.workConn.Close()
}
pxy.udpConn.Close() pxy.udpConn.Close()
// all channels only closed here // all channels only closed here

View File

@ -66,7 +66,7 @@
}, },
methods: { methods: {
fetchData() { fetchData() {
fetch('/api/serverinfo') fetch('/api/serverinfo', {credentials: 'include'})
.then(res => { .then(res => {
return res.json() return res.json()
}).then(json => { }).then(json => {

View File

@ -110,7 +110,7 @@
return Humanize.fileSize(row.traffic_out) return Humanize.fileSize(row.traffic_out)
}, },
fetchData() { fetchData() {
fetch('/api/serverinfo') fetch('/api/serverinfo', {credentials: 'include'})
.then(res => { .then(res => {
return res.json() return res.json()
}).then(json => { }).then(json => {
@ -119,7 +119,7 @@
if (this.vhost_http_port == null || this.vhost_http_port == 0) { if (this.vhost_http_port == null || this.vhost_http_port == 0) {
return return
} else { } else {
fetch('/api/proxy/http') fetch('/api/proxy/http', {credentials: 'include'})
.then(res => { .then(res => {
return res.json() return res.json()
}).then(json => { }).then(json => {

View File

@ -105,7 +105,7 @@
return Humanize.fileSize(row.traffic_out) return Humanize.fileSize(row.traffic_out)
}, },
fetchData() { fetchData() {
fetch('/api/serverinfo') fetch('/api/serverinfo', {credentials: 'include'})
.then(res => { .then(res => {
return res.json() return res.json()
}).then(json => { }).then(json => {
@ -114,7 +114,7 @@
if (this.vhost_https_port == null || this.vhost_https_port == 0) { if (this.vhost_https_port == null || this.vhost_https_port == 0) {
return return
} else { } else {
fetch('/api/proxy/https') fetch('/api/proxy/https', {credentials: 'include'})
.then(res => { .then(res => {
return res.json() return res.json()
}).then(json => { }).then(json => {

View File

@ -97,7 +97,7 @@
return Humanize.fileSize(row.traffic_out) return Humanize.fileSize(row.traffic_out)
}, },
fetchData() { fetchData() {
fetch('/api/proxy/tcp') fetch('/api/proxy/tcp', {credentials: 'include'})
.then(res => { .then(res => {
return res.json() return res.json()
}).then(json => { }).then(json => {

View File

@ -99,7 +99,7 @@
return Humanize.fileSize(row.traffic_out) return Humanize.fileSize(row.traffic_out)
}, },
fetchData() { fetchData() {
fetch('/api/proxy/udp') fetch('/api/proxy/udp', {credentials: 'include'})
.then(res => { .then(res => {
return res.json() return res.json()
}).then(json => { }).then(json => {

View File

@ -15,7 +15,7 @@ export default {
methods: { methods: {
fetchData() { fetchData() {
let url = '/api/proxy/traffic/' + this.proxy_name let url = '/api/proxy/traffic/' + this.proxy_name
fetch(url) fetch(url, {credentials: 'include'})
.then(res => { .then(res => {
return res.json() return res.json()
}).then(json => { }).then(json => {