Skip to content

Commit 3feb6ce

Browse files
authored
feat: add Kwai OAuth provider (casdoor#3480)
* feat: add Kwai OAuth provider * fix: incorrect parameter in getAuthUrl
1 parent 08d6b45 commit 3feb6ce

File tree

9 files changed

+211
-1
lines changed

9 files changed

+211
-1
lines changed

idp/kwai.go

+161
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,161 @@
1+
// Copyright 2024 The Casdoor Authors. All Rights Reserved.
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package idp
16+
17+
import (
18+
"encoding/json"
19+
"fmt"
20+
"io"
21+
"net/http"
22+
"time"
23+
24+
"golang.org/x/oauth2"
25+
)
26+
27+
type KwaiIdProvider struct {
28+
Client *http.Client
29+
Config *oauth2.Config
30+
}
31+
32+
func NewKwaiIdProvider(clientId string, clientSecret string, redirectUrl string) *KwaiIdProvider {
33+
idp := &KwaiIdProvider{}
34+
idp.Config = idp.getConfig(clientId, clientSecret, redirectUrl)
35+
return idp
36+
}
37+
38+
func (idp *KwaiIdProvider) SetHttpClient(client *http.Client) {
39+
idp.Client = client
40+
}
41+
42+
func (idp *KwaiIdProvider) getConfig(clientId string, clientSecret string, redirectUrl string) *oauth2.Config {
43+
endpoint := oauth2.Endpoint{
44+
TokenURL: "https://open.kuaishou.com/oauth2/access_token",
45+
AuthURL: "https://open.kuaishou.com/oauth2/authorize", // qr code: /oauth2/connect
46+
}
47+
48+
config := &oauth2.Config{
49+
Scopes: []string{"user_info"},
50+
Endpoint: endpoint,
51+
ClientID: clientId,
52+
ClientSecret: clientSecret,
53+
RedirectURL: redirectUrl,
54+
}
55+
56+
return config
57+
}
58+
59+
type KwaiTokenResp struct {
60+
Result int `json:"result"`
61+
ErrorMsg string `json:"error_msg"`
62+
AccessToken string `json:"access_token"`
63+
ExpiresIn int `json:"expires_in"`
64+
RefreshToken string `json:"refresh_token"`
65+
RefreshTokenExpiresIn int `json:"refresh_token_expires_in"`
66+
OpenId string `json:"open_id"`
67+
Scopes []string `json:"scopes"`
68+
}
69+
70+
// GetToken use code to get access_token
71+
func (idp *KwaiIdProvider) GetToken(code string) (*oauth2.Token, error) {
72+
params := map[string]string{
73+
"app_id": idp.Config.ClientID,
74+
"app_secret": idp.Config.ClientSecret,
75+
"code": code,
76+
"grant_type": "authorization_code",
77+
}
78+
tokenUrl := fmt.Sprintf("%s?app_id=%s&app_secret=%s&code=%s&grant_type=authorization_code",
79+
idp.Config.Endpoint.TokenURL, params["app_id"], params["app_secret"], params["code"])
80+
resp, err := idp.Client.Get(tokenUrl)
81+
if err != nil {
82+
return nil, err
83+
}
84+
defer resp.Body.Close()
85+
body, err := io.ReadAll(resp.Body)
86+
if err != nil {
87+
return nil, err
88+
}
89+
var tokenResp KwaiTokenResp
90+
err = json.Unmarshal(body, &tokenResp)
91+
if err != nil {
92+
return nil, err
93+
}
94+
if tokenResp.Result != 1 {
95+
return nil, fmt.Errorf("get token error: %s", tokenResp.ErrorMsg)
96+
}
97+
98+
token := &oauth2.Token{
99+
AccessToken: tokenResp.AccessToken,
100+
RefreshToken: tokenResp.RefreshToken,
101+
Expiry: time.Now().Add(time.Duration(tokenResp.ExpiresIn) * time.Second),
102+
}
103+
104+
raw := make(map[string]interface{})
105+
raw["open_id"] = tokenResp.OpenId
106+
token = token.WithExtra(raw)
107+
108+
return token, nil
109+
}
110+
111+
// More details: https://open.kuaishou.com/openapi/user_info
112+
type KwaiUserInfo struct {
113+
Result int `json:"result"`
114+
ErrorMsg string `json:"error_msg"`
115+
UserInfo struct {
116+
Head string `json:"head"`
117+
Name string `json:"name"`
118+
Sex string `json:"sex"`
119+
City string `json:"city"`
120+
} `json:"user_info"`
121+
}
122+
123+
// GetUserInfo use token to get user profile
124+
func (idp *KwaiIdProvider) GetUserInfo(token *oauth2.Token) (*UserInfo, error) {
125+
userInfoUrl := fmt.Sprintf("https://open.kuaishou.com/openapi/user_info?app_id=%s&access_token=%s",
126+
idp.Config.ClientID, token.AccessToken)
127+
128+
resp, err := idp.Client.Get(userInfoUrl)
129+
if err != nil {
130+
return nil, err
131+
}
132+
defer resp.Body.Close()
133+
134+
body, err := io.ReadAll(resp.Body)
135+
if err != nil {
136+
return nil, err
137+
}
138+
139+
var kwaiUserInfo KwaiUserInfo
140+
err = json.Unmarshal(body, &kwaiUserInfo)
141+
if err != nil {
142+
return nil, err
143+
}
144+
145+
if kwaiUserInfo.Result != 1 {
146+
return nil, fmt.Errorf("get user info error: %s", kwaiUserInfo.ErrorMsg)
147+
}
148+
149+
userInfo := &UserInfo{
150+
Id: token.Extra("open_id").(string),
151+
Username: kwaiUserInfo.UserInfo.Name,
152+
DisplayName: kwaiUserInfo.UserInfo.Name,
153+
AvatarUrl: kwaiUserInfo.UserInfo.Head,
154+
Extra: map[string]string{
155+
"gender": kwaiUserInfo.UserInfo.Sex,
156+
"city": kwaiUserInfo.UserInfo.City,
157+
},
158+
}
159+
160+
return userInfo, nil
161+
}

idp/provider.go

+2
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,8 @@ func GetIdProvider(idpInfo *ProviderInfo, redirectUrl string) (IdProvider, error
113113
return NewOktaIdProvider(idpInfo.ClientId, idpInfo.ClientSecret, redirectUrl, idpInfo.HostUrl), nil
114114
case "Douyin":
115115
return NewDouyinIdProvider(idpInfo.ClientId, idpInfo.ClientSecret, redirectUrl), nil
116+
case "Kwai":
117+
return NewKwaiIdProvider(idpInfo.ClientId, idpInfo.ClientSecret, redirectUrl), nil
116118
case "Bilibili":
117119
return NewBilibiliIdProvider(idpInfo.ClientId, idpInfo.ClientSecret, redirectUrl), nil
118120
case "MetaMask":

object/user.go

+2-1
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,7 @@ type User struct {
129129
Bilibili string `xorm:"bilibili varchar(100)" json:"bilibili"`
130130
Okta string `xorm:"okta varchar(100)" json:"okta"`
131131
Douyin string `xorm:"douyin varchar(100)" json:"douyin"`
132+
Kwai string `xorm:"kwai varchar(100)" json:"kwai"`
132133
Line string `xorm:"line varchar(100)" json:"line"`
133134
Amazon string `xorm:"amazon varchar(100)" json:"amazon"`
134135
Auth0 string `xorm:"auth0 varchar(100)" json:"auth0"`
@@ -698,7 +699,7 @@ func UpdateUser(id string, user *User, columns []string, isAdmin bool) (bool, er
698699
"is_admin", "is_forbidden", "is_deleted", "hash", "is_default_avatar", "properties", "webauthnCredentials", "managedAccounts", "face_ids", "mfaAccounts",
699700
"signin_wrong_times", "last_change_password_time", "last_signin_wrong_time", "groups", "access_key", "access_secret", "mfa_phone_enabled", "mfa_email_enabled",
700701
"github", "google", "qq", "wechat", "facebook", "dingtalk", "weibo", "gitee", "linkedin", "wecom", "lark", "gitlab", "adfs",
701-
"baidu", "alipay", "casdoor", "infoflow", "apple", "azuread", "azureadb2c", "slack", "steam", "bilibili", "okta", "douyin", "line", "amazon",
702+
"baidu", "alipay", "casdoor", "infoflow", "apple", "azuread", "azureadb2c", "slack", "steam", "bilibili", "okta", "douyin", "kwai", "line", "amazon",
702703
"auth0", "battlenet", "bitbucket", "box", "cloudfoundry", "dailymotion", "deezer", "digitalocean", "discord", "dropbox",
703704
"eveonline", "fitbit", "gitea", "heroku", "influxcloud", "instagram", "intercom", "kakao", "lastfm", "mailru", "meetup",
704705
"microsoftonline", "naver", "nextcloud", "onedrive", "oura", "patreon", "paypal", "salesforce", "shopify", "soundcloud",

swagger/swagger.json

+3
Original file line numberDiff line numberDiff line change
@@ -7558,6 +7558,9 @@
75587558
"type": "integer",
75597559
"format": "int64"
75607560
},
7561+
"kwai": {
7562+
"type": "string"
7563+
},
75617564
"language": {
75627565
"type": "string"
75637566
},

swagger/swagger.yml

+2
Original file line numberDiff line numberDiff line change
@@ -4981,6 +4981,8 @@ definitions:
49814981
karma:
49824982
type: integer
49834983
format: int64
4984+
kwai:
4985+
type: string
49844986
language:
49854987
type: string
49864988
lark:

web/src/Setting.js

+1
Original file line numberDiff line numberDiff line change
@@ -985,6 +985,7 @@ export function getProviderTypeOptions(category) {
985985
{id: "Bilibili", name: "Bilibili"},
986986
{id: "Okta", name: "Okta"},
987987
{id: "Douyin", name: "Douyin"},
988+
{id: "Kwai", name: "Kwai"},
988989
{id: "Line", name: "Line"},
989990
{id: "Amazon", name: "Amazon"},
990991
{id: "Auth0", name: "Auth0"},

web/src/auth/KwaiLoginButton.js

+31
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
// Copyright 2024 The Casdoor Authors. All Rights Reserved.
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
import {createButton} from "react-social-login-buttons";
16+
import {StaticBaseUrl} from "../Setting";
17+
18+
function Icon({width = 24, height = 24}) {
19+
return <img src={`${StaticBaseUrl}/buttons/kwai.svg`} alt="Sign in with Kwai" style={{width: width, height: height}} />;
20+
}
21+
22+
const config = {
23+
text: "Sign in with Kwai",
24+
icon: Icon,
25+
style: {background: "#ffffff", color: "#000000"},
26+
activeStyle: {background: "#ededee"},
27+
};
28+
29+
const KwaiLoginButton = createButton(config);
30+
31+
export default KwaiLoginButton;

web/src/auth/Provider.js

+6
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,10 @@ const authInfo = {
119119
scope: "user_info",
120120
endpoint: "https://open.douyin.com/platform/oauth/connect",
121121
},
122+
Kwai: {
123+
scope: "user_info",
124+
endpoint: "https://open.kuaishou.com/oauth2/connect",
125+
},
122126
Custom: {
123127
endpoint: "https://example.com/",
124128
},
@@ -470,6 +474,8 @@ export function getAuthUrl(application, provider, method, code) {
470474
return `${provider.domain}/v1/authorize?client_id=${provider.clientId}&redirect_uri=${redirectUri}&state=${state}&response_type=code&scope=${scope}`;
471475
} else if (provider.type === "Douyin" || provider.type === "TikTok") {
472476
return `${endpoint}?client_key=${provider.clientId}&redirect_uri=${redirectUri}&state=${state}&response_type=code&scope=${scope}`;
477+
} else if (provider.type === "Kwai") {
478+
return `${endpoint}?app_id=${provider.clientId}&redirect_uri=${redirectUri}&state=${state}&response_type=code&scope=${scope}`;
473479
} else if (provider.type === "Custom") {
474480
return `${provider.customAuthUrl}?client_id=${provider.clientId}&redirect_uri=${redirectUri}&scope=${provider.scopes}&response_type=code&state=${state}`;
475481
} else if (provider.type === "Bilibili") {

web/src/auth/ProviderButton.js

+3
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ import SteamLoginButton from "./SteamLoginButton";
4040
import BilibiliLoginButton from "./BilibiliLoginButton";
4141
import OktaLoginButton from "./OktaLoginButton";
4242
import DouyinLoginButton from "./DouyinLoginButton";
43+
import KwaiLoginButton from "./KwaiLoginButton";
4344
import LoginButton from "./LoginButton";
4445
import * as AuthBackend from "./AuthBackend";
4546
import {WechatOfficialAccountModal} from "./Util";
@@ -96,6 +97,8 @@ function getSigninButton(provider) {
9697
return <OktaLoginButton text={text} align={"center"} />;
9798
} else if (provider.type === "Douyin") {
9899
return <DouyinLoginButton text={text} align={"center"} />;
100+
} else if (provider.type === "Kwai") {
101+
return <KwaiLoginButton text={text} align={"center"} />;
99102
} else {
100103
return <LoginButton key={provider.type} type={provider.type} logoUrl={getProviderLogoURL(provider)} />;
101104
}

0 commit comments

Comments
 (0)