Browse Source

web: support http basic auth in dashboard

fatedier 7 years ago
parent
commit
3f17837a2c

+ 4 - 2
Makefile

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

+ 1 - 1
assets/static/index.html

@@ -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 it is too large
+ 0 - 0
assets/static/index.js


+ 1 - 1
assets/static/manifest.js

@@ -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 it is too large
+ 0 - 0
assets/statik/statik.go


+ 48 - 21
server/dashboard.go

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

+ 3 - 1
server/proxy.go

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

+ 1 - 1
web/frps/src/components/Overview.vue

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

+ 2 - 2
web/frps/src/components/ProxiesHttp.vue

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

+ 2 - 2
web/frps/src/components/ProxiesHttps.vue

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

+ 1 - 1
web/frps/src/components/ProxiesTcp.vue

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

+ 1 - 1
web/frps/src/components/ProxiesUdp.vue

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

+ 1 - 1
web/frps/src/components/Traffic.vue

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

Some files were not shown because too many files changed in this diff