server.go 7.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309
  1. package login
  2. import (
  3. "encoding/json"
  4. "fmt"
  5. "net"
  6. "net/http"
  7. "net/url"
  8. "strings"
  9. "time"
  10. "github.com/porter-dev/porter/cli/cmd/utils"
  11. )
  12. func redirect(
  13. codechan chan string,
  14. ) func(http.ResponseWriter, *http.Request) {
  15. return func(w http.ResponseWriter, r *http.Request) {
  16. w.Header().Set("Content-Type", "text/html; charset=utf-8")
  17. fmt.Fprint(w, successScreen)
  18. queryParams, _ := url.ParseQuery(r.URL.RawQuery)
  19. codechan <- queryParams["code"][0]
  20. }
  21. }
  22. func Login(
  23. host string,
  24. ) (string, error) {
  25. listener, err := net.Listen("tcp", ":0")
  26. if err != nil {
  27. panic(err)
  28. }
  29. port := listener.Addr().(*net.TCPAddr).Port
  30. errorchan := make(chan error)
  31. codechan := make(chan string)
  32. go func() {
  33. http.HandleFunc("/", redirect(
  34. codechan,
  35. ))
  36. err := http.Serve(listener, nil)
  37. errorchan <- err
  38. }()
  39. // open browser for host login
  40. redirectHost := fmt.Sprintf("http://localhost:%d", port)
  41. loginURL := fmt.Sprintf("%s/api/cli/login?redirect=%s", host, url.QueryEscape(redirectHost))
  42. err = utils.OpenBrowser(loginURL)
  43. if err != nil {
  44. return "", fmt.Errorf("Could not open browser: %v", err)
  45. }
  46. for {
  47. select {
  48. case err = <-errorchan:
  49. return "", err
  50. case code := <-codechan:
  51. return ExchangeToken(host, code)
  52. }
  53. }
  54. }
  55. type ExchangeResponse struct {
  56. Token string `json:"token"`
  57. }
  58. func ExchangeToken(host, code string) (string, error) {
  59. req, err := http.NewRequest(
  60. "GET",
  61. fmt.Sprintf("%s/api/cli/login/exchange", host),
  62. strings.NewReader(fmt.Sprintf(`{"authorization_code": "%s"}`, code)),
  63. )
  64. if err != nil {
  65. return "", err
  66. }
  67. // create a request with the authorization code
  68. req.Header.Set("Content-Type", "application/json; charset=utf-8")
  69. req.Header.Set("Accept", "application/json; charset=utf-8")
  70. client := &http.Client{
  71. Timeout: time.Minute,
  72. }
  73. res, err := client.Do(req)
  74. if err != nil {
  75. return "", err
  76. }
  77. defer res.Body.Close()
  78. resp := &ExchangeResponse{}
  79. if err = json.NewDecoder(res.Body).Decode(resp); err != nil {
  80. return "", err
  81. }
  82. return resp.Token, nil
  83. }
  84. const successScreen = `
  85. <!DOCTYPE html>
  86. <html lang="en">
  87. <head>
  88. <meta charset='UTF-8'>
  89. <title>Porter | Login</title>
  90. <link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet">
  91. <link href="https://fonts.googleapis.com/css?family=Assistant:400,700|Noto+Sans:400,600,700|Work+Sans:400,500,600|Source+Sans+Pro:400,600,700|Hind+Siliguri:500|Cabin:400,600" rel="stylesheet">
  92. <link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet">
  93. <link href="//cdnjs.cloudflare.com/ajax/libs/KaTeX/0.9.0/katex.min.css" rel="stylesheet" />
  94. <link href="https://fonts.googleapis.com/css2?family=Open+Sans:wght@600&display=swap" rel="stylesheet">
  95. <style>
  96. #logo {
  97. width: 80px;
  98. margin-top: -30px;
  99. margin-bottom: 40px;
  100. }
  101. #success {
  102. font-family: 'Open Sans', sans-serif;
  103. font-size: 18px;
  104. color: #CBCBD8;
  105. margin-bottom: 17px;
  106. }
  107. #subtitle {
  108. font-family: 'Open Sans', sans-serif;
  109. font-size: 14px;
  110. color: #CBCBD8;
  111. }
  112. body{
  113. margin: 0;
  114. padding: 0;
  115. width: 100%;
  116. height: 100vh;
  117. background: #f1f3f5;
  118. display: flex;
  119. flex-direction: column;
  120. align-items: center;
  121. justify-content: center;
  122. }
  123. #text {
  124. display: flex;
  125. height: 100vh;
  126. align-items: center;
  127. justify-content: center;
  128. text-align: center;
  129. }
  130. h2{
  131. color: #fff;
  132. font-size: 47px;
  133. line-height: 40px;
  134. }
  135. #container {
  136. left: 0px;
  137. top: -100px;
  138. height: calc(100vh + 100px);
  139. overflow: hidden;
  140. position: relative;
  141. }
  142. #animate{
  143. margin: 0 auto;
  144. width: 20px;
  145. overflow: visible;
  146. position: relative;
  147. }
  148. #all{
  149. overflow: hidden;
  150. height: 100vh;
  151. width: 100%;
  152. position: fixed;
  153. }
  154. #footer{
  155. color: #808080;
  156. text-decoration: none;
  157. position: fixed;
  158. width: 752px;
  159. bottom: 20px;
  160. align-content: center;
  161. float: none;
  162. margin-left: calc(50% - 376px);
  163. }
  164. a, p{
  165. text-decoration: none;
  166. color: #808080;
  167. letter-spacing: 6px;
  168. transition: all 0.5s ease-in-out;
  169. width: auto;
  170. float: left;
  171. margin: 0;
  172. margin-right: 9px;
  173. }
  174. a:hover{
  175. color: #fff;
  176. letter-spacing: 2px;
  177. transition: all 0.5s ease-in-out;
  178. }
  179. </style>
  180. </head>
  181. <body>
  182. <link href="https://fonts.googleapis.com/css?family=Oswald:600,700" rel="stylesheet">
  183. <div id="all">
  184. <div id="container">
  185. <div id="animate">
  186. </div>
  187. </div>
  188. </div>
  189. <noscript>You need to enable JavaScript to run this app.</noscript>
  190. <img id='logo' src='https://i.ibb.co/y64zfm5/porter.png'>
  191. <div id='success'>Authentication successful!</div>
  192. <div id='subtitle'>You can now close this window.</div>
  193. <script>
  194. /* setTimeout(function () {
  195. window.close();
  196. }, 1000)
  197. */
  198. var container = document.getElementById('animate');
  199. var emoji = ['🎉'];
  200. var circles = [];
  201. for (var i = 0; i < 15; i++) {
  202. addCircle(i * 550, [10 + 0, 300], emoji[Math.floor(Math.random() * emoji.length)]);
  203. addCircle(i * 550, [10 + 0, -300], emoji[Math.floor(Math.random() * emoji.length)]);
  204. addCircle(i * 550, [10 - 200, -300], emoji[Math.floor(Math.random() * emoji.length)]);
  205. addCircle(i * 550, [10 + 200, 300], emoji[Math.floor(Math.random() * emoji.length)]);
  206. addCircle(i * 550, [10 - 400, -300], emoji[Math.floor(Math.random() * emoji.length)]);
  207. addCircle(i * 550, [10 + 400, 300], emoji[Math.floor(Math.random() * emoji.length)]);
  208. addCircle(i * 550, [10 - 600, -300], emoji[Math.floor(Math.random() * emoji.length)]);
  209. addCircle(i * 550, [10 + 600, 300], emoji[Math.floor(Math.random() * emoji.length)]);
  210. }
  211. function addCircle(delay, range, color) {
  212. setTimeout(function() {
  213. var c = new Circle(range[0] + Math.random() * range[1], 80 + Math.random() * 4, color, {
  214. x: -0.15 + Math.random() * 0.3,
  215. y: 1 + Math.random() * 1
  216. }, range);
  217. circles.push(c);
  218. }, delay);
  219. }
  220. function Circle(x, y, c, v, range) {
  221. var _this = this;
  222. this.x = x;
  223. this.y = y;
  224. this.color = c;
  225. this.v = v;
  226. this.range = range;
  227. this.element = document.createElement('span');
  228. /*this.element.style.display = 'block';*/
  229. this.element.style.opacity = 0;
  230. this.element.style.position = 'absolute';
  231. this.element.style.fontSize = '26px';
  232. this.element.style.color = 'hsl('+(Math.random()*360|0)+',80%,50%)';
  233. this.element.innerHTML = c;
  234. container.appendChild(this.element);
  235. this.update = function() {
  236. if (_this.y > 800) {
  237. _this.y = 80 + Math.random() * 4;
  238. _this.x = _this.range[0] + Math.random() * _this.range[1];
  239. }
  240. _this.y += _this.v.y;
  241. _this.x += _this.v.x;
  242. this.element.style.opacity = 1;
  243. this.element.style.transform = 'translate3d(' + _this.x + 'px, ' + _this.y + 'px, 0px)';
  244. this.element.style.webkitTransform = 'translate3d(' + _this.x + 'px, ' + _this.y + 'px, 0px)';
  245. this.element.style.mozTransform = 'translate3d(' + _this.x + 'px, ' + _this.y + 'px, 0px)';
  246. };
  247. }
  248. function animate() {
  249. for (var i in circles) {
  250. circles[i].update();
  251. }
  252. requestAnimationFrame(animate);
  253. }
  254. if (Math.random() < 0.001) {
  255. animate();
  256. }
  257. </script>
  258. </body>
  259. </html>
  260. `