From 3bb15385707fa72b2d95e9f8d96958da6cc72435 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 10 Jul 2023 04:56:45 +0000 Subject: [PATCH 1/6] Bump @types/react-dom from 18.0.10 to 18.2.6 Bumps [@types/react-dom](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/react-dom) from 18.0.10 to 18.2.6. - [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases) - [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/react-dom) --- updated-dependencies: - dependency-name: "@types/react-dom" dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- package.json | 2 +- yarn.lock | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index 3445a77..2411a9d 100644 --- a/package.json +++ b/package.json @@ -26,7 +26,7 @@ "@types/node": "20.3.3", "@types/nodemailer": "^6.4.8", "@types/react": "18.0.27", - "@types/react-dom": "18.0.10", + "@types/react-dom": "18.2.6", "bcrypt": "^5.1.0", "colorthief": "^2.4.0", "crypto-js": "^4.1.1", diff --git a/yarn.lock b/yarn.lock index 6163f0d..1cd0bd7 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1521,10 +1521,10 @@ resolved "https://registry.yarnpkg.com/@types/prop-types/-/prop-types-15.7.5.tgz#5f19d2b85a98e9558036f6a3cacc8819420f05cf" integrity sha512-JCB8C6SnDoQf0cNycqd/35A7MjcnK+ZTqE7judS6o7utxUCg6imJg3QK2qzHKszlTjcj2cn+NwMB2i96ubpj7w== -"@types/react-dom@18.0.10": - version "18.0.10" - resolved "https://registry.yarnpkg.com/@types/react-dom/-/react-dom-18.0.10.tgz#3b66dec56aa0f16a6cc26da9e9ca96c35c0b4352" - integrity sha512-E42GW/JA4Qv15wQdqJq8DL4JhNpB3prJgjgapN3qJT9K2zO5IIAQh4VXvCEDupoqAwnz0cY4RlXeC/ajX5SFHg== +"@types/react-dom@18.2.6": + version "18.2.6" + resolved "https://registry.yarnpkg.com/@types/react-dom/-/react-dom-18.2.6.tgz#ad621fa71a8db29af7c31b41b2ea3d8a6f4144d1" + integrity sha512-2et4PDvg6PVCyS7fuTc4gPoksV58bW0RwSxWKcPRcHZf0PRUGq03TKcD/rUHe3azfV6/5/biUBJw+HhCQjaP0A== dependencies: "@types/react" "*" From dfa8caea43316b3b0581ce304fbd4b46daf9057d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 10 Jul 2023 04:56:54 +0000 Subject: [PATCH 2/6] Bump @types/react from 18.0.27 to 18.2.14 Bumps [@types/react](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/react) from 18.0.27 to 18.2.14. - [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases) - [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/react) --- updated-dependencies: - dependency-name: "@types/react" dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- package.json | 2 +- yarn.lock | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index 3445a77..186630d 100644 --- a/package.json +++ b/package.json @@ -25,7 +25,7 @@ "@types/crypto-js": "^4.1.1", "@types/node": "20.3.3", "@types/nodemailer": "^6.4.8", - "@types/react": "18.0.27", + "@types/react": "18.2.14", "@types/react-dom": "18.0.10", "bcrypt": "^5.1.0", "colorthief": "^2.4.0", diff --git a/yarn.lock b/yarn.lock index 6163f0d..06dcb0b 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1535,10 +1535,10 @@ dependencies: "@types/react" "*" -"@types/react@*", "@types/react@18.0.27": - version "18.0.27" - resolved "https://registry.yarnpkg.com/@types/react/-/react-18.0.27.tgz#d9425abe187a00f8a5ec182b010d4fd9da703b71" - integrity sha512-3vtRKHgVxu3Jp9t718R9BuzoD4NcQ8YJ5XRzsSKxNDiDonD2MXIT1TmSkenxuCycZJoQT5d2vE8LwWJxBC1gmA== +"@types/react@*", "@types/react@18.2.14": + version "18.2.14" + resolved "https://registry.yarnpkg.com/@types/react/-/react-18.2.14.tgz#fa7a6fecf1ce35ca94e74874f70c56ce88f7a127" + integrity sha512-A0zjq+QN/O0Kpe30hA1GidzyFjatVvrpIvWLxD+xv67Vt91TWWgco9IvrJBkeyHm1trGaFS/FSGqPlhyeZRm0g== dependencies: "@types/prop-types" "*" "@types/scheduler" "*" From d4b9e43ba508e73ff431bd7aa83e6427f7725f5b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 10 Jul 2023 04:57:08 +0000 Subject: [PATCH 3/6] Bump @next/font from 13.4.7 to 13.4.9 Bumps [@next/font](https://github.com/vercel/next.js/tree/HEAD/packages/font) from 13.4.7 to 13.4.9. - [Release notes](https://github.com/vercel/next.js/releases) - [Changelog](https://github.com/vercel/next.js/blob/canary/release.js) - [Commits](https://github.com/vercel/next.js/commits/v13.4.9/packages/font) --- updated-dependencies: - dependency-name: "@next/font" dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- package.json | 2 +- yarn.lock | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index 3445a77..275599f 100644 --- a/package.json +++ b/package.json @@ -20,7 +20,7 @@ "@fortawesome/free-solid-svg-icons": "^6.4.0", "@fortawesome/react-fontawesome": "^0.2.0", "@headlessui/react": "^1.7.15", - "@next/font": "13.4.7", + "@next/font": "13.4.9", "@prisma/client": "^4.16.2", "@types/crypto-js": "^4.1.1", "@types/node": "20.3.3", diff --git a/yarn.lock b/yarn.lock index 6163f0d..c020c0a 100644 --- a/yarn.lock +++ b/yarn.lock @@ -951,10 +951,10 @@ dependencies: glob "7.1.7" -"@next/font@13.4.7": - version "13.4.7" - resolved "https://registry.yarnpkg.com/@next/font/-/font-13.4.7.tgz#32f9e2c4a856e23370d2be4b46eddaccce11b88d" - integrity sha512-l28ifb3pjKznQeXmiCD5VXkmqdpMrcUQMr4ZChAgTKrjckl/aGyRLOIlL/ABhTHmzMujdt/TTWUsdABArNK5gA== +"@next/font@13.4.9": + version "13.4.9" + resolved "https://registry.yarnpkg.com/@next/font/-/font-13.4.9.tgz#5540e69a1a5fbd1113d622a89cdd21c0ab3906c8" + integrity sha512-aR0XEyd1cxqaKuelQFDGwUBYV0wyZfJTNiRoSk1XsECTyMhiSMmCOY7yOPMuPlw+6cctca0GyZXGGFb5EVhiRw== "@next/swc-android-arm-eabi@13.1.6": version "13.1.6" From 1a1a89dff3b068451ee5fcf601ce197629389558 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 10 Jul 2023 04:57:31 +0000 Subject: [PATCH 4/6] Bump eslint-config-next from 13.4.7 to 13.4.9 Bumps [eslint-config-next](https://github.com/vercel/next.js/tree/HEAD/packages/eslint-config-next) from 13.4.7 to 13.4.9. - [Release notes](https://github.com/vercel/next.js/releases) - [Changelog](https://github.com/vercel/next.js/blob/canary/release.js) - [Commits](https://github.com/vercel/next.js/commits/v13.4.9/packages/eslint-config-next) --- updated-dependencies: - dependency-name: eslint-config-next dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- package.json | 2 +- yarn.lock | 28 ++++++++++++++-------------- 2 files changed, 15 insertions(+), 15 deletions(-) diff --git a/package.json b/package.json index 3445a77..6ad93df 100644 --- a/package.json +++ b/package.json @@ -31,7 +31,7 @@ "colorthief": "^2.4.0", "crypto-js": "^4.1.1", "eslint": "8.44.0", - "eslint-config-next": "13.4.7", + "eslint-config-next": "13.4.9", "next": "13.1.6", "next-auth": "^4.22.1", "nodemailer": "^6.9.3", diff --git a/yarn.lock b/yarn.lock index 6163f0d..3749f68 100644 --- a/yarn.lock +++ b/yarn.lock @@ -944,10 +944,10 @@ resolved "https://registry.yarnpkg.com/@next/env/-/env-13.1.6.tgz#c4925609f16142ded1a5cb833359ab17359b7a93" integrity sha512-s+W9Fdqh5MFk6ECrbnVmmAOwxKQuhGMT7xXHrkYIBMBcTiOqNWhv5KbJIboKR5STXxNXl32hllnvKaffzFaWQg== -"@next/eslint-plugin-next@13.4.7": - version "13.4.7" - resolved "https://registry.yarnpkg.com/@next/eslint-plugin-next/-/eslint-plugin-next-13.4.7.tgz#7efeff2af76be0d9a176a957da21e3710b2e79cf" - integrity sha512-ANEPltxzXbyyG7CvqxdY4PmeM5+RyWdAJGufTHnU+LA/i3J6IDV2r8Z4onKwskwKEhwqzz5lMaSYGGXLyHX+mg== +"@next/eslint-plugin-next@13.4.9": + version "13.4.9" + resolved "https://registry.yarnpkg.com/@next/eslint-plugin-next/-/eslint-plugin-next-13.4.9.tgz#0e7e2135232a8ca43e8f9971c5ff46466f1c7671" + integrity sha512-nDtGpa992tNyAkT/KmSMy7QkHfNZmGCBYhHtafU97DubqxzNdvLsqRtliQ4FU04CysRCtvP2hg8rRC1sAKUTUA== dependencies: glob "7.1.7" @@ -2370,12 +2370,12 @@ escape-string-regexp@^4.0.0: resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz#14ba83a5d373e3d311e5afca29cf5bfad965bf34" integrity sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA== -eslint-config-next@13.4.7: - version "13.4.7" - resolved "https://registry.yarnpkg.com/eslint-config-next/-/eslint-config-next-13.4.7.tgz#59c48ecb37175ccc057f621a07af894cc931574f" - integrity sha512-+IRAyD0+J1MZaTi9RQMPUfr6Q+GCZ1wOkK6XM52Vokh7VI4R6YFGOFzdkEFHl4ZyIX4FKa5vcwUP2WscSFNjNQ== +eslint-config-next@13.4.9: + version "13.4.9" + resolved "https://registry.yarnpkg.com/eslint-config-next/-/eslint-config-next-13.4.9.tgz#c418b2955f0347f9008888d5e894fbb800003c8f" + integrity sha512-0fLtKRR268NArpqeXXwnLgMXPvF64YESQvptVg+RMLCaijKm3FICN9Y7Jc1p2o+yrWwE4DufJXDM/Vo53D1L7g== dependencies: - "@next/eslint-plugin-next" "13.4.7" + "@next/eslint-plugin-next" "13.4.9" "@rushstack/eslint-patch" "^1.1.3" "@typescript-eslint/parser" "^5.42.0" eslint-import-resolver-node "^0.3.6" @@ -2383,7 +2383,7 @@ eslint-config-next@13.4.7: eslint-plugin-import "^2.26.0" eslint-plugin-jsx-a11y "^6.5.1" eslint-plugin-react "^7.31.7" - eslint-plugin-react-hooks "^4.5.0" + eslint-plugin-react-hooks "5.0.0-canary-7118f5dd7-20230705" eslint-import-resolver-node@^0.3.6, eslint-import-resolver-node@^0.3.7: version "0.3.7" @@ -2457,10 +2457,10 @@ eslint-plugin-jsx-a11y@^6.5.1: object.fromentries "^2.0.6" semver "^6.3.0" -eslint-plugin-react-hooks@^4.5.0: - version "4.6.0" - resolved "https://registry.yarnpkg.com/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-4.6.0.tgz#4c3e697ad95b77e93f8646aaa1630c1ba607edd3" - integrity sha512-oFc7Itz9Qxh2x4gNHStv3BqJq54ExXmfC+a1NjAta66IAN87Wu0R/QArgIS9qKzX3dXKPI9H5crl9QchNMY9+g== +eslint-plugin-react-hooks@5.0.0-canary-7118f5dd7-20230705: + version "5.0.0-canary-7118f5dd7-20230705" + resolved "https://registry.yarnpkg.com/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-5.0.0-canary-7118f5dd7-20230705.tgz#4d55c50e186f1a2b0636433d2b0b2f592ddbccfd" + integrity sha512-AZYbMo/NW9chdL7vk6HQzQhT+PvTAEVqWk9ziruUoW2kAOcN5qNyelv70e0F1VNQAbvutOC9oc+xfWycI9FxDw== eslint-plugin-react@^7.31.7: version "7.32.2" From ad4b5d53e78e820a8be4703bc7b141a8aa1e8ec5 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 10 Jul 2023 04:57:39 +0000 Subject: [PATCH 5/6] Bump @fortawesome/free-regular-svg-icons from 6.3.0 to 6.4.0 Bumps [@fortawesome/free-regular-svg-icons](https://github.com/FortAwesome/Font-Awesome) from 6.3.0 to 6.4.0. - [Release notes](https://github.com/FortAwesome/Font-Awesome/releases) - [Changelog](https://github.com/FortAwesome/Font-Awesome/blob/6.x/CHANGELOG.md) - [Commits](https://github.com/FortAwesome/Font-Awesome/compare/6.3.0...6.4.0) --- updated-dependencies: - dependency-name: "@fortawesome/free-regular-svg-icons" dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- package.json | 2 +- yarn.lock | 15 +++++---------- 2 files changed, 6 insertions(+), 11 deletions(-) diff --git a/package.json b/package.json index 3445a77..1562e15 100644 --- a/package.json +++ b/package.json @@ -16,7 +16,7 @@ "@auth/prisma-adapter": "^1.0.0", "@aws-sdk/client-s3": "^3.363.0", "@fortawesome/fontawesome-svg-core": "^6.4.0", - "@fortawesome/free-regular-svg-icons": "^6.3.0", + "@fortawesome/free-regular-svg-icons": "^6.4.0", "@fortawesome/free-solid-svg-icons": "^6.4.0", "@fortawesome/react-fontawesome": "^0.2.0", "@headlessui/react": "^1.7.15", diff --git a/yarn.lock b/yarn.lock index 6163f0d..5abb6ae 100644 --- a/yarn.lock +++ b/yarn.lock @@ -818,11 +818,6 @@ dependencies: "@floating-ui/core" "^1.2.1" -"@fortawesome/fontawesome-common-types@6.3.0": - version "6.3.0" - resolved "https://registry.yarnpkg.com/@fortawesome/fontawesome-common-types/-/fontawesome-common-types-6.3.0.tgz#51f734e64511dbc3674cd347044d02f4dd26e86b" - integrity sha512-4BC1NMoacEBzSXRwKjZ/X/gmnbp/HU5Qqat7E8xqorUtBFZS+bwfGH5/wqOC2K6GV0rgEobp3OjGRMa5fK9pFg== - "@fortawesome/fontawesome-common-types@6.4.0": version "6.4.0" resolved "https://registry.yarnpkg.com/@fortawesome/fontawesome-common-types/-/fontawesome-common-types-6.4.0.tgz#88da2b70d6ca18aaa6ed3687832e11f39e80624b" @@ -835,12 +830,12 @@ dependencies: "@fortawesome/fontawesome-common-types" "6.4.0" -"@fortawesome/free-regular-svg-icons@^6.3.0": - version "6.3.0" - resolved "https://registry.yarnpkg.com/@fortawesome/free-regular-svg-icons/-/free-regular-svg-icons-6.3.0.tgz#286f87f777e6c96af59151e86647c81083029ee2" - integrity sha512-cZnwiVHZ51SVzWHOaNCIA+u9wevZjCuAGSvSYpNlm6A4H4Vhwh8481Bf/5rwheIC3fFKlgXxLKaw8Xeroz8Ntg== +"@fortawesome/free-regular-svg-icons@^6.4.0": + version "6.4.0" + resolved "https://registry.yarnpkg.com/@fortawesome/free-regular-svg-icons/-/free-regular-svg-icons-6.4.0.tgz#cacc53bd8d832d46feead412d9ea9ce80a55e13a" + integrity sha512-ZfycI7D0KWPZtf7wtMFnQxs8qjBXArRzczABuMQqecA/nXohquJ5J/RCR77PmY5qGWkxAZDxpnUFVXKwtY/jPw== dependencies: - "@fortawesome/fontawesome-common-types" "6.3.0" + "@fortawesome/fontawesome-common-types" "6.4.0" "@fortawesome/free-solid-svg-icons@^6.4.0": version "6.4.0" From 0050e14e7f8009f6892afbfebedb4556bd3122d0 Mon Sep 17 00:00:00 2001 From: Daniel Date: Wed, 12 Jul 2023 13:26:34 -0500 Subject: [PATCH 6/6] fully implemented email authentication --- .env.sample | 12 +- components/Modal/EmailConfirmaion.tsx | 24 ++++ components/Modal/User/ChangePassword.tsx | 27 ++--- components/Modal/User/PrivacySettings.tsx | 4 +- components/Modal/User/ProfileSettings.tsx | 24 +++- layouts/AuthRedirect.tsx | 6 +- lib/api/controllers/users/updateUser.ts | 40 ++----- lib/api/sendVerificationRequest.ts | 77 ++++++++++++ pages/api/auth/[...nextauth].ts | 113 ++++++++++-------- pages/api/auth/register.ts | 39 +++++- pages/forgot.tsx | 86 +++++++++++++ pages/login.tsx | 21 +++- pages/register.tsx | 91 ++++++++++---- .../migration.sql | 32 ----- .../migration.sql | 61 +++++++++- prisma/schema.prisma | 52 ++++---- types/enviornment.d.ts | 6 +- types/global.ts | 1 - 18 files changed, 520 insertions(+), 196 deletions(-) create mode 100644 components/Modal/EmailConfirmaion.tsx create mode 100644 lib/api/sendVerificationRequest.ts create mode 100644 pages/forgot.tsx delete mode 100644 prisma/migrations/20230704065656_renamed_email_to_username/migration.sql rename prisma/migrations/{20230625202057_init => 20230711222012_init}/migration.sql (68%) diff --git a/.env.sample b/.env.sample index c43e252..e6faff3 100644 --- a/.env.sample +++ b/.env.sample @@ -2,11 +2,17 @@ NEXTAUTH_SECRET=very_sensitive_secret DATABASE_URL=postgresql://user:password@localhost:5432/linkwarden NEXTAUTH_URL=http://localhost:3000 PAGINATION_TAKE_COUNT=20 + +# Don't define this if you're defining the "AWS S3 Settings" below STORAGE_FOLDER=data -# Linkwarden Cloud specific configs (Ignore - Not applicable for self-hosted version) -IS_CLOUD_INSTANCE= +# AWS S3 Settings (Optional) SPACES_KEY= SPACES_SECRET= SPACES_ENDPOINT= -SPACES_REGION= \ No newline at end of file +SPACES_REGION= + +# SMTP Settings (Optional) +NEXT_PUBLIC_EMAIL_PROVIDER= +EMAIL_FROM= +EMAIL_SERVER= \ No newline at end of file diff --git a/components/Modal/EmailConfirmaion.tsx b/components/Modal/EmailConfirmaion.tsx new file mode 100644 index 0000000..966287b --- /dev/null +++ b/components/Modal/EmailConfirmaion.tsx @@ -0,0 +1,24 @@ +import { signIn } from "next-auth/react"; +import React from "react"; + +export default function EmailConfirmaion({ email }: { email: string }) { + return ( +
+
+

Please check your email

+

A sign in link has been sent to your email address.

+
+ signIn("email", { + email, + redirect: false, + }) + } + className="mx-auto font-semibold mt-2 cursor-pointer w-fit" + > + Resend? +
+
+
+ ); +} diff --git a/components/Modal/User/ChangePassword.tsx b/components/Modal/User/ChangePassword.tsx index cab0518..0cb2168 100644 --- a/components/Modal/User/ChangePassword.tsx +++ b/components/Modal/User/ChangePassword.tsx @@ -17,7 +17,6 @@ export default function ChangePassword({ setUser, user, }: Props) { - const [oldPassword, setOldPassword] = useState(""); const [newPassword, setNewPassword1] = useState(""); const [newPassword2, setNewPassword2] = useState(""); @@ -28,15 +27,15 @@ export default function ChangePassword({ useEffect(() => { if ( - !(oldPassword == "" || newPassword == "" || newPassword2 == "") && + !(newPassword == "" || newPassword2 == "") && newPassword === newPassword2 ) { - setUser({ ...user, oldPassword, newPassword }); + setUser({ ...user, newPassword }); } - }, [oldPassword, newPassword, newPassword2]); + }, [newPassword, newPassword2]); const submit = async () => { - if (oldPassword == "" || newPassword == "" || newPassword2 == "") { + if (newPassword == "" || newPassword2 == "") { toast.error("Please fill all the fields."); } else if (newPassword === newPassword2) { setSubmitLoader(true); @@ -56,11 +55,15 @@ export default function ChangePassword({ setSubmitLoader(false); - if (user.username !== account.username || user.name !== account.name) + if ( + (user.username !== account.username || user.name !== account.name) && + user.username && + user.email + ) update({ username: user.username, name: user.name }); if (response.ok) { - setUser({ ...user, oldPassword: undefined, newPassword: undefined }); + setUser({ ...user, newPassword: undefined }); togglePasswordFormModal(); } } else { @@ -71,20 +74,13 @@ export default function ChangePassword({ return (
-

Old Password

- - setOldPassword(e.target.value)} - type="password" - className="w-full rounded-md p-3 mx-auto border-sky-100 border-solid border outline-none focus:border-sky-500 duration-100" - />

New Password

setNewPassword1(e.target.value)} type="password" + placeholder="*****************" className="w-full rounded-md p-3 mx-auto border-sky-100 border-solid border outline-none focus:border-sky-500 duration-100" />

Re-enter New Password

@@ -93,6 +89,7 @@ export default function ChangePassword({ value={newPassword2} onChange={(e) => setNewPassword2(e.target.value)} type="password" + placeholder="*****************" className="w-full rounded-md p-3 mx-auto border-sky-100 border-solid border outline-none focus:border-sky-500 duration-100" /> diff --git a/components/Modal/User/PrivacySettings.tsx b/components/Modal/User/PrivacySettings.tsx index 9ce6197..8957d5f 100644 --- a/components/Modal/User/PrivacySettings.tsx +++ b/components/Modal/User/PrivacySettings.tsx @@ -35,7 +35,7 @@ export default function PrivacySettings({ }, [whitelistedUsersTextbox]); useEffect(() => { - setUser({ ...user, oldPassword: undefined, newPassword: undefined }); + setUser({ ...user, newPassword: undefined }); }, []); const stringToArray = (str: string) => { @@ -68,7 +68,7 @@ export default function PrivacySettings({ update({ username: user.username, name: user.name }); if (response.ok) { - setUser({ ...user, oldPassword: undefined, newPassword: undefined }); + setUser({ ...user, newPassword: undefined }); toggleSettingsModal(); } }; diff --git a/components/Modal/User/ProfileSettings.tsx b/components/Modal/User/ProfileSettings.tsx index 376668e..19d364f 100644 --- a/components/Modal/User/ProfileSettings.tsx +++ b/components/Modal/User/ProfileSettings.tsx @@ -16,6 +16,8 @@ type Props = { user: AccountSettings; }; +const EmailProvider = process.env.NEXT_PUBLIC_EMAIL_PROVIDER; + export default function ProfileSettings({ toggleSettingsModal, setUser, @@ -59,7 +61,7 @@ export default function ProfileSettings({ }; useEffect(() => { - setUser({ ...user, oldPassword: undefined, newPassword: undefined }); + setUser({ ...user, newPassword: undefined }); }, []); const submit = async () => { @@ -80,7 +82,11 @@ export default function ProfileSettings({ setSubmitLoader(false); - if (user.username !== account.username || user.name !== account.name) + if ( + user.username !== account.username || + user.name !== account.name || + user.email !== account.email + ) update({ username: user.username, email: user.username, @@ -88,7 +94,7 @@ export default function ProfileSettings({ }); if (response.ok) { - setUser({ ...user, oldPassword: undefined, newPassword: undefined }); + setUser({ ...user, newPassword: undefined }); toggleSettingsModal(); } }; @@ -158,6 +164,18 @@ export default function ProfileSettings({ className="w-full rounded-md p-2 border-sky-100 border-solid border outline-none focus:border-sky-500 duration-100" />
+ + {EmailProvider ? ( +
+

Email

+ setUser({ ...user, email: e.target.value })} + className="w-full rounded-md p-2 border-sky-100 border-solid border outline-none focus:border-sky-500 duration-100" + /> +
+ ) : undefined}
diff --git a/layouts/AuthRedirect.tsx b/layouts/AuthRedirect.tsx index 67e234d..916dc03 100644 --- a/layouts/AuthRedirect.tsx +++ b/layouts/AuthRedirect.tsx @@ -39,7 +39,7 @@ export default function AuthRedirect({ children }: Props) { } }, [status]); - return <>{children}; - // if (status !== "loading" && !redirect) return <>{children}; - // else return <>; + if (status !== "loading" && !redirect) return <>{children}; + else return <>; + // return <>{children}; } diff --git a/lib/api/controllers/users/updateUser.ts b/lib/api/controllers/users/updateUser.ts index d3098a8..62fcf3e 100644 --- a/lib/api/controllers/users/updateUser.ts +++ b/lib/api/controllers/users/updateUser.ts @@ -8,33 +8,11 @@ export default async function updateUser( user: AccountSettings, userId: number ) { - // Password Settings - if (user.newPassword && user.oldPassword) { - const targetUser = await prisma.user.findUnique({ - where: { - id: user.id, - }, - }); - - if ( - targetUser && - bcrypt.compareSync(user.oldPassword, targetUser.password) - ) { - const saltRounds = 10; - const newHashedPassword = bcrypt.hashSync(user.newPassword, saltRounds); - - await prisma.user.update({ - where: { - id: userId, - }, - data: { - password: newHashedPassword, - }, - }); - } else { - return { response: "Old password is incorrect.", status: 400 }; - } - } + if (!user.username || !user.email) + return { + response: "Username/Email invalid.", + status: 400, + }; // Avatar Settings @@ -66,6 +44,9 @@ export default async function updateUser( // Other settings + const saltRounds = 10; + const newHashedPassword = bcrypt.hashSync(user.newPassword || "", saltRounds); + const updatedUser = await prisma.user.update({ where: { id: userId, @@ -73,8 +54,13 @@ export default async function updateUser( data: { name: user.name, username: user.username.toLowerCase(), + email: user.email?.toLowerCase(), isPrivate: user.isPrivate, whitelistedUsers: user.whitelistedUsers, + password: + user.newPassword && user.newPassword !== "" + ? newHashedPassword + : undefined, }, }); diff --git a/lib/api/sendVerificationRequest.ts b/lib/api/sendVerificationRequest.ts new file mode 100644 index 0000000..4cb85a2 --- /dev/null +++ b/lib/api/sendVerificationRequest.ts @@ -0,0 +1,77 @@ +import { Theme } from "next-auth"; +import { SendVerificationRequestParams } from "next-auth/providers"; +import { createTransport } from "nodemailer"; + +export default async function sendVerificationRequest( + params: SendVerificationRequestParams +) { + console.log(params); + + const { identifier, url, provider, theme } = params; + const { host } = new URL(url); + const transport = createTransport(provider.server); + const result = await transport.sendMail({ + to: identifier, + from: provider.from, + subject: `Sign in to ${host}`, + text: text({ url, host }), + html: html({ url, host, theme }), + }); + const failed = result.rejected.concat(result.pending).filter(Boolean); + if (failed.length) { + throw new Error(`Email (${failed.join(", ")}) could not be sent`); + } +} + +function html(params: { url: string; host: string; theme: Theme }) { + const { url, host, theme } = params; + + const escapedHost = host.replace(/\./g, "​."); + + const brandColor = theme.brandColor || "#346df1"; + const color = { + background: "#f9f9f9", + text: "#444", + mainBackground: "#fff", + buttonBackground: brandColor, + buttonBorder: brandColor, + buttonText: theme.buttonText || "#fff", + }; + + return ` + + + + + + + + + + + +
+ Sign in to ${escapedHost} +
+ + + + +
Sign + in
+
+ If you did not request this email you can safely ignore it. +
+ +`; +} + +/** Email Text body (fallback for email clients that don't render HTML, e.g. feature phones) */ +function text({ url, host }: { url: string; host: string }) { + return `Sign in to ${host}\n${url}\n\n`; +} diff --git a/pages/api/auth/[...nextauth].ts b/pages/api/auth/[...nextauth].ts index fdd7d95..3f2d8ef 100644 --- a/pages/api/auth/[...nextauth].ts +++ b/pages/api/auth/[...nextauth].ts @@ -5,64 +5,80 @@ import { AuthOptions, Session } from "next-auth"; import bcrypt from "bcrypt"; import EmailProvider from "next-auth/providers/email"; import { JWT } from "next-auth/jwt"; +import { PrismaAdapter } from "@auth/prisma-adapter"; +import { Adapter } from "next-auth/adapters"; +import sendVerificationRequest from "@/lib/api/sendVerificationRequest"; +import { Provider } from "next-auth/providers"; + +const providers: Provider[] = [ + CredentialsProvider({ + type: "credentials", + credentials: { + username: { + label: "Username", + type: "text", + }, + password: { + label: "Password", + type: "password", + }, + }, + async authorize(credentials, req) { + if (!credentials) return null; + + const findUser = await prisma.user.findFirst({ + where: { + OR: [ + { + username: credentials.username.toLowerCase(), + }, + { + email: credentials.username.toLowerCase(), + }, + ], + emailVerified: { not: null }, + }, + }); + + let passwordMatches: boolean = false; + + if (findUser?.password) { + passwordMatches = bcrypt.compareSync( + credentials.password, + findUser.password + ); + } + + if (passwordMatches) { + return findUser; + } else return null as any; + }, + }), +]; + +if (process.env.EMAIL_SERVER && process.env.EMAIL_FROM) + providers.push( + EmailProvider({ + server: process.env.EMAIL_SERVER, + from: process.env.EMAIL_FROM, + maxAge: 600, + sendVerificationRequest(params) { + sendVerificationRequest(params); + }, + }) + ); export const authOptions: AuthOptions = { + adapter: PrismaAdapter(prisma) as Adapter, session: { strategy: "jwt", }, - providers: [ - // EmailProvider({ - // server: process.env.EMAIL_SERVER, - // from: process.env.EMAIL_FROM, - // }), - CredentialsProvider({ - type: "credentials", - credentials: { - username: { - label: "Username", - type: "text", - }, - password: { - label: "Password", - type: "password", - }, - }, - async authorize(credentials, req) { - if (!credentials) return null; - - // const { username, password } = credentials as { - // id: number; - // username: string; - // password: string; - // }; - - const findUser = await prisma.user.findFirst({ - where: { - username: credentials.username.toLowerCase(), - }, - }); - - let passwordMatches: boolean = false; - - if (findUser?.password) { - passwordMatches = bcrypt.compareSync( - credentials.password, - findUser.password - ); - } - - if (passwordMatches) { - return findUser; - } else return null as any; - }, - }), - ], + providers, pages: { signIn: "/login", }, callbacks: { session: async ({ session, token }: { session: Session; token: JWT }) => { - console.log(token); session.user.id = parseInt(token.id as string); session.user.username = token.username as string; @@ -70,7 +86,6 @@ export const authOptions: AuthOptions = { }, // Using the `...rest` parameter to be able to narrow down the type based on `trigger` jwt({ token, trigger, session, user }) { - console.log(user); if (trigger === "signIn") { token.id = user.id; token.username = (user as any).username; diff --git a/pages/api/auth/register.ts b/pages/api/auth/register.ts index 488dd42..e9632f8 100644 --- a/pages/api/auth/register.ts +++ b/pages/api/auth/register.ts @@ -2,6 +2,9 @@ import { prisma } from "@/lib/api/db"; import type { NextApiRequest, NextApiResponse } from "next"; import bcrypt from "bcrypt"; +const EmailProvider = + process.env.EMAIL_FROM && process.env.EMAIL_SERVER ? true : false; + interface Data { response: string | object; } @@ -9,6 +12,7 @@ interface Data { interface User { name: string; username: string; + email?: string; password: string; } @@ -18,17 +22,43 @@ export default async function Index( ) { const body: User = req.body; - if (!body.username || !body.password || !body.name) + const checkHasEmptyFields = EmailProvider + ? !body.username || !body.password || !body.name || !body.email + : !body.username || !body.password || !body.name; + + if (checkHasEmptyFields) return res .status(400) .json({ response: "Please fill out all the fields." }); - const checkIfUserExists = await prisma.user.findFirst({ + const tenMinutesAgo = new Date(Date.now() - 10 * 60 * 1000); + + // Remove user's who aren't verified for more than 10 minutes + await prisma.user.deleteMany({ where: { - username: body.username.toLowerCase(), + createdAt: { + lt: tenMinutesAgo, + }, + emailVerified: null, }, }); + const checkIfUserExists = await prisma.user.findFirst({ + where: EmailProvider + ? { + OR: [ + { username: body.username.toLowerCase() }, + { + email: body.email?.toLowerCase(), + }, + ], + emailVerified: { not: null }, + } + : { + username: body.username.toLowerCase(), + }, + }); + if (!checkIfUserExists) { const saltRounds = 10; @@ -38,12 +68,13 @@ export default async function Index( data: { name: body.name, username: body.username.toLowerCase(), + email: body.email?.toLowerCase(), password: hashedPassword, }, }); res.status(201).json({ response: "User successfully created." }); } else if (checkIfUserExists) { - res.status(400).json({ response: "User already exists." }); + res.status(400).json({ response: "Username and/or Email already exists." }); } } diff --git a/pages/forgot.tsx b/pages/forgot.tsx new file mode 100644 index 0000000..2ae2029 --- /dev/null +++ b/pages/forgot.tsx @@ -0,0 +1,86 @@ +import EmailConfirmaion from "@/components/Modal/EmailConfirmaion"; +import SubmitButton from "@/components/SubmitButton"; +import { signIn } from "next-auth/react"; +import Link from "next/link"; +import { useState } from "react"; +import { toast } from "react-hot-toast"; + +interface FormData { + email: string; +} + +export default function Forgot() { + const [submitLoader, setSubmitLoader] = useState(false); + const [showConfirmation, setShowConfirmation] = useState(false); + + const [form, setForm] = useState({ + email: "", + }); + + async function loginUser() { + if (form.email !== "") { + setSubmitLoader(true); + + const load = toast.loading("Sending login link..."); + + const res = await signIn("email", { + email: form.email, + callbackUrl: window.location.origin, + }); + + setShowConfirmation(true); + + toast.dismiss(load); + + setSubmitLoader(false); + + if (!res?.ok) { + toast.error("Invalid login."); + } + } else { + toast.error("Please fill out all the fields."); + } + } + + return ( + <> + {showConfirmation && form.email ? ( + + ) : undefined} +

+ Linkwarden +

+
+
+

Password reset

+
+ +

Email

+ + setForm({ ...form, email: e.target.value })} + className="w-full rounded-md p-3 mx-auto border-sky-100 border-solid border outline-none focus:border-sky-500 duration-100" + /> + +

+ Make sure to change your password in the profile settings afterwards. +

+ + +
+
+ + Go back + +
+ + ); +} diff --git a/pages/login.tsx b/pages/login.tsx index a4a88f9..89a9b85 100644 --- a/pages/login.tsx +++ b/pages/login.tsx @@ -9,6 +9,8 @@ interface FormData { password: string; } +const EmailProvider = process.env.NEXT_PUBLIC_EMAIL_PROVIDER; + export default function Login() { const [submitLoader, setSubmitLoader] = useState(false); @@ -43,7 +45,7 @@ export default function Login() { return ( <> -

+

Linkwarden

@@ -54,11 +56,14 @@ export default function Login() {

-

Username

+

+ Username + {EmailProvider ? " or Email" : undefined} +

setForm({ ...form, username: e.target.value })} className="w-full rounded-md p-3 mx-auto border-sky-100 border-solid border outline-none focus:border-sky-500 duration-100" @@ -80,12 +85,20 @@ export default function Login() { loading={submitLoader} /> -
+

New here?

Sign Up
+ {EmailProvider && ( +
+

Forgot your password?

+ + Send login link + +
+ )} ); } diff --git a/pages/register.tsx b/pages/register.tsx index 40dee63..8e22b76 100644 --- a/pages/register.tsx +++ b/pages/register.tsx @@ -1,35 +1,60 @@ import Link from "next/link"; import { useState } from "react"; -import { useRouter } from "next/router"; import { toast } from "react-hot-toast"; import SubmitButton from "@/components/SubmitButton"; +import { signIn } from "next-auth/react"; +import EmailConfirmaion from "@/components/Modal/EmailConfirmaion"; -interface FormData { +const EmailProvider = process.env.NEXT_PUBLIC_EMAIL_PROVIDER; + +type FormData = { name: string; username: string; + email?: string; password: string; passwordConfirmation: string; -} +}; export default function Register() { - const router = useRouter(); - const [submitLoader, setSubmitLoader] = useState(false); + const [showConfirmation, setShowConfirmation] = useState(false); const [form, setForm] = useState({ name: "", username: "", + email: EmailProvider ? "" : undefined, password: "", passwordConfirmation: "", }); async function registerUser() { - if ( - form.name !== "" && - form.username !== "" && - form.password !== "" && - form.passwordConfirmation !== "" - ) { + const checkHasEmptyFields = () => { + if (EmailProvider) { + return ( + form.name !== "" && + form.username !== "" && + form.email !== "" && + form.password !== "" && + form.passwordConfirmation !== "" + ); + } else { + return ( + form.name !== "" && + form.username !== "" && + form.password !== "" && + form.passwordConfirmation !== "" + ); + } + }; + + const sendConfirmation = async () => { + await signIn("email", { + email: form.email, + redirect: false, + }); + }; + + if (checkHasEmptyFields()) { if (form.password === form.passwordConfirmation) { const { passwordConfirmation, ...request } = form; @@ -48,20 +73,19 @@ export default function Register() { const data = await response.json(); toast.dismiss(load); - setSubmitLoader(false); if (response.ok) { - setForm({ - name: "", - username: "", - password: "", - passwordConfirmation: "", - }); + if (form.email) { + await sendConfirmation(); + setShowConfirmation(true); + } - toast.success("User Created!"); - - router.push("/login"); + toast.success( + EmailProvider + ? "User Created! Please check you email." + : "User Created!" + ); } else { toast.error(data.response); } @@ -75,7 +99,10 @@ export default function Register() { return ( <> -

+ {showConfirmation && form.email ? ( + + ) : undefined} +

Linkwarden

@@ -100,12 +127,26 @@ export default function Register() { setForm({ ...form, username: e.target.value })} className="w-full rounded-md p-3 mx-auto border-sky-100 border-solid border outline-none focus:border-sky-500 duration-100" /> + {EmailProvider ? ( + <> +

Email

+ + setForm({ ...form, email: e.target.value })} + className="w-full rounded-md p-3 mx-auto border-sky-100 border-solid border outline-none focus:border-sky-500 duration-100" + /> + + ) : undefined} +

Password

-
-

Have an account?

+
+

Already have an account?

Login diff --git a/prisma/migrations/20230704065656_renamed_email_to_username/migration.sql b/prisma/migrations/20230704065656_renamed_email_to_username/migration.sql deleted file mode 100644 index 8261148..0000000 --- a/prisma/migrations/20230704065656_renamed_email_to_username/migration.sql +++ /dev/null @@ -1,32 +0,0 @@ -/* - Warnings: - - - You are about to drop the column `email` on the `User` table. All the data in the column will be lost. - - A unique constraint covering the columns `[username]` on the table `User` will be added. If there are existing duplicate values, this will fail. - - Added the required column `username` to the `User` table without a default value. This is not possible if the table is not empty. - -*/ --- DropIndex -DROP INDEX "User_email_key"; - -ALTER TABLE "User" RENAME COLUMN "email" TO "username"; - --- AlterTable -ALTER TABLE "User" ADD COLUMN "emailVerified" TIMESTAMP(3), -ADD COLUMN "image" TEXT; - --- CreateTable -CREATE TABLE "VerificationToken" ( - "identifier" TEXT NOT NULL, - "token" TEXT NOT NULL, - "expires" TIMESTAMP(3) NOT NULL -); - --- CreateIndex -CREATE UNIQUE INDEX "VerificationToken_token_key" ON "VerificationToken"("token"); - --- CreateIndex -CREATE UNIQUE INDEX "VerificationToken_identifier_token_key" ON "VerificationToken"("identifier", "token"); - --- CreateIndex -CREATE UNIQUE INDEX "User_username_key" ON "User"("username"); diff --git a/prisma/migrations/20230625202057_init/migration.sql b/prisma/migrations/20230711222012_init/migration.sql similarity index 68% rename from prisma/migrations/20230625202057_init/migration.sql rename to prisma/migrations/20230711222012_init/migration.sql index 989f508..408f99f 100644 --- a/prisma/migrations/20230625202057_init/migration.sql +++ b/prisma/migrations/20230711222012_init/migration.sql @@ -1,8 +1,39 @@ +-- CreateTable +CREATE TABLE "Account" ( + "id" TEXT NOT NULL, + "userId" INTEGER NOT NULL, + "type" TEXT NOT NULL, + "provider" TEXT NOT NULL, + "providerAccountId" TEXT NOT NULL, + "refresh_token" TEXT, + "access_token" TEXT, + "expires_at" INTEGER, + "token_type" TEXT, + "scope" TEXT, + "id_token" TEXT, + "session_state" TEXT, + + CONSTRAINT "Account_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "Session" ( + "id" TEXT NOT NULL, + "sessionToken" TEXT NOT NULL, + "userId" INTEGER NOT NULL, + "expires" TIMESTAMP(3) NOT NULL, + + CONSTRAINT "Session_pkey" PRIMARY KEY ("id") +); + -- CreateTable CREATE TABLE "User" ( "id" SERIAL NOT NULL, "name" TEXT NOT NULL, - "email" TEXT NOT NULL, + "username" TEXT NOT NULL, + "email" TEXT, + "emailVerified" TIMESTAMP(3), + "image" TEXT, "password" TEXT NOT NULL, "isPrivate" BOOLEAN NOT NULL DEFAULT false, "whitelistedUsers" TEXT[] DEFAULT ARRAY[]::TEXT[], @@ -11,6 +42,13 @@ CREATE TABLE "User" ( CONSTRAINT "User_pkey" PRIMARY KEY ("id") ); +-- CreateTable +CREATE TABLE "VerificationToken" ( + "identifier" TEXT NOT NULL, + "token" TEXT NOT NULL, + "expires" TIMESTAMP(3) NOT NULL +); + -- CreateTable CREATE TABLE "Collection" ( "id" SERIAL NOT NULL, @@ -68,9 +106,24 @@ CREATE TABLE "_LinkToTag" ( "B" INTEGER NOT NULL ); +-- CreateIndex +CREATE UNIQUE INDEX "Account_provider_providerAccountId_key" ON "Account"("provider", "providerAccountId"); + +-- CreateIndex +CREATE UNIQUE INDEX "Session_sessionToken_key" ON "Session"("sessionToken"); + +-- CreateIndex +CREATE UNIQUE INDEX "User_username_key" ON "User"("username"); + -- CreateIndex CREATE UNIQUE INDEX "User_email_key" ON "User"("email"); +-- CreateIndex +CREATE UNIQUE INDEX "VerificationToken_token_key" ON "VerificationToken"("token"); + +-- CreateIndex +CREATE UNIQUE INDEX "VerificationToken_identifier_token_key" ON "VerificationToken"("identifier", "token"); + -- CreateIndex CREATE UNIQUE INDEX "Collection_name_ownerId_key" ON "Collection"("name", "ownerId"); @@ -89,6 +142,12 @@ CREATE UNIQUE INDEX "_LinkToTag_AB_unique" ON "_LinkToTag"("A", "B"); -- CreateIndex CREATE INDEX "_LinkToTag_B_index" ON "_LinkToTag"("B"); +-- AddForeignKey +ALTER TABLE "Account" ADD CONSTRAINT "Account_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "Session" ADD CONSTRAINT "Session_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE; + -- AddForeignKey ALTER TABLE "Collection" ADD CONSTRAINT "Collection_ownerId_fkey" FOREIGN KEY ("ownerId") REFERENCES "User"("id") ON DELETE RESTRICT ON UPDATE CASCADE; diff --git a/prisma/schema.prisma b/prisma/schema.prisma index a365dcd..13aec07 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -7,32 +7,32 @@ datasource db { url = env("DATABASE_URL") } -// model Account { -// id String @id @default(cuid()) -// userId Int -// type String -// provider String -// providerAccountId String -// refresh_token String? @db.Text -// access_token String? @db.Text -// expires_at Int? -// token_type String? -// scope String? -// id_token String? @db.Text -// session_state String? +model Account { + id String @id @default(cuid()) + userId Int + type String + provider String + providerAccountId String + refresh_token String? @db.Text + access_token String? @db.Text + expires_at Int? + token_type String? + scope String? + id_token String? @db.Text + session_state String? -// user User @relation(fields: [userId], references: [id], onDelete: Cascade) + user User @relation(fields: [userId], references: [id], onDelete: Cascade) -// @@unique([provider, providerAccountId]) -// } + @@unique([provider, providerAccountId]) +} -// model Session { -// id String @id @default(cuid()) -// sessionToken String @unique -// userId Int -// expires DateTime -// user User @relation(fields: [userId], references: [id], onDelete: Cascade) -// } +model Session { + id String @id @default(cuid()) + sessionToken String @unique + userId Int + expires DateTime + user User @relation(fields: [userId], references: [id], onDelete: Cascade) +} model User { id Int @id @default(autoincrement()) @@ -40,12 +40,12 @@ model User { username String @unique - // email String? @unique + email String? @unique emailVerified DateTime? image String? - // accounts Account[] - // sessions Session[] + accounts Account[] + sessions Session[] password String collections Collection[] diff --git a/types/enviornment.d.ts b/types/enviornment.d.ts index 20fe67c..62a004b 100644 --- a/types/enviornment.d.ts +++ b/types/enviornment.d.ts @@ -6,12 +6,16 @@ declare global { NEXTAUTH_URL: string; PAGINATION_TAKE_COUNT: string; STORAGE_FOLDER?: string; - IS_CLOUD_INSTANCE?: true; + SPACES_KEY?: string; SPACES_SECRET?: string; SPACES_ENDPOINT?: string; BUCKET_NAME?: string; SPACES_REGION?: string; + + NEXT_PUBLIC_EMAIL_PROVIDER?: true; + EMAIL_FROM?: string; + EMAIL_SERVER?: string; } } } diff --git a/types/global.ts b/types/global.ts index 350cba2..95b2640 100644 --- a/types/global.ts +++ b/types/global.ts @@ -35,7 +35,6 @@ export interface CollectionIncludingMembersAndLinkCount export interface AccountSettings extends User { profilePic: string; - oldPassword?: string; newPassword?: string; }