9
9
from fastapi import APIRouter , Depends , Request
10
10
from fastui import AnyComponent , FastUI
11
11
from fastui import components as c
12
- from fastui .auth import GitHubAuthProvider
12
+ from fastui .auth import AuthRedirect , GitHubAuthProvider
13
13
from fastui .events import AuthEvent , GoToEvent , PageEvent
14
14
from fastui .forms import fastui_form
15
15
from httpx import AsyncClient
20
20
21
21
router = APIRouter ()
22
22
23
-
23
+ GITHUB_CLIENT_ID = os . getenv ( 'GITHUB_CLIENT_ID' , '0d0315f9c2e055d032e2' )
24
24
# this will give an error when making requests to GitHub, but at least the app will run
25
25
GITHUB_CLIENT_SECRET = SecretStr (os .getenv ('GITHUB_CLIENT_SECRET' , 'dummy-secret' ))
26
+ # use 'http://localhost:3000/auth/login/github/redirect' in development
27
+ GITHUB_REDIRECT = os .getenv ('GITHUB_REDIRECT' )
26
28
27
29
28
30
async def get_github_auth (request : Request ) -> GitHubAuthProvider :
29
31
client : AsyncClient = request .app .state .httpx_client
30
32
return GitHubAuthProvider (
31
33
httpx_client = client ,
32
- github_client_id = '9eddf87b27f71f52194a' ,
34
+ github_client_id = GITHUB_CLIENT_ID ,
33
35
github_client_secret = GITHUB_CLIENT_SECRET ,
36
+ redirect_uri = GITHUB_REDIRECT ,
34
37
scopes = ['user:email' ],
35
38
)
36
39
@@ -39,44 +42,42 @@ async def get_github_auth(request: Request) -> GitHubAuthProvider:
39
42
40
43
41
44
@router .get ('/login/{kind}' , response_model = FastUI , response_model_exclude_none = True )
42
- async def auth_login (
45
+ def auth_login (
43
46
kind : LoginKind ,
44
- user : Annotated [User | None , Depends (User .from_request )],
45
- github_auth : Annotated [GitHubAuthProvider , Depends (get_github_auth )],
47
+ user : Annotated [User | None , Depends (User .from_request_opt )],
46
48
) -> list [AnyComponent ]:
47
- if user is None :
48
- return demo_page (
49
- c .LinkList (
50
- links = [
51
- c .Link (
52
- components = [c .Text (text = 'Password Login' )],
53
- on_click = PageEvent (name = 'tab' , push_path = '/auth/login/password' , context = {'kind' : 'password' }),
54
- active = '/auth/login/password' ,
55
- ),
56
- c .Link (
57
- components = [c .Text (text = 'GitHub Login' )],
58
- on_click = PageEvent (name = 'tab' , push_path = '/auth/login/github' , context = {'kind' : 'github' }),
59
- active = '/auth/login/github' ,
60
- ),
61
- ],
62
- mode = 'tabs' ,
63
- class_name = '+ mb-4' ,
64
- ),
65
- c .ServerLoad (
66
- path = '/auth/login/content/{kind}' ,
67
- load_trigger = PageEvent (name = 'tab' ),
68
- components = await auth_login_content (kind , github_auth ),
69
- ),
70
- title = 'Authentication' ,
71
- )
72
- else :
73
- return [c .FireEvent (event = GoToEvent (url = '/auth/profile' ))]
49
+ if user is not None :
50
+ # already logged in
51
+ raise AuthRedirect ('/auth/profile' )
52
+
53
+ return demo_page (
54
+ c .LinkList (
55
+ links = [
56
+ c .Link (
57
+ components = [c .Text (text = 'Password Login' )],
58
+ on_click = PageEvent (name = 'tab' , push_path = '/auth/login/password' , context = {'kind' : 'password' }),
59
+ active = '/auth/login/password' ,
60
+ ),
61
+ c .Link (
62
+ components = [c .Text (text = 'GitHub Login' )],
63
+ on_click = PageEvent (name = 'tab' , push_path = '/auth/login/github' , context = {'kind' : 'github' }),
64
+ active = '/auth/login/github' ,
65
+ ),
66
+ ],
67
+ mode = 'tabs' ,
68
+ class_name = '+ mb-4' ,
69
+ ),
70
+ c .ServerLoad (
71
+ path = '/auth/login/content/{kind}' ,
72
+ load_trigger = PageEvent (name = 'tab' ),
73
+ components = auth_login_content (kind ),
74
+ ),
75
+ title = 'Authentication' ,
76
+ )
74
77
75
78
76
79
@router .get ('/login/content/{kind}' , response_model = FastUI , response_model_exclude_none = True )
77
- async def auth_login_content (
78
- kind : LoginKind , github_auth : Annotated [GitHubAuthProvider , Depends (get_github_auth )]
79
- ) -> list [AnyComponent ]:
80
+ def auth_login_content (kind : LoginKind ) -> list [AnyComponent ]:
80
81
match kind :
81
82
case 'password' :
82
83
return [
@@ -87,16 +88,15 @@ async def auth_login_content(
87
88
'here you can "login" with any email address and password.'
88
89
)
89
90
),
90
- c .Paragraph (text = '(Passwords are not saved and email stored in the browser via a JWT)' ),
91
+ c .Paragraph (text = '(Passwords are not saved and is email stored in the browser via a JWT only )' ),
91
92
c .ModelForm (model = LoginForm , submit_url = '/api/auth/login' ),
92
93
]
93
94
case 'github' :
94
- auth_url = await github_auth .authorization_url ()
95
95
return [
96
96
c .Heading (text = 'GitHub Login' , level = 3 ),
97
97
c .Paragraph (text = 'Demo of GitHub authentication.' ),
98
- c .Paragraph (text = '(Credentials are stored in the browser via a JWT)' ),
99
- c .Button (text = 'Login with GitHub' , on_click = GoToEvent (url = auth_url )),
98
+ c .Paragraph (text = '(Credentials are stored in the browser via a JWT only )' ),
99
+ c .Button (text = 'Login with GitHub' , on_click = GoToEvent (url = '/auth/login/github/gen' )),
100
100
]
101
101
case _:
102
102
raise ValueError (f'Invalid kind { kind !r} ' )
@@ -121,30 +121,33 @@ async def login_form_post(form: Annotated[LoginForm, fastui_form(LoginForm)]) ->
121
121
122
122
123
123
@router .get ('/profile' , response_model = FastUI , response_model_exclude_none = True )
124
- async def profile (user : Annotated [User | None , Depends (User .from_request )]) -> list [AnyComponent ]:
125
- if user is None :
126
- return [c .FireEvent (event = GoToEvent (url = '/auth/login' ))]
127
- else :
128
- return demo_page (
129
- c .Paragraph (text = f'You are logged in as "{ user .email } ".' ),
130
- c .Button (text = 'Logout' , on_click = PageEvent (name = 'submit-form' )),
131
- c .Heading (text = 'User Data:' , level = 3 ),
132
- c .Code (language = 'json' , text = json .dumps (asdict (user ), indent = 2 )),
133
- c .Form (
134
- submit_url = '/api/auth/logout' ,
135
- form_fields = [c .FormFieldInput (name = 'test' , title = '' , initial = 'data' , html_type = 'hidden' )],
136
- footer = [],
137
- submit_trigger = PageEvent (name = 'submit-form' ),
138
- ),
139
- title = 'Authentication' ,
140
- )
124
+ async def profile (user : Annotated [User , Depends (User .from_request )]) -> list [AnyComponent ]:
125
+ return demo_page (
126
+ c .Paragraph (text = f'You are logged in as "{ user .email } ".' ),
127
+ c .Button (text = 'Logout' , on_click = PageEvent (name = 'submit-form' )),
128
+ c .Heading (text = 'User Data:' , level = 3 ),
129
+ c .Code (language = 'json' , text = json .dumps (asdict (user ), indent = 2 )),
130
+ c .Form (
131
+ submit_url = '/api/auth/logout' ,
132
+ form_fields = [c .FormFieldInput (name = 'test' , title = '' , initial = 'data' , html_type = 'hidden' )],
133
+ footer = [],
134
+ submit_trigger = PageEvent (name = 'submit-form' ),
135
+ ),
136
+ title = 'Authentication' ,
137
+ )
141
138
142
139
143
140
@router .post ('/logout' , response_model = FastUI , response_model_exclude_none = True )
144
141
async def logout_form_post () -> list [AnyComponent ]:
145
142
return [c .FireEvent (event = AuthEvent (token = False , url = '/auth/login/password' ))]
146
143
147
144
145
+ @router .get ('/login/github/gen' , response_model = FastUI , response_model_exclude_none = True )
146
+ async def auth_github_gen (github_auth : Annotated [GitHubAuthProvider , Depends (get_github_auth )]) -> list [AnyComponent ]:
147
+ auth_url = await github_auth .authorization_url ()
148
+ return [c .FireEvent (event = GoToEvent (url = auth_url ))]
149
+
150
+
148
151
@router .get ('/login/github/redirect' , response_model = FastUI , response_model_exclude_none = True )
149
152
async def github_redirect (
150
153
code : str ,
0 commit comments