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.
+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" />+ Sign in to ${escapedHost} + | +|
+
|
+ |
+ If you did not request this email you can safely ignore it. + | +
+ Linkwarden +
+Password reset
++ Make sure to change your password in the profile settings afterwards. +
+ ++
Linkwarden
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 UpForgot your password?
+ + Send login link + +
+ {showConfirmation && form.email ? (
+
Linkwarden
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; } diff --git a/yarn.lock b/yarn.lock index 6163f0d..007824e 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" @@ -944,17 +939,17 @@ 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" -"@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" @@ -1521,10 +1516,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" "*" @@ -1535,10 +1530,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" "*" @@ -2370,12 +2365,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 +2378,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 +2452,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"