diff --git a/controllers/auth.go b/controllers/auth.go index 5e171e4e..ffad4576 100644 --- a/controllers/auth.go +++ b/controllers/auth.go @@ -109,7 +109,7 @@ func (c *ApiController) GetApplicationLogin() { } func setHttpClient(idProvider idp.IdProvider, providerType string) { - if providerType == "GitHub" || providerType == "Google" || providerType == "Facebook" || providerType == "LinkedIn" { + if providerType == "GitHub" || providerType == "Google" || providerType == "Facebook" || providerType == "LinkedIn" || providerType == "Steam" { idProvider.SetHttpClient(proxy.ProxyHttpClient) } else { idProvider.SetHttpClient(proxy.DefaultHttpClient) diff --git a/idp/goth.go b/idp/goth.go index 825df42f..2b9fe5fb 100644 --- a/idp/goth.go +++ b/idp/goth.go @@ -45,6 +45,7 @@ import ( "github.com/markbates/goth/providers/salesforce" "github.com/markbates/goth/providers/shopify" "github.com/markbates/goth/providers/slack" + "github.com/markbates/goth/providers/steam" "github.com/markbates/goth/providers/tumblr" "github.com/markbates/goth/providers/twitter" "github.com/markbates/goth/providers/yahoo" @@ -171,6 +172,11 @@ func NewGothIdProvider(providerType string, clientId string, clientSecret string Provider: slack.New(clientId, clientSecret, redirectUrl), Session: &slack.Session{}, } + case "Steam": + idp = GothIdProvider{ + Provider: steam.New(clientSecret, redirectUrl), + Session: &steam.Session{}, + } case "Tumblr": idp = GothIdProvider{ Provider: tumblr.New(clientId, clientSecret, redirectUrl), @@ -209,10 +215,21 @@ func (idp *GothIdProvider) SetHttpClient(client *http.Client) { func (idp *GothIdProvider) GetToken(code string) (*oauth2.Token, error) { var expireAt time.Time - //Need to construct variables supported by goth - //to call the function to obtain accessToken - value := url.Values{} - value.Add("code", code) + var value url.Values + var err error + if idp.Provider.Name() == "steam" { + value, err = url.ParseQuery(code) + returnUrl := reflect.ValueOf(idp.Session).Elem().FieldByName("CallbackURL") + returnUrl.Set(reflect.ValueOf(value.Get("openid.return_to"))) + if err != nil { + return nil, err + } + } else { + //Need to construct variables supported by goth + //to call the function to obtain accessToken + value = url.Values{} + value.Add("code", code) + } accessToken, err := idp.Session.Authorize(idp.Provider, value) //Get ExpiresAt's value valueOfExpire := reflect.ValueOf(idp.Session).Elem().FieldByName("ExpiresAt") @@ -231,10 +248,10 @@ func (idp *GothIdProvider) GetUserInfo(token *oauth2.Token) (*UserInfo, error) { if err != nil { return nil, err } - return getUser(gothUser), nil + return getUser(gothUser, idp.Provider.Name()), nil } -func getUser(gothUser goth.User) *UserInfo { +func getUser(gothUser goth.User, provider string) *UserInfo { user := UserInfo{ Id: gothUser.UserID, Username: gothUser.Name, @@ -258,7 +275,10 @@ func getUser(gothUser goth.User) *UserInfo { user.DisplayName = user.Username } } - + if provider == "steam" { + user.Username = user.DisplayName + user.Email = "" + } return &user } diff --git a/idp/provider.go b/idp/provider.go index a7015496..44582970 100644 --- a/idp/provider.go +++ b/idp/provider.go @@ -83,7 +83,7 @@ func GetIdProvider(typ string, subType string, clientId string, clientSecret str return nil } -var gothList = []string{"Apple", "AzureAd", "Slack"} +var gothList = []string{"Apple", "AzureAd", "Slack", "Steam"} func isGothSupport(provider string) bool { for _, value := range gothList { diff --git a/object/user.go b/object/user.go index 535b5cce..0d500dfe 100644 --- a/object/user.go +++ b/object/user.go @@ -84,6 +84,7 @@ type User struct { 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"` diff --git a/web/src/Setting.js b/web/src/Setting.js index 20a3313d..11fead41 100644 --- a/web/src/Setting.js +++ b/web/src/Setting.js @@ -403,6 +403,7 @@ export function getProviderTypeOptions(category) { {id: 'Apple', name: 'Apple'}, {id: 'AzureAD', name: 'AzureAD'}, {id: 'Slack', name: 'Slack'}, + {id: 'Steam', name: 'Steam'}, ] ); } else if (category === "Email") { diff --git a/web/src/auth/AuthCallback.js b/web/src/auth/AuthCallback.js index f57bfcb9..df146a8e 100644 --- a/web/src/auth/AuthCallback.js +++ b/web/src/auth/AuthCallback.js @@ -69,6 +69,7 @@ class AuthCallback extends React.Component { UNSAFE_componentWillMount() { const params = new URLSearchParams(this.props.location.search); + let isSteam = params.get("openid.mode") let code = params.get("code"); // WeCom returns "auth_code=xxx" instead of "code=xxx" if (code === null) { @@ -78,6 +79,10 @@ class AuthCallback extends React.Component { if (code === null) { code = params.get("authCode") } + //Steam don't use code, so we should use all params as code. + if (isSteam !== null && code === null) { + code = this.props.location.search + } const innerParams = this.getInnerParams(); const applicationName = innerParams.get("application"); diff --git a/web/src/auth/LoginPage.js b/web/src/auth/LoginPage.js index 22154503..c0c6ed30 100644 --- a/web/src/auth/LoginPage.js +++ b/web/src/auth/LoginPage.js @@ -39,6 +39,7 @@ import InfoflowLoginButton from "./InfoflowLoginButton"; import AppleLoginButton from "./AppleLoginButton" import AzureADLoginButton from "./AzureADLoginButton"; import SlackLoginButton from "./SlackLoginButton"; +import SteamLoginButton from "./SteamLoginButton"; import CustomGithubCorner from "../CustomGithubCorner"; import {CountDownInput} from "../common/CountDownInput"; @@ -197,6 +198,8 @@ class LoginPage extends React.Component { return } else if (type === "Slack") { return + } else if (type === "Steam") { + return } return text; diff --git a/web/src/auth/Provider.js b/web/src/auth/Provider.js index cec90af3..fde9e720 100644 --- a/web/src/auth/Provider.js +++ b/web/src/auth/Provider.js @@ -89,6 +89,9 @@ const authInfo = { scope: "users:read", endpoint: "https://slack.com/oauth/authorize", }, + Steam: { + endpoint: "https://steamcommunity.com/openid/login", + }, }; const otherProviderInfo = { @@ -274,5 +277,7 @@ export function getAuthUrl(application, provider, method) { return `${endpoint}?client_id=${provider.clientId}&redirect_uri=${redirectUri}&state=${state}&response_type=code&scope=${scope}&resource=https://graph.windows.net/`; } else if (provider.type === "Slack") { return `${endpoint}?client_id=${provider.clientId}&redirect_uri=${redirectUri}&state=${state}&response_type=code&scope=${scope}`; + } else if (provider.type === "Steam") { + return `${endpoint}?openid.claimed_id=http://specs.openid.net/auth/2.0/identifier_select&openid.identity=http://specs.openid.net/auth/2.0/identifier_select&openid.mode=checkid_setup&openid.ns=http://specs.openid.net/auth/2.0&openid.realm=${window.location.origin}&openid.return_to=${redirectUri}?state=${state}`; } } diff --git a/web/src/auth/SteamLoginButton.js b/web/src/auth/SteamLoginButton.js new file mode 100644 index 00000000..2bbeb4f4 --- /dev/null +++ b/web/src/auth/SteamLoginButton.js @@ -0,0 +1,32 @@ +// Copyright 2022 The casbin 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 Sign in with Steam; +} + +const config = { + text: "Sign in with Steam", + icon: Icon, + iconFormat: name => `fa fa-${name}`, + style: {background: "#ffffff", color: "#000000"}, + activeStyle: {background: "#ededee"}, +}; + +const SteamLoginButton = createButton(config); + +export default SteamLoginButton;