mirror of
https://github.com/gmemstr/sliproad.git
synced 2024-09-19 16:11:11 +01:00
Authentication handling with Keycloak.
Implemented a rudementary authentication method using Keycloak as the IdP - still very barebones, but login does function. Next steps will include a Docker Compose file (most likely) for managing this integration. The application will work fine without setting up the integration however, and will just throw a warning message. Setup should be relatively self explanatory, but some documentation is TBD, along with some automation when spinning up for the first time. Still not super happy with the implementation.
This commit is contained in:
parent
5e85a600d8
commit
c559f28ebb
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -16,6 +16,7 @@
|
|||
|
||||
# Config files
|
||||
providers.yml
|
||||
auth.yml
|
||||
|
||||
# Binary
|
||||
nas
|
||||
|
|
3
assets/config_examples/auth.yml
Normal file
3
assets/config_examples/auth.yml
Normal file
|
@ -0,0 +1,3 @@
|
|||
provider_url: "http://localhost:8080"
|
||||
realm: "nas"
|
||||
redirect_base_url: "http://localhost:3000"
|
23
authentication/keycloak.go
Normal file
23
authentication/keycloak.go
Normal file
|
@ -0,0 +1,23 @@
|
|||
package authentication
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/Nerzal/gocloak/v5"
|
||||
)
|
||||
|
||||
var AuthConfig map[string]string
|
||||
|
||||
func HasAuth(accessToken string) (success bool) {
|
||||
client := gocloak.NewClient(AuthConfig["provider_url"])
|
||||
_, err := client.GetUserInfo(accessToken, AuthConfig["realm"])
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func GetLoginLink() (url string) {
|
||||
baseString := "%v/auth/realms/%v/protocol/openid-connect/auth?client_id=account&response_mode=fragment&response_type=token&login=true&redirect_uri=%v/api/auth/callback"
|
||||
authUrl := fmt.Sprintf(baseString, AuthConfig["provider_url"], AuthConfig["realm"], AuthConfig["redirect_base_url"])
|
||||
return authUrl
|
||||
}
|
3
go.mod
3
go.mod
|
@ -3,11 +3,10 @@ module github.com/gmemstr/nas
|
|||
go 1.13
|
||||
|
||||
require (
|
||||
github.com/Nerzal/gocloak/v5 v5.1.0
|
||||
github.com/go-yaml/yaml v2.1.0+incompatible
|
||||
github.com/gorilla/mux v1.7.4
|
||||
github.com/kr/pretty v0.2.0 // indirect
|
||||
github.com/mattn/go-sqlite3 v2.0.3+incompatible
|
||||
golang.org/x/crypto v0.0.0-20200221231518-2aa609cf4a9d
|
||||
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 // indirect
|
||||
gopkg.in/yaml.v2 v2.2.8 // indirect
|
||||
)
|
||||
|
|
26
go.sum
26
go.sum
|
@ -1,3 +1,12 @@
|
|||
github.com/Nerzal/gocloak/v5 v5.1.0 h1:1YP4+GoY1DZ1k7WyNNr8xbFyt55B9ORn2ZHu+XqUK0Q=
|
||||
github.com/Nerzal/gocloak/v5 v5.1.0/go.mod h1:8v53okuWiWXOKOS6qil8cOn7+5JSQfX1t1d+Nj8FpYk=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumCAMpl/TFQ4/5kLM=
|
||||
github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
|
||||
github.com/go-resty/resty/v2 v2.0.0 h1:9Nq/U+V4xsoDnDa/iTrABDWUCuk3Ne92XFHPe6dKWUc=
|
||||
github.com/go-resty/resty/v2 v2.0.0/go.mod h1:dZGr0i9PLlaaTD4H/hoZIDjQ+r6xq8mgbRzHZf7f2J8=
|
||||
github.com/go-yaml/yaml v2.1.0+incompatible h1:RYi2hDdss1u4YE7GwixGzWwVo47T8UQwnTLB6vQiq+o=
|
||||
github.com/go-yaml/yaml v2.1.0+incompatible/go.mod h1:w2MrLa16VYP0jy6N7M5kHaCkaLENm+P+Tv+MfurjSw0=
|
||||
github.com/gorilla/mux v1.7.4 h1:VuZ8uybHlWmqV03+zRzdwKL4tUnIp1MAQtp1mIFE1bc=
|
||||
|
@ -7,17 +16,22 @@ github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfn
|
|||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
|
||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||
github.com/mattn/go-sqlite3 v2.0.3+incompatible h1:gXHsfypPkaMZrKbD5209QV9jbUTJKjyR5WD3HYQSd+U=
|
||||
github.com/mattn/go-sqlite3 v2.0.3+incompatible/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
|
||||
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk=
|
||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20200221231518-2aa609cf4a9d h1:1ZiEyfaQIg3Qh0EoqpwAakHVhecoE5wlSg5GjnafJGw=
|
||||
golang.org/x/crypto v0.0.0-20200221231518-2aa609cf4a9d/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190628185345-da137c7871d7 h1:rTIdg5QFRR7XCaK4LCjBiPbx8j4DQRpdYMnGn/bJUEU=
|
||||
golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo=
|
||||
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.7/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10=
|
||||
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
|
|
54
router/authrouter.go
Normal file
54
router/authrouter.go
Normal file
|
@ -0,0 +1,54 @@
|
|||
package router
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/gmemstr/nas/authentication"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
var AuthEnabled bool = true
|
||||
|
||||
func requiresAuth() Handler {
|
||||
return func(context *Context, w http.ResponseWriter, r *http.Request) *HTTPError {
|
||||
if !AuthEnabled {
|
||||
return nil
|
||||
}
|
||||
cookie, err := r.Cookie("NAS-SESSION")
|
||||
if err != nil || !authentication.HasAuth(cookie.Value) {
|
||||
if err != nil {
|
||||
fmt.Println("Error", err.Error())
|
||||
}
|
||||
http.Redirect(w, r, authentication.GetLoginLink(), 307)
|
||||
return &HTTPError{
|
||||
Message: "Unauthorized! Redirecting to /login",
|
||||
StatusCode: http.StatusTemporaryRedirect,
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func callbackAuth() Handler {
|
||||
return func(context *Context, w http.ResponseWriter, r *http.Request) *HTTPError {
|
||||
// Translate callback GET to POST to set cookie, then redirect.
|
||||
if r.Method == "GET" {
|
||||
javascript := `
|
||||
<script>fetch("/api/auth/callback", {method:"POST", body: window.location.hash.split("&")[1].split("=")[1]}).then((r) => window.location.href = "/")</script>`
|
||||
w.Write([]byte(javascript))
|
||||
return nil
|
||||
}
|
||||
token, _ := ioutil.ReadAll(r.Body)
|
||||
|
||||
// Set as HttpOnly cookie to mitigate XSS risk.
|
||||
jwtCookie := http.Cookie{Name: "NAS-SESSION",
|
||||
Value: string(token),
|
||||
HttpOnly: true,
|
||||
Path: "/",
|
||||
}
|
||||
|
||||
http.SetCookie(w, &jwtCookie)
|
||||
|
||||
return nil
|
||||
}
|
||||
}
|
|
@ -47,21 +47,31 @@ func Init() *mux.Router {
|
|||
|
||||
// Paths that require specific handlers
|
||||
r.Handle("/", Handle(
|
||||
requiresAuth(),
|
||||
rootHandler(),
|
||||
)).Methods("GET")
|
||||
|
||||
// File & Provider API
|
||||
r.Handle("/api/providers", Handle(
|
||||
requiresAuth(),
|
||||
ListProviders(),
|
||||
)).Methods("GET")
|
||||
|
||||
r.Handle(`/api/files/{provider:[a-zA-Z0-9]+\/*}`, Handle(
|
||||
requiresAuth(),
|
||||
HandleProvider(),
|
||||
)).Methods("GET", "POST")
|
||||
|
||||
r.Handle(`/api/files/{provider:[a-zA-Z0-9]+}/{file:.+}`, Handle(
|
||||
requiresAuth(),
|
||||
HandleProvider(),
|
||||
)).Methods("GET", "POST", "DELETE")
|
||||
|
||||
// Auth API & Endpoints
|
||||
r.Handle(`/api/auth/callback`, Handle(
|
||||
callbackAuth(),
|
||||
)).Methods("GET", "POST")
|
||||
|
||||
return r
|
||||
}
|
||||
|
||||
|
|
15
webserver.go
15
webserver.go
|
@ -2,6 +2,7 @@ package main
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/gmemstr/nas/authentication"
|
||||
"github.com/gmemstr/nas/files"
|
||||
"github.com/gmemstr/nas/router"
|
||||
"github.com/go-yaml/yaml"
|
||||
|
@ -23,6 +24,20 @@ func main() {
|
|||
}
|
||||
files.SetupProviders()
|
||||
|
||||
// Initialize auth if set up.
|
||||
authConfig, err := ioutil.ReadFile("auth.yml")
|
||||
if err != nil {
|
||||
fmt.Println("!! No Keycloack configuration found !!\n!! Requests will be unauthenticated !!")
|
||||
router.AuthEnabled = false
|
||||
} else {
|
||||
err = yaml.Unmarshal(authConfig, &authentication.AuthConfig)
|
||||
if err != nil {
|
||||
fmt.Println("Unable to parse auth.yml file, is it correct?")
|
||||
router.AuthEnabled = false
|
||||
}
|
||||
fmt.Println("Keycloak configured")
|
||||
}
|
||||
|
||||
r := router.Init()
|
||||
fmt.Println("Your NAS instance is live on port :3000")
|
||||
log.Fatal(http.ListenAndServe(":3000", r))
|
||||
|
|
Loading…
Reference in a new issue