diff --git a/idp/bilibili.go b/idp/bilibili.go
new file mode 100644
index 00000000..aaba4362
--- /dev/null
+++ b/idp/bilibili.go
@@ -0,0 +1,220 @@
+// Copyright 2021 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"
+ "io/ioutil"
+ "net/http"
+ "net/url"
+ "strings"
+ "time"
+
+ "golang.org/x/oauth2"
+)
+
+type BilibiliIdProvider struct {
+ Client *http.Client
+ Config *oauth2.Config
+}
+
+func NewBilibiliIdProvider(clientId string, clientSecret string, redirectUrl string) *BilibiliIdProvider {
+ idp := &BilibiliIdProvider{}
+
+ config := idp.getConfig(clientId, clientSecret, redirectUrl)
+ idp.Config = config
+
+ return idp
+}
+
+func (idp *BilibiliIdProvider) SetHttpClient(client *http.Client) {
+ idp.Client = client
+}
+
+// getConfig return a point of Config, which describes a typical 3-legged OAuth2 flow
+func (idp *BilibiliIdProvider) getConfig(clientId string, clientSecret string, redirectUrl string) *oauth2.Config {
+ var endpoint = oauth2.Endpoint{
+ TokenURL: "https://api.bilibili.com/x/account-oauth2/v1/token",
+ AuthURL: "http://member.bilibili.com/arcopen/fn/user/account/info",
+ }
+
+ var config = &oauth2.Config{
+ Scopes: []string{"", ""},
+ Endpoint: endpoint,
+ ClientID: clientId,
+ ClientSecret: clientSecret,
+ RedirectURL: redirectUrl,
+ }
+
+ return config
+}
+
+type BilibiliProviderToken struct {
+ AccessToken string `json:"access_token"`
+ ExpiresIn int `json:"expires_in"`
+ RefreshToken string `json:"refresh_token"`
+}
+
+type BilibiliIdProviderTokenResponse struct {
+ Code int `json:"code"`
+ Message string `json:"message"`
+ TTL int `json:"ttl"`
+ Data BilibiliProviderToken `json:"data"`
+}
+
+/*
+{
+ "code": 0,
+ "message": "0",
+ "ttl": 1,
+ "data": {
+ "access_token": "d30bedaa4d8eb3128cf35ddc1030e27d",
+ "expires_in": 1630220614,
+ "refresh_token": "WxFDKwqScZIQDm4iWmKDvetyFugM6HkX"
+ }
+}
+*/
+// GetToken use code get access_token (*operation of getting code ought to be done in front)
+// get more detail via: https://openhome.bilibili.com/doc/4/eaf0e2b5-bde9-b9a0-9be1-019bb455701c
+func (idp *BilibiliIdProvider) GetToken(code string) (*oauth2.Token, error) {
+ pTokenParams := &struct {
+ ClientId string `json:"client_id"`
+ ClientSecret string `json:"client_secret"`
+ GrantType string `json:"grant_type"`
+ Code string `json:"code"`
+ }{
+ idp.Config.ClientID,
+ idp.Config.ClientSecret,
+ "authorization_code",
+ code,
+ }
+
+ data, err := idp.postWithBody(pTokenParams, idp.Config.Endpoint.TokenURL)
+
+ if err != nil {
+ return nil, err
+ }
+
+ response := &BilibiliIdProviderTokenResponse{}
+ err = json.Unmarshal(data, response)
+ if err != nil {
+ return nil, err
+ }
+
+ if response.Code != 0 {
+ return nil, fmt.Errorf("pToken.Errcode = %d, pToken.Errmsg = %s", response.Code, response.Message)
+ }
+
+ token := &oauth2.Token{
+ AccessToken: response.Data.AccessToken,
+ Expiry: time.Unix(time.Now().Unix()+int64(response.Data.ExpiresIn), 0),
+ RefreshToken: response.Data.RefreshToken,
+ }
+
+ return token, nil
+}
+
+/*
+{
+ "code": 0,
+ "message": "0",
+ "ttl": 1,
+ "data": {
+ "name":"bilibili",
+ "face":"http://i0.hdslb.com/bfs/face/e1c99895a9f9df4f260a70dc7e227bcb46cf319c.jpg",
+ "openid":"9205eeaa1879skxys969ed47874f225c3"
+ }
+}
+*/
+
+type BilibiliUserInfo struct {
+ Name string `json:"name"`
+ Face string `json:"face"`
+ OpenId string `json:"openid`
+}
+
+type BilibiliUserInfoResponse struct {
+ Code int `json:"code"`
+ Message string `json:"message"`
+ TTL int `json:"ttl"`
+ Data BilibiliUserInfo `json:"data"`
+}
+
+// GetUserInfo Use access_token to get UserInfo
+// get more detail via: https://openhome.bilibili.com/doc/4/feb66f99-7d87-c206-00e7-d84164cd701c
+func (idp *BilibiliIdProvider) GetUserInfo(token *oauth2.Token) (*UserInfo, error) {
+ accessToken := token.AccessToken
+ clientId := idp.Config.ClientID
+
+ params := url.Values{}
+ params.Add("client_id", clientId)
+ params.Add("access_token", accessToken)
+
+ userInfoUrl := fmt.Sprintf("%s?%s", idp.Config.Endpoint.AuthURL, params.Encode())
+
+ resp, err := idp.Client.Get(userInfoUrl)
+
+ if err != nil {
+ return nil, err
+ }
+
+ data, err := ioutil.ReadAll(resp.Body)
+ if err != nil {
+ return nil, err
+ }
+
+ bUserInfoResponse := &BilibiliUserInfoResponse{}
+ if err = json.Unmarshal(data, bUserInfoResponse); err != nil {
+ return nil, err
+ }
+
+ if bUserInfoResponse.Code != 0 {
+ return nil, fmt.Errorf("userinfo.Errcode = %d, userinfo.Errmsg = %s", bUserInfoResponse.Code, bUserInfoResponse.Message)
+ }
+
+ userInfo := &UserInfo{
+ Id: bUserInfoResponse.Data.OpenId,
+ Username: bUserInfoResponse.Data.Name,
+ AvatarUrl: bUserInfoResponse.Data.Face,
+ }
+
+ return userInfo, nil
+}
+
+func (idp *BilibiliIdProvider) postWithBody(body interface{}, url string) ([]byte, error) {
+ bs, err := json.Marshal(body)
+ if err != nil {
+ return nil, err
+ }
+ r := strings.NewReader(string(bs))
+ resp, err := idp.Client.Post(url, "application/json;charset=UTF-8", r)
+ if err != nil {
+ return nil, err
+ }
+ data, err := ioutil.ReadAll(resp.Body)
+ if err != nil {
+ return nil, err
+ }
+ defer func(Body io.ReadCloser) {
+ err := Body.Close()
+ if err != nil {
+ return
+ }
+ }(resp.Body)
+
+ return data, nil
+}
diff --git a/idp/provider.go b/idp/provider.go
index cd5e76c1..a29fc536 100644
--- a/idp/provider.go
+++ b/idp/provider.go
@@ -88,6 +88,8 @@ func GetIdProvider(typ string, subType string, clientId string, clientSecret str
return NewOktaIdProvider(clientId, clientSecret, redirectUrl, hostUrl)
} else if isGothSupport(typ) {
return NewGothIdProvider(typ, clientId, clientSecret, redirectUrl)
+ } else if typ == "Bilibili" {
+ return NewBilibiliIdProvider(clientId, clientSecret, redirectUrl)
}
return nil
diff --git a/object/user.go b/object/user.go
index ef3c0f6f..79a90969 100644
--- a/object/user.go
+++ b/object/user.go
@@ -94,6 +94,7 @@ type User struct {
AzureAD string `xorm:"azuread varchar(100)" json:"azuread"`
Slack string `xorm:"slack varchar(100)" json:"slack"`
Steam string `xorm:"steam varchar(100)" json:"steam"`
+ Bilibili string `xorm:"bilibili varchar(100)" json:"bilibili"`
Okta string `xorm:"okta varchar(100)" json:"okta"`
Custom string `xorm:"custom varchar(100)" json:"custom"`
diff --git a/web/src/Setting.js b/web/src/Setting.js
index 17b155f5..1f108a4b 100644
--- a/web/src/Setting.js
+++ b/web/src/Setting.js
@@ -554,6 +554,7 @@ export function getProviderTypeOptions(category) {
{id: 'AzureAD', name: 'AzureAD'},
{id: 'Slack', name: 'Slack'},
{id: 'Steam', name: 'Steam'},
+ {id: 'Bilibili', name: 'Bilibili'},
{id: 'Okta', name: 'Okta'},
{id: 'Custom', name: 'Custom'},
]
diff --git a/web/src/auth/BilibiliLoginButton.js b/web/src/auth/BilibiliLoginButton.js
new file mode 100644
index 00000000..3fa69141
--- /dev/null
+++ b/web/src/auth/BilibiliLoginButton.js
@@ -0,0 +1,32 @@
+// Copyright 2021 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.
+
+import {createButton} from "react-social-login-buttons";
+import {StaticBaseUrl} from "../Setting";
+
+function Icon({ width = 24, height = 24, color }) {
+ return
;
+}
+
+const config = {
+ text: "Sign in with Bilibili",
+ icon: Icon,
+ iconFormat: name => `fa fa-${name}`,
+ style: {background: "#0191e0"},
+ activeStyle: {background: "rgb(76,143,208)"},
+};
+
+const BilibiliLoginButton = createButton(config);
+
+export default BilibiliLoginButton;
diff --git a/web/src/auth/LoginPage.js b/web/src/auth/LoginPage.js
index 0c261380..46f65e24 100644
--- a/web/src/auth/LoginPage.js
+++ b/web/src/auth/LoginPage.js
@@ -46,6 +46,7 @@ import SteamLoginButton from "./SteamLoginButton";
import OktaLoginButton from "./OktaLoginButton";
import CustomGithubCorner from "../CustomGithubCorner";
import {CountDownInput} from "../common/CountDownInput";
+import BilibiliLoginButton from "./BilibiliLoginButton";
class LoginPage extends React.Component {
constructor(props) {
@@ -279,6 +280,8 @@ class LoginPage extends React.Component {
return
} else if (type === "Steam") {
return
+ } else if (type === "Bilibili") {
+ return
} else if (type === "Okta") {
return
}
diff --git a/web/src/auth/Provider.js b/web/src/auth/Provider.js
index 428f1dc9..4170c7db 100644
--- a/web/src/auth/Provider.js
+++ b/web/src/auth/Provider.js
@@ -114,6 +114,9 @@ const authInfo = {
Custom: {
endpoint: "https://example.com/",
},
+ Bilibili: {
+ endpoint: "https://passport.bilibili.com/register/pc_oauth2.html"
+ }
};
export function getProviderUrl(provider) {
@@ -238,5 +241,7 @@ export function getAuthUrl(application, provider, method) {
return `${provider.domain}/v1/authorize?client_id=${provider.clientId}&redirect_uri=${redirectUri}&state=${state}&response_type=code&scope=${scope}`;
} else if (provider.type === "Custom") {
return `${provider.customAuthUrl}?client_id=${provider.clientId}&redirect_uri=${redirectUri}&scope=${provider.customScope}&response_type=code&state=${state}`;
- }
+ } else if (provider.type === "Bilibili") {
+ return `${endpoint}#/?client_id=${provider.clientId}&return_url=${redirectUri}&state=${state}&response_type=code`
+ }
}