1
0
mirror of https://github.com/bingohuang/docker-labs.git synced 2025-07-14 10:17:26 +08:00

Enable use of override for session timeout in hours, fix captcha bypass bug. (#51)

* - Enable use of override for session timeout. This is more useful than having to hard-code and rebuild the code for the previous 4 hour limit. Just set environmental variable and start the app.
- Future work may involve breaking down into minutes, but this is a good minimum delivery to provide value to end-user/developer.

- Fixes bug in Captcha code by introducing new landing page. This is not a new go template, it's a separate HTML file because SRP - single reponsibility principle. Happy for this to be refacted after merging commit.

- Fix for including Docker 1.12 override has been removed for later PR.

* Merge

* Reinstate 'material' JS include'

* https for JS includes

* HTTPs for JS in bypass
This commit is contained in:
Alex Ellis 2016-11-30 18:17:18 +00:00 committed by Marcos Nils
parent d3e20724e9
commit 5eda323477
6 changed files with 78 additions and 25 deletions

27
api.go
View File

@ -5,28 +5,24 @@ import (
"net/http" "net/http"
"os" "os"
"flag"
"strconv"
"github.com/franela/play-with-docker/handlers" "github.com/franela/play-with-docker/handlers"
"github.com/franela/play-with-docker/services" "github.com/franela/play-with-docker/services"
"github.com/franela/play-with-docker/templates" "github.com/franela/play-with-docker/templates"
"github.com/gorilla/mux" "github.com/gorilla/mux"
"github.com/urfave/negroni" "github.com/urfave/negroni"
"flag"
"strconv"
) )
func main() { func main() {
var portNumber int var portNumber int
flag.IntVar(&portNumber, "port", 3000, "Give a TCP port to run the application") flag.IntVar(&portNumber, "port", 3000, "Give a TCP port to run the application")
flag.Parse() flag.Parse()
welcome, tmplErr := templates.GetWelcomeTemplate() bypassCaptcha := len(os.Getenv("GOOGLE_RECAPTCHA_DISABLED")) > 0
if tmplErr != nil {
log.Fatal(tmplErr)
}
server := services.CreateWSServer() server := services.CreateWSServer()
server.On("connection", handlers.WS) server.On("connection", handlers.WS)
server.On("error", handlers.WSError) server.On("error", handlers.WSError)
@ -45,9 +41,19 @@ func main() {
r.StrictSlash(false) r.StrictSlash(false)
r.HandleFunc("/ping", http.HandlerFunc(handlers.Ping)).Methods("GET") r.HandleFunc("/ping", http.HandlerFunc(handlers.Ping)).Methods("GET")
r.HandleFunc("/", http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) { r.HandleFunc("/", http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
rw.Write(welcome) if bypassCaptcha {
http.ServeFile(rw, r, "./www/bypass.html")
} else {
welcome, tmplErr := templates.GetWelcomeTemplate()
if tmplErr != nil {
log.Fatal(tmplErr)
}
rw.Write(welcome)
}
})).Methods("GET") })).Methods("GET")
r.HandleFunc("/", http.HandlerFunc(handlers.NewSession)).Methods("POST") r.HandleFunc("/", http.HandlerFunc(handlers.NewSession)).Methods("POST")
r.HandleFunc("/sessions/{sessionId}", http.HandlerFunc(handlers.GetSession)).Methods("GET") r.HandleFunc("/sessions/{sessionId}", http.HandlerFunc(handlers.GetSession)).Methods("GET")
@ -57,6 +63,7 @@ func main() {
h := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { h := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
http.ServeFile(w, r, "./www/index.html") http.ServeFile(w, r, "./www/index.html")
}) })
r.HandleFunc("/p/{sessionId}", h).Methods("GET") r.HandleFunc("/p/{sessionId}", h).Methods("GET")
r.PathPrefix("/assets").Handler(http.FileServer(http.Dir("./www"))) r.PathPrefix("/assets").Handler(http.FileServer(http.Dir("./www")))
r.HandleFunc("/robots.txt", http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) { r.HandleFunc("/robots.txt", http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
@ -68,7 +75,7 @@ func main() {
n := negroni.Classic() n := negroni.Classic()
n.UseHandler(r) n.UseHandler(r)
log.Println("Listening on port "+ strconv.Itoa(portNumber)) log.Println("Listening on port " + strconv.Itoa(portNumber))
log.Fatal(http.ListenAndServe("0.0.0.0:"+strconv.Itoa(portNumber), n)) log.Fatal(http.ListenAndServe("0.0.0.0:"+strconv.Itoa(portNumber), n))
} }

View File

@ -5,6 +5,7 @@ import (
"log" "log"
"math" "math"
"os" "os"
"strconv"
"sync" "sync"
"time" "time"
@ -91,18 +92,35 @@ func CloseSession(s *Session) error {
return nil return nil
} }
// Todo: this handles minimum viable product and removes hard-coding of hours value :)
// For future enhance to return time.Duration and parse a string / flag.
func getExpiryHours() int {
hours := 4
override := os.Getenv("EXPIRY")
if len(override) > 0 {
value, err := strconv.Atoi(override)
if err == nil {
hours = value
}
}
return hours
}
func NewSession() (*Session, error) { func NewSession() (*Session, error) {
hours := getExpiryHours()
duration := time.Duration(hours) * time.Hour
s := &Session{} s := &Session{}
s.Id = uuid.NewV4().String() s.Id = uuid.NewV4().String()
s.Instances = map[string]*Instance{} s.Instances = map[string]*Instance{}
s.CreatedAt = time.Now() s.CreatedAt = time.Now()
s.ExpiresAt = s.CreatedAt.Add(4 * time.Hour) s.ExpiresAt = s.CreatedAt.Add(duration)
log.Printf("NewSession id=[%s]\n", s.Id) log.Printf("NewSession id=[%s]\n", s.Id)
sessions[s.Id] = s sessions[s.Id] = s
// Schedule cleanup of the session // Schedule cleanup of the session
CloseSessionAfter(s, 4*time.Hour) CloseSessionAfter(s, duration)
if err := CreateNetwork(s.Id); err != nil { if err := CreateNetwork(s.Id); err != nil {
log.Println("ERROR NETWORKING") log.Println("ERROR NETWORKING")

View File

@ -3,6 +3,14 @@
var app = angular.module('DockerPlay', ['ngMaterial']); var app = angular.module('DockerPlay', ['ngMaterial']);
// Automatically redirects user to a new session when bypassing captcha.
// Controller keeps code/logic separate from the HTML
app.controller("BypassController", ['$scope', '$log', '$http', '$location', '$timeout', '$mdDialog', '$window', function($scope, $log, $http, $location, $timeout, $mdDialog, $window) {
setTimeout(function() {
var el = document.querySelector("#submit");
el.click();
}, 500);
}]);
app.controller('PlayController', ['$scope', '$log', '$http', '$location', '$timeout', '$mdDialog', '$window', function($scope, $log, $http, $location, $timeout, $mdDialog, $window) { app.controller('PlayController', ['$scope', '$log', '$http', '$location', '$timeout', '$mdDialog', '$window', function($scope, $log, $http, $location, $timeout, $mdDialog, $window) {
$scope.sessionId = window.location.pathname.replace('/p/', ''); $scope.sessionId = window.location.pathname.replace('/p/', '');

25
www/bypass.html Normal file
View File

@ -0,0 +1,25 @@
<!doctype html>
<html ng-app="DockerPlay" ng-controller="BypassController">
<head>
<title>Docker Playground</title>
<link rel="stylesheet" href="http://ajax.googleapis.com/ajax/libs/angular_material/1.1.0/angular-material.min.css">
<link rel="stylesheet" href="/assets/style.css" />
</head>
<body class="welcome">
<div>
<h1>Welcome!</h1>
<h2>We're bypassing the Captcha and redirecting you now..</h2>
<form id="welcomeFormBypass" method="POST" action="/">
<button id="submit" type="submit">Start Session</button>
</form>
</div>
</body>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.5.5/angular.min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.5.5/angular-animate.min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.5.5/angular-aria.min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.5.5/angular-messages.min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/angular_material/1.1.0/angular-material.min.js"></script>
<script src="/assets/app.js"></script>
</html>

View File

@ -8,6 +8,7 @@
<link rel="stylesheet" href="/assets/style.css" /> <link rel="stylesheet" href="/assets/style.css" />
</head> </head>
<body> <body>
<div layout="column" style="height:100%;" ng-cloak> <div layout="column" style="height:100%;" ng-cloak>
<section id="sessionEnd" layout="row" flex ng-if="!isAlive"> <section id="sessionEnd" layout="row" flex ng-if="!isAlive">
<md-content flex layout-padding ng-if="!instances.length"> <md-content flex layout-padding ng-if="!instances.length">
@ -19,10 +20,12 @@
<div flex></div> <div flex></div>
</md-content> </md-content>
</section> </section>
<section ng-if="!connected" class="disconnected" layout="row" layout-align="center center"> <section ng-if="!connected" class="disconnected" layout="row" layout-align="center center">
<h1 class="md-headline">No connection to server. Reconnecting...</h1> <h1 class="md-headline">No connection to server. Reconnecting...</h1>
<md-progress-circular class="md-hue-2" md-diameter="20px"></md-progress-circular> <md-progress-circular class="md-hue-2" md-diameter="20px"></md-progress-circular>
</section> </section>
<section id="popupContainer" layout="row" flex ng-if="isAlive"> <section id="popupContainer" layout="row" flex ng-if="isAlive">
<md-sidenav <md-sidenav
class="md-sidenav-left" class="md-sidenav-left"
@ -36,9 +39,7 @@
<h1 class="md-toolbar-tools">Instances</h1> <h1 class="md-toolbar-tools">Instances</h1>
</md-toolbar> </md-toolbar>
<md-content layout-padding> <md-content layout-padding>
<md-button ng-click="newInstance()" class="md-primary"> <md-button ng-click="newInstance()" class="md-primary">+ Add new instance</md-button>
+ Add new instance
</md-button>
<md-list> <md-list>
<md-list-item ng-switch on="instance.isManager" class="md-3-line" ng-repeat="instance in instances" ng-click="showInstance(instance)" ng-class="instance.name == selectedInstance.name ? 'selected' : false"> <md-list-item ng-switch on="instance.isManager" class="md-3-line" ng-repeat="instance in instances" ng-click="showInstance(instance)" ng-class="instance.name == selectedInstance.name ? 'selected' : false">
<md-icon ng-switch-when="true" style="color: blue" md-svg-icon="person"></md-icon> <md-icon ng-switch-when="true" style="color: blue" md-svg-icon="person"></md-icon>
@ -52,12 +53,8 @@
</md-sidenav> </md-sidenav>
<md-content flex layout-padding ng-if="!instances.length"> <md-content flex layout-padding ng-if="!instances.length">
<div layout="column" layout-align="top center"> <div layout="column" layout-align="top center">
<p> <p>Add instances to your playground.</p>
Add instances to your playground. <p><strong>Sessions and all their instances are deleted after {{ttl}} hours.</strong></p>
</p>
<p>
<strong>Sessions and all their instances are deleted after 4 hours.</strong>
</p>
</div> </div>
<div flex></div> <div flex></div>
@ -92,10 +89,7 @@
</md-card-content> </md-card-content>
</md-card> </md-card>
</md-content> </md-content>
</section> </section>
</div> </div>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.5.5/angular.min.js"></script> <script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.5.5/angular.min.js"></script>
@ -103,6 +97,7 @@
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.5.5/angular-aria.min.js"></script> <script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.5.5/angular-aria.min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.5.5/angular-messages.min.js"></script> <script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.5.5/angular-messages.min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/angular_material/1.1.0/angular-material.min.js"></script> <script src="https://ajax.googleapis.com/ajax/libs/angular_material/1.1.0/angular-material.min.js"></script>
<script src="https://cdn.socket.io/socket.io-1.3.7.js"></script> <script src="https://cdn.socket.io/socket.io-1.3.7.js"></script>
<script src="/assets/app.js"></script> <script src="/assets/app.js"></script>
<script src="/assets/xterm.js"></script> <script src="/assets/xterm.js"></script>

View File

@ -1,6 +1,6 @@
{{define "GOOGLE_RECAPTCHA_SITE_KEY"}} {{define "GOOGLE_RECAPTCHA_SITE_KEY"}}
<!doctype html> <!doctype html>
<html ng-app="DockerPlay" ng-controller="PlayController"> <html>
<head> <head>
<title>Docker Playground</title> <title>Docker Playground</title>
<link rel="stylesheet" href="https://ajax.googleapis.com/ajax/libs/angular_material/1.1.0/angular-material.min.css"> <link rel="stylesheet" href="https://ajax.googleapis.com/ajax/libs/angular_material/1.1.0/angular-material.min.css">