-
-
Notifications
You must be signed in to change notification settings - Fork 337
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[core] Add authentication to Toolpad Core #3609
Changes from all commits
461b19d
b4712c4
55eb08e
334288b
d1399c0
87fa644
d32d986
ae2f352
781aae9
f62244e
0fc1812
39272e9
4b81c0f
0ee065c
3aa4c3b
53ae9b7
bab1320
4915f32
3e93ab7
536d535
b469551
174442c
8459f29
a005117
1a4731a
2c190a2
56b0256
8adeef5
d063c32
caaba76
b5ac9b4
5695d06
01f2191
d72fbec
63683e2
9f6f67e
0054171
d1d4316
3afc720
66144bb
3e9551d
13b1824
f6f9a8a
5c7ad8b
c23bcf6
9a1f1c7
e5f20be
dbaee51
2e35386
0211d27
99f9b88
0b2b624
6aa4749
739aca8
cb15e87
6705a18
4d52271
88ccdd8
299ef52
e9ce877
a34546b
eb544a6
1b938cd
6decf84
c8d611f
e3b3373
2976e7a
d715b2f
9e9cde5
cdb20b5
a031656
5dc229f
93f91d6
f3cc43f
5255d07
1c0c15d
54f26d3
361e2b2
e96fb7c
877cc25
cf4cde4
13f3ebe
ac73348
ea3f4c7
758ac45
add83a9
331232a
c2dc414
b4497ea
e0221f1
6e30459
a6a3e84
70d08e0
77a36e4
76302f4
9e9803d
76b2c16
d9372f3
3e015bd
03af9de
8acf683
82156d8
f6837c9
25a61b8
208c502
799cbe1
d21db58
cc7aca3
1aa62e8
fd30c66
faabae3
a6bfb00
805d508
6c9e3f3
339a2eb
435d768
1916886
3664521
67b7ae9
62033aa
7b00b52
f6e4336
9131ca2
06d605f
30f3ec8
c54d22b
54b628d
885b06b
4f8d253
4e9ae6e
5889ea8
742daa4
877b047
e6dc77f
4439766
7c884f6
02fbb37
391b33f
f9ca96f
ac6f0ca
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -16,3 +16,4 @@ build | |
|
||
.generated | ||
.github | ||
.coverage |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,47 @@ | ||
import * as React from 'react'; | ||
import { Account, AuthenticationContext, SessionContext } from '@toolpad/core'; | ||
|
||
export default function AccountCustom() { | ||
const [session, setSession] = React.useState(null); | ||
const authentication = React.useMemo(() => { | ||
return { | ||
signIn: () => { | ||
setSession({ | ||
user: { | ||
name: 'Bharat Kashyap', | ||
email: '[email protected]', | ||
image: 'https://avatars.githubusercontent.com/u/19550456', | ||
}, | ||
}); | ||
}, | ||
signOut: () => { | ||
setSession(null); | ||
}, | ||
}; | ||
}, []); | ||
|
||
return ( | ||
// preview-start | ||
<AuthenticationContext.Provider value={authentication}> | ||
<SessionContext.Provider value={session}> | ||
<Account | ||
slotProps={{ | ||
signInButton: { | ||
color: 'info', | ||
variant: 'outlined', | ||
sx: { | ||
color: 'primaryDark', | ||
fontFamily: 'Inter', | ||
fontSize: '1em', | ||
}, | ||
}, | ||
signOutButton: { color: 'primary', variant: 'outlined' }, | ||
}} | ||
signInLabel="Login" | ||
signOutLabel="Logout" | ||
/> | ||
</SessionContext.Provider> | ||
</AuthenticationContext.Provider> | ||
// preview-end | ||
); | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,52 @@ | ||
import * as React from 'react'; | ||
import { | ||
Account, | ||
AuthenticationContext, | ||
SessionContext, | ||
Session, | ||
} from '@toolpad/core'; | ||
|
||
export default function AccountCustom() { | ||
const [session, setSession] = React.useState<Session | null>(null); | ||
const authentication = React.useMemo(() => { | ||
return { | ||
signIn: () => { | ||
setSession({ | ||
user: { | ||
name: 'Bharat Kashyap', | ||
email: '[email protected]', | ||
image: 'https://avatars.githubusercontent.com/u/19550456', | ||
}, | ||
}); | ||
}, | ||
signOut: () => { | ||
setSession(null); | ||
}, | ||
}; | ||
}, []); | ||
|
||
return ( | ||
// preview-start | ||
<AuthenticationContext.Provider value={authentication}> | ||
<SessionContext.Provider value={session}> | ||
<Account | ||
slotProps={{ | ||
signInButton: { | ||
color: 'info', | ||
variant: 'outlined', | ||
sx: { | ||
color: 'primaryDark', | ||
fontFamily: 'Inter', | ||
fontSize: '1em', | ||
}, | ||
}, | ||
signOutButton: { color: 'primary', variant: 'outlined' }, | ||
}} | ||
signInLabel="Login" | ||
signOutLabel="Logout" | ||
/> | ||
</SessionContext.Provider> | ||
</AuthenticationContext.Provider> | ||
// preview-end | ||
); | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
<AuthenticationContext.Provider value={authentication}> | ||
<SessionContext.Provider value={session}> | ||
<Account | ||
slotProps={{ | ||
signInButton: { | ||
color: 'info', | ||
variant: 'outlined', | ||
sx: { | ||
color: 'primaryDark', | ||
fontFamily: 'Inter', | ||
fontSize: '1em', | ||
}, | ||
}, | ||
signOutButton: { color: 'primary', variant: 'outlined' }, | ||
}} | ||
signInLabel="Login" | ||
signOutLabel="Logout" | ||
/> | ||
</SessionContext.Provider> | ||
</AuthenticationContext.Provider> |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
import * as React from 'react'; | ||
import { Account, AuthenticationContext, SessionContext } from '@toolpad/core'; | ||
|
||
export default function AccountDemo() { | ||
const [session, setSession] = React.useState(null); | ||
const authentication = React.useMemo(() => { | ||
return { | ||
signIn: () => { | ||
setSession({ | ||
user: { | ||
name: 'Bharat Kashyap', | ||
email: '[email protected]', | ||
image: 'https://avatars.githubusercontent.com/u/19550456', | ||
}, | ||
}); | ||
}, | ||
signOut: () => { | ||
setSession(null); | ||
}, | ||
}; | ||
}, []); | ||
|
||
return ( | ||
<AuthenticationContext.Provider value={authentication}> | ||
<SessionContext.Provider value={session}> | ||
<Account /> | ||
</SessionContext.Provider> | ||
</AuthenticationContext.Provider> | ||
); | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,35 @@ | ||
import * as React from 'react'; | ||
import { | ||
Account, | ||
AuthenticationContext, | ||
SessionContext, | ||
Session, | ||
} from '@toolpad/core'; | ||
|
||
export default function AccountDemo() { | ||
const [session, setSession] = React.useState<Session | null>(null); | ||
const authentication = React.useMemo(() => { | ||
return { | ||
signIn: () => { | ||
setSession({ | ||
user: { | ||
name: 'Bharat Kashyap', | ||
email: '[email protected]', | ||
image: 'https://avatars.githubusercontent.com/u/19550456', | ||
}, | ||
}); | ||
}, | ||
signOut: () => { | ||
setSession(null); | ||
}, | ||
}; | ||
}, []); | ||
|
||
return ( | ||
<AuthenticationContext.Provider value={authentication}> | ||
<SessionContext.Provider value={session}> | ||
<Account /> | ||
</SessionContext.Provider> | ||
</AuthenticationContext.Provider> | ||
); | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
<AuthenticationContext.Provider value={authentication}> | ||
<SessionContext.Provider value={session}> | ||
<Account /> | ||
</SessionContext.Provider> | ||
</AuthenticationContext.Provider> |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,37 @@ | ||
import * as React from 'react'; | ||
import Box from '@mui/material/Box'; | ||
import { AppProvider, DashboardLayout } from '@toolpad/core'; | ||
|
||
export default function AccountWithDashboard() { | ||
const [session, setSession] = React.useState({ | ||
user: { | ||
name: 'Bharat Kashyap', | ||
email: '[email protected]', | ||
image: 'https://avatars.githubusercontent.com/u/19550456', | ||
}, | ||
}); | ||
const authentication = React.useMemo(() => { | ||
return { | ||
signIn: () => { | ||
setSession({ | ||
user: { | ||
name: 'Bharat Kashyap', | ||
email: '[email protected]', | ||
image: 'https://avatars.githubusercontent.com/u/19550456', | ||
}, | ||
}); | ||
}, | ||
signOut: () => { | ||
setSession(null); | ||
}, | ||
}; | ||
}, []); | ||
|
||
return ( | ||
<AppProvider session={session} authentication={authentication}> | ||
<DashboardLayout> | ||
<Box sx={{ px: 6, py: 2 }}>Dashboard content</Box> | ||
</DashboardLayout> | ||
</AppProvider> | ||
); | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,37 @@ | ||
import * as React from 'react'; | ||
import Box from '@mui/material/Box'; | ||
import { Session, AppProvider, DashboardLayout } from '@toolpad/core'; | ||
|
||
export default function AccountWithDashboard() { | ||
const [session, setSession] = React.useState<Session | null>({ | ||
user: { | ||
name: 'Bharat Kashyap', | ||
email: '[email protected]', | ||
image: 'https://avatars.githubusercontent.com/u/19550456', | ||
}, | ||
}); | ||
const authentication = React.useMemo(() => { | ||
return { | ||
signIn: () => { | ||
setSession({ | ||
user: { | ||
name: 'Bharat Kashyap', | ||
email: '[email protected]', | ||
image: 'https://avatars.githubusercontent.com/u/19550456', | ||
}, | ||
}); | ||
}, | ||
signOut: () => { | ||
setSession(null); | ||
}, | ||
}; | ||
}, []); | ||
|
||
return ( | ||
<AppProvider session={session} authentication={authentication}> | ||
<DashboardLayout> | ||
<Box sx={{ px: 6, py: 2 }}>Dashboard content</Box> | ||
</DashboardLayout> | ||
</AppProvider> | ||
); | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
<DashboardLayout> | ||
<Box sx={{ px: 6, py: 2 }}>Dashboard content</Box> | ||
</DashboardLayout> |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
--- | ||
productId: toolpad-core | ||
title: Account | ||
components: Account | ||
--- | ||
|
||
# Account | ||
|
||
<p class="description">A component that renders an account management dropdown for your application.</p> | ||
|
||
The `Account` component is a quick and easy way to display an account management menu for authenticated users. It is deeply integrated with the `SignInPage` and `DashboardLayout` components, meaning that it automatically appears in the top navigation bar inside `DashboardLayout` once your users have signed in through the `SignInPage`. | ||
|
||
## States | ||
|
||
When signed out, the component renders as an inline sign in button within the dashboard layout. If a `session` object is present, the component is rendered as a dropdown containing the user's account details as well as an option to sign out. | ||
|
||
{{"demo": "AccountDemo.js", "bg": "gradient" }} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. follow up |
||
|
||
## Customization | ||
|
||
### Components | ||
|
||
`Account` can take different labels for the sign in and sign out buttons through the `signInLabel` and `signOutLabel` props. Deeper changes can be made by passing in `slotProps` to the underlying components. | ||
|
||
{{"demo": "AccountCustom.js", "bg": "gradient" }} | ||
|
||
### 🚧 Composition | ||
|
||
The `Account` component allows adding your own menu options, including deeply nested options. This is in progress. |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,7 +1,7 @@ | ||
--- | ||
productId: toolpad-core | ||
title: Dashboard Layout | ||
components: AppProvider, DashboardLayout | ||
components: AppProvider, DashboardLayout, Account | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Needs a section and demo on the user button. And an explanation on how to customize it (slots?). |
||
--- | ||
|
||
# Dashboard Layout | ||
|
@@ -50,3 +50,13 @@ The main navigation items that can be used are: | |
Navigation links have an optional `action` prop that can be used to render any content on the right-side of the respective list item, such as badges with numbers, or buttons to toggle a popover menu. | ||
|
||
{{"demo": "DashboardLayoutNavigationActions.js", "height": 500, "iframe": true}} | ||
|
||
## Account | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't mind the name There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The issue with components starting with There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ok, if that's the main/only reason I guess we can make the part that checks "use" in the scripts be case-sensitive? Anyway it's not that important to change this name, only if everyone agrees. |
||
|
||
The `DashboardLayout` comes integrated with the [`<Account />`](/toolpad/core/react-account/) component. It renders as an account management menu when a user is signed in – a `session` object is present – and a button when not. | ||
|
||
:::warning | ||
The use of an `iframe` may cause some spacing issues in the following demo. | ||
::: | ||
|
||
{{"demo": "../account/AccountWithDashboard.js", "iframe": true, "height": 320 }} |
This file was deleted.
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,46 @@ | ||
/* eslint-disable import/no-unresolved */ | ||
|
||
import * as React from 'react'; | ||
import { AuthError } from 'next-auth'; | ||
|
||
import { SignInPage } from '@toolpad/core/SignInPage'; | ||
import { providerMap, signIn } from './auth'; | ||
|
||
export default function AuthJsSignInApp() { | ||
return ( | ||
<SignInPage | ||
providers={providerMap} | ||
signIn={async (provider, formData, callbackUrl) => { | ||
'use server'; | ||
try { | ||
return await signIn(provider.id, { | ||
...(formData && { | ||
email: formData.get('email'), | ||
password: formData.get('password'), | ||
}), | ||
redirectTo: callbackUrl ?? '/', | ||
}); | ||
} catch (error) { | ||
if (error instanceof Error && error.message === 'NEXT_REDIRECT') { | ||
throw error; | ||
} | ||
// Handle Auth.js errors | ||
if (error instanceof AuthError) { | ||
return { | ||
error: | ||
error.type === 'CredentialsSignin' | ||
? 'Invalid credentials.' | ||
: 'An error with Auth.js occurred.', | ||
type: error.type, | ||
}; | ||
} | ||
// An error boundary must exist to handle unknown errors | ||
return { | ||
error: 'Something went wrong.', | ||
type: 'UnknownError', | ||
}; | ||
} | ||
}} | ||
/> | ||
); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Needs a bit more context. Explanation about why and how to use this.