✨ Complete magic link auth #562
jump to
@@ -39,11 +39,13 @@ email,
username: email, provider: "local", confirmed: true, + role: 1, // authenticated }); const jwt = strapi .plugin("users-permissions") .service("jwt") .issue({ id: user.id }); + return { jwt, user: {
@@ -9,7 +9,7 @@ .findOne({
where: { email }, }); - if (existingUser.provider === "google") { + if (existingUser?.provider === "google") { strapi.log.warn( `User ${email} is linked to Google account. Can't login with magic link.` );
@@ -6,7 +6,7 @@ const start = Date.now();
await next(); const delta = Math.ceil(Date.now() - start); - if (ctx.url === "/graphql") { + if (ctx.url.startsWith("/graphql")) { const user = ctx.state?.user?.username; const { operationName = "", variables, query } = ctx.request.body || {}; const status = graphqlStatus(ctx.body);@@ -38,10 +38,14 @@ const graphqlStatus = (response) => {
if (!response) return "NOT FOUND"; try { - const { errors = null } = response ? JSON.parse(response) : {}; + let errors = null; + if (typeof response === "string") { + const parsed = response ? JSON.parse(response) : {}; + errors = parsed.errors; + } else errors = response?.errors; if (errors) return "ERROR"; } catch (error) { - console.error(error); + console.error("PARSE ERROR", error); return "ERROR"; }
@@ -21,23 +21,10 @@ color="textSecondary"
> {t('event.loginToAttend.desc')} </Typography> - <Box display="flex" justifyContent="space-between" pt={2} gap={1}> - <Link - href={`/auth/login?redirectPath=${router.asPath}`} - passHref - style={{width: '100%'}} - > - <Button fullWidth sx={{mr: 0.5}} variant="outlined"> + <Box display="flex" justifyContent="center" pt={2} gap={1}> + <Link href={`/auth/login?redirectPath=${router.asPath}`} passHref> + <Button sx={{mr: 0.5}} variant="outlined"> {t('event.loginToAttend.login')} - </Button> - </Link> - <Link - href={`/auth/register?redirectPath=${router.asPath}`} - passHref - style={{width: '100%'}} - > - <Button fullWidth sx={{ml: 0.5}} variant="contained"> - {t('event.loginToAttend.signup')} </Button> </Link> </Box>
@@ -1,38 +0,0 @@
-import {useEffect} from 'react'; -import useSettings from './useSettings'; - -const getHeadScript = gtmId => `(function(w,d,s,l,i){w[l]=w[l]||[];w[l].push({'gtm.start': -new Date().getTime(),event:'gtm.js'});var f=d.getElementsByTagName(s)[0], -j=d.createElement(s),dl=l!='dataLayer'?'&l='+l:'';j.async=true;j.src= -'https://www.googletagmanager.com/gtm.js?id='+i+dl;f.parentNode.insertBefore(j,f); -})(window,document,'script','dataLayer','${gtmId}'); -`; - -const getBodyScript = gtmId => `<iframe src="https://www.googletagmanager.com/ns.html?id=${gtmId}" -height="0" width="0" style="display:none;visibility:hidden"></iframe> -`; - -const useGTM = () => { - const settings = useSettings(); - - if (process.env.NODE_ENV !== 'production') return null; - - useEffect(() => { - if (settings) { - const {gtm_id: gtmId} = settings; - if (gtmId) loadGTM(gtmId); - } - }, [settings]); - - const loadGTM = gtmId => { - const headScript = document.createElement('script'); - headScript.innerHTML = getHeadScript(gtmId); - const bodyScript = document.createElement('noscript'); - bodyScript.innerHTML = getBodyScript(gtmId); - - document.head.insertBefore(headScript, document.head.childNodes[0]); - document.body.insertBefore(bodyScript, document.body.childNodes[0]); - }; -}; - -export default useGTM;
@@ -1,7 +1,6 @@
import {ReactNode} from 'react'; import Box from '@mui/material/Box'; import {Helmet} from 'react-helmet'; -import useGTM from '../hooks/useGTM'; import GenericToolbar from '../containers/GenericToolbar'; import Banner from '../components/Banner'; import useMatomo from '../hooks/useMatomo';@@ -20,7 +19,6 @@ announcement?: string;
} const DefaultLayout = (props: Props) => { - useGTM(); useMatomo(); const { children,
@@ -64,7 +64,6 @@ "event.filter.title": "Termine filtern",
"event.loginToAttend": "Möchten Sie an dieser Veranstaltung teilnehmen?", "event.loginToAttend.desc": "Registrieren Sie sich oder melden Sie sich an, um Fahrgemeinschaften zu bilden", "event.loginToAttend.login": "$t(menu.login)", - "event.loginToAttend.signup": "$t(signup.title)", "event.no_other_travel.title": "Derzeit gibt es keine anderen Fahrten", "event.no_travel.desc": "1. Tragen Sie sich in die Warteliste ein\n2. Teilen Sie die Veranstaltung\n3. Sie werden benachrichtigt, wenn eine neue Fahrt hinzugefügt wird", "event.no_travel.plus.action": "Einen Alarm erstellen",
@@ -72,7 +72,6 @@ "event.loginToSetAlert": "Alerts are only available to the attendees of this carpool.",
"event.loginToAttend": "Do you want to attend this event ?", "event.loginToAttend.desc": "Signup or log in to carpool to the event", "event.loginToAttend.login": "$t(menu.login)", - "event.loginToAttend.signup": "$t(signup.title)", "event.no_other_travel.title": "There are currently no other trip", "event.no_travel.desc": "1. Subscribe to the waiting list\n2. Share the event\n3. You will be notified when a new trip is added", "event.no_travel.plus.action": "Create an alert",@@ -190,6 +189,7 @@ "profile.title": "Profile",
"signin.email": "Email", "signin.emailConfirmation": "Your account has been confirmed. You can now login.", "signin.errors.CredentialsSignin": "Login link has expired. Please enter your email to receive a new link.", + "signin.errors.GoogleAccount": "This email address is linked to Google. Please use sign-in with Google.", "signin.login": "$t(menu.login)", "signin.no_account": "You don't have an account ?", "signin.check_email": "A login link has been sent to your email. Please check your email to complete the login.",@@ -200,6 +200,10 @@ "signin.title": "Sign in",
"signin.withGoogle": "Use a Google account", "emailConfirmation.invalidMessage": "This confirmation link is no longer valid because it has already been used or has expired.", "emailConfirmation.toLogin": "Go to login page", + "signup.create": "Create an account", + "signup.firstName": "Firstname", + "signup.lastName": "lastname", + "signup.newsletter.consent": "I am interested in carpooling, I want to subscribe to the newsletter.", "signup.tos.consent": "I accept <tos-link>terms of service</tos-link> and <data-privacy-link>data privacy policy</data-privacy-link>", "supportCaroster": "Support Caroster", "travel.actions.remove_alert": "Are you sure you want to remove this trip and add the subscribers to the waiting list?",
@@ -68,7 +68,6 @@ "event.loginToSetAlert": "Les alertes ne sont disponibles que pour les participants à ce covoiturage.",
"event.loginToAttend": "Voulez-vous rejoindre cet évènement ?", "event.loginToAttend.desc": "Créez un compte ou connectez-vous pour covoiturer à l'événement", "event.loginToAttend.login": "$t(menu.login)", - "event.loginToAttend.signup": "$t(signup.title)", "event.no_other_travel.title": "Pas d'autres trajets pour le moment", "event.no_travel.desc": "1. Inscrivez-vous dans la liste d’attente \n2. Partagez l’événement \n3. Vous serez notifié lorsqu’un nouveau trajet sera ajouté", "event.no_travel.plus.action": "Créer une alerte",@@ -185,14 +184,17 @@ "profile.stripe_link.title": "Facturation",
"profile.title": "Profil", "signin.email": "Email", "signin.errors.CredentialsSignin": "Le lien de connexion est échu. Merci d'entrer votre email pour recevoir un nouveau lien.", + "signin.errors.GoogleAccount": "Cette adresse email est liée à Google. Merci d'utilisation la connexion avec Google.", "signin.login": "$t(menu.login)", "signin.check_email": "Un lien de connexion vous a été envoyé par e-mail. Veuillez vérifier vos e-mail pour terminer la connexion.", "signin.or": "OU", "signin.password": "Mot de passe", "signin.title": "Se connecter", "signin.withGoogle": "Continuer avec Google", + "signup.create": "Créer un compte", "signup.firstName": "Prénom", "signup.lastName": "Nom", + "signup.newsletter.consent": "Le covoiturage m'intéresse, je souhaite recevoir des nouvelles de Caroster", "signup.tos.consent": "J'accepte les <tos-link>conditions d'utilisation</tos-link> et la <data-privacy-link>politique de gestion de données</data-privacy-link>", "supportCaroster": "Soutenir Caroster", "travel.actions.remove_alert": "Voulez-vous vraiment supprimer ce trajet et ajouter les inscrits à la liste d'attente ?",
@@ -117,7 +117,6 @@ "event.filter.dates": "Più date",
"event.loginToAttend": "Vuoi partecipare a questo evento?", "event.loginToAttend.desc": "Accedi o registrati per partecipare all'evento", "event.loginToAttend.login": "$t(menu.login)", - "event.loginToAttend.signup": "$t(signup.title)", "event.no_other_travel.title": "Al momento non ci sono altri passaggi", "event.no_travel.plus.action": "Crea un avviso", "event.no_travel.plus.desc": "Crea un avviso per ricevere un'email su partenze vicine",
@@ -70,7 +70,6 @@ "event.loginToSetAlert": "Waarschuwingen zijn alleen beschikbaar voor de deelnemers aan deze carpool.",
"event.loginToAttend": "Wilt u deelnemen aan deze afspraak?", "event.loginToAttend.desc": "Registreer of log in om deel te nemen", "event.loginToAttend.login": "$t(menu.login)", - "event.loginToAttend.signup": "$t(signup.title)", "event.no_other_travel.title": "Er is momenteel geen ander voertuig", "event.no_travel.desc": "1. Zet uzelf op de wachtlijst;\n2. Deel de afspraak;\n3. Ontvang een melding zodra er een reis beschikbaar is.", "event.no_travel.plus.action": "Maak een waarschuwing",
@@ -25,17 +25,6 @@ <link
rel="stylesheet" href="https://fonts.googleapis.com/icon?family=Material+Icons|Material+Icons+Outlined" /> - <link - rel="stylesheet" - type="text/css" - charSet="UTF-8" - href="https://cdnjs.cloudflare.com/ajax/libs/slick-carousel/1.6.0/slick.min.css" - /> - <link - rel="stylesheet" - type="text/css" - href="https://cdnjs.cloudflare.com/ajax/libs/slick-carousel/1.6.0/slick-theme.min.css" - /> <meta name="apple-mobile-web-app-capable" content="yes" /> <meta name="apple-mobile-web-app-status-bar-style"
@@ -12,7 +12,6 @@ credentials: {
token: {label: 'Token', type: 'password'}, }, async authorize(credentials) { - console.log({credentials}); const response = await fetch(`${STRAPI_URL}/api/auth/magic-link`, { method: 'POST', headers: {'Content-Type': 'application/json'},
@@ -5,10 +5,10 @@ import Button from '@mui/material/Button';
import Box from '@mui/material/Box'; import {useTranslation, Trans} from 'next-i18next'; import {useMemo, useState} from 'react'; -import pageUtils from '../../../lib/pageUtils'; -import CommonConfirm from '../../../layouts/ConfirmLayout'; -import {useUpdateMeMutation} from '../../../generated/graphql'; -import useSettings from '../../../hooks/useSettings'; +import pageUtils from '../../lib/pageUtils'; +import CommonConfirm from '../../layouts/ConfirmLayout'; +import {useUpdateMeMutation} from '../../generated/graphql'; +import useSettings from '../../hooks/useSettings'; import moment from 'moment'; import {useSession} from 'next-auth/react'; import {TextField, useMediaQuery} from '@mui/material';
@@ -28,14 +28,17 @@ const {error} = props;
const {t, i18n} = useTranslation(); const [email, setEmail] = useState(''); const [sent, setSent] = useState(false); + const [magicLinkError, setMagicLinkError] = useState<string>(null); const [sendMagicLink] = useSendMagicLinkMutation(); const handleSubmit = async (e: React.FormEvent<HTMLButtonElement>) => { try { + setMagicLinkError(null); if (email) await sendMagicLink({variables: {email, lang: i18n.language}}); setSent(true); } catch (error) { console.error(error); + if (error.message === 'GoogleAccount') setMagicLinkError(error.message); } };@@ -50,9 +53,9 @@ <Stack spacing={2}>
<Typography variant="h6" align="center"> {t('signin.title')} </Typography> - {error && ( + {(error || magicLinkError) && ( <FormHelperText error sx={{textAlign: 'center'}}> - {t(errorsMap[error])} + {t(errorsMap[error || magicLinkError])} </FormHelperText> )} {!sent && (@@ -94,6 +97,7 @@ };
const errorsMap = { CredentialsSignin: 'signin.errors.CredentialsSignin', + GoogleAccount: 'signin.errors.GoogleAccount', }; export const getServerSideProps = async (context: any) => {
@@ -110,13 +110,12 @@ permanent: false,
}, }; - const provider = session?.token?.provider; const hasAcceptedTos = !!session?.profile?.tosAcceptationDate; - if (provider === 'google' && !hasAcceptedTos) + if (!hasAcceptedTos) return { redirect: { - destination: '/auth/confirm/google', + destination: '/auth/confirm', permanent: false, }, };