mirror of
https://github.com/APIParkLab/APIPark.git
synced 2026-06-04 10:13:53 +08:00
新增飞书登录驱动
This commit is contained in:
@@ -86,7 +86,7 @@ require (
|
||||
// github.com/eolinker/eosc => ../../eolinker/eosc
|
||||
//)
|
||||
|
||||
//replace github.com/eolinker/ap-account => ../../eolinker/ap-account
|
||||
replace github.com/eolinker/ap-account => ../../eolinker/ap-account
|
||||
|
||||
//
|
||||
//replace github.com/eolinker/go-common => ../../eolinker/go-common
|
||||
|
||||
@@ -4,6 +4,7 @@ package main
|
||||
import (
|
||||
_ "github.com/APIParkLab/APIPark/frontend"
|
||||
_ "github.com/APIParkLab/APIPark/gateway/apinto"
|
||||
_ "github.com/APIParkLab/APIPark/login_driver/feishu"
|
||||
_ "github.com/APIParkLab/APIPark/plugins/core"
|
||||
_ "github.com/APIParkLab/APIPark/plugins/openapi"
|
||||
_ "github.com/APIParkLab/APIPark/plugins/permit"
|
||||
|
||||
@@ -0,0 +1,56 @@
|
||||
package feishu
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"time"
|
||||
)
|
||||
|
||||
var (
|
||||
client = http.Client{
|
||||
Timeout: 10 * time.Second,
|
||||
}
|
||||
)
|
||||
|
||||
func SendRequest[T any](uri string, method string, header http.Header, query url.Values, body []byte) (*T, error) {
|
||||
if uri == "" {
|
||||
return nil, fmt.Errorf("invalid URL")
|
||||
}
|
||||
|
||||
req, err := http.NewRequest(method, uri, bytes.NewReader(body))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if query != nil {
|
||||
req.URL.RawQuery = query.Encode()
|
||||
}
|
||||
|
||||
if header != nil {
|
||||
req.Header = header
|
||||
}
|
||||
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
respBody, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
result := new(T)
|
||||
err = json.Unmarshal(respBody, result)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return nil, fmt.Errorf("status code error: %d, response: %s", resp.StatusCode, respBody)
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
package feishu
|
||||
|
||||
type UserTokenResponse struct {
|
||||
Code int `json:"code"`
|
||||
AccessToken string `json:"access_token"`
|
||||
ExpiresIn int `json:"expires_in"`
|
||||
RefreshToken string `json:"refresh_token"`
|
||||
RefreshTokenExpiresIn int `json:"refresh_token_expires_in"`
|
||||
TokenType string `json:"token_type"`
|
||||
Scope string `json:"scope"`
|
||||
Error string `json:"error"`
|
||||
ErrorDescription string `json:"error_description"`
|
||||
}
|
||||
|
||||
type UserInfoResponse struct {
|
||||
Code int `json:"code"`
|
||||
Msg string `json:"msg"`
|
||||
Data UserInfo `json:"data"`
|
||||
}
|
||||
|
||||
type UserInfo struct {
|
||||
Name string `json:"name"`
|
||||
OpenID string `json:"open_id"`
|
||||
UnionId string `json:"union_id"`
|
||||
Email string `json:"email"`
|
||||
Mobile string `json:"mobile"`
|
||||
}
|
||||
@@ -0,0 +1,144 @@
|
||||
package feishu
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/url"
|
||||
|
||||
"github.com/eolinker/eosc/common/bean"
|
||||
|
||||
"github.com/eolinker/ap-account/service/user"
|
||||
|
||||
"github.com/eolinker/go-common/utils"
|
||||
|
||||
"github.com/google/uuid"
|
||||
|
||||
"gorm.io/gorm"
|
||||
|
||||
"github.com/eolinker/ap-account/service/account"
|
||||
|
||||
"github.com/eolinker/ap-account/auth_driver"
|
||||
)
|
||||
|
||||
const (
|
||||
name = "feishu"
|
||||
title = "飞书"
|
||||
getTokenUri = "https://open.feishu.cn/open-apis/authen/v2/oauth/token"
|
||||
getUserInfoUri = "https://open.feishu.cn/open-apis/authen/v1/user_info"
|
||||
)
|
||||
|
||||
var _ auth_driver.IDriver = (*Driver)(nil)
|
||||
|
||||
func init() {
|
||||
d := &Driver{}
|
||||
bean.Autowired(&d.accountService)
|
||||
bean.Autowired(&d.userService)
|
||||
auth_driver.Register(name, d)
|
||||
}
|
||||
|
||||
type Driver struct {
|
||||
accountService account.IAccountService `autowired:""`
|
||||
userService user.IUserService `autowired:""`
|
||||
}
|
||||
|
||||
func (d *Driver) FilterConfig(config map[string]string) {
|
||||
delete(config, "client_secret")
|
||||
}
|
||||
|
||||
func (d *Driver) Name() string {
|
||||
return name
|
||||
}
|
||||
|
||||
func (d *Driver) Title() string {
|
||||
return title
|
||||
}
|
||||
|
||||
func (d *Driver) ThirdLogin(ctx context.Context, args map[string]string) (string, error) {
|
||||
code, ok := args["code"]
|
||||
if !ok {
|
||||
return "", fmt.Errorf("missing code parameter")
|
||||
}
|
||||
clientId, ok := args["client_id"]
|
||||
if !ok {
|
||||
return "", fmt.Errorf("missing client_id parameter")
|
||||
}
|
||||
clientSecret, ok := args["client_secret"]
|
||||
if !ok {
|
||||
return "", fmt.Errorf("missing client_secret parameter")
|
||||
}
|
||||
tokenResp, err := getUserToken(code, clientId, clientSecret)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
userInfoResp, err := getUserInfo(tokenResp.TokenType, tokenResp.AccessToken)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
userId := userInfoResp.Data.UnionId
|
||||
username := userInfoResp.Data.Name
|
||||
email := userInfoResp.Data.Email
|
||||
mobile := userInfoResp.Data.Mobile
|
||||
info, err := d.accountService.GetIdentifier(ctx, name, userId)
|
||||
if err != nil {
|
||||
if !errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return "", err
|
||||
}
|
||||
uId := uuid.NewString()
|
||||
err = d.accountService.Save(ctx, name, uId, userId, utils.Md5(fmt.Sprintf("%s%s", uId, userId)))
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
_, err = d.userService.Create(ctx, uId, username, email, mobile, "")
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return userId, nil
|
||||
}
|
||||
_, err = d.userService.Update(ctx, info.Uid, &username, &email, &mobile)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return userId, nil
|
||||
}
|
||||
|
||||
func getUserToken(code string, clientId string, clientSecret string) (*UserTokenResponse, error) {
|
||||
headers := http.Header{}
|
||||
headers.Set("Content-Type", "application/json")
|
||||
body := url.Values{}
|
||||
body.Set("grant_type", "authorization_code")
|
||||
body.Set("code", code)
|
||||
body.Set("client_id", clientId)
|
||||
body.Set("client_secret", clientSecret)
|
||||
resp, err := SendRequest[UserTokenResponse](getTokenUri, http.MethodPost, headers, nil, []byte(body.Encode()))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get user token: %w", err)
|
||||
}
|
||||
if resp.Code != 0 {
|
||||
return nil, fmt.Errorf("failed to get user token: %s", resp.ErrorDescription)
|
||||
}
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
func getUserInfo(tokenType string, token string) (*UserInfoResponse, error) {
|
||||
headers := http.Header{}
|
||||
headers.Set("Content-Type", "application/json")
|
||||
switch tokenType {
|
||||
case "Bearer":
|
||||
headers.Set("Authorization", fmt.Sprintf("Bearer %s", token))
|
||||
}
|
||||
resp, err := SendRequest[UserInfoResponse](getUserInfoUri, http.MethodGet, headers, nil, nil)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get user info: %w", err)
|
||||
}
|
||||
if resp.Code != 0 {
|
||||
return nil, fmt.Errorf("failed to get user info: %s", resp.Msg)
|
||||
}
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
func (d *Driver) Delete(ctx context.Context, ids ...string) error {
|
||||
return d.accountService.OnRemoveUsers(ctx, ids...)
|
||||
}
|
||||
Reference in New Issue
Block a user