user-center-slack/handler.go (104 lines of code) (raw):
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package slack_user_center
import (
"crypto/rand"
"encoding/base64"
"encoding/hex"
"encoding/json"
"fmt"
"net/http"
"time"
"github.com/apache/answer/plugin"
"github.com/gin-gonic/gin"
"github.com/segmentfault/pacman/log"
)
// RespBody response body.
type RespBody struct {
// http code
Code int `json:"code"`
// reason key
Reason string `json:"reason"`
// response message
Message string `json:"msg"`
// response data
Data interface{} `json:"data"`
}
// NewRespBodyData new response body with data
func NewRespBodyData(code int, reason string, data interface{}) *RespBody {
return &RespBody{
Code: code,
Reason: reason,
Data: data,
}
}
func (uc *UserCenter) BuildSlackBaseRedirectURL() string {
clientID := uc.Config.ClientID
log.Debug("Get client ID:", clientID)
scope := "chat:write,commands,groups:write,im:write,incoming-webhook,mpim:write,users:read,users:read.email"
response_type := "code"
redirect_uri := fmt.Sprintf("%s/answer/api/v1/user-center/login/callback", plugin.SiteURL())
base_redirectURL := fmt.Sprintf(
"https://slack.com/oauth/v2/authorize?client_id=%s&scope=%s&response_type=%s&redirect_uri=%s",
clientID, scope, response_type, redirect_uri,
)
state := genState()
nonce := genNonce()
uc.Cache.Set("oauth_state_"+state, state, time.Minute*5)
redirectURL := fmt.Sprintf("%s&state=%s&nonce=%s", base_redirectURL, state, nonce)
log.Debug("RedirectURL from BuildSlackBaseRedirectURL:", redirectURL)
return redirectURL
}
func (uc *UserCenter) GetSlackRedirectURL(ctx *gin.Context) {
redirectURL := uc.BuildSlackBaseRedirectURL()
log.Debug("Processing GetSlackRedirectURL")
ctx.Writer.Header().Set("Content-Type", "application/json")
encoder := json.NewEncoder(ctx.Writer)
encoder.SetEscapeHTML(false)
respData := NewRespBodyData(http.StatusOK, "success", map[string]string{
"redirect_url": redirectURL,
})
ctx.Writer.WriteHeader(http.StatusOK)
if err := encoder.Encode(respData); err != nil {
ctx.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to encode response"})
return
}
}
func genNonce() string {
bytes := make([]byte, 10)
_, _ = rand.Read(bytes)
return hex.EncodeToString(bytes)
}
func genState() string {
bytes := make([]byte, 32)
_, _ = rand.Read(bytes)
return base64.URLEncoding.EncodeToString(bytes)
}
func (uc *UserCenter) Sync(ctx *gin.Context) {
uc.syncSlackClient()
if uc.syncSuccess {
ctx.JSON(http.StatusOK, NewRespBodyData(http.StatusOK, "success", map[string]any{
"message": "User data synced successfully",
}))
return
}
errRespBodyData := NewRespBodyData(http.StatusBadRequest, "error", map[string]any{
"err_type": "toast",
})
errRespBodyData.Message = "Failed to sync user data"
ctx.JSON(http.StatusBadRequest, errRespBodyData)
}
func (uc *UserCenter) syncSlackClient() {
if !uc.syncLock.TryLock() {
log.Infof("sync data is running")
return
}
defer func() {
uc.syncing = false
if uc.syncSuccess {
uc.syncTime = time.Now()
}
uc.syncLock.Unlock()
}()
log.Info("start sync slack data")
uc.syncing = true
uc.syncSuccess = true
if err := uc.SlackClient.UpdateUserInfo(); err != nil {
log.Errorf("list user error: %s", err)
uc.syncSuccess = false
return
}
log.Info("end sync slack data")
}