-
-
Notifications
You must be signed in to change notification settings - Fork 274
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] Support magic links in SignInPage
#4085
base: master
Are you sure you want to change the base?
Changes from 33 commits
31ea476
c952172
131b443
65e0949
c9e3956
2c61b88
bbdddef
4d7a335
c0796ab
000f694
7882158
3b6309d
2dd6554
31ba678
70ef2aa
d390f71
4532fb2
110a06e
55b316f
cf82dc8
b139284
2ee806b
02073b5
f80bfab
b4ff718
dc5ebf4
c0bf20b
2422a15
56ceca0
7f68521
56b4466
91d95f0
f139401
16f2a3b
5a1b0b4
040fcaf
dc9f688
d534daa
22de185
ffde5f0
c72c511
b87bdc6
745221e
4ba4168
6d540a7
5949071
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 |
---|---|---|
@@ -0,0 +1,30 @@ | ||
import * as React from 'react'; | ||
import { AppProvider, SignInPage } from '@toolpad/core'; | ||
import { useTheme } from '@mui/material/styles'; | ||
|
||
const providers = [{ id: 'nodemailer', name: 'Email' }]; | ||
|
||
const signIn = async (provider) => { | ||
const promise = new Promise((resolve) => { | ||
setTimeout(() => { | ||
console.log(`Sign in with ${provider.id}`); | ||
// preview-start | ||
resolve({ | ||
success: 'Check your email for a verification link.', | ||
}); | ||
// preview-end | ||
}, 500); | ||
}); | ||
return promise; | ||
}; | ||
|
||
export default function MagicLinkAlertSignInPage() { | ||
const theme = useTheme(); | ||
return ( | ||
// preview-start | ||
<AppProvider theme={theme}> | ||
<SignInPage signIn={signIn} providers={providers} /> | ||
</AppProvider> | ||
// preview-end | ||
); | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,40 @@ | ||
import * as React from 'react'; | ||
import { | ||
AuthProvider, | ||
AppProvider, | ||
SignInPage, | ||
AuthResponse, | ||
SupportedAuthProvider, | ||
} from '@toolpad/core'; | ||
import { useTheme } from '@mui/material/styles'; | ||
|
||
const providers: { id: SupportedAuthProvider; name: string }[] = [ | ||
{ id: 'nodemailer', name: 'Email' }, | ||
]; | ||
|
||
const signIn: (provider: AuthProvider) => Promise<AuthResponse> = async ( | ||
provider, | ||
) => { | ||
const promise = new Promise<AuthResponse>((resolve) => { | ||
setTimeout(() => { | ||
console.log(`Sign in with ${provider.id}`); | ||
// preview-start | ||
resolve({ | ||
success: 'Check your email for a verification link.', | ||
}); | ||
// preview-end | ||
}, 500); | ||
}); | ||
return promise; | ||
}; | ||
|
||
export default function MagicLinkAlertSignInPage() { | ||
const theme = useTheme(); | ||
return ( | ||
// preview-start | ||
<AppProvider theme={theme}> | ||
<SignInPage signIn={signIn} providers={providers} /> | ||
</AppProvider> | ||
// preview-end | ||
); | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
resolve({ | ||
success: 'Check your email for a verification link.', | ||
}); | ||
|
||
// ... | ||
|
||
<AppProvider theme={theme}> | ||
<SignInPage signIn={signIn} providers={providers} /> | ||
</AppProvider> |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
import * as React from 'react'; | ||
import { AppProvider, SignInPage } from '@toolpad/core'; | ||
import { useTheme } from '@mui/material/styles'; | ||
|
||
// preview-start | ||
const providers = [{ id: 'nodemailer', name: 'Email' }]; | ||
|
||
// preview-end | ||
|
||
const signIn = async (provider) => { | ||
const promise = new Promise((resolve) => { | ||
setTimeout(() => { | ||
console.log(`Sign in with ${provider.id}`); | ||
resolve(); | ||
}, 500); | ||
}); | ||
return promise; | ||
}; | ||
|
||
export default function MagicLinkSignInPage() { | ||
const theme = useTheme(); | ||
return ( | ||
// preview-start | ||
<AppProvider theme={theme}> | ||
<SignInPage signIn={signIn} providers={providers} /> | ||
</AppProvider> | ||
// preview-end | ||
); | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,35 @@ | ||
import * as React from 'react'; | ||
import { | ||
AuthProvider, | ||
AppProvider, | ||
SignInPage, | ||
SupportedAuthProvider, | ||
} from '@toolpad/core'; | ||
import { useTheme } from '@mui/material/styles'; | ||
|
||
// preview-start | ||
const providers: { id: SupportedAuthProvider; name: string }[] = [ | ||
{ id: 'nodemailer', name: 'Email' }, | ||
]; | ||
// preview-end | ||
|
||
const signIn: (provider: AuthProvider) => void = async (provider) => { | ||
const promise = new Promise<void>((resolve) => { | ||
setTimeout(() => { | ||
console.log(`Sign in with ${provider.id}`); | ||
resolve(); | ||
}, 500); | ||
}); | ||
return promise; | ||
}; | ||
|
||
export default function MagicLinkSignInPage() { | ||
const theme = useTheme(); | ||
return ( | ||
// preview-start | ||
<AppProvider theme={theme}> | ||
<SignInPage signIn={signIn} providers={providers} /> | ||
</AppProvider> | ||
// preview-end | ||
); | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
const providers: { id: SupportedAuthProvider; name: string }[] = [ | ||
{ id: 'nodemailer', name: 'Email' }, | ||
]; | ||
|
||
// ... | ||
|
||
<AppProvider theme={theme}> | ||
<SignInPage signIn={signIn} providers={providers} /> | ||
</AppProvider> |
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
|
@@ -22,7 +22,7 @@ | |||||
|
||||||
:::info | ||||||
|
||||||
The following providers are supported and maintained by default: | ||||||
The following OAuth providers are supported and maintained by default: | ||||||
|
||||||
- GitHub | ||||||
|
@@ -44,15 +44,33 @@ | |||||
- Twitch | ||||||
- Discord | ||||||
- Keycloak | ||||||
- Credentials (username/password) | ||||||
|
||||||
Find details on how to set up each provider in the [Auth.js documentation](https://authjs.dev/getting-started/authentication/oauth). | ||||||
::: | ||||||
|
||||||
## Magic Link | ||||||
|
||||||
The `SignIn` page component supports magic links. To enable this, you will have to set up a provider such as Auth.js NodeMailer. See more details in the Auth.js docs on [database setup for email](https://authjs.dev/getting-started/authentication/email) and [Nodemailer configuration](https://authjs.dev/getting-started/providers/nodemailer/). | ||||||
Check warning on line 53 in docs/data/toolpad/core/components/sign-in-page/sign-in-page.md GitHub Actions / runner / vale
|
||||||
bharatkashyap marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||
|
||||||
To render a magic link form, pass in a provider with `nodemailer` as the `id` property. | ||||||
|
||||||
{{"demo": "MagicLinkSignInPage.js", "iframe": true, "height": 400}} | ||||||
|
||||||
### Alerts | ||||||
|
||||||
The `SignInPage` component can display a success alert if the email is sent successfully. You can enable this by passing a `success` property in the | ||||||
response object of the `signIn` prop. | ||||||
|
||||||
{{"demo": "MagicLinkAlertSignInPage.js", "iframe": true, "height": 400}} | ||||||
|
||||||
:::info | ||||||
Check out the complete [Next.js Auth.js Magic Link example](https://github.com/mui/mui-toolpad/tree/master/examples/core-auth-nextjs-email/) example for a working implementation of a magic link sign-in page with Auth.js, Nodemailer, Prisma and PostgreSQL. | ||||||
::: | ||||||
|
||||||
## Credentials | ||||||
|
||||||
:::warning | ||||||
It is recommended to use the OAuth provider for more robust maintenance, support, and security. | ||||||
The Credentials provider is not the most secure way to authenticate users. We recommend using any of the other providers for a more robust solution. | ||||||
Check warning on line 73 in docs/data/toolpad/core/components/sign-in-page/sign-in-page.md GitHub Actions / runner / vale
|
||||||
bharatkashyap marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||
::: | ||||||
|
||||||
To render a username password form, pass in a provider with `credentials` as the `id` property. The `signIn` function accepts a `formData` parameter in this case. | ||||||
|
@@ -220,4 +238,4 @@ | |||||
|
||||||
## 🚧 Other authentication Flows | ||||||
|
||||||
The `SignInPage` will be accompanied by other components to allow users to sign up, verify emails and reset passwords. This is in progress. | ||||||
The `SignInPage` will be accompanied by other components to allow users to sign up, and reset passwords. This is in progress. | ||||||
Check warning on line 241 in docs/data/toolpad/core/components/sign-in-page/sign-in-page.md GitHub Actions / runner / vale
|
||||||
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. Address vale comment
Suggested change
Perhaps we need links to issues? |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
{ | ||
"extends": "next/core-web-vitals" | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
node_modules/ | ||
/test-results/ | ||
/playwright-report/ | ||
/blob-report/ | ||
/playwright/.cache/ |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,45 @@ | ||
# Use Node.js 20 Alpine as the base image | ||
FROM node:20-alpine AS builder | ||
|
||
# Set working directory | ||
WORKDIR /app | ||
|
||
# Copy package.json and package-lock.json | ||
COPY package*.json ./ | ||
|
||
# Install dependencies | ||
RUN npm install | ||
|
||
# Copy all files | ||
COPY . . | ||
|
||
# Build the Next.js app | ||
RUN npm run build | ||
|
||
# Start a new stage for a smaller final image | ||
FROM node:20-alpine AS runner | ||
|
||
WORKDIR /app | ||
|
||
# Copy built assets from the builder stage | ||
COPY --from=builder /app/.next ./.next | ||
COPY --from=builder /app/node_modules ./node_modules | ||
COPY --from=builder /app/package.json ./package.json | ||
COPY --from=builder /app/src/prisma ./prisma | ||
|
||
# Set environment variables | ||
ENV NODE_ENV production | ||
ENV PORT 3000 | ||
|
||
# Copy the entrypoint script | ||
COPY entrypoint.sh /entrypoint.sh | ||
RUN chmod +x /entrypoint.sh | ||
|
||
# Set the entrypoint | ||
ENTRYPOINT ["/entrypoint.sh"] | ||
|
||
# Expose the port Next.js runs on | ||
EXPOSE 3000 | ||
|
||
# Run the Next.js app | ||
CMD ["npm", "start"] |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,34 @@ | ||
# Toolpad Core Next.js App Router app with email provider | ||
|
||
This is a [Next.js](https://nextjs.org/) project bootstrapped with [`create-next-app`](https://github.com/vercel/next.js/tree/canary/packages/create-next-app). | ||
|
||
## Getting Started | ||
|
||
First, run the development server: | ||
|
||
```bash | ||
npm run dev | ||
# or | ||
yarn dev | ||
# or | ||
pnpm dev | ||
# or | ||
bun dev | ||
``` | ||
|
||
Open [http://localhost:3000](http://localhost:3000) with your browser to see the result. | ||
|
||
## Learn More | ||
|
||
To learn more about Next.js, take a look at the following resources: | ||
|
||
- [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API. | ||
- [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial. | ||
|
||
You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js/) - your feedback and contributions are welcome! | ||
|
||
## Deploy on Vercel | ||
|
||
The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js. | ||
|
||
Check out our [Next.js deployment documentation](https://nextjs.org/docs/deployment) for more details. | ||
Check warning on line 34 in examples/core-auth-nextjs-email/README.md GitHub Actions / runner / vale
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,52 @@ | ||
version: '3.8' | ||
|
||
services: | ||
app: | ||
build: | ||
context: . | ||
dockerfile: Dockerfile | ||
ports: | ||
- '3000:3000' | ||
environment: | ||
- DATABASE_URL=${DATABASE_URL} | ||
- AUTH_URL=${AUTH_URL} | ||
- AUTH_TRUST_HOST=true | ||
- NODE_ENV=production | ||
- GITHUB_CLIENT_ID=${GITHUB_CLIENT_ID} | ||
- GITHUB_CLIENT_SECRET=${GITHUB_CLIENT_SECRET} | ||
- AUTH_SECRET=${AUTH_SECRET} | ||
- EMAIL_SERVER_HOST=${EMAIL_SERVER_HOST} | ||
- EMAIL_SERVER_PORT=${EMAIL_SERVER_PORT} | ||
- EMAIL_SERVER_USER=${EMAIL_SERVER_USER} | ||
- EMAIL_SERVER_PASSWORD=${EMAIL_SERVER_PASSWORD} | ||
- EMAIL_FROM=${EMAIL_FROM} | ||
depends_on: | ||
db: | ||
condition: service_healthy | ||
networks: | ||
- app-network | ||
|
||
db: | ||
image: postgres:13 | ||
environment: | ||
- POSTGRES_USER=${POSTGRES_USER} | ||
- POSTGRES_PASSWORD=${POSTGRES_PASSWORD} | ||
- POSTGRES_DB=${POSTGRES_DB} | ||
ports: | ||
- '${POSTGRES_PORT}:5432' | ||
volumes: | ||
- postgres_data:/var/lib/postgresql/data | ||
healthcheck: | ||
test: ['CMD-SHELL', 'pg_isready -U ${POSTGRES_USER} -d ${POSTGRES_DB}'] | ||
interval: 5s | ||
timeout: 5s | ||
retries: 5 | ||
networks: | ||
- app-network | ||
|
||
volumes: | ||
postgres_data: | ||
|
||
networks: | ||
app-network: | ||
driver: bridge |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
#!/bin/sh | ||
|
||
# Wait for the database to be ready | ||
until nc -z db 5432; do | ||
echo "Waiting for database to be ready..." | ||
sleep 2 | ||
done | ||
|
||
# Run migrations | ||
npx prisma migrate deploy | ||
npx prisma generate | ||
|
||
# Start the application | ||
exec npm start |
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.
Should there be a default success alert that would already show in this demo?
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.
We have a separate demo on the Alerts just below this one, so I think this one is okay? If you think strongly that we should merge them, I can do that
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.
Not a huge deal, it's just that the first demo doesn't provide any feedback when you press the submit button so maybe that will feel a bit weird if it's the first thing a user tries.
But I just noticed that other demos in the same page work the same... maybe they could always show a standard browser alert or something (instead of a console log that most people will miss), but I guess that's something you can add at any time, doesn't need to block this PR.