mirror of
https://github.com/bingohuang/docker-labs.git
synced 2025-07-15 02:37:27 +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:
parent
d3e20724e9
commit
5eda323477
23
api.go
23
api.go
@ -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) {
|
||||||
|
if bypassCaptcha {
|
||||||
|
http.ServeFile(rw, r, "./www/bypass.html")
|
||||||
|
} else {
|
||||||
|
welcome, tmplErr := templates.GetWelcomeTemplate()
|
||||||
|
if tmplErr != nil {
|
||||||
|
log.Fatal(tmplErr)
|
||||||
|
}
|
||||||
rw.Write(welcome)
|
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) {
|
||||||
|
@ -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")
|
||||||
|
@ -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
25
www/bypass.html
Normal 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>
|
@ -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>
|
||||||
|
@ -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">
|
||||||
|
Loading…
x
Reference in New Issue
Block a user