mirror of
https://github.com/casdoor/casdoor.git
synced 2025-05-23 18:54:03 +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")
|
scope := c.Input().Get("scope")
|
||||||
username := c.Input().Get("username")
|
username := c.Input().Get("username")
|
||||||
password := c.Input().Get("password")
|
password := c.Input().Get("password")
|
||||||
|
tag := c.Input().Get("tag")
|
||||||
|
avatar := c.Input().Get("avatar")
|
||||||
|
|
||||||
if clientId == "" && clientSecret == "" {
|
if clientId == "" && clientSecret == "" {
|
||||||
clientId, clientSecret, _ = c.Ctx.Request.BasicAuth()
|
clientId, clientSecret, _ = c.Ctx.Request.BasicAuth()
|
||||||
@ -191,11 +193,13 @@ func (c *ApiController) GetOAuthToken() {
|
|||||||
scope = tokenRequest.Scope
|
scope = tokenRequest.Scope
|
||||||
username = tokenRequest.Username
|
username = tokenRequest.Username
|
||||||
password = tokenRequest.Password
|
password = tokenRequest.Password
|
||||||
|
tag = tokenRequest.Tag
|
||||||
|
avatar = tokenRequest.Avatar
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
host := c.Ctx.Request.Host
|
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()
|
c.ServeJSON()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -23,4 +23,6 @@ type TokenRequest struct {
|
|||||||
Scope string `json:"scope"`
|
Scope string `json:"scope"`
|
||||||
Username string `json:"username"`
|
Username string `json:"username"`
|
||||||
Password string `json:"password"`
|
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")
|
id := c.Input().Get("id")
|
||||||
columnsStr := c.Input().Get("columns")
|
columnsStr := c.Input().Get("columns")
|
||||||
|
|
||||||
|
if id == "" {
|
||||||
|
id = c.GetSessionUsername()
|
||||||
|
}
|
||||||
|
|
||||||
var user object.User
|
var user object.User
|
||||||
err := json.Unmarshal(c.Ctx.Input.RequestBody, &user)
|
err := json.Unmarshal(c.Ctx.Input.RequestBody, &user)
|
||||||
if err != nil {
|
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
|
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 {
|
func UpdateProvider(id string, provider *Provider) bool {
|
||||||
owner, name := util.GetOwnerAndNameFromId(id)
|
owner, name := util.GetOwnerAndNameFromId(id)
|
||||||
if getProvider(owner, name) == nil {
|
if getProvider(owner, name) == nil {
|
||||||
|
@ -22,6 +22,7 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/casdoor/casdoor/idp"
|
||||||
"github.com/casdoor/casdoor/util"
|
"github.com/casdoor/casdoor/util"
|
||||||
"xorm.io/core"
|
"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
|
var errString string
|
||||||
application := GetApplicationByClientId(clientId)
|
application := GetApplicationByClientId(clientId)
|
||||||
if application == nil {
|
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
|
//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)
|
errString = fmt.Sprintf("error: grant_type: %s is not supported in this application", grantType)
|
||||||
return &TokenWrapper{
|
return &TokenWrapper{
|
||||||
AccessToken: errString,
|
AccessToken: errString,
|
||||||
@ -343,6 +346,11 @@ func GetOAuthToken(grantType string, clientId string, clientSecret string, code
|
|||||||
token, err = GetClientCredentialsToken(application, clientSecret, scope, host)
|
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 {
|
if err != nil {
|
||||||
errString = err.Error()
|
errString = err.Error()
|
||||||
return &TokenWrapper{
|
return &TokenWrapper{
|
||||||
@ -629,3 +637,74 @@ func GetTokenByUser(application *Application, user *User, scope string, host str
|
|||||||
AddToken(token)
|
AddToken(token)
|
||||||
return token, nil
|
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"`
|
LastSigninTime string `xorm:"varchar(100)" json:"lastSigninTime"`
|
||||||
LastSigninIp string `xorm:"varchar(100)" json:"lastSigninIp"`
|
LastSigninIp string `xorm:"varchar(100)" json:"lastSigninIp"`
|
||||||
|
|
||||||
Github string `xorm:"varchar(100)" json:"github"`
|
Github string `xorm:"varchar(100)" json:"github"`
|
||||||
Google string `xorm:"varchar(100)" json:"google"`
|
Google string `xorm:"varchar(100)" json:"google"`
|
||||||
QQ string `xorm:"qq varchar(100)" json:"qq"`
|
QQ string `xorm:"qq varchar(100)" json:"qq"`
|
||||||
WeChat string `xorm:"wechat varchar(100)" json:"wechat"`
|
WeChat string `xorm:"wechat varchar(100)" json:"wechat"`
|
||||||
Facebook string `xorm:"facebook varchar(100)" json:"facebook"`
|
WeChatUnionId string `xorm:"varchar(100)" json:"unionId"`
|
||||||
DingTalk string `xorm:"dingtalk varchar(100)" json:"dingtalk"`
|
Facebook string `xorm:"facebook varchar(100)" json:"facebook"`
|
||||||
Weibo string `xorm:"weibo varchar(100)" json:"weibo"`
|
DingTalk string `xorm:"dingtalk varchar(100)" json:"dingtalk"`
|
||||||
Gitee string `xorm:"gitee varchar(100)" json:"gitee"`
|
Weibo string `xorm:"weibo varchar(100)" json:"weibo"`
|
||||||
LinkedIn string `xorm:"linkedin varchar(100)" json:"linkedin"`
|
Gitee string `xorm:"gitee varchar(100)" json:"gitee"`
|
||||||
Wecom string `xorm:"wecom varchar(100)" json:"wecom"`
|
LinkedIn string `xorm:"linkedin varchar(100)" json:"linkedin"`
|
||||||
Lark string `xorm:"lark varchar(100)" json:"lark"`
|
Wecom string `xorm:"wecom varchar(100)" json:"wecom"`
|
||||||
Gitlab string `xorm:"gitlab varchar(100)" json:"gitlab"`
|
Lark string `xorm:"lark varchar(100)" json:"lark"`
|
||||||
Adfs string `xorm:"adfs varchar(100)" json:"adfs"`
|
Gitlab string `xorm:"gitlab varchar(100)" json:"gitlab"`
|
||||||
Baidu string `xorm:"baidu varchar(100)" json:"baidu"`
|
Adfs string `xorm:"adfs varchar(100)" json:"adfs"`
|
||||||
Alipay string `xorm:"alipay varchar(100)" json:"alipay"`
|
Baidu string `xorm:"baidu varchar(100)" json:"baidu"`
|
||||||
Casdoor string `xorm:"casdoor varchar(100)" json:"casdoor"`
|
Alipay string `xorm:"alipay varchar(100)" json:"alipay"`
|
||||||
Infoflow string `xorm:"infoflow varchar(100)" json:"infoflow"`
|
Casdoor string `xorm:"casdoor varchar(100)" json:"casdoor"`
|
||||||
Apple string `xorm:"apple varchar(100)" json:"apple"`
|
Infoflow string `xorm:"infoflow varchar(100)" json:"infoflow"`
|
||||||
AzureAD string `xorm:"azuread varchar(100)" json:"azuread"`
|
Apple string `xorm:"apple varchar(100)" json:"apple"`
|
||||||
Slack string `xorm:"slack varchar(100)" json:"slack"`
|
AzureAD string `xorm:"azuread varchar(100)" json:"azuread"`
|
||||||
Steam string `xorm:"steam varchar(100)" json:"steam"`
|
Slack string `xorm:"slack varchar(100)" json:"slack"`
|
||||||
|
Steam string `xorm:"steam varchar(100)" json:"steam"`
|
||||||
|
|
||||||
Ldap string `xorm:"ldap varchar(100)" json:"ldap"`
|
Ldap string `xorm:"ldap varchar(100)" json:"ldap"`
|
||||||
Properties map[string]string `json:"properties"`
|
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 {
|
func GetUserByEmail(owner string, email string) *User {
|
||||||
if owner == "" || email == "" {
|
if owner == "" || email == "" {
|
||||||
return nil
|
return nil
|
||||||
|
@ -76,6 +76,10 @@ export function isProviderVisible(providerItem) {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (providerItem.provider.type === "WeChatMiniProgram"){
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -392,6 +396,7 @@ export function getProviderTypeOptions(category) {
|
|||||||
{id: 'GitHub', name: 'GitHub'},
|
{id: 'GitHub', name: 'GitHub'},
|
||||||
{id: 'QQ', name: 'QQ'},
|
{id: 'QQ', name: 'QQ'},
|
||||||
{id: 'WeChat', name: 'WeChat'},
|
{id: 'WeChat', name: 'WeChat'},
|
||||||
|
{id: 'WeChatMiniProgram', name: 'WeChat Mini Program'},
|
||||||
{id: 'Facebook', name: 'Facebook'},
|
{id: 'Facebook', name: 'Facebook'},
|
||||||
{id: 'DingTalk', name: 'DingTalk'},
|
{id: 'DingTalk', name: 'DingTalk'},
|
||||||
{id: 'Weibo', name: 'Weibo'},
|
{id: 'Weibo', name: 'Weibo'},
|
||||||
|
@ -36,6 +36,9 @@ const authInfo = {
|
|||||||
mpScope: "snsapi_userinfo",
|
mpScope: "snsapi_userinfo",
|
||||||
mpEndpoint: "https://open.weixin.qq.com/connect/oauth2/authorize"
|
mpEndpoint: "https://open.weixin.qq.com/connect/oauth2/authorize"
|
||||||
},
|
},
|
||||||
|
WeChatMiniProgram: {
|
||||||
|
endpoint: "https://mp.weixin.qq.com/",
|
||||||
|
},
|
||||||
Facebook: {
|
Facebook: {
|
||||||
scope: "email,public_profile",
|
scope: "email,public_profile",
|
||||||
endpoint: "https://www.facebook.com/dialog/oauth",
|
endpoint: "https://www.facebook.com/dialog/oauth",
|
||||||
|
Loading…
x
Reference in New Issue
Block a user