server.go 7.8 KB

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