diff --git a/api.go b/api.go index fc652d7..9a909fc 100644 --- a/api.go +++ b/api.go @@ -7,12 +7,18 @@ import ( "github.com/franela/play-with-docker/handlers" "github.com/franela/play-with-docker/services" + "github.com/franela/play-with-docker/templates" "github.com/gorilla/mux" "github.com/urfave/negroni" ) func main() { + welcome, tmplErr := templates.GetWelcomeTemplate() + if tmplErr != nil { + log.Fatal(tmplErr) + } + server := services.CreateWSServer() server.On("connection", handlers.WS) @@ -27,7 +33,11 @@ func main() { r.StrictSlash(false) r.HandleFunc("/ping", http.HandlerFunc(handlers.Ping)).Methods("GET") - r.HandleFunc("/", http.HandlerFunc(handlers.NewSession)).Methods("GET") + r.HandleFunc("/", http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) { + rw.Write(welcome) + })).Methods("GET") + r.HandleFunc("/", http.HandlerFunc(handlers.NewSession)).Methods("POST") + r.HandleFunc("/sessions/{sessionId}", http.HandlerFunc(handlers.GetSession)).Methods("GET") r.HandleFunc("/sessions/{sessionId}/instances", http.HandlerFunc(handlers.NewInstance)).Methods("POST") r.HandleFunc("/sessions/{sessionId}/instances/{instanceName}", http.HandlerFunc(handlers.DeleteInstance)).Methods("DELETE") diff --git a/handlers/new_session.go b/handlers/new_session.go index d476f6a..8cb497c 100644 --- a/handlers/new_session.go +++ b/handlers/new_session.go @@ -9,6 +9,13 @@ import ( ) func NewSession(rw http.ResponseWriter, req *http.Request) { + if !services.IsHuman(req) { + // User it not a human + rw.WriteHeader(http.StatusConflict) + rw.Write([]byte("Only humans are allowed!")) + return + } + s, err := services.NewSession() if err != nil { log.Println(err) diff --git a/services/recaptcha.go b/services/recaptcha.go new file mode 100644 index 0000000..b3eb150 --- /dev/null +++ b/services/recaptcha.go @@ -0,0 +1,59 @@ +package services + +import ( + "encoding/json" + "log" + "net/http" + "net/url" + "os" + "strings" +) + +func GetGoogleRecaptchaSiteKey() string { + key := os.Getenv("GOOGLE_RECAPTCHA_SITE_KEY") + if key == "" { + // This is a development default. The environment variable should always be set in production. + key = "6LeY_QsUAAAAAOlpVw4MhoLEr50h-dM80oz6M2AX" + } + return key +} +func GetGoogleRecaptchaSiteSecret() string { + key := os.Getenv("GOOGLE_RECAPTCHA_SITE_SECRET") + if key == "" { + // This is a development default. The environment variable should always be set in production. + key = "6LeY_QsUAAAAAHIALCtm0GKfk-UhtXoyJKarnRV8" + } + + return key +} + +type recaptchaResponse struct { + Success bool `json:"success"` +} + +func IsHuman(req *http.Request) bool { + req.ParseForm() + challenge := req.Form.Get("g-recaptcha-response") + + // Of X-Forwarded-For exists, it means we are behind a loadbalancer and we should use the real IP address of the user + ip := req.Header.Get("X-Forwarded-For") + if ip == "" { + // Use the standard remote IP address of the request + + ip = req.RemoteAddr + } + + parts := strings.Split(ip, ":") + + resp, postErr := http.PostForm("https://www.google.com/recaptcha/api/siteverify", url.Values{"secret": {GetGoogleRecaptchaSiteSecret()}, "response": {challenge}, "remoteip": {parts[0]}}) + if postErr != nil { + log.Println(postErr) + // If there is a problem to connect to google, assume the user is a human so we don't block real users because of technical issues + return true + } + + var r recaptchaResponse + json.NewDecoder(resp.Body).Decode(&r) + + return r.Success +} diff --git a/templates/welcome.go b/templates/welcome.go new file mode 100644 index 0000000..d8eed5a --- /dev/null +++ b/templates/welcome.go @@ -0,0 +1,21 @@ +package templates + +import ( + "bytes" + "html/template" + + "github.com/franela/play-with-docker/services" +) + +func GetWelcomeTemplate() ([]byte, error) { + welcomeTemplate, tplErr := template.New("welcome").ParseFiles("www/welcome.html") + if tplErr != nil { + return nil, tplErr + } + var b bytes.Buffer + tplExecuteErr := welcomeTemplate.ExecuteTemplate(&b, "GOOGLE_RECAPTCHA_SITE_KEY", services.GetGoogleRecaptchaSiteKey()) + if tplExecuteErr != nil { + return nil, tplExecuteErr + } + return b.Bytes(), nil +} diff --git a/www/assets/large_h.png b/www/assets/large_h.png new file mode 100644 index 0000000..eddd2b6 Binary files /dev/null and b/www/assets/large_h.png differ diff --git a/www/assets/style.css b/www/assets/style.css index d98c539..388c256 100644 --- a/www/assets/style.css +++ b/www/assets/style.css @@ -27,3 +27,18 @@ md-card-content.terminal { color: #1da4eb; text-align: center; } + +.welcome { + background-color: #e7e7e7; +} + +.welcome > div { + text-align: center; +} + +.g-recaptcha div { + margin-left: auto; + margin-right: auto; + margin-bottom: auto; + margin-top: 50px; +} diff --git a/www/welcome.html b/www/welcome.html new file mode 100644 index 0000000..512d7a1 --- /dev/null +++ b/www/welcome.html @@ -0,0 +1,27 @@ +{{define "GOOGLE_RECAPTCHA_SITE_KEY"}} + + + + Docker Playground + + + + + +
+

Welcome!

+

Before starting we need to verify you are a human

+
+
+
+ +
+ + + + +{{end}}