Skip to content

Commit 8cb4930

Browse files
committed
feat(gitlab-op): beautify callback page based html/template
1 parent 3cec57e commit 8cb4930

File tree

4 files changed

+204
-60
lines changed

4 files changed

+204
-60
lines changed

go.mod

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
module github.com/yeqown/gitlab-flow
22

3-
go 1.15
3+
go 1.16
44

55
require (
66
github.com/AlecAivazis/survey/v2 v2.2.7
+96
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
<!DOCTYPE html>
2+
<html lang="en">
3+
4+
<head>
5+
<meta charset="UTF-8">
6+
<meta http-equiv="X-UA-Compatible" content="IE=edge">
7+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
8+
<title>Gitlab flow callback</title>
9+
</head>
10+
11+
<body>
12+
<div style="height: 100px; width: 100%;">
13+
<!-- <h1 style="text-align: center;">Gitlab Oauth callback</h1> -->
14+
</div>
15+
<div id="tip-container">
16+
<div id="tip-header">
17+
<div class="icon" style="background-color: #fe6057;"></div>
18+
<div class="icon" style="background-color: #febd2e;"></div>
19+
<div class="icon" style="background-color: #25c93f;"></div>
20+
</div>
21+
22+
<div id="tip-content">
23+
<p class="text"> Last login: {{ .Now }} on ttys011 </p>
24+
<p class="command"><span class="text">$</span> gitlab-flow <span class="text">authorize</span>
25+
</p>
26+
<p class="text"> [1/3] requesting token... </p>
27+
28+
{{if .Error}}
29+
<p class="error"> Oops! {{.ErrorMessage}} </p>
30+
{{else}}
31+
<p class="text"> [2/3] save tokens into local... </p>
32+
<p class="text"> [3/3] we are almost ready... </p>
33+
<p class="text"> DONE! </p>
34+
{{end}}
35+
</div>
36+
</div>
37+
</body>
38+
39+
<style>
40+
body {
41+
background-color: #eeeeee;
42+
font-family: monospace, serif;
43+
}
44+
45+
#tip-container {
46+
background-color: #322931;
47+
width: 800px;
48+
height: 500px;
49+
border-radius: 20px;
50+
margin: 0 auto;
51+
52+
display: flex;
53+
flex-direction: column;
54+
}
55+
56+
#tip-header {
57+
max-height: 100%;
58+
display: flex;
59+
align-items: flex-start;
60+
padding-left: 20px;
61+
padding-top: 20px;
62+
border-bottom: #e6f7ff 5px;
63+
}
64+
65+
.icon {
66+
width: 16px;
67+
height: 16px;
68+
border-radius: 8px;
69+
margin: 5px;
70+
}
71+
72+
#tip-content {
73+
color: green;
74+
padding: 1em;
75+
padding-left: 20px;
76+
}
77+
78+
#tip-content>p {
79+
margin: 0.2em;
80+
word-break: break-all;
81+
}
82+
83+
.command {
84+
color: #25c93f;
85+
}
86+
87+
.text {
88+
color: whitesmoke;
89+
}
90+
91+
.error {
92+
color: #fe6057;
93+
}
94+
</style>
95+
96+
</html>

internal/gitlab-operator/gitlab_oauth2.go

+75-45
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,11 @@ package gitlabop
22

33
import (
44
"context"
5+
"embed"
56
"encoding/json"
67
"fmt"
8+
"html/template"
79
"io"
8-
"io/ioutil"
910
"math/rand"
1011
"net/http"
1112
"net/url"
@@ -103,7 +104,7 @@ func NewOAuth2Support(c *OAuth2Config) IGitlabOauth2Support {
103104
tokenC: make(chan struct{}),
104105
}
105106

106-
go g._proc()
107+
go g.serve()
107108

108109
return g
109110
}
@@ -144,55 +145,84 @@ func (g *gitlabOAuth2Support) Load() (accessToken, refreshToken string) {
144145
return g.oc.AccessToken, g.oc.RefreshToken
145146
}
146147

147-
// _proc serving a backend HTTP server process to receive redirect requests from gitlab.
148-
func (g *gitlabOAuth2Support) _proc() {
149-
var err error
150-
defer func() {
151-
if err != nil {
152-
log.Errorf("gitlabOAuth2Support _proc quit: %v", err)
153-
}
154-
}()
148+
//go:embed callback.tmpl
149+
var callbackTmpl embed.FS
155150

156-
http.HandleFunc("/callback", func(w http.ResponseWriter, r *http.Request) {
157-
_ = r.ParseForm()
158-
code := r.Form.Get("code")
159-
state := r.Form.Get("state")
160-
_error := r.Form.Get("_error")
161-
errorDescription := r.Form.Get("error_description")
151+
func (g *gitlabOAuth2Support) callbackHandl(w http.ResponseWriter, r *http.Request) {
152+
_ = r.ParseForm()
153+
code := r.Form.Get("code")
154+
state := r.Form.Get("state")
155+
_error := r.Form.Get("_error")
156+
errorDescription := r.Form.Get("error_description")
162157

163-
log.
164-
WithFields(log.Fields{
165-
"code": code,
166-
"state": state,
167-
"_error": _error,
168-
"errorDescription": errorDescription,
169-
}).
170-
Debug("gitlabOAuth2Support _proc gets a callback request")
171-
172-
if len(_error) != 0 {
173-
w.WriteHeader(http.StatusInternalServerError)
174-
_, _ = fmt.Fprint(w, _error, errorDescription)
175-
return
176-
}
158+
log.WithFields(log.Fields{
159+
"code": code,
160+
"state": state,
161+
"_error": _error,
162+
"errorDescription": errorDescription,
163+
}).Debug("gitlabOAuth2Support serve gets a callback request")
177164

178-
if code != "" && state != "" {
179-
// authorization callback
180-
if err2 := g.requestToken(r.Context(), code, false); err2 != nil {
181-
log.Errorf("gitlabOAuth2Support _proc failed to get requestToken: %v", err2)
182-
w.WriteHeader(http.StatusInternalServerError)
183-
_, _ = fmt.Fprint(w, err2.Error())
184-
return
185-
}
165+
tmpl, err := template.ParseFS(callbackTmpl, "callback.tmpl")
166+
if err != nil {
167+
log.Errorf("gitlabOAuth2Support.callbackHandl parse FS failed: %v", err)
168+
}
169+
170+
var (
171+
status = http.StatusOK
172+
data = struct {
173+
Error bool
174+
ErrorMessage string
175+
Now string
176+
}{
177+
Error: false,
178+
ErrorMessage: "",
179+
Now: time.Now().Format(time.RFC1123),
186180
}
181+
)
182+
183+
// error check and parameters validation check.
184+
if len(_error) != 0 {
185+
data.Error = true
186+
data.ErrorMessage = fmt.Sprintf("%s (%s)", _error, errorDescription)
187+
status = http.StatusInternalServerError
188+
goto render
189+
}
190+
if code == "" || state == "" {
191+
data.Error = true
192+
data.ErrorMessage = fmt.Sprintf("Invalid Parameter(code:%s empty or state:%s is empty)", code, state)
193+
status = http.StatusBadRequest
194+
goto render
195+
}
196+
197+
// authorization callback is in line with forecast.
198+
if err := g.requestToken(r.Context(), code, false); err != nil {
199+
log.Errorf("gitlabOAuth2Support callbackHandl failed to requestToken: %v", err)
200+
data.Error = true
201+
data.ErrorMessage = fmt.Sprintf("Request Token Failed (%s)", err.Error())
202+
status = http.StatusInternalServerError
203+
goto render
204+
}
205+
206+
log.Info("gitlab-flow oauth authorization succeeded!")
187207

188-
w.WriteHeader(http.StatusOK)
189-
_, _ = fmt.Fprint(w, "gitlab-flow oauth authorization succeeded!")
190-
})
208+
render:
209+
w.WriteHeader(status)
210+
if err := tmpl.Execute(w, &data); err != nil {
211+
log.Errorf("gitlabOAuth2Support.callbackHandl failed to render: %v", err)
212+
}
213+
}
191214

192-
err = http.ListenAndServe(g.oc.ServeAddr, nil)
215+
// serve is serving a backend HTTP server process to
216+
// receive redirect requests from gitlab.
217+
func (g *gitlabOAuth2Support) serve() {
218+
http.HandleFunc("/callback", g.callbackHandl)
219+
err := http.ListenAndServe(g.oc.ServeAddr, nil)
220+
if err != nil {
221+
log.Errorf("gitlabOAuth2Support serve quit: %v", err)
222+
}
193223
}
194224

195-
func (g *gitlabOAuth2Support) calcState() string {
225+
func (g *gitlabOAuth2Support) generateState() string {
196226
// DONE(@yeqown): replace state calculation with random int
197227
g.state = strconv.Itoa(rand.Intn(int(time.Now().UnixNano())))
198228
return g.state
@@ -204,7 +234,7 @@ func (g *gitlabOAuth2Support) triggerAuthorize(ctx context.Context) {
204234
form.Add("client_id", OAuth2AppID)
205235
form.Add("redirect_uri", fmt.Sprintf("http://%s/callback", g.oc.ServeAddr))
206236
form.Add("response_type", "code")
207-
form.Add("state", g.calcState())
237+
form.Add("state", g.generateState())
208238
form.Add("scope", defaultScope)
209239

210240
fmt.Println("Your access token is invalid or expired, please click following link to authorize:")
@@ -299,7 +329,7 @@ func (g *gitlabOAuth2Support) _execPost(ctx context.Context, uri string, form ur
299329
_ = Body.Close()
300330
}(r.Body)
301331

302-
data, err := ioutil.ReadAll(r.Body)
332+
data, err := io.ReadAll(r.Body)
303333
if err != nil {
304334
return errors.Wrap(err, "failed to read")
305335
}

internal/gitlab-operator/gitlab_oauth2_test.go

+32-14
Original file line numberDiff line numberDiff line change
@@ -2,19 +2,18 @@ package gitlabop
22

33
import (
44
"context"
5+
"net/http"
6+
"net/http/httptest"
57
"testing"
68
"time"
79
)
810

911
func Test_OAuth2(t *testing.T) {
1012
v := NewOAuth2Support(&OAuth2Config{
11-
AppID: "",
12-
AppSecret: "",
13-
Host: "https://git.example.com",
14-
ServeAddr: "localhost:2333",
15-
AccessToken: "",
16-
RefreshToken: "",
17-
RequestTokenHook: nil,
13+
Host: "https://git.example.com",
14+
ServeAddr: "localhost:2333",
15+
AccessToken: "",
16+
RefreshToken: "",
1817
})
1918

2019
s := v.(*gitlabOAuth2Support)
@@ -26,13 +25,10 @@ func Test_OAuth2(t *testing.T) {
2625

2726
func Test_OAuth2_authorize(t *testing.T) {
2827
v := NewOAuth2Support(&OAuth2Config{
29-
AppID: "",
30-
AppSecret: "",
31-
Host: "https://git.example.com",
32-
ServeAddr: "localhost:2333",
33-
AccessToken: "",
34-
RefreshToken: "",
35-
RequestTokenHook: nil,
28+
Host: "https://git.example.com",
29+
ServeAddr: "localhost:2333",
30+
AccessToken: "",
31+
RefreshToken: "",
3632
})
3733

3834
s := v.(*gitlabOAuth2Support)
@@ -46,3 +42,25 @@ func Test_OAuth2_authorize(t *testing.T) {
4642
t.Error(err)
4743
}
4844
}
45+
46+
func Test_OAuth2_callback(t *testing.T) {
47+
OAuth2AppID = "gitlab-flow"
48+
OAuth2AppSecret = "your-secret"
49+
50+
v := NewOAuth2Support(&OAuth2Config{
51+
Host: "https://git.example.com",
52+
ServeAddr: "localhost:2333",
53+
AccessToken: "",
54+
RefreshToken: "",
55+
})
56+
57+
time.Sleep(30 * time.Second)
58+
59+
s := v.(*gitlabOAuth2Support)
60+
req := httptest.NewRequest(http.MethodGet, "http://localhost:2333/callback", nil)
61+
w := httptest.NewRecorder()
62+
s.callbackHandl(w, req)
63+
64+
t.Logf("status: %d", w.Code)
65+
t.Logf("%v", w.Body.String())
66+
}

0 commit comments

Comments
 (0)