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; }