server.go 7.9 KB

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