/
routes.go
205 lines (184 loc) · 6.46 KB
/
routes.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
package app
import (
"bytes"
"encoding/json"
"fmt"
"net/http"
"net/url"
"golang.org/x/oauth2"
"github.com/gorilla/mux"
"github.com/nlopes/slack"
)
func (app *App) setupRouter() *mux.Router {
router := mux.NewRouter()
router.HandleFunc("/", app.handleIndex).Methods(http.MethodGet)
router.HandleFunc("/favicon.ico", app.handleFavicon).Methods(http.MethodGet)
router.HandleFunc("/success", app.handleAuthSuccess).Methods(http.MethodGet)
router.HandleFunc("/oauth/salesforce/callback", app.handleSalesforceOAuthCallback).Methods(http.MethodGet)
router.HandleFunc("/oauth/salesforce/authenticate/{state}", app.handleSalesforceAuthenticate).Methods(http.MethodGet)
router.HandleFunc("/oauth/slack/callback", app.handleSlackOAuthCallback).Methods(http.MethodGet)
router.HandleFunc("/oauth/slack/authenticate/{team}/{state}", app.handleSlackAuthenticate).Methods(http.MethodGet)
router.HandleFunc("/hooks/slash", app.handleSlashCommand).Methods(http.MethodPost)
router.HandleFunc("/hooks/interactive", app.handleActionCallback).Methods(http.MethodPost)
return router
}
func (app *App) handleIndex(w http.ResponseWriter, r *http.Request) {
app.handleAsset("index.html", w, r)
}
func (app *App) handleAuthSuccess(w http.ResponseWriter, r *http.Request) {
app.handleAsset("success.html", w, r)
}
func (app *App) handleFavicon(w http.ResponseWriter, r *http.Request) {
app.handleAsset("favicon.ico", w, r)
}
func (app *App) handleAsset(filename string, w http.ResponseWriter, r *http.Request) {
data, err := Asset("assets/" + filename)
if err != nil {
http.Error(w, err.Error(), http.StatusNotFound)
} else {
w.Write(data)
}
}
func (app *App) handleSlackAuthenticate(w http.ResponseWriter, r *http.Request) {
app.reconnectRedisIfNeeeded()
vars := mux.Vars(r)
stateKey := vars["state"]
team := vars["team"]
ctx := app.createContext(r)
state := ctx.getState(stateKey)
if state == nil {
w.WriteHeader(http.StatusNotFound)
return
}
q := url.Values{
"client_id": []string{app.SlackClientID},
"redirect_uri": []string{ctx.getSlackOAuthCallbackURL()},
"state": []string{stateKey},
"scope": []string{"chat:write:user"},
"team": []string{team},
}
url := "https://slack.com/oauth/authorize?" + q.Encode()
http.Redirect(w, r, url, http.StatusSeeOther)
}
func (app *App) handleSlackOAuthCallback(w http.ResponseWriter, r *http.Request) {
app.reconnectRedisIfNeeeded()
code := r.URL.Query().Get("code")
stateKey := r.URL.Query().Get("state")
ctx := app.createContext(r)
redirectURL := ctx.getSlackOAuthCallbackURL()
token, _, err := slack.GetOAuthToken(app.SlackClientID, app.SlackClientSecret, code, redirectURL, false)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
state := ctx.getState(stateKey)
ctx.UserID = state.UserID
ctx.setSlackAccessToken(token)
ctx.deleteState(stateKey)
go func() {
params, _ := ctx.getChannelSelectSlackMessage()
params.Text = "認証が完了しました :white_check_mark:"
b, _ := json.Marshal(params)
http.Post(state.ResponseURL, "application/json", bytes.NewBuffer(b))
}()
http.Redirect(w, r, "/success", http.StatusFound)
}
func (app *App) handleSalesforceAuthenticate(w http.ResponseWriter, r *http.Request) {
app.reconnectRedisIfNeeeded()
vars := mux.Vars(r)
stateKey := vars["state"]
ctx := app.createContext(r)
state := ctx.getState(stateKey)
if state == nil {
w.WriteHeader(http.StatusNotFound)
return
}
config := ctx.getSalesforceOAuth2Config()
config.Scopes = []string{"refresh_token", "full"}
url := config.AuthCodeURL(stateKey, oauth2.AccessTypeOffline)
http.Redirect(w, r, url, http.StatusSeeOther)
}
func (app *App) handleSalesforceOAuthCallback(w http.ResponseWriter, r *http.Request) {
app.reconnectRedisIfNeeeded()
code := r.URL.Query().Get("code")
stateKey := r.URL.Query().Get("state")
ctx := app.createContext(r)
token, err := ctx.getSalesforceAccessToken(code, stateKey)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
state := ctx.getState(stateKey)
ctx.UserID = state.UserID
ctx.setSalesforceAccessToken(token)
http.Redirect(w, r, ctx.getSlackAuthenticateURL(state.TeamID, stateKey), http.StatusFound)
}
func (app *App) handleSlashCommand(w http.ResponseWriter, r *http.Request) {
app.reconnectRedisIfNeeeded()
s, err := slack.SlashCommandParse(r)
if err != nil {
w.WriteHeader(http.StatusInternalServerError)
return
}
if !s.ValidateToken(app.SlackVerificationToken) {
w.WriteHeader(http.StatusUnauthorized)
return
}
ctx := app.createContext(r)
ctx.UserID = s.UserID
go func() {
params, _ := ctx.getSlackMessage(s)
b, _ := json.Marshal(params)
http.Post(s.ResponseURL, "application/json", bytes.NewBuffer(b))
}()
w.Header().Set("Content-Type", "text/plain")
w.Write([]byte(""))
}
func (app *App) handleActionCallback(w http.ResponseWriter, r *http.Request) {
app.reconnectRedisIfNeeeded()
ctx := app.createContext(r)
r.ParseForm()
payload := r.PostForm.Get("payload")
var data slack.AttachmentActionCallback
if err := json.Unmarshal([]byte(payload), &data); err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
if data.Token != ctx.SlackVerificationToken {
http.Error(w, "Invlaid token", http.StatusUnauthorized)
return
}
ctx.UserID = data.User.ID
if data.CallbackID == callbackIDChannelSelect {
action := data.Actions[0]
channelID := ""
text := "通知を止めました :no_bell:"
if action.Name == actionTypeSelectChannel {
opt := action.SelectedOptions[0]
channelID = opt.Value
text = "<#" + channelID + "> に通知します :mega:"
}
ctx.setVariableInHash(ctx.NotifyChannelStoreKey, channelID)
w.Header().Set("Content-Type", "text/plain")
w.Write([]byte(text))
return
}
go func() {
params, responseURL, err := ctx.getActionCallback(&data)
if err != nil && params == nil && responseURL != "" {
http.Post(responseURL, "text/plain", bytes.NewBufferString(err.Error()))
return
} else if err != nil {
fmt.Printf("Handle Action Callback Error: %+v\n", err.Error())
}
slackToken := ctx.getSlackAccessTokenForUser()
slackChannel := ctx.getSlackNotifyChannelForUser()
if slackToken != "" && slackChannel != "" {
slack.New(slackToken).PostMessage(slackChannel, params.Text, slack.PostMessageParameters{AsUser: true})
}
b, _ := json.Marshal(params)
http.Post(responseURL, "application/json", bytes.NewBuffer(b))
}()
w.Header().Set("Content-Type", "text/plain")
w.Write([]byte("勤務表を更新中 :hourglass_flowing_sand:"))
}