summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMelonai <einebeere@gmail.com>2021-07-26 23:50:43 +0200
committerMelonai <einebeere@gmail.com>2021-07-26 23:50:43 +0200
commit175da8f22cd791e81338fe61e6099125868cf5a0 (patch)
tree91a234d8aafd54be34aae5ff5b948b34e52fb020
parent83a8214119eccb39f4c38e7b1ae54daebdeb0184 (diff)
downloadportgate-175da8f22cd791e81338fe61e6099125868cf5a0.tar.zst
portgate-175da8f22cd791e81338fe61e6099125868cf5a0.zip
Basic Authentication and Authorization
-rw-r--r--assets/templates/authenticate.template.html8
-rw-r--r--assets/templates/information.template.html2
-rw-r--r--config.go11
-rw-r--r--go.mod1
-rw-r--r--go.sum2
-rw-r--r--handlers/passthrough.go8
-rw-r--r--handlers/portgate.go43
-rw-r--r--token.go42
8 files changed, 111 insertions, 6 deletions
diff --git a/assets/templates/authenticate.template.html b/assets/templates/authenticate.template.html
index 21f31e4..7d6d72c 100644
--- a/assets/templates/authenticate.template.html
+++ b/assets/templates/authenticate.template.html
@@ -1,3 +1,9 @@
{{define "content"}}
-<h1>This is the authentication page...</h1>
+<h1>Authentication</h1>
+<form method="POST">
+ <label>
+ <input type="text" name="key">
+ </label>
+ <input type="submit" value="Authenticate">
+</form>
{{end}} \ No newline at end of file
diff --git a/assets/templates/information.template.html b/assets/templates/information.template.html
index e87885c..a881cf8 100644
--- a/assets/templates/information.template.html
+++ b/assets/templates/information.template.html
@@ -1,3 +1,3 @@
{{define "content"}}
-<h1>This is the information page...</h1>
+<h1>You are authenticated!</h1>
{{end}} \ No newline at end of file
diff --git a/config.go b/config.go
index b4e0754..7630e87 100644
--- a/config.go
+++ b/config.go
@@ -13,6 +13,10 @@ type Config struct {
allowedPorts []int
forbiddenPorts []int
+
+ key string
+
+ jwtSecret string
}
// GetConfig creates the Portgate config from outside sources such as
@@ -25,6 +29,8 @@ func GetConfig() (Config, error) {
allowedPorts: []int{80},
forbiddenPorts: []int{},
+
+ key: "password",
}, nil
}
@@ -43,3 +49,8 @@ func (c *Config) MakeUrl(p Path) string {
// TODO: Figure out what to do with TLS
return fmt.Sprintf("http://%s:%d%s", c.targetHost, p.DestinationIdentifier, p.ResourcePath)
}
+
+// CheckKey checks whether the givenKey matches the one in the config.
+func (c *Config) CheckKey(givenKey string) bool {
+ return c.key == givenKey
+}
diff --git a/go.mod b/go.mod
index 6ba1ccd..122ac96 100644
--- a/go.mod
+++ b/go.mod
@@ -4,6 +4,7 @@ go 1.16
require (
github.com/valyala/fasthttp v1.28.0
+ github.com/golang-jwt/jwt v3.2.1+incompatible
github.com/andybalholm/brotli v1.0.3 // indirect
github.com/klauspost/compress v1.13.1 // indirect
diff --git a/go.sum b/go.sum
index c912579..0a7383f 100644
--- a/go.sum
+++ b/go.sum
@@ -1,6 +1,8 @@
github.com/andybalholm/brotli v1.0.2/go.mod h1:loMXtMfwqflxFJPmdbJO0a3KNoPuLBgiu3qAvBg8x/Y=
github.com/andybalholm/brotli v1.0.3 h1:fpcw+r1N1h0Poc1F/pHbW40cUm/lMEQslZtCkBQ0UnM=
github.com/andybalholm/brotli v1.0.3/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig=
+github.com/golang-jwt/jwt v3.2.1+incompatible h1:73Z+4BJcrTC+KczS6WvTPvRGOp1WmfEP4Q1lOd9Z/+c=
+github.com/golang-jwt/jwt v3.2.1+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I=
github.com/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
github.com/klauspost/compress v1.12.2/go.mod h1:8dP1Hq4DHOhN9w426knH3Rhby4rFm6D8eO+e+Dq5Gzg=
github.com/klauspost/compress v1.13.1 h1:wXr2uRxZTJXHLly6qhJabee5JqIhTRoLBhDOA74hDEQ=
diff --git a/handlers/passthrough.go b/handlers/passthrough.go
index b2daa88..3f8aafc 100644
--- a/handlers/passthrough.go
+++ b/handlers/passthrough.go
@@ -2,6 +2,7 @@ package handlers
import (
"github.com/valyala/fasthttp"
+ "net/http"
"portgate"
)
@@ -9,9 +10,14 @@ import (
// If the user is authorized they are allowed to pass, otherwise they should be redirected to
// the authentication page. (/_portgate)
func (h *RequestHandler) handlePassthroughRequest(ctx *fasthttp.RequestCtx, p portgate.Path) {
- // TODO: Check authorization.
// TODO: Check whether port is allowed to be accessed.
+ // Check whether given cookie is ok, if not redirect to the authentication page.
+ if !portgate.VerifyTokenFromCookie(h.config, ctx) {
+ ctx.Redirect("/_portgate", http.StatusTemporaryRedirect)
+ return
+ }
+
// We reuse the request given to us by the user with minor changes to route it to the
// destination host.
ctx.Request.SetRequestURI(h.config.MakeUrl(p))
diff --git a/handlers/portgate.go b/handlers/portgate.go
index f53f3c9..6e002e7 100644
--- a/handlers/portgate.go
+++ b/handlers/portgate.go
@@ -2,7 +2,9 @@ package handlers
import (
"github.com/valyala/fasthttp"
+ "net/http"
"portgate"
+ "time"
)
// handlePortgateRequest handles all Portgate specific request for either showing Portgate
@@ -32,14 +34,49 @@ func (h *RequestHandler) handlePortgateIndexRequest(ctx *fasthttp.RequestCtx) {
// handlePortgatePageRequest renders the Portgate page with either the authentication page or
// a basic information page.
func (h *RequestHandler) handlePortgatePageRequest(ctx *fasthttp.RequestCtx) {
- // We render the page template and pass it to the user.
ctx.Response.Header.SetContentType("text/html")
- err := h.templates.ExecuteTemplate(ctx, "authenticate.template.html", nil)
+
+ var err error
+
+ // We render the page template and pass it to the user.
+ if portgate.VerifyTokenFromCookie(h.config, ctx) {
+ // User is authenticated, show the information page
+ err = h.templates.ExecuteTemplate(ctx, "information.template.html", nil)
+ } else {
+ // Show the authentication page
+ err = h.templates.ExecuteTemplate(ctx, "authenticate.template.html", nil)
+ }
+
if err != nil {
h.handleError(ctx)
}
}
func (h *RequestHandler) handleAuthenticateRequest(ctx *fasthttp.RequestCtx) {
- // TODO
+
+ givenKey := ctx.PostArgs().Peek("key")
+ if givenKey == nil || !h.config.CheckKey(string(givenKey)) {
+ ctx.Error("Wrong key.", http.StatusUnauthorized)
+ return
+ }
+
+ token, err := portgate.CreateToken(h.config, string(givenKey))
+ if err != nil {
+ h.handleError(ctx)
+ }
+
+ cookie := fasthttp.AcquireCookie()
+ defer fasthttp.ReleaseCookie(cookie)
+
+ cookie.SetExpire(portgate.GetExpirationDateFrom(time.Now()))
+ cookie.SetSameSite(fasthttp.CookieSameSiteStrictMode)
+ cookie.SetHTTPOnly(true)
+ cookie.SetKey("_portgate_token")
+ cookie.SetValue(token)
+
+ ctx.Response.Header.SetCookie(cookie)
+
+ // TODO: Redirect to previously request path.
+ // http.StatusFound redirects a POST request to a GET request.
+ ctx.Redirect("/_portgate", http.StatusFound)
}
diff --git a/token.go b/token.go
new file mode 100644
index 0000000..1031e7c
--- /dev/null
+++ b/token.go
@@ -0,0 +1,42 @@
+package portgate
+
+import (
+ "github.com/golang-jwt/jwt"
+ "github.com/valyala/fasthttp"
+ "time"
+)
+
+func CreateToken(config *Config, givenKey string) (string, error) {
+ // Our token will last 7 days
+ token := jwt.NewWithClaims(jwt.SigningMethodHS512, jwt.StandardClaims{
+ ExpiresAt: GetExpirationDateFrom(time.Now()).Unix(),
+ IssuedAt: time.Now().Unix(),
+ })
+
+ return token.SignedString([]byte(config.jwtSecret))
+}
+
+func VerifyToken(config *Config, tokenString string) (bool, error) {
+ token, err := jwt.Parse(tokenString, func(token *jwt.Token) (interface{}, error) {
+ return []byte(config.jwtSecret), nil
+ })
+
+ if err == nil && token.Valid {
+ return true, nil
+ } else {
+ return false, err
+ }
+}
+
+func VerifyTokenFromCookie(config *Config, ctx *fasthttp.RequestCtx) bool {
+ cookie := ctx.Request.Header.Cookie("_portgate_token")
+ if cookie != nil {
+ ok, _ := VerifyToken(config, string(cookie))
+ return ok
+ }
+ return false
+}
+
+func GetExpirationDateFrom(date time.Time) time.Time {
+ return date.AddDate(0, 0, 7)
+}