reflex-google-auth 0.0.7__tar.gz → 0.0.9a1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (24) hide show
  1. reflex_google_auth-0.0.9a1/.github/workflows/publish.yml +41 -0
  2. reflex_google_auth-0.0.9a1/.gitignore +6 -0
  3. {reflex_google_auth-0.0.7 → reflex_google_auth-0.0.9a1}/PKG-INFO +42 -4
  4. {reflex_google_auth-0.0.7 → reflex_google_auth-0.0.9a1}/README.md +40 -2
  5. {reflex_google_auth-0.0.7 → reflex_google_auth-0.0.9a1}/custom_components/reflex_google_auth/__init__.py +2 -1
  6. reflex_google_auth-0.0.9a1/custom_components/reflex_google_auth/decorator.py +70 -0
  7. reflex_google_auth-0.0.9a1/custom_components/reflex_google_auth/google_auth.py +70 -0
  8. reflex_google_auth-0.0.9a1/custom_components/reflex_google_auth/google_auth.pyi +24 -0
  9. {reflex_google_auth-0.0.7 → reflex_google_auth-0.0.9a1}/custom_components/reflex_google_auth/state.py +38 -3
  10. {reflex_google_auth-0.0.7 → reflex_google_auth-0.0.9a1}/custom_components/reflex_google_auth.egg-info/PKG-INFO +42 -4
  11. {reflex_google_auth-0.0.7 → reflex_google_auth-0.0.9a1}/custom_components/reflex_google_auth.egg-info/SOURCES.txt +10 -1
  12. {reflex_google_auth-0.0.7 → reflex_google_auth-0.0.9a1}/custom_components/reflex_google_auth.egg-info/requires.txt +1 -1
  13. reflex_google_auth-0.0.9a1/google_auth_demo/.gitignore +5 -0
  14. reflex_google_auth-0.0.9a1/google_auth_demo/assets/favicon.ico +0 -0
  15. reflex_google_auth-0.0.9a1/google_auth_demo/google_auth_demo/__init__.py +0 -0
  16. reflex_google_auth-0.0.9a1/google_auth_demo/google_auth_demo/google_auth_demo.py +84 -0
  17. reflex_google_auth-0.0.9a1/google_auth_demo/requirements.txt +2 -0
  18. reflex_google_auth-0.0.9a1/google_auth_demo/rxconfig.py +5 -0
  19. {reflex_google_auth-0.0.7 → reflex_google_auth-0.0.9a1}/pyproject.toml +6 -3
  20. reflex_google_auth-0.0.7/custom_components/reflex_google_auth/decorator.py +0 -25
  21. reflex_google_auth-0.0.7/custom_components/reflex_google_auth/google_auth.py +0 -37
  22. {reflex_google_auth-0.0.7 → reflex_google_auth-0.0.9a1}/custom_components/reflex_google_auth.egg-info/dependency_links.txt +0 -0
  23. {reflex_google_auth-0.0.7 → reflex_google_auth-0.0.9a1}/custom_components/reflex_google_auth.egg-info/top_level.txt +0 -0
  24. {reflex_google_auth-0.0.7 → reflex_google_auth-0.0.9a1}/setup.cfg +0 -0
@@ -0,0 +1,41 @@
1
+ name: Publish Component to PyPI
2
+
3
+ on:
4
+ push:
5
+ tags:
6
+ - "*"
7
+
8
+ jobs:
9
+ publish:
10
+ name: Publish Component to PyPI
11
+ runs-on: ubuntu-latest
12
+ steps:
13
+ - uses: actions/checkout@master
14
+ - name: Set up Python 3.12
15
+ uses: actions/setup-python@v3
16
+ with:
17
+ python-version: "3.12"
18
+ - name: Install package
19
+ run: pip install .
20
+ - name: Publish to PyPI
21
+ run: reflex component publish -t ${{ secrets.PYPI_TOKEN }} --no-share --no-validate-project-info
22
+ deploy:
23
+ name: Deploy Demo App to Reflex Cloud
24
+ runs-on: ubuntu-latest
25
+ steps:
26
+ - uses: actions/checkout@master
27
+ - name: Set up Python 3.12
28
+ uses: actions/setup-python@v3
29
+ with:
30
+ python-version: "3.12"
31
+ - name: Set reflex version for deploy
32
+ run: sed -e "s/^reflex-google-auth[ >=].*$/reflex-google-auth==${{ github.event.release.tag_name }}/" -i google_auth_demo/requirements.txt
33
+ - name: Deploy to ReflexCloud
34
+ uses: reflex-dev/reflex-deploy-action@v2
35
+ with:
36
+ auth_token: ${{ secrets.REFLEX_AUTH_TOKEN }}
37
+ project_id: ${{ secrets.REFLEX_PROJECT_ID }}
38
+ app_directory: google_auth_demo
39
+ extra_args: ${{ env.EXTRA_ARGS }}
40
+ dry_run: ${{ vars.DRY_RUN }}
41
+ skip_checkout: "true"
@@ -0,0 +1,6 @@
1
+ *.db
2
+ *.egg-info/
3
+ *.py[cod]
4
+ .web
5
+ __pycache__/
6
+ dist/
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.1
1
+ Metadata-Version: 2.2
2
2
  Name: reflex-google-auth
3
- Version: 0.0.7
3
+ Version: 0.0.9a1
4
4
  Summary: Sign in with Google
5
5
  Author-email: Masen Furer <m_github@0x26.net>
6
6
  License: Apache-2.0
@@ -9,7 +9,7 @@ Keywords: reflex,reflex-custom-components
9
9
  Classifier: Development Status :: 4 - Beta
10
10
  Requires-Python: >=3.8
11
11
  Description-Content-Type: text/markdown
12
- Requires-Dist: reflex>=0.4.6
12
+ Requires-Dist: reflex>=0.6.6
13
13
  Requires-Dist: google-auth[requests]
14
14
  Provides-Extra: dev
15
15
  Requires-Dist: build; extra == "dev"
@@ -42,11 +42,22 @@ Head over to https://console.developers.google.com/apis/credentials and sign in
42
42
  - From the "Credentials" page, click "+ Create Credentials", then "OAuth client ID"
43
43
  - Select Application Type: "Web Application"
44
44
  - Add Authorized Javascript Origins: http://localhost, http://localhost:3000, https://example.com (prod domain must be HTTPS)
45
+ - If using custom button, add the same origins to "Authorized redirect URIs"
45
46
  - Click "Save"
46
- - Copy the OAuth "Client ID" and save it for later. Mine looks like 309209880368-4uqd9e44h7t4alhhdqn48pvvr63cc5j5.apps.googleusercontent.com
47
+ - Copy the OAuth "Client ID" and "Client Secret" and save it for later. Mine looks like 309209880368-4uqd9e44h7t4alhhdqn48pvvr63cc5j5.apps.googleusercontent.com
47
48
 
48
49
  https://github.com/reflex-dev/reflex-examples/assets/1524005/af2499a6-0bda-4d60-b52b-4f51b7322fd5
49
50
 
51
+ ### Configure Environment Variables
52
+
53
+ Set the following environment variables based on your deployment.
54
+
55
+ ```bash
56
+ export GOOGLE_CLIENT_ID="309209880368-4uqd9e44h7t4alhhdqn48pvvr63cc5j5.apps.googleusercontent.com"
57
+ export GOOGLE_CLIENT_SECRET="your_client_secret"
58
+ export GOOGLE_REDIRECT_URI="http://localhost:3000"
59
+ ```
60
+
50
61
  ### Integrate with Reflex app
51
62
 
52
63
  The `GoogleAuthState` provided by this component has a `token_is_valid` var that
@@ -89,3 +100,30 @@ See the example in
89
100
  [masenf/rx_shout](https://github.com/masenf/rx_shout/blob/main/rx_shout/state.py)
90
101
  for how to integrate an authenticated Google user with other app-specific user
91
102
  data.
103
+
104
+ ### Customizing the Button
105
+
106
+ If you want to use your own login button, you may use whatever component you
107
+ like, as long as it is wrapped in a `reflex_google_auth.google_oauth_provider`
108
+ component and the `on_click` triggers
109
+ `reflex_google_auth.handle_google_login()`. Note that this cannot be combined
110
+ with other event handlers.
111
+
112
+ This functionality is also exposed in the `require_google_auth` decorator, which
113
+ accepts a `button` keyword argument.
114
+
115
+ When using a custom button, the returned auth-code _must be validated on the
116
+ backend_, which is handled by this library, but **requires additionally setting
117
+ `GOOGLE_CLIENT_SECRET` and `GOOGLE_REDIRECT_URI` environment variables**. These
118
+ can be configured in the Google Cloud Console as described above.
119
+
120
+ ```python
121
+ from reflex_google_auth import handle_google_login, require_google_login, GoogleAuthState
122
+
123
+
124
+ @require_google_login(button=rx.button("Google Login 🚀", on_click=handle_google_login()))
125
+ def custom_button() -> rx.Component:
126
+ return rx.vstack(
127
+ f"{GoogleAuthState.tokeninfo['email']} clicked a custom button to login, nice",
128
+ )
129
+ ```
@@ -25,11 +25,22 @@ Head over to https://console.developers.google.com/apis/credentials and sign in
25
25
  - From the "Credentials" page, click "+ Create Credentials", then "OAuth client ID"
26
26
  - Select Application Type: "Web Application"
27
27
  - Add Authorized Javascript Origins: http://localhost, http://localhost:3000, https://example.com (prod domain must be HTTPS)
28
+ - If using custom button, add the same origins to "Authorized redirect URIs"
28
29
  - Click "Save"
29
- - Copy the OAuth "Client ID" and save it for later. Mine looks like 309209880368-4uqd9e44h7t4alhhdqn48pvvr63cc5j5.apps.googleusercontent.com
30
+ - Copy the OAuth "Client ID" and "Client Secret" and save it for later. Mine looks like 309209880368-4uqd9e44h7t4alhhdqn48pvvr63cc5j5.apps.googleusercontent.com
30
31
 
31
32
  https://github.com/reflex-dev/reflex-examples/assets/1524005/af2499a6-0bda-4d60-b52b-4f51b7322fd5
32
33
 
34
+ ### Configure Environment Variables
35
+
36
+ Set the following environment variables based on your deployment.
37
+
38
+ ```bash
39
+ export GOOGLE_CLIENT_ID="309209880368-4uqd9e44h7t4alhhdqn48pvvr63cc5j5.apps.googleusercontent.com"
40
+ export GOOGLE_CLIENT_SECRET="your_client_secret"
41
+ export GOOGLE_REDIRECT_URI="http://localhost:3000"
42
+ ```
43
+
33
44
  ### Integrate with Reflex app
34
45
 
35
46
  The `GoogleAuthState` provided by this component has a `token_is_valid` var that
@@ -71,4 +82,31 @@ To uniquely identify a user, the `GoogleAuthState.tokeninfo['sub']` field can be
71
82
  See the example in
72
83
  [masenf/rx_shout](https://github.com/masenf/rx_shout/blob/main/rx_shout/state.py)
73
84
  for how to integrate an authenticated Google user with other app-specific user
74
- data.
85
+ data.
86
+
87
+ ### Customizing the Button
88
+
89
+ If you want to use your own login button, you may use whatever component you
90
+ like, as long as it is wrapped in a `reflex_google_auth.google_oauth_provider`
91
+ component and the `on_click` triggers
92
+ `reflex_google_auth.handle_google_login()`. Note that this cannot be combined
93
+ with other event handlers.
94
+
95
+ This functionality is also exposed in the `require_google_auth` decorator, which
96
+ accepts a `button` keyword argument.
97
+
98
+ When using a custom button, the returned auth-code _must be validated on the
99
+ backend_, which is handled by this library, but **requires additionally setting
100
+ `GOOGLE_CLIENT_SECRET` and `GOOGLE_REDIRECT_URI` environment variables**. These
101
+ can be configured in the Google Cloud Console as described above.
102
+
103
+ ```python
104
+ from reflex_google_auth import handle_google_login, require_google_login, GoogleAuthState
105
+
106
+
107
+ @require_google_login(button=rx.button("Google Login 🚀", on_click=handle_google_login()))
108
+ def custom_button() -> rx.Component:
109
+ return rx.vstack(
110
+ f"{GoogleAuthState.tokeninfo['email']} clicked a custom button to login, nice",
111
+ )
112
+ ```
@@ -1,11 +1,12 @@
1
1
  from .decorator import require_google_login
2
- from .google_auth import google_login, google_oauth_provider
2
+ from .google_auth import google_login, google_oauth_provider, handle_google_login
3
3
  from .state import GoogleAuthState, set_client_id
4
4
 
5
5
  __all__ = [
6
6
  "GoogleAuthState",
7
7
  "google_oauth_provider",
8
8
  "google_login",
9
+ "handle_google_login",
9
10
  "set_client_id",
10
11
  "require_google_login",
11
12
  ]
@@ -0,0 +1,70 @@
1
+ import functools
2
+ from typing import Callable, overload
3
+
4
+ import reflex as rx
5
+
6
+ from . import google_auth
7
+ from .state import GoogleAuthState
8
+
9
+
10
+ ComponentCallable = Callable[[], rx.Component]
11
+
12
+
13
+ @overload
14
+ def require_google_login(
15
+ page: ComponentCallable,
16
+ ) -> ComponentCallable: ...
17
+
18
+
19
+ @overload
20
+ def require_google_login() -> Callable[[ComponentCallable], ComponentCallable]: ...
21
+
22
+
23
+ @overload
24
+ def require_google_login(
25
+ *,
26
+ button: rx.Component | None,
27
+ ) -> Callable[[ComponentCallable], ComponentCallable]: ...
28
+
29
+
30
+ def require_google_login(
31
+ page: ComponentCallable | None = None,
32
+ *,
33
+ button: rx.Component | None = None,
34
+ ) -> ComponentCallable | Callable[[ComponentCallable], ComponentCallable]:
35
+ """Decorator to require Google login before rendering a page.
36
+
37
+ The login button should have on_click set to `reflex_google_auth.handle_google_login`.
38
+
39
+ Args:
40
+ page: Page to render after Google login.
41
+ button: Button to render if Google login is required.
42
+
43
+ Returns:
44
+ A decorator function or the decorated page.
45
+ """
46
+
47
+ if button is None:
48
+ button = google_auth.google_login()
49
+
50
+ def _inner(page: Callable[[], rx.Component]) -> Callable[[], rx.Component]:
51
+ @functools.wraps(page)
52
+ def _auth_wrapper() -> rx.Component:
53
+ return google_auth.google_oauth_provider(
54
+ rx.cond(
55
+ rx.State.is_hydrated,
56
+ rx.cond(
57
+ GoogleAuthState.token_is_valid,
58
+ page(),
59
+ button,
60
+ ),
61
+ ),
62
+ )
63
+
64
+ _auth_wrapper.__name__ = page.__name__
65
+
66
+ return _auth_wrapper
67
+
68
+ if page is None:
69
+ return _inner
70
+ return _inner(page=page)
@@ -0,0 +1,70 @@
1
+ from typing import cast
2
+ import reflex as rx
3
+ from reflex.event import EventType, BASE_STATE
4
+
5
+ from .state import GoogleAuthState
6
+
7
+
8
+ LIBRARY = "@react-oauth/google"
9
+
10
+
11
+ class GoogleOAuthProvider(rx.Component):
12
+ library = LIBRARY
13
+ tag = "GoogleOAuthProvider"
14
+
15
+ client_id: rx.Var[str]
16
+
17
+ @classmethod
18
+ def create(cls, *children, **props) -> rx.Component:
19
+ props.setdefault("client_id", GoogleAuthState.client_id)
20
+ return super().create(*children, **props)
21
+
22
+
23
+ google_oauth_provider = GoogleOAuthProvider.create
24
+
25
+
26
+ def _on_success_signature(data: rx.Var[dict]) -> tuple[rx.Var[dict]]:
27
+ return (data,)
28
+
29
+
30
+ class GoogleLogin(rx.Component):
31
+ library = LIBRARY
32
+ tag = "GoogleLogin"
33
+
34
+ on_success: rx.EventHandler[_on_success_signature]
35
+
36
+ @classmethod
37
+ def create(cls, **props) -> "GoogleLogin":
38
+ props.setdefault("on_success", GoogleAuthState.on_success)
39
+ return cast("GoogleLogin", super().create(**props))
40
+
41
+
42
+ google_login = GoogleLogin.create
43
+
44
+
45
+ def handle_google_login(
46
+ on_success: EventType[[dict], BASE_STATE] = GoogleAuthState.on_success,
47
+ ) -> rx.Var[rx.EventChain]:
48
+ on_success_event_chain = rx.Var.create(
49
+ # TODO: rx.EventChain.create(
50
+ rx.Component()._create_event_chain(
51
+ value=on_success, # type: ignore
52
+ args_spec=_on_success_signature,
53
+ key="on_success",
54
+ )
55
+ )
56
+ return rx.Var(
57
+ "() => login()",
58
+ _var_type=rx.EventChain,
59
+ _var_data=rx.vars.VarData(
60
+ hooks={
61
+ """
62
+ const login = useGoogleLogin({
63
+ onSuccess: %s,
64
+ flow: 'auth-code',
65
+ });"""
66
+ % on_success_event_chain: None,
67
+ },
68
+ imports={LIBRARY: "useGoogleLogin"},
69
+ ).merge(on_success_event_chain._get_all_var_data()),
70
+ )
@@ -0,0 +1,24 @@
1
+ from typing import Any, Dict, Literal, Optional, Union, overload
2
+ from reflex.vars import Var, BaseVar, ComputedVar
3
+ from reflex.event import EventChain, EventHandler, EventSpec
4
+ from reflex.style import Style
5
+ import reflex as rx
6
+ from .state import GoogleAuthState
7
+
8
+ class GoogleOAuthProvider(rx.Component):
9
+
10
+ @overload
11
+ @classmethod
12
+ def create(cls, *children, client_id: Optional[Union[Var[str], str]]=None, style: Optional[Style]=None, key: Optional[Any]=None, id: Optional[Any]=None, class_name: Optional[Any]=None, autofocus: Optional[bool]=None, custom_attrs: Optional[Dict[str, Union[Var, str]]]=None, on_blur: Optional[Union[EventHandler, EventSpec, list, function, BaseVar]]=None, on_click: Optional[Union[EventHandler, EventSpec, list, function, BaseVar]]=None, on_context_menu: Optional[Union[EventHandler, EventSpec, list, function, BaseVar]]=None, on_double_click: Optional[Union[EventHandler, EventSpec, list, function, BaseVar]]=None, on_focus: Optional[Union[EventHandler, EventSpec, list, function, BaseVar]]=None, on_mount: Optional[Union[EventHandler, EventSpec, list, function, BaseVar]]=None, on_mouse_down: Optional[Union[EventHandler, EventSpec, list, function, BaseVar]]=None, on_mouse_enter: Optional[Union[EventHandler, EventSpec, list, function, BaseVar]]=None, on_mouse_leave: Optional[Union[EventHandler, EventSpec, list, function, BaseVar]]=None, on_mouse_move: Optional[Union[EventHandler, EventSpec, list, function, BaseVar]]=None, on_mouse_out: Optional[Union[EventHandler, EventSpec, list, function, BaseVar]]=None, on_mouse_over: Optional[Union[EventHandler, EventSpec, list, function, BaseVar]]=None, on_mouse_up: Optional[Union[EventHandler, EventSpec, list, function, BaseVar]]=None, on_scroll: Optional[Union[EventHandler, EventSpec, list, function, BaseVar]]=None, on_unmount: Optional[Union[EventHandler, EventSpec, list, function, BaseVar]]=None, **props) -> 'GoogleOAuthProvider':
13
+ """"""
14
+ ...
15
+ google_oauth_provider = GoogleOAuthProvider.create
16
+
17
+ class GoogleLogin(rx.Component):
18
+
19
+ @overload
20
+ @classmethod
21
+ def create(cls, *children, on_success: Optional[EventHandler[Any]]=None, style: Optional[Style]=None, key: Optional[Any]=None, id: Optional[Any]=None, class_name: Optional[Any]=None, autofocus: Optional[bool]=None, custom_attrs: Optional[Dict[str, Union[Var, str]]]=None, on_blur: Optional[Union[EventHandler, EventSpec, list, function, BaseVar]]=None, on_click: Optional[Union[EventHandler, EventSpec, list, function, BaseVar]]=None, on_context_menu: Optional[Union[EventHandler, EventSpec, list, function, BaseVar]]=None, on_double_click: Optional[Union[EventHandler, EventSpec, list, function, BaseVar]]=None, on_focus: Optional[Union[EventHandler, EventSpec, list, function, BaseVar]]=None, on_mount: Optional[Union[EventHandler, EventSpec, list, function, BaseVar]]=None, on_mouse_down: Optional[Union[EventHandler, EventSpec, list, function, BaseVar]]=None, on_mouse_enter: Optional[Union[EventHandler, EventSpec, list, function, BaseVar]]=None, on_mouse_leave: Optional[Union[EventHandler, EventSpec, list, function, BaseVar]]=None, on_mouse_move: Optional[Union[EventHandler, EventSpec, list, function, BaseVar]]=None, on_mouse_out: Optional[Union[EventHandler, EventSpec, list, function, BaseVar]]=None, on_mouse_over: Optional[Union[EventHandler, EventSpec, list, function, BaseVar]]=None, on_mouse_up: Optional[Union[EventHandler, EventSpec, list, function, BaseVar]]=None, on_scroll: Optional[Union[EventHandler, EventSpec, list, function, BaseVar]]=None, on_success: Optional[Union[EventHandler, EventSpec, list, function, BaseVar]]=None, on_unmount: Optional[Union[EventHandler, EventSpec, list, function, BaseVar]]=None, **props) -> 'GoogleLogin':
22
+ """"""
23
+ ...
24
+ google_login = GoogleLogin.create
@@ -6,11 +6,15 @@ import time
6
6
 
7
7
  from google.auth.transport import requests
8
8
  from google.oauth2.id_token import verify_oauth2_token
9
+ from httpx import AsyncClient
9
10
 
10
11
  import reflex as rx
11
12
 
12
13
 
13
- CLIENT_ID = None
14
+ TOKEN_URI = "https://oauth2.googleapis.com/token"
15
+ CLIENT_ID = os.environ.get("GOOGLE_CLIENT_ID", "")
16
+ CLIENT_SECRET = os.environ.get("GOOGLE_CLIENT_SECRET", "")
17
+ REDIRECT_URI = os.environ.get("GOOGLE_REDIRECT_URI", "")
14
18
 
15
19
 
16
20
  def set_client_id(client_id: str):
@@ -19,10 +23,39 @@ def set_client_id(client_id: str):
19
23
  CLIENT_ID = client_id
20
24
 
21
25
 
26
+ async def get_id_token(auth_code) -> str:
27
+ """Get the id token credential from an auth code.
28
+
29
+ Args:
30
+ auth_code: Returned from an 'auth-code' flow.
31
+
32
+ Returns:
33
+ The id token credential.
34
+ """
35
+ async with AsyncClient() as client:
36
+ response = await client.post(
37
+ TOKEN_URI,
38
+ data={
39
+ "code": auth_code,
40
+ "client_id": CLIENT_ID,
41
+ "client_secret": CLIENT_SECRET,
42
+ "redirect_uri": REDIRECT_URI,
43
+ "grant_type": "authorization_code",
44
+ },
45
+ )
46
+ response.raise_for_status()
47
+ response_data = response.json()
48
+ return response_data.get("id_token")
49
+
50
+
22
51
  class GoogleAuthState(rx.State):
23
52
  id_token_json: str = rx.LocalStorage()
24
53
 
25
- def on_success(self, id_token: dict):
54
+ @rx.event
55
+ async def on_success(self, id_token: dict):
56
+ if "code" in id_token:
57
+ # Handle auth-code flow
58
+ id_token["credential"] = await get_id_token(id_token["code"])
26
59
  self.id_token_json = json.dumps(id_token)
27
60
 
28
61
  @rx.var(cache=True)
@@ -39,9 +72,11 @@ class GoogleAuthState(rx.State):
39
72
  )
40
73
  except Exception as exc:
41
74
  if self.id_token_json:
42
- print(f"Error verifying token: {exc}")
75
+ print(f"Error verifying token: {exc!r}")
76
+ self.id_token_json = ""
43
77
  return {}
44
78
 
79
+ @rx.event
45
80
  def logout(self):
46
81
  self.id_token_json = ""
47
82
 
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.1
1
+ Metadata-Version: 2.2
2
2
  Name: reflex-google-auth
3
- Version: 0.0.7
3
+ Version: 0.0.9a1
4
4
  Summary: Sign in with Google
5
5
  Author-email: Masen Furer <m_github@0x26.net>
6
6
  License: Apache-2.0
@@ -9,7 +9,7 @@ Keywords: reflex,reflex-custom-components
9
9
  Classifier: Development Status :: 4 - Beta
10
10
  Requires-Python: >=3.8
11
11
  Description-Content-Type: text/markdown
12
- Requires-Dist: reflex>=0.4.6
12
+ Requires-Dist: reflex>=0.6.6
13
13
  Requires-Dist: google-auth[requests]
14
14
  Provides-Extra: dev
15
15
  Requires-Dist: build; extra == "dev"
@@ -42,11 +42,22 @@ Head over to https://console.developers.google.com/apis/credentials and sign in
42
42
  - From the "Credentials" page, click "+ Create Credentials", then "OAuth client ID"
43
43
  - Select Application Type: "Web Application"
44
44
  - Add Authorized Javascript Origins: http://localhost, http://localhost:3000, https://example.com (prod domain must be HTTPS)
45
+ - If using custom button, add the same origins to "Authorized redirect URIs"
45
46
  - Click "Save"
46
- - Copy the OAuth "Client ID" and save it for later. Mine looks like 309209880368-4uqd9e44h7t4alhhdqn48pvvr63cc5j5.apps.googleusercontent.com
47
+ - Copy the OAuth "Client ID" and "Client Secret" and save it for later. Mine looks like 309209880368-4uqd9e44h7t4alhhdqn48pvvr63cc5j5.apps.googleusercontent.com
47
48
 
48
49
  https://github.com/reflex-dev/reflex-examples/assets/1524005/af2499a6-0bda-4d60-b52b-4f51b7322fd5
49
50
 
51
+ ### Configure Environment Variables
52
+
53
+ Set the following environment variables based on your deployment.
54
+
55
+ ```bash
56
+ export GOOGLE_CLIENT_ID="309209880368-4uqd9e44h7t4alhhdqn48pvvr63cc5j5.apps.googleusercontent.com"
57
+ export GOOGLE_CLIENT_SECRET="your_client_secret"
58
+ export GOOGLE_REDIRECT_URI="http://localhost:3000"
59
+ ```
60
+
50
61
  ### Integrate with Reflex app
51
62
 
52
63
  The `GoogleAuthState` provided by this component has a `token_is_valid` var that
@@ -89,3 +100,30 @@ See the example in
89
100
  [masenf/rx_shout](https://github.com/masenf/rx_shout/blob/main/rx_shout/state.py)
90
101
  for how to integrate an authenticated Google user with other app-specific user
91
102
  data.
103
+
104
+ ### Customizing the Button
105
+
106
+ If you want to use your own login button, you may use whatever component you
107
+ like, as long as it is wrapped in a `reflex_google_auth.google_oauth_provider`
108
+ component and the `on_click` triggers
109
+ `reflex_google_auth.handle_google_login()`. Note that this cannot be combined
110
+ with other event handlers.
111
+
112
+ This functionality is also exposed in the `require_google_auth` decorator, which
113
+ accepts a `button` keyword argument.
114
+
115
+ When using a custom button, the returned auth-code _must be validated on the
116
+ backend_, which is handled by this library, but **requires additionally setting
117
+ `GOOGLE_CLIENT_SECRET` and `GOOGLE_REDIRECT_URI` environment variables**. These
118
+ can be configured in the Google Cloud Console as described above.
119
+
120
+ ```python
121
+ from reflex_google_auth import handle_google_login, require_google_login, GoogleAuthState
122
+
123
+
124
+ @require_google_login(button=rx.button("Google Login 🚀", on_click=handle_google_login()))
125
+ def custom_button() -> rx.Component:
126
+ return rx.vstack(
127
+ f"{GoogleAuthState.tokeninfo['email']} clicked a custom button to login, nice",
128
+ )
129
+ ```
@@ -1,11 +1,20 @@
1
+ .gitignore
1
2
  README.md
2
3
  pyproject.toml
4
+ .github/workflows/publish.yml
3
5
  custom_components/reflex_google_auth/__init__.py
4
6
  custom_components/reflex_google_auth/decorator.py
5
7
  custom_components/reflex_google_auth/google_auth.py
8
+ custom_components/reflex_google_auth/google_auth.pyi
6
9
  custom_components/reflex_google_auth/state.py
7
10
  custom_components/reflex_google_auth.egg-info/PKG-INFO
8
11
  custom_components/reflex_google_auth.egg-info/SOURCES.txt
9
12
  custom_components/reflex_google_auth.egg-info/dependency_links.txt
10
13
  custom_components/reflex_google_auth.egg-info/requires.txt
11
- custom_components/reflex_google_auth.egg-info/top_level.txt
14
+ custom_components/reflex_google_auth.egg-info/top_level.txt
15
+ google_auth_demo/.gitignore
16
+ google_auth_demo/requirements.txt
17
+ google_auth_demo/rxconfig.py
18
+ google_auth_demo/assets/favicon.ico
19
+ google_auth_demo/google_auth_demo/__init__.py
20
+ google_auth_demo/google_auth_demo/google_auth_demo.py
@@ -1,4 +1,4 @@
1
- reflex>=0.4.6
1
+ reflex>=0.6.6
2
2
  google-auth[requests]
3
3
 
4
4
  [dev]
@@ -0,0 +1,5 @@
1
+ *.db
2
+ *.py[cod]
3
+ .web
4
+ __pycache__/
5
+ assets/external/
@@ -0,0 +1,84 @@
1
+ import reflex as rx
2
+
3
+ from reflex_google_auth import (
4
+ GoogleAuthState,
5
+ handle_google_login,
6
+ require_google_login,
7
+ )
8
+
9
+
10
+ class State(GoogleAuthState):
11
+ @rx.var(cache=True)
12
+ def protected_content(self) -> str:
13
+ if self.token_is_valid:
14
+ return f"This content can only be viewed by a logged in User. Nice to see you {self.tokeninfo['name']}"
15
+ return "Not logged in."
16
+
17
+
18
+ def user_info(tokeninfo: rx.vars.ObjectVar) -> rx.Component:
19
+ return rx.hstack(
20
+ rx.avatar(
21
+ src=tokeninfo["picture"],
22
+ fallback=tokeninfo["name"],
23
+ size="5",
24
+ ),
25
+ rx.vstack(
26
+ rx.heading(tokeninfo["name"], size="6"),
27
+ rx.text(tokeninfo["email"]),
28
+ align_items="flex-start",
29
+ ),
30
+ rx.button("Logout", on_click=GoogleAuthState.logout),
31
+ padding="10px",
32
+ )
33
+
34
+
35
+ def index():
36
+ return rx.vstack(
37
+ rx.heading("Google OAuth", size="8"),
38
+ rx.link("Protected Page", href="/protected"),
39
+ rx.link("Partially Protected Page", href="/partially-protected"),
40
+ rx.link("Custom Login Button", href="/custom-button"),
41
+ align="center",
42
+ )
43
+
44
+
45
+ @rx.page(route="/protected")
46
+ @require_google_login
47
+ def protected() -> rx.Component:
48
+ return rx.vstack(
49
+ user_info(GoogleAuthState.tokeninfo),
50
+ rx.text(State.protected_content),
51
+ rx.link("Home", href="/"),
52
+ )
53
+
54
+
55
+ @require_google_login
56
+ def user_name_or_sign_in() -> rx.Component:
57
+ return rx.heading(GoogleAuthState.tokeninfo["name"], size="6")
58
+
59
+
60
+ @rx.page(route="/partially-protected")
61
+ def partially_protected() -> rx.Component:
62
+ return rx.vstack(
63
+ rx.heading("This page is partially protected."),
64
+ rx.text(
65
+ "If you are signed in with google, you should see your name below, otherwise "
66
+ "you will see a sign in button",
67
+ ),
68
+ user_name_or_sign_in(),
69
+ )
70
+
71
+
72
+ @rx.page(route="/custom-button")
73
+ @require_google_login(
74
+ button=rx.button("Google Login 🚀", on_click=handle_google_login())
75
+ )
76
+ def custom_button() -> rx.Component:
77
+ return rx.vstack(
78
+ user_info(GoogleAuthState.tokeninfo),
79
+ "You clicked a custom button to login, nice",
80
+ )
81
+
82
+
83
+ app = rx.App()
84
+ app.add_page(index)
@@ -0,0 +1,2 @@
1
+ reflex>=0.6.6
2
+ reflex-google-auth
@@ -0,0 +1,5 @@
1
+ import reflex as rx
2
+
3
+ config = rx.Config(
4
+ app_name="google_auth_demo",
5
+ )
@@ -1,13 +1,13 @@
1
1
  [build-system]
2
2
  requires = [
3
- "setuptools",
3
+ "setuptools>=64",
4
+ "setuptools_scm>=8",
4
5
  "wheel",
5
6
  ]
6
7
  build-backend = "setuptools.build_meta"
7
8
 
8
9
  [project]
9
10
  name = "reflex-google-auth"
10
- version = "0.0.7"
11
11
  description = "Sign in with Google"
12
12
  readme = "README.md"
13
13
  license = { text = "Apache-2.0" }
@@ -16,13 +16,14 @@ authors = [{ name = "Masen Furer", email = "m_github@0x26.net" }]
16
16
  keywords = ["reflex", "reflex-custom-components"]
17
17
 
18
18
  dependencies = [
19
- "reflex>=0.4.6",
19
+ "reflex>=0.6.6",
20
20
  "google-auth[requests]",
21
21
  ]
22
22
 
23
23
  classifiers = [
24
24
  "Development Status :: 4 - Beta",
25
25
  ]
26
+ dynamic = ["version"]
26
27
 
27
28
  [project.urls]
28
29
  homepage = "https://github.com/masenf/reflex-google-auth"
@@ -32,3 +33,5 @@ dev = ["build", "twine"]
32
33
 
33
34
  [tool.setuptools.packages.find]
34
35
  where = ["custom_components"]
36
+
37
+ [tool.setuptools_scm]
@@ -1,25 +0,0 @@
1
- import functools
2
-
3
- import reflex as rx
4
-
5
- from . import google_auth
6
- from .state import GoogleAuthState
7
-
8
-
9
- def require_google_login(page) -> rx.Component:
10
- @functools.wraps(page)
11
- def _auth_wrapper() -> rx.Component:
12
- return google_auth.google_oauth_provider(
13
- rx.cond(
14
- rx.State.is_hydrated,
15
- rx.cond(
16
- GoogleAuthState.token_is_valid,
17
- page(),
18
- google_auth.google_login(),
19
- ),
20
- ),
21
- )
22
-
23
- _auth_wrapper.__name__ = page.__name__
24
-
25
- return _auth_wrapper
@@ -1,37 +0,0 @@
1
- import reflex as rx
2
-
3
- from .state import GoogleAuthState
4
-
5
-
6
- class GoogleOAuthProvider(rx.Component):
7
- library = "@react-oauth/google"
8
- tag = "GoogleOAuthProvider"
9
-
10
- client_id: rx.Var[str]
11
-
12
- @classmethod
13
- def create(cls, *children, **props) -> rx.Component:
14
- props.setdefault("client_id", GoogleAuthState.client_id)
15
- return super().create(*children, **props)
16
-
17
-
18
- google_oauth_provider = GoogleOAuthProvider.create
19
-
20
-
21
- def _on_success_signature(data: rx.Var[dict]) -> tuple[rx.Var[dict]]:
22
- return data,
23
-
24
-
25
- class GoogleLogin(rx.Component):
26
- library = "@react-oauth/google"
27
- tag = "GoogleLogin"
28
-
29
- on_success: rx.EventHandler[_on_success_signature]
30
-
31
- @classmethod
32
- def create(cls, **props) -> "GoogleLogin":
33
- props.setdefault("on_success", GoogleAuthState.on_success)
34
- return super().create(**props)
35
-
36
-
37
- google_login = GoogleLogin.create