Skip to content

Commit

Permalink
Merge pull request #400 from reedu-reengineering-education/fix/passwo…
Browse files Browse the repository at this point in the history
…rd-login

Fix/password login
  • Loading branch information
Thiemann96 authored Nov 27, 2024
2 parents 18acbe3 + dd3f763 commit 702f0b8
Show file tree
Hide file tree
Showing 31 changed files with 1,164 additions and 231 deletions.
56 changes: 56 additions & 0 deletions emails/passwordRequest.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import { Html } from '@react-email/html'
import * as React from 'react'
import { Tailwind } from '@react-email/tailwind'
import { Head } from '@react-email/head'
import { Heading } from '@react-email/heading'
import { Text } from '@react-email/text'
import { Font } from '@react-email/font'
import { Container } from '@react-email/container'

interface PasswordRequestProps {
password: string
}

export default function PasswordRequest({ password }: PasswordRequestProps) {
return (
<Tailwind
config={{
theme: {
extend: {
colors: {
brand: '#38383a',
},
},
},
}}
>
<Head>
<title>Mapstories Passwort Request</title>
<Font
fallbackFontFamily="Helvetica"
fontFamily="sans-serif"
fontStyle="normal"
fontWeight={400}
/>
</Head>
<Html>
<Container className="p-4">
<Heading as="h2" className="text-brand text-lg font-bold">
Mapstories Password Request
</Heading>
<Text className="text-base text-gray-700">
Du hast ein neues Passwort für deinen Zugang bei Mapstories
angefordert. Unten findest du dein neues Passwort:
</Text>
<Text className="mt-4 text-lg font-medium text-gray-900">
Passwort: <span className="text-brand font-bold">{password}</span>
</Text>
<Text className="mt-4 text-sm text-gray-600">
Bitte ändere dein Passwort nach dem Einloggen, um deine Sicherheit
zu gewährleisten.
</Text>
</Container>
</Html>
</Tailwind>
)
}
3 changes: 3 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
"@next-auth/prisma-adapter": "^1.0.5",
"@prisma/client": "^4.12.0",
"@radix-ui/react-avatar": "^1.0.1",
"@radix-ui/react-checkbox": "^1.1.2",
"@radix-ui/react-collapsible": "^1.0.3",
"@radix-ui/react-dialog": "^1.1.1",
"@radix-ui/react-dropdown-menu": "^2.0.3",
Expand All @@ -41,13 +42,15 @@
"@react-email/tailwind": "0.0.7",
"@react-email/text": "0.0.5",
"@react-spring/web": "^9.7.2",
"@types/bcrypt": "^5.0.2",
"@types/multer": "^1.4.7",
"@types/node": "18.14.2",
"@types/react": "18.2.25",
"@types/react-dom": "18.0.11",
"@uiw/react-md-editor": "^3.20.5",
"accept-language": "^3.0.18",
"axios": "^1.3.4",
"bcrypt": "^5.1.1",
"class-variance-authority": "^0.4.0",
"classnames": "^2.3.2",
"create-email": "^0.0.23",
Expand Down
28 changes: 24 additions & 4 deletions pages/api/users/[userId].ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
import { NextApiRequest, NextApiResponse } from 'next'
import * as z from 'zod'
import { getServerSession } from 'next-auth/next'
import bcrypt from 'bcrypt'

import { db } from '@/src/lib/db'
import { userUpdateSchema } from '@/src/lib/validations/user'
import { authOptions } from '@/src/lib/auth'
import { withCurrentUser } from '@/src/lib/apiMiddlewares/withCurrentUser'
import { withMethods } from '@/src/lib/apiMiddlewares/withMethods'
import { sendConfirmationRequest } from '@/src/lib/sendChangeEmailMail'
// import jwt from “jsonwebtoken”;

async function handler(req: NextApiRequest, res: NextApiResponse) {
if (req.method === 'PATCH') {
Expand All @@ -21,44 +21,64 @@ async function handler(req: NextApiRequest, res: NextApiResponse) {
if (!user) {
throw new Error('Unauthenticated')
}

// Validiere die Eingabedaten
const payload = userUpdateSchema.parse(body)
let newPayload: any
let newPayload: any = { ...payload }

if (payload.email && payload.email != user.email) {
// Prüfe, ob die E-Mail geändert wird
if (payload.email && payload.email !== user.email) {
const userExists = await db.user.findUnique({
where: {
email: payload.email,
},
})

if (userExists) {
return res.status(422).json({ error: 'E-Mail Address not valid.' })
}

const token = Math.random().toString(36).slice(2)

newPayload = {
...payload,
...newPayload,
verify_new_email_token: token,
new_email: payload.email,
}

sendConfirmationRequest({
url: req.headers.host + '/confirmMail?token=' + token,
identifier: payload.email,
token: token,
})

delete newPayload.email
}

// Prüfe, ob ein Passwort aktualisiert wird und ob es nicht leer ist
if (payload.password && payload.password.trim() !== '') {
const hashedPassword = await bcrypt.hash(payload.password, 10) // Hash das Passwort
newPayload.password = hashedPassword
} else {
// Entferne das Passwort aus dem Payload, falls es leer ist
delete newPayload.password
}

// Aktualisiere den Benutzer in der Datenbank
const updatedUser = await db.user.update({
where: {
id: user.id,
},
data: newPayload,
})

// Gib den aktualisierten Benutzer zurück
return res.status(200).json(updatedUser)
} catch (error) {
return res.status(422).json(error)
}
}

if (req.method === 'DELETE') {
try {
const session = await getServerSession(req, res, authOptions)
Expand Down
71 changes: 71 additions & 0 deletions pages/api/users/requestPassword.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
import { NextApiRequest, NextApiResponse } from 'next'
import { db } from '@/src/lib/db'
import nodemailer from 'nodemailer'
import crypto from 'crypto'
import { withMethods } from '@/src/lib/apiMiddlewares/withMethods'
import bcrypt from 'bcrypt'
import { render } from '@react-email/render'
import PasswordRequest from '@/emails/passwordRequest'

async function handler(req: NextApiRequest, res: NextApiResponse) {
const { email } = req.body

if (!email) {
return res.status(400).json({ message: 'Email is required' })
}

try {
// Generate a new random password
const newPassword = crypto.randomBytes(8).toString('hex')
const hashedPassword = await bcrypt.hash(newPassword, 10)

// Update the user's password in the database
const updatedUser = await db.user.findUnique({
where: { email },
})

if (!updatedUser) {
return res.status(404).json({ message: 'User not found' })
}

if (updatedUser.password && updatedUser.password.length > 0) {
return res.status(400).json({
message: 'User already has a password set',
})
}
await db.user.update({
where: { email },
data: { password: hashedPassword },
})

// Send an email to the user with the new password
const transporter = nodemailer.createTransport({
host: process.env.SMTP_HOST,
port: 587,
secure: false,
auth: {
user: process.env.SMTP_USER,
pass: process.env.SMTP_PASS,
},
})

const emailHmtl = render(PasswordRequest({ password: newPassword }))

const mailOptions = {
from: process.env.SMTP_USER,
to: email,
subject: 'Your new password',
html: emailHmtl,
}
await transporter.sendMail(mailOptions)

res
.status(200)
.json({ message: 'New password has been set and emailed to you' })
} catch (error) {
console.error(error)
res.status(500).json({ message: 'Internal server error' })
}
}

export default withMethods(['POST'], handler)
2 changes: 2 additions & 0 deletions prisma/migrations/20240927084220_lamapoll/migration.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
-- AlterEnum
ALTER TYPE "MediaType" ADD VALUE 'LAMAPOLL';
3 changes: 3 additions & 0 deletions prisma/migrations/20241119170455_password/migration.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@

-- AlterTable
ALTER TABLE "users" ADD COLUMN "password" TEXT;
1 change: 1 addition & 0 deletions prisma/schema.prisma
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ model User {
story Story[] @relation("contributors")
verify_new_email_token String?
new_email String?
password String?
@@map(name: "users")
}
Expand Down
12 changes: 12 additions & 0 deletions src/app/[lng]/(auth)/login/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,19 @@ export default async function LoginPage({
{t('enter_email_for_signin')}
</p>
</div>

<UserAuthForm />

<div className="mt-4 text-center text-sm text-slate-600">
<p>{t('password_login_hint')}</p>
<Link
className="text-brand hover:text-brand-dark underline"
href="/passwordLogin"
>
{t('password_login_link')}
</Link>
</div>

<h1 className="text-center">Noch keinen Account?</h1>
<p className="px-8 text-center text-sm text-slate-600">
{t('disclaimerRegister')}{' '}
Expand Down
56 changes: 56 additions & 0 deletions src/app/[lng]/(auth)/passwordLogin/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import Link from 'next/link'

import { Button } from '@/src/components/Elements/Button'
import { ChevronLeftIcon } from '@heroicons/react/24/outline'
import { useTranslation } from '@/src/app/i18n'
import { LogoWithClaimAndBackground } from '@/src/components/Layout/MapstoriesLogo'
import UserAuthPassword from '@/src/components/Auth/UserAuthPassword'

export default async function LoginPage({
params: { lng },
}: {
params: { lng: string }
}) {
const { t } = await useTranslation(lng, 'login')

return (
<div className="container flex h-screen w-screen flex-col items-center justify-center">
<Link className="absolute left-4 top-4" href="/">
<Button
startIcon={<ChevronLeftIcon className="w-4" />}
variant={'inverse'}
>
{t('back')}
</Button>
</Link>
<div className="mx-auto flex w-full flex-col justify-center space-y-6 sm:w-[350px]">
<div className="flex flex-col space-y-2 text-center">
<LogoWithClaimAndBackground className="mx-auto h-32 w-60" />
<h1 className="text-2xl font-bold">{t('welcome_back')}</h1>
<p className="text-sm text-slate-600">
{t('enter_email_and_password_for_signin')}
</p>
<p className="textz-sm text-slate-800">
{t('enable_password_login')}&nbsp;
<a className="text-blue-500" href="/passwordRequest">
{t('link')}
</a>
{t('enable_password_login_end')}
</p>
</div>
<UserAuthPassword />
{/* <h1 className="text-center">Noch keinen Account?</h1>
<p className="px-8 text-center text-sm text-slate-600">
{t('disclaimerRegister')}{' '}
<Link className="hover:text-brand underline" href="/terms">
{t('TOS')}
</Link>{' '}
{t('and')}{' '}
<Link className="hover:text-brand underline" href="/privacy">
{t('PP')}
</Link>
</p> */}
</div>
</div>
)
}
38 changes: 38 additions & 0 deletions src/app/[lng]/(auth)/passwordRequest/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import { useTranslation } from '@/src/app/i18n'
import UserAuthRequestPassword from '@/src/components/Auth/UserAuthRequestPassword'
import { LogoWithClaimAndBackground } from '@/src/components/Layout/MapstoriesLogo'
import React from 'react'

export default async function RequestPasswordPage({
params: { lng },
}: {
params: { lng: string }
}) {
const { t } = await useTranslation(lng, 'login')

return (
<div className="container flex h-screen w-screen flex-col items-center justify-center">
<div className="mx-auto flex w-full flex-col justify-center space-y-6 sm:w-[350px]">
<div className="flex flex-col space-y-2 text-center">
<LogoWithClaimAndBackground className="mx-auto h-32 w-60" />
<h1 className="text-2xl font-bold">{t('welcome_back')}</h1>
<p className="text-sm text-slate-600">
{t('enter_email_for_password_request')}
</p>
</div>
<UserAuthRequestPassword />
{/* <h1 className="text-center">Noch keinen Account?</h1>
<p className="px-8 text-center text-sm text-slate-600">
{t('disclaimerRegister')}{' '}
<Link className="hover:text-brand underline" href="/terms">
{t('TOS')}
</Link>{' '}
{t('and')}{' '}
<Link className="hover:text-brand underline" href="/privacy">
{t('PP')}
</Link>
</p> */}
</div>
</div>
)
}
1 change: 1 addition & 0 deletions src/app/[lng]/(main)/storylab/(general)/settings/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ export default async function SettingsPage({
heading={t('settings')}
text={t('edit your account')}
></StudioHeader>
{/* @ts-ignore */}
<UserSettingsForm user={user} />
</StudioShell>
)
Expand Down
12 changes: 11 additions & 1 deletion src/app/i18n/locales/de/login.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,5 +18,15 @@
"PP": "Privacy Policy",
"and": "und",
"disclaimerRegister": "Gib einfach deine E-Mail-Adresse ein, und wir erstellen automatisch ein Konto für dich. Die",
"endDisclaimRegister": " kannst du unter den Links nachlesen."
"endDisclaimRegister": " kannst du unter den Links nachlesen.",
"loginSuccess": "Erfolgreich angemeldet",
"loginSuccessText": "Du wurdest erfolgreich angemeldet. Viel Spaß!",
"enter_email_and_password_for_signin": "Gib deine E-Mail-Adresse und dein Passwort ein, um dich",
"password_login_hint": "Wenn Sie das Passwortverfahren aktiviert haben, können Sie sich auch damit einloggen.",
"password_login_link": "Hier klicken, um zum Passwort-Login zu gelangen.",
"enable_password_login": "Wenn du das Passwortverfahren aktivieren möchtest, folge diesem ",
"link": "Link ",
"enable_password_login_end": "und folge den Anweisungen.",
"enter_email_for_password_request": "Gib deine E-Mail-Adresse ein, um ein Passwort anzufragen",
"password_request": "Passwort anfragen"
}
Loading

0 comments on commit 702f0b8

Please sign in to comment.