mirror of
https://github.com/casdoor/casdoor.git
synced 2025-05-23 02:35:49 +08:00
feat: add wechat mini program support (#658)
* feat: add wechat mini program support Signed-off-by: Steve0x2a <stevesough@gmail.com> * fix: accept suggestions. Signed-off-by: Steve0x2a <stevesough@gmail.com> * fix: error message and code level modification Signed-off-by: Steve0x2a <stevesough@gmail.com> * fix: simplify the use process Signed-off-by: Steve0x2a <stevesough@gmail.com>
This commit is contained in:
parent
9877174780
commit
b92d03e2bb
@ -175,6 +175,8 @@ func (c *ApiController) GetOAuthToken() {
|
||||
scope := c.Input().Get("scope")
|
||||
username := c.Input().Get("username")
|
||||
password := c.Input().Get("password")
|
||||
tag := c.Input().Get("tag")
|
||||
avatar := c.Input().Get("avatar")
|
||||
|
||||
if clientId == "" && clientSecret == "" {
|
||||
clientId, clientSecret, _ = c.Ctx.Request.BasicAuth()
|
||||
@ -191,11 +193,13 @@ func (c *ApiController) GetOAuthToken() {
|
||||
scope = tokenRequest.Scope
|
||||
username = tokenRequest.Username
|
||||
password = tokenRequest.Password
|
||||
tag = tokenRequest.Tag
|
||||
avatar = tokenRequest.Avatar
|
||||
}
|
||||
}
|
||||
host := c.Ctx.Request.Host
|
||||
|
||||
c.Data["json"] = object.GetOAuthToken(grantType, clientId, clientSecret, code, verifier, scope, username, password, host)
|
||||
c.Data["json"] = object.GetOAuthToken(grantType, clientId, clientSecret, code, verifier, scope, username, password, host, tag, avatar)
|
||||
c.ServeJSON()
|
||||
}
|
||||
|
||||
|
@ -23,4 +23,6 @@ type TokenRequest struct {
|
||||
Scope string `json:"scope"`
|
||||
Username string `json:"username"`
|
||||
Password string `json:"password"`
|
||||
Tag string `json:"tag"`
|
||||
Avatar string `json:"avatar"`
|
||||
}
|
||||
|
@ -111,6 +111,10 @@ func (c *ApiController) UpdateUser() {
|
||||
id := c.Input().Get("id")
|
||||
columnsStr := c.Input().Get("columns")
|
||||
|
||||
if id == "" {
|
||||
id = c.GetSessionUsername()
|
||||
}
|
||||
|
||||
var user object.User
|
||||
err := json.Unmarshal(c.Ctx.Input.RequestBody, &user)
|
||||
if err != nil {
|
||||
|
82
idp/wechat_miniprogram.go
Normal file
82
idp/wechat_miniprogram.go
Normal file
@ -0,0 +1,82 @@
|
||||
// Copyright 2022 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 (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
|
||||
"golang.org/x/oauth2"
|
||||
)
|
||||
|
||||
type WeChatMiniProgramIdProvider struct {
|
||||
Client *http.Client
|
||||
Config *oauth2.Config
|
||||
}
|
||||
|
||||
func NewWeChatMiniProgramIdProvider(clientId string, clientSecret string) *WeChatMiniProgramIdProvider {
|
||||
idp := &WeChatMiniProgramIdProvider{}
|
||||
|
||||
config := idp.getConfig(clientId, clientSecret)
|
||||
idp.Config = config
|
||||
idp.Client = &http.Client{}
|
||||
return idp
|
||||
}
|
||||
|
||||
func (idp *WeChatMiniProgramIdProvider) SetHttpClient(client *http.Client) {
|
||||
idp.Client = client
|
||||
}
|
||||
|
||||
func (idp *WeChatMiniProgramIdProvider) getConfig(clientId string, clientSecret string) *oauth2.Config {
|
||||
var config = &oauth2.Config{
|
||||
ClientID: clientId,
|
||||
ClientSecret: clientSecret,
|
||||
}
|
||||
|
||||
return config
|
||||
}
|
||||
|
||||
type WeChatMiniProgramSessionResponse struct {
|
||||
Openid string `json:"openid"`
|
||||
SessionKey string `json:"session_key"`
|
||||
Unionid string `json:"unionid"`
|
||||
Errcode int `json:"errcode"`
|
||||
Errmsg string `json:"errmsg"`
|
||||
}
|
||||
|
||||
func (idp *WeChatMiniProgramIdProvider) GetSessionByCode(code string) (*WeChatMiniProgramSessionResponse, error) {
|
||||
sessionUri := fmt.Sprintf("https://api.weixin.qq.com/sns/jscode2session?appid=%s&secret=%s&js_code=%s&grant_type=authorization_code", idp.Config.ClientID, idp.Config.ClientSecret, code)
|
||||
sessionResponse, err := idp.Client.Get(sessionUri)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer sessionResponse.Body.Close()
|
||||
data, err := ioutil.ReadAll(sessionResponse.Body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var session WeChatMiniProgramSessionResponse
|
||||
err = json.Unmarshal(data, &session)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if session.Errcode != 0 {
|
||||
return nil, fmt.Errorf("err: %s", session.Errmsg)
|
||||
}
|
||||
return &session, nil
|
||||
|
||||
}
|
@ -151,6 +151,16 @@ func GetDefaultHumanCheckProvider() *Provider {
|
||||
return &provider
|
||||
}
|
||||
|
||||
func GetWechatMiniProgramProvider(application *Application) *Provider {
|
||||
providers := application.Providers
|
||||
for _, provider := range providers {
|
||||
if provider.Provider.Type == "WeChatMiniProgram" {
|
||||
return provider.Provider
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func UpdateProvider(id string, provider *Provider) bool {
|
||||
owner, name := util.GetOwnerAndNameFromId(id)
|
||||
if getProvider(owner, name) == nil {
|
||||
|
@ -22,6 +22,7 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/casdoor/casdoor/idp"
|
||||
"github.com/casdoor/casdoor/util"
|
||||
"xorm.io/core"
|
||||
)
|
||||
@ -306,7 +307,8 @@ func GetOAuthCode(userId string, clientId string, responseType string, redirectU
|
||||
}
|
||||
}
|
||||
|
||||
func GetOAuthToken(grantType string, clientId string, clientSecret string, code string, verifier string, scope string, username string, password string, host string) *TokenWrapper {
|
||||
|
||||
func GetOAuthToken(grantType string, clientId string, clientSecret string, code string, verifier string, scope string, username string, password string, host string, tag string, avatar string) *TokenWrapper {
|
||||
var errString string
|
||||
application := GetApplicationByClientId(clientId)
|
||||
if application == nil {
|
||||
@ -321,7 +323,8 @@ func GetOAuthToken(grantType string, clientId string, clientSecret string, code
|
||||
}
|
||||
|
||||
//Check if grantType is allowed in the current application
|
||||
if !IsGrantTypeValid(grantType, application.GrantTypes) {
|
||||
|
||||
if !IsGrantTypeValid(grantType, application.GrantTypes) && tag == "" {
|
||||
errString = fmt.Sprintf("error: grant_type: %s is not supported in this application", grantType)
|
||||
return &TokenWrapper{
|
||||
AccessToken: errString,
|
||||
@ -343,6 +346,11 @@ func GetOAuthToken(grantType string, clientId string, clientSecret string, code
|
||||
token, err = GetClientCredentialsToken(application, clientSecret, scope, host)
|
||||
}
|
||||
|
||||
if tag == "wechat_miniprogram" {
|
||||
// Wechat Mini Program
|
||||
token, err = GetWechatMiniProgramToken(application, code, host, username, avatar)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
errString = err.Error()
|
||||
return &TokenWrapper{
|
||||
@ -629,3 +637,74 @@ func GetTokenByUser(application *Application, user *User, scope string, host str
|
||||
AddToken(token)
|
||||
return token, nil
|
||||
}
|
||||
|
||||
// Wechat Mini Program flow
|
||||
func GetWechatMiniProgramToken(application *Application, code string, host string, username string, avatar string) (*Token, error) {
|
||||
mpProvider := GetWechatMiniProgramProvider(application)
|
||||
if mpProvider == nil {
|
||||
return nil, errors.New("error: the application does not support wechat mini program")
|
||||
}
|
||||
provider := GetProvider(util.GetId(mpProvider.Name))
|
||||
mpIdp := idp.NewWeChatMiniProgramIdProvider(provider.ClientId, provider.ClientSecret)
|
||||
session, err := mpIdp.GetSessionByCode(code)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
openId, unionId := session.Openid, session.Unionid
|
||||
if openId == "" && unionId == "" {
|
||||
return nil, errors.New("err: WeChat's openid and unionid are empty")
|
||||
}
|
||||
user := getUserByWechatId(openId, unionId)
|
||||
if user == nil {
|
||||
if !application.EnableSignUp {
|
||||
return nil, errors.New("err: the application does not allow to sign up new account")
|
||||
}
|
||||
//Add new user
|
||||
var name string
|
||||
if username != "" {
|
||||
name = username
|
||||
} else {
|
||||
name = fmt.Sprintf("wechat-%s", openId)
|
||||
}
|
||||
|
||||
user = &User{
|
||||
Owner: application.Organization,
|
||||
Id: util.GenerateId(),
|
||||
Name: name,
|
||||
Avatar: avatar,
|
||||
SignupApplication: application.Name,
|
||||
WeChat: openId,
|
||||
WeChatUnionId: unionId,
|
||||
Type: "normal-user",
|
||||
CreatedTime: util.GetCurrentTime(),
|
||||
IsAdmin: false,
|
||||
IsGlobalAdmin: false,
|
||||
IsForbidden: false,
|
||||
IsDeleted: false,
|
||||
}
|
||||
AddUser(user)
|
||||
}
|
||||
|
||||
accessToken, refreshToken, err := generateJwtToken(application, user, "", "", host)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
token := &Token{
|
||||
Owner: application.Owner,
|
||||
Name: util.GenerateId(),
|
||||
CreatedTime: util.GetCurrentTime(),
|
||||
Application: application.Name,
|
||||
Organization: user.Owner,
|
||||
User: user.Name,
|
||||
Code: session.SessionKey, //a trick, because miniprogram does not use the code, so use the code field to save the session_key
|
||||
AccessToken: accessToken,
|
||||
RefreshToken: refreshToken,
|
||||
ExpiresIn: application.ExpireInHours * 60,
|
||||
Scope: "",
|
||||
TokenType: "Bearer",
|
||||
CodeIsUsed: true,
|
||||
}
|
||||
AddToken(token)
|
||||
return token, nil
|
||||
}
|
||||
|
@ -72,27 +72,28 @@ type User struct {
|
||||
LastSigninTime string `xorm:"varchar(100)" json:"lastSigninTime"`
|
||||
LastSigninIp string `xorm:"varchar(100)" json:"lastSigninIp"`
|
||||
|
||||
Github string `xorm:"varchar(100)" json:"github"`
|
||||
Google string `xorm:"varchar(100)" json:"google"`
|
||||
QQ string `xorm:"qq varchar(100)" json:"qq"`
|
||||
WeChat string `xorm:"wechat varchar(100)" json:"wechat"`
|
||||
Facebook string `xorm:"facebook varchar(100)" json:"facebook"`
|
||||
DingTalk string `xorm:"dingtalk varchar(100)" json:"dingtalk"`
|
||||
Weibo string `xorm:"weibo varchar(100)" json:"weibo"`
|
||||
Gitee string `xorm:"gitee varchar(100)" json:"gitee"`
|
||||
LinkedIn string `xorm:"linkedin varchar(100)" json:"linkedin"`
|
||||
Wecom string `xorm:"wecom varchar(100)" json:"wecom"`
|
||||
Lark string `xorm:"lark varchar(100)" json:"lark"`
|
||||
Gitlab string `xorm:"gitlab varchar(100)" json:"gitlab"`
|
||||
Adfs string `xorm:"adfs varchar(100)" json:"adfs"`
|
||||
Baidu string `xorm:"baidu varchar(100)" json:"baidu"`
|
||||
Alipay string `xorm:"alipay varchar(100)" json:"alipay"`
|
||||
Casdoor string `xorm:"casdoor varchar(100)" json:"casdoor"`
|
||||
Infoflow string `xorm:"infoflow varchar(100)" json:"infoflow"`
|
||||
Apple string `xorm:"apple varchar(100)" json:"apple"`
|
||||
AzureAD string `xorm:"azuread varchar(100)" json:"azuread"`
|
||||
Slack string `xorm:"slack varchar(100)" json:"slack"`
|
||||
Steam string `xorm:"steam varchar(100)" json:"steam"`
|
||||
Github string `xorm:"varchar(100)" json:"github"`
|
||||
Google string `xorm:"varchar(100)" json:"google"`
|
||||
QQ string `xorm:"qq varchar(100)" json:"qq"`
|
||||
WeChat string `xorm:"wechat varchar(100)" json:"wechat"`
|
||||
WeChatUnionId string `xorm:"varchar(100)" json:"unionId"`
|
||||
Facebook string `xorm:"facebook varchar(100)" json:"facebook"`
|
||||
DingTalk string `xorm:"dingtalk varchar(100)" json:"dingtalk"`
|
||||
Weibo string `xorm:"weibo varchar(100)" json:"weibo"`
|
||||
Gitee string `xorm:"gitee varchar(100)" json:"gitee"`
|
||||
LinkedIn string `xorm:"linkedin varchar(100)" json:"linkedin"`
|
||||
Wecom string `xorm:"wecom varchar(100)" json:"wecom"`
|
||||
Lark string `xorm:"lark varchar(100)" json:"lark"`
|
||||
Gitlab string `xorm:"gitlab varchar(100)" json:"gitlab"`
|
||||
Adfs string `xorm:"adfs varchar(100)" json:"adfs"`
|
||||
Baidu string `xorm:"baidu varchar(100)" json:"baidu"`
|
||||
Alipay string `xorm:"alipay varchar(100)" json:"alipay"`
|
||||
Casdoor string `xorm:"casdoor varchar(100)" json:"casdoor"`
|
||||
Infoflow string `xorm:"infoflow varchar(100)" json:"infoflow"`
|
||||
Apple string `xorm:"apple varchar(100)" json:"apple"`
|
||||
AzureAD string `xorm:"azuread varchar(100)" json:"azuread"`
|
||||
Slack string `xorm:"slack varchar(100)" json:"slack"`
|
||||
Steam string `xorm:"steam varchar(100)" json:"steam"`
|
||||
|
||||
Ldap string `xorm:"ldap varchar(100)" json:"ldap"`
|
||||
Properties map[string]string `json:"properties"`
|
||||
@ -227,6 +228,23 @@ func getUserById(owner string, id string) *User {
|
||||
}
|
||||
}
|
||||
|
||||
func getUserByWechatId(wechatOpenId string, wechatUnionId string) *User {
|
||||
if wechatUnionId == "" {
|
||||
wechatUnionId = wechatOpenId
|
||||
}
|
||||
user := &User{}
|
||||
existed, err := adapter.Engine.Where("wechat = ? OR wechat = ? OR unionid = ?", wechatOpenId, wechatUnionId, wechatUnionId).Get(user)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
if existed {
|
||||
return user
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func GetUserByEmail(owner string, email string) *User {
|
||||
if owner == "" || email == "" {
|
||||
return nil
|
||||
|
@ -76,6 +76,10 @@ export function isProviderVisible(providerItem) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (providerItem.provider.type === "WeChatMiniProgram"){
|
||||
return false
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -392,6 +396,7 @@ export function getProviderTypeOptions(category) {
|
||||
{id: 'GitHub', name: 'GitHub'},
|
||||
{id: 'QQ', name: 'QQ'},
|
||||
{id: 'WeChat', name: 'WeChat'},
|
||||
{id: 'WeChatMiniProgram', name: 'WeChat Mini Program'},
|
||||
{id: 'Facebook', name: 'Facebook'},
|
||||
{id: 'DingTalk', name: 'DingTalk'},
|
||||
{id: 'Weibo', name: 'Weibo'},
|
||||
|
@ -36,6 +36,9 @@ const authInfo = {
|
||||
mpScope: "snsapi_userinfo",
|
||||
mpEndpoint: "https://open.weixin.qq.com/connect/oauth2/authorize"
|
||||
},
|
||||
WeChatMiniProgram: {
|
||||
endpoint: "https://mp.weixin.qq.com/",
|
||||
},
|
||||
Facebook: {
|
||||
scope: "email,public_profile",
|
||||
endpoint: "https://www.facebook.com/dialog/oauth",
|
||||
|
Loading…
x
Reference in New Issue
Block a user