server.go 8.0 KB

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