forked from casdoor/casdoor
171 lines
4.5 KiB
Go
171 lines
4.5 KiB
Go
// Copyright 2025 The Casdoor Authors. All Rights Reserved.
|
|
//
|
|
// Licensed 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 idp
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/json"
|
|
"errors"
|
|
"fmt"
|
|
"io"
|
|
"net/http"
|
|
"net/url"
|
|
"time"
|
|
|
|
"golang.org/x/oauth2"
|
|
)
|
|
|
|
// WeChatMobileIdProvider is for WeChat OAuth Mobile (in-app browser) login
|
|
// This uses snsapi_userinfo scope for mobile authorization
|
|
type WeChatMobileIdProvider struct {
|
|
Client *http.Client
|
|
Config *oauth2.Config
|
|
}
|
|
|
|
func NewWeChatMobileIdProvider(clientId string, clientSecret string, redirectUrl string) *WeChatMobileIdProvider {
|
|
idp := &WeChatMobileIdProvider{}
|
|
|
|
config := idp.getConfig(clientId, clientSecret, redirectUrl)
|
|
idp.Config = config
|
|
|
|
return idp
|
|
}
|
|
|
|
func (idp *WeChatMobileIdProvider) SetHttpClient(client *http.Client) {
|
|
idp.Client = client
|
|
}
|
|
|
|
// getConfig returns OAuth2 config for WeChat Mobile
|
|
func (idp *WeChatMobileIdProvider) getConfig(clientId string, clientSecret string, redirectUrl string) *oauth2.Config {
|
|
endpoint := oauth2.Endpoint{
|
|
AuthURL: "https://open.weixin.qq.com/connect/oauth2/authorize",
|
|
TokenURL: "https://api.weixin.qq.com/sns/oauth2/access_token",
|
|
}
|
|
|
|
config := &oauth2.Config{
|
|
Scopes: []string{"snsapi_userinfo"},
|
|
Endpoint: endpoint,
|
|
ClientID: clientId,
|
|
ClientSecret: clientSecret,
|
|
RedirectURL: redirectUrl,
|
|
}
|
|
|
|
return config
|
|
}
|
|
|
|
// GetToken exchanges authorization code for access token
|
|
func (idp *WeChatMobileIdProvider) GetToken(code string) (*oauth2.Token, error) {
|
|
params := url.Values{}
|
|
params.Add("grant_type", "authorization_code")
|
|
params.Add("appid", idp.Config.ClientID)
|
|
params.Add("secret", idp.Config.ClientSecret)
|
|
params.Add("code", code)
|
|
|
|
accessTokenUrl := fmt.Sprintf("https://api.weixin.qq.com/sns/oauth2/access_token?%s", params.Encode())
|
|
tokenResponse, err := idp.Client.Get(accessTokenUrl)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
defer func(Body io.ReadCloser) {
|
|
err := Body.Close()
|
|
if err != nil {
|
|
return
|
|
}
|
|
}(tokenResponse.Body)
|
|
|
|
buf := new(bytes.Buffer)
|
|
_, err = buf.ReadFrom(tokenResponse.Body)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// Check for error response
|
|
if bytes.Contains(buf.Bytes(), []byte("errcode")) {
|
|
return nil, errors.New(buf.String())
|
|
}
|
|
|
|
var wechatAccessToken WechatAccessToken
|
|
if err = json.Unmarshal(buf.Bytes(), &wechatAccessToken); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
token := oauth2.Token{
|
|
AccessToken: wechatAccessToken.AccessToken,
|
|
TokenType: "WeChatAccessToken",
|
|
RefreshToken: wechatAccessToken.RefreshToken,
|
|
Expiry: time.Time{},
|
|
}
|
|
|
|
raw := make(map[string]string)
|
|
raw["Openid"] = wechatAccessToken.Openid
|
|
token.WithExtra(raw)
|
|
|
|
return &token, nil
|
|
}
|
|
|
|
// GetUserInfo retrieves user information using the access token
|
|
func (idp *WeChatMobileIdProvider) GetUserInfo(token *oauth2.Token) (*UserInfo, error) {
|
|
var wechatUserInfo WechatUserInfo
|
|
accessToken := token.AccessToken
|
|
openid := token.Extra("Openid")
|
|
|
|
userInfoUrl := fmt.Sprintf("https://api.weixin.qq.com/sns/userinfo?access_token=%s&openid=%s&lang=zh_CN", accessToken, openid)
|
|
resp, err := idp.Client.Get(userInfoUrl)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
defer func(Body io.ReadCloser) {
|
|
err := Body.Close()
|
|
if err != nil {
|
|
return
|
|
}
|
|
}(resp.Body)
|
|
|
|
buf := new(bytes.Buffer)
|
|
_, err = buf.ReadFrom(resp.Body)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if err = json.Unmarshal(buf.Bytes(), &wechatUserInfo); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// Check for error response
|
|
if wechatUserInfo.Openid == "" {
|
|
return nil, fmt.Errorf("failed to get user info: %s", buf.String())
|
|
}
|
|
|
|
id := wechatUserInfo.Unionid
|
|
if id == "" {
|
|
id = wechatUserInfo.Openid
|
|
}
|
|
|
|
extra := make(map[string]string)
|
|
extra["wechat_unionid"] = wechatUserInfo.Openid
|
|
// For WeChat, different appId corresponds to different openId
|
|
extra[BuildWechatOpenIdKey(idp.Config.ClientID)] = wechatUserInfo.Openid
|
|
userInfo := UserInfo{
|
|
Id: id,
|
|
Username: wechatUserInfo.Nickname,
|
|
DisplayName: wechatUserInfo.Nickname,
|
|
AvatarUrl: wechatUserInfo.Headimgurl,
|
|
Extra: extra,
|
|
}
|
|
return &userInfo, nil
|
|
}
|