From d1f88ca9b8e6b5d36192fba2385b5137bb6de270 Mon Sep 17 00:00:00 2001 From: haiwu <54203997+Chinoholo0807@users.noreply.github.com> Date: Tue, 25 Jul 2023 15:49:15 +0800 Subject: [PATCH] feat: support google one tap signin (#2131) * feat: add google one tap support * feat: gofumpt * feat: add google provider rule conf * feat: update i18n --- idp/google.go | 57 +++++++++++++++++++++++++++++++ web/package.json | 1 + web/src/auth/GoogleLoginButton.js | 28 +++++++++++++++ web/src/auth/LoginPage.js | 18 +++++++++- web/src/locales/de/data.json | 3 ++ web/src/locales/en/data.json | 3 ++ web/src/locales/es/data.json | 3 ++ web/src/locales/fr/data.json | 3 ++ web/src/locales/id/data.json | 3 ++ web/src/locales/ja/data.json | 3 ++ web/src/locales/ko/data.json | 3 ++ web/src/locales/pt/data.json | 3 ++ web/src/locales/ru/data.json | 3 ++ web/src/locales/vi/data.json | 3 ++ web/src/locales/zh/data.json | 3 ++ web/src/table/ProviderTable.js | 42 ++++++++++++++++------- web/yarn.lock | 5 +++ 17 files changed, 170 insertions(+), 14 deletions(-) diff --git a/idp/google.go b/idp/google.go index ca22a690..5dfb976c 100644 --- a/idp/google.go +++ b/idp/google.go @@ -21,15 +21,39 @@ import ( "fmt" "io" "net/http" + "strings" + "time" + "github.com/casdoor/casdoor/util" "golang.org/x/oauth2" ) +const GoogleIdTokenKey = "GoogleIdToken" + type GoogleIdProvider struct { Client *http.Client Config *oauth2.Config } +// https://developers.google.com/identity/sign-in/web/backend-auth#calling-the-tokeninfo-endpoint +type GoogleIdToken struct { + // These six fields are included in all Google ID Tokens. + Iss string `json:"iss"` // The issuer, or signer, of the token. For Google-signed ID tokens, this value is https://accounts.google.com. + Sub string `json:"sub"` // The subject: the ID that represents the principal making the request. + Azp string `json:"azp"` // Optional. Who the token was issued to. Here is the ClientID + Aud string `json:"aud"` // The audience of the token. Here is the ClientID + Iat string `json:"iat"` // Unix epoch time when the token was issued. + Exp string `json:"exp"` // Unix epoch time when the token expires. + // These seven fields are only included when the user has granted the "profile" and "email" OAuth scopes to the application. + Email string `json:"email"` + EmailVerified string `json:"email_verified"` + Name string `json:"name"` + Picture string `json:"picture"` + GivenName string `json:"given_name"` + FamilyName string `json:"family_name"` + Locale string `json:"locale"` +} + func NewGoogleIdProvider(clientId string, clientSecret string, redirectUrl string) *GoogleIdProvider { idp := &GoogleIdProvider{} @@ -61,6 +85,25 @@ func (idp *GoogleIdProvider) getConfig() *oauth2.Config { } func (idp *GoogleIdProvider) GetToken(code string) (*oauth2.Token, error) { + // Obtained the GoogleIdToken through Google OneTap authorization. + if strings.HasPrefix(code, GoogleIdTokenKey) { + code = strings.TrimPrefix(code, GoogleIdTokenKey+"-") + var googleIdToken GoogleIdToken + if err := json.Unmarshal([]byte(code), &googleIdToken); err != nil { + return nil, err + } + expiry := int64(util.ParseInt(googleIdToken.Exp)) + token := &oauth2.Token{ + AccessToken: fmt.Sprintf("%v-%v", GoogleIdTokenKey, googleIdToken.Sub), + TokenType: "Bearer", + Expiry: time.Unix(expiry, 0), + } + token = token.WithExtra(map[string]interface{}{ + GoogleIdTokenKey: googleIdToken, + }) + return token, nil + } + ctx := context.WithValue(context.Background(), oauth2.HTTPClient, idp.Client) return idp.Config.Exchange(ctx, code) } @@ -88,6 +131,20 @@ type GoogleUserInfo struct { } func (idp *GoogleIdProvider) GetUserInfo(token *oauth2.Token) (*UserInfo, error) { + if strings.HasPrefix(token.AccessToken, GoogleIdTokenKey) { + googleIdToken, ok := token.Extra(GoogleIdTokenKey).(GoogleIdToken) + if !ok { + return nil, errors.New("invalid googleIdToken") + } + userInfo := UserInfo{ + Id: googleIdToken.Sub, + Username: googleIdToken.Email, + DisplayName: googleIdToken.Name, + Email: googleIdToken.Email, + AvatarUrl: googleIdToken.Picture, + } + return &userInfo, nil + } url := fmt.Sprintf("https://www.googleapis.com/oauth2/v2/userinfo?alt=json&access_token=%s", token.AccessToken) resp, err := idp.Client.Get(url) if err != nil { diff --git a/web/package.json b/web/package.json index bd3e16e9..9f29d2f7 100644 --- a/web/package.json +++ b/web/package.json @@ -33,6 +33,7 @@ "react-device-detect": "^2.2.2", "react-dom": "^18.2.0", "react-github-corner": "^2.5.0", + "react-google-one-tap-login": "^0.1.1", "react-helmet": "^6.1.0", "react-highlight-words": "^0.18.0", "react-i18next": "^11.8.7", diff --git a/web/src/auth/GoogleLoginButton.js b/web/src/auth/GoogleLoginButton.js index f186e314..164921d4 100644 --- a/web/src/auth/GoogleLoginButton.js +++ b/web/src/auth/GoogleLoginButton.js @@ -14,6 +14,9 @@ import {createButton} from "react-social-login-buttons"; import {StaticBaseUrl} from "../Setting"; +import {useGoogleOneTapLogin} from "react-google-one-tap-login"; +import * as Setting from "../Setting"; +import * as Provider from "./Provider"; function Icon({width = 24, height = 24, color}) { return Sign in with Google; @@ -29,4 +32,29 @@ const config = { const GoogleLoginButton = createButton(config); +export function GoogleOneTapLoginVirtualButton(prop) { + const application = prop.application; + const providerConf = prop.providerConf; + // https://stackoverflow.com/questions/62281579/google-one-tap-sign-in-ui-not-displayed-after-clicking-the-close-button + // document.cookie = "g_state=;path=/;expires=Thu, 01 Jan 1970 00:00:01 GMT"; + useGoogleOneTapLogin({ + googleAccountConfigs: { + client_id: providerConf.provider.clientId, + }, + onError: (error) => { + Setting.showMessage("error", error); + }, + onSuccess: (response) => { + const code = "GoogleIdToken-" + JSON.stringify(response); + const authUrlParams = new URLSearchParams(Provider.getAuthUrl(application, providerConf.provider, "signup")); + const state = authUrlParams.get("state"); + let redirectUri = authUrlParams.get("redirect_uri"); + redirectUri = `${redirectUri}?state=${state}&code=${encodeURIComponent(code)}`; + Setting.goToLink(redirectUri); + }, + disableCancelOnUnmount: true, + }); + +} + export default GoogleLoginButton; diff --git a/web/src/auth/LoginPage.js b/web/src/auth/LoginPage.js index cea5586a..93fe004b 100644 --- a/web/src/auth/LoginPage.js +++ b/web/src/auth/LoginPage.js @@ -36,7 +36,7 @@ import {CaptchaModal} from "../common/modal/CaptchaModal"; import {CaptchaRule} from "../common/modal/CaptchaModal"; import RedirectForm from "../common/RedirectForm"; import {MfaAuthVerifyForm, NextMfa, RequiredMfa} from "./mfa/MfaAuthVerifyForm"; - +import {GoogleOneTapLoginVirtualButton} from "./GoogleLoginButton"; class LoginPage extends React.Component { constructor(props) { super(props); @@ -423,6 +423,16 @@ class LoginPage extends React.Component { } } + renderOtherFormProvider(application) { + for (const providerConf of application.providers) { + if (providerConf.provider?.type === "Google" && providerConf.rule === "OneTap" && this.props.preview !== "auto") { + return ( + + ); + } + } + } + renderForm(application) { if (this.state.msg !== null) { return Util.renderMessage(this.state.msg); @@ -580,6 +590,9 @@ class LoginPage extends React.Component { return ProviderButton.renderProviderLogo(providerItem.provider, application, 30, 5, "small", this.props.location); }) } + { + this.renderOtherFormProvider(application) + } ); @@ -601,6 +614,9 @@ class LoginPage extends React.Component { return ProviderButton.renderProviderLogo(providerItem.provider, application, 40, 10, "big", this.props.location); }) } + { + this.renderOtherFormProvider(application) + }

{ diff --git a/web/src/locales/de/data.json b/web/src/locales/de/data.json index 8be8f12f..419716a1 100644 --- a/web/src/locales/de/data.json +++ b/web/src/locales/de/data.json @@ -197,6 +197,7 @@ "Confirm": "Confirm", "Created time": "Erstellte Zeit", "Custom": "Custom", + "Default": "Default", "Default application": "Standard Anwendung", "Default application - Tooltip": "Standard-Anwendung für Benutzer, die direkt von der Organisationsseite registriert wurden", "Default avatar": "Standard-Avatar", @@ -722,6 +723,8 @@ "Secret key - Tooltip": "Vom Server verwendet, um die API des Verifizierungscodes-Providers für die Verifizierung aufzurufen", "Send Testing Email": "Senden Sie eine Test-E-Mail", "Send Testing SMS": "Sende Test-SMS", + "Sender number": "Sender number", + "Sender number - Tooltip": "Sender number - Tooltip", "Sign Name": "Signatur Namen", "Sign Name - Tooltip": "Name der Signatur, die verwendet werden soll", "Sign request": "Signaturanfrage", diff --git a/web/src/locales/en/data.json b/web/src/locales/en/data.json index 3de3127e..e8eb701a 100644 --- a/web/src/locales/en/data.json +++ b/web/src/locales/en/data.json @@ -197,6 +197,7 @@ "Confirm": "Confirm", "Created time": "Created time", "Custom": "Custom", + "Default": "Default", "Default application": "Default application", "Default application - Tooltip": "Default application for users registered directly from the organization page", "Default avatar": "Default avatar", @@ -722,6 +723,8 @@ "Secret key - Tooltip": "Used by the server to call the verification code provider API for verification", "Send Testing Email": "Send Testing Email", "Send Testing SMS": "Send Testing SMS", + "Sender number": "Sender number", + "Sender number - Tooltip": "Sender number - Tooltip", "Sign Name": "Sign Name", "Sign Name - Tooltip": "Name of the signature to be used", "Sign request": "Sign request", diff --git a/web/src/locales/es/data.json b/web/src/locales/es/data.json index 2dd937ef..58aa4b0a 100644 --- a/web/src/locales/es/data.json +++ b/web/src/locales/es/data.json @@ -197,6 +197,7 @@ "Confirm": "Confirm", "Created time": "Tiempo creado", "Custom": "Custom", + "Default": "Default", "Default application": "Aplicación predeterminada", "Default application - Tooltip": "Aplicación predeterminada para usuarios registrados directamente desde la página de la organización", "Default avatar": "Avatar predeterminado", @@ -722,6 +723,8 @@ "Secret key - Tooltip": "Utilizado por el servidor para llamar a la API del proveedor de códigos de verificación para verificar", "Send Testing Email": "Enviar correo electrónico de prueba", "Send Testing SMS": "Enviar SMS de prueba", + "Sender number": "Sender number", + "Sender number - Tooltip": "Sender number - Tooltip", "Sign Name": "Firma de Nombre", "Sign Name - Tooltip": "Nombre de la firma a ser utilizada", "Sign request": "Solicitud de firma", diff --git a/web/src/locales/fr/data.json b/web/src/locales/fr/data.json index c9353bc8..c925557f 100644 --- a/web/src/locales/fr/data.json +++ b/web/src/locales/fr/data.json @@ -197,6 +197,7 @@ "Confirm": "Confirm", "Created time": "Temps créé", "Custom": "Custom", + "Default": "Default", "Default application": "Application par défaut", "Default application - Tooltip": "Application par défaut pour les utilisateurs enregistrés directement depuis la page de l'organisation", "Default avatar": "Avatar par défaut", @@ -722,6 +723,8 @@ "Secret key - Tooltip": "Utilisé par le serveur pour appeler l'API du fournisseur de code de vérification pour vérifier", "Send Testing Email": "Envoyer un e-mail de test", "Send Testing SMS": "Envoyer des messages SMS de tests", + "Sender number": "Sender number", + "Sender number - Tooltip": "Sender number - Tooltip", "Sign Name": "Nom de signature", "Sign Name - Tooltip": "Nom de la signature à utiliser", "Sign request": "Demande de signature", diff --git a/web/src/locales/id/data.json b/web/src/locales/id/data.json index fde7488b..2e33fd0b 100644 --- a/web/src/locales/id/data.json +++ b/web/src/locales/id/data.json @@ -197,6 +197,7 @@ "Confirm": "Confirm", "Created time": "Waktu dibuat", "Custom": "Custom", + "Default": "Default", "Default application": "Aplikasi default", "Default application - Tooltip": "Aplikasi default untuk pengguna yang terdaftar langsung dari halaman organisasi", "Default avatar": "Avatar default", @@ -722,6 +723,8 @@ "Secret key - Tooltip": "Digunakan oleh server untuk memanggil API penyedia kode verifikasi untuk melakukan verifikasi", "Send Testing Email": "Kirim Email Uji Coba", "Send Testing SMS": "Kirim SMS Uji Coba", + "Sender number": "Sender number", + "Sender number - Tooltip": "Sender number - Tooltip", "Sign Name": "Tanda Tangan", "Sign Name - Tooltip": "Nama tanda tangan yang akan digunakan", "Sign request": "Permintaan tanda tangan", diff --git a/web/src/locales/ja/data.json b/web/src/locales/ja/data.json index 17b14f99..fc8ea12a 100644 --- a/web/src/locales/ja/data.json +++ b/web/src/locales/ja/data.json @@ -197,6 +197,7 @@ "Confirm": "Confirm", "Created time": "作成された時間", "Custom": "Custom", + "Default": "Default", "Default application": "デフォルトアプリケーション", "Default application - Tooltip": "組織ページから直接登録されたユーザーのデフォルトアプリケーション", "Default avatar": "デフォルトのアバター", @@ -722,6 +723,8 @@ "Secret key - Tooltip": "認証のためにサーバーによって使用され、認証コードプロバイダAPIを呼び出すためのもの", "Send Testing Email": "テスト用メールを送信する", "Send Testing SMS": "テストSMSを送信してください", + "Sender number": "Sender number", + "Sender number - Tooltip": "Sender number - Tooltip", "Sign Name": "署名", "Sign Name - Tooltip": "使用する署名の名前", "Sign request": "サインリクエスト", diff --git a/web/src/locales/ko/data.json b/web/src/locales/ko/data.json index 38c1747f..3cb0bddb 100644 --- a/web/src/locales/ko/data.json +++ b/web/src/locales/ko/data.json @@ -197,6 +197,7 @@ "Confirm": "Confirm", "Created time": "작성한 시간", "Custom": "Custom", + "Default": "Default", "Default application": "기본 애플리케이션", "Default application - Tooltip": "조직 페이지에서 직접 등록한 사용자의 기본 응용 프로그램", "Default avatar": "기본 아바타", @@ -722,6 +723,8 @@ "Secret key - Tooltip": "검증을 위해 서버에서 인증 코드 공급자 API를 호출하는 데 사용됩니다", "Send Testing Email": "테스트 이메일을 보내기", "Send Testing SMS": "테스트 SMS를 보내세요", + "Sender number": "Sender number", + "Sender number - Tooltip": "Sender number - Tooltip", "Sign Name": "신명서", "Sign Name - Tooltip": "사용할 서명의 이름", "Sign request": "표지 요청", diff --git a/web/src/locales/pt/data.json b/web/src/locales/pt/data.json index f1cb2506..3e7570a0 100644 --- a/web/src/locales/pt/data.json +++ b/web/src/locales/pt/data.json @@ -197,6 +197,7 @@ "Confirm": "Confirm", "Created time": "Hora de Criação", "Custom": "Custom", + "Default": "Default", "Default application": "Aplicação padrão", "Default application - Tooltip": "Aplicação padrão para usuários registrados diretamente na página da organização", "Default avatar": "Avatar padrão", @@ -722,6 +723,8 @@ "Secret key - Tooltip": "Usada pelo servidor para chamar a API do fornecedor de código de verificação para verificação", "Send Testing Email": "Enviar E-mail de Teste", "Send Testing SMS": "Enviar SMS de Teste", + "Sender number": "Sender number", + "Sender number - Tooltip": "Sender number - Tooltip", "Sign Name": "Nome do Sinal", "Sign Name - Tooltip": "Nome da assinatura a ser usada", "Sign request": "Solicitação de assinatura", diff --git a/web/src/locales/ru/data.json b/web/src/locales/ru/data.json index 5661e346..d066750f 100644 --- a/web/src/locales/ru/data.json +++ b/web/src/locales/ru/data.json @@ -197,6 +197,7 @@ "Confirm": "Confirm", "Created time": "Созданное время", "Custom": "Custom", + "Default": "Default", "Default application": "Приложение по умолчанию", "Default application - Tooltip": "По умолчанию приложение для пользователей, зарегистрированных непосредственно со страницы организации", "Default avatar": "Стандартный аватар", @@ -722,6 +723,8 @@ "Secret key - Tooltip": "Используется сервером для вызова API-интерфейса поставщика кода подтверждения для проверки", "Send Testing Email": "Отправить тестовое письмо", "Send Testing SMS": "Отправить тестовое SMS-сообщение", + "Sender number": "Sender number", + "Sender number - Tooltip": "Sender number - Tooltip", "Sign Name": "Подпись имени", "Sign Name - Tooltip": "Имя подписи, которую нужно использовать", "Sign request": "Подписать запрос", diff --git a/web/src/locales/vi/data.json b/web/src/locales/vi/data.json index 696c6a62..e30066b1 100644 --- a/web/src/locales/vi/data.json +++ b/web/src/locales/vi/data.json @@ -197,6 +197,7 @@ "Confirm": "Confirm", "Created time": "Thời gian tạo", "Custom": "Custom", + "Default": "Default", "Default application": "Ứng dụng mặc định", "Default application - Tooltip": "Ứng dụng mặc định cho người dùng đăng ký trực tiếp từ trang tổ chức", "Default avatar": "Hình đại diện mặc định", @@ -722,6 +723,8 @@ "Secret key - Tooltip": "Được sử dụng bởi máy chủ để gọi API nhà cung cấp mã xác minh để xác minh", "Send Testing Email": "Gửi Email kiểm tra", "Send Testing SMS": "Gửi SMS kiểm tra", + "Sender number": "Sender number", + "Sender number - Tooltip": "Sender number - Tooltip", "Sign Name": "Ký tên", "Sign Name - Tooltip": "Tên chữ ký sẽ được sử dụng", "Sign request": "Yêu cầu ký tên", diff --git a/web/src/locales/zh/data.json b/web/src/locales/zh/data.json index ab75d3a2..92179c16 100644 --- a/web/src/locales/zh/data.json +++ b/web/src/locales/zh/data.json @@ -197,6 +197,7 @@ "Confirm": "确认", "Created time": "创建时间", "Custom": "自定义", + "Default": "默认", "Default application": "默认应用", "Default application - Tooltip": "直接从组织页面注册的用户默认所属的应用", "Default avatar": "默认头像", @@ -722,6 +723,8 @@ "Secret key - Tooltip": "用于服务端调用验证码提供商API进行验证", "Send Testing Email": "发送测试邮件", "Send Testing SMS": "发送测试短信", + "Sender number": "Sender number", + "Sender number - Tooltip": "Sender number - Tooltip", "Sign Name": "签名名称", "Sign Name - Tooltip": "签名名称", "Sign request": "签名请求", diff --git a/web/src/table/ProviderTable.js b/web/src/table/ProviderTable.js index 6899816d..8295d5c2 100644 --- a/web/src/table/ProviderTable.js +++ b/web/src/table/ProviderTable.js @@ -178,21 +178,37 @@ class ProviderTable extends React.Component { key: "rule", width: "100px", render: (text, record, index) => { - if (record.provider?.category !== "Captcha") { + if (record.provider?.type === "Google") { + if (text === "None") { + text = "Default"; + } + return ( + + ); + } else if (record.provider?.category === "Captcha") { + return ( + + ); + } else { return null; } - return ( - - ); }, }, { diff --git a/web/yarn.lock b/web/yarn.lock index 5b868403..16b0f643 100644 --- a/web/yarn.lock +++ b/web/yarn.lock @@ -10199,6 +10199,11 @@ react-github-corner@^2.5.0: resolved "https://registry.yarnpkg.com/react-github-corner/-/react-github-corner-2.5.0.tgz#e350d0c69f69c075bc0f1d2a6f1df6ee91da31f2" integrity sha512-ofds9l6n61LJc6ML+jSE6W9ZSQvATcMR9evnHPXua1oDYj289HKODnVqFUB/g2a4ieBjDHw416iHP3MjqnU76Q== +react-google-one-tap-login@^0.1.1: + version "0.1.1" + resolved "https://registry.yarnpkg.com/react-google-one-tap-login/-/react-google-one-tap-login-0.1.1.tgz#12a61e63e19251622cf1575b601d79fd4d07847a" + integrity sha512-RCbOfR3Z7VcRae4AzrLoBfY/aqqdN24pkQeNz9CJ0W65C9SY3vAwF0Yp8mwpeFwsugaZ5b1HZsfhUYWtb3IMrw== + react-helmet@^6.1.0: version "6.1.0" resolved "https://registry.yarnpkg.com/react-helmet/-/react-helmet-6.1.0.tgz#a750d5165cb13cf213e44747502652e794468726"