diff --git a/.env b/.env.example similarity index 73% rename from .env rename to .env.example index 84889c02..382ce5d6 100644 --- a/.env +++ b/.env.example @@ -4,4 +4,6 @@ JWT_SECRET=secret NEXTAUTH_URL=http://localhost:3000 NEXTAUTH_SECRET=secret GITHUB_CLIENT_ID=secret -GITHUB_CLIENT_SECRET=secret \ No newline at end of file +GITHUB_CLIENT_SECRET=secret +AUTH_ADMIN_EMAIL=secret +AUTH_ADMIN_PASSWORD=secret \ No newline at end of file diff --git a/README.md b/README.md index 0c2ac7de..6798f2bb 100644 --- a/README.md +++ b/README.md @@ -75,27 +75,33 @@ To get started with this boilerplate, follow these steps: git clone https://github.com/rharkor/next-boilerplate ``` -2. Initialize the project: +2. Create a `.env.local` file and add the following environment variables: + +```bash +cp .env.example .env.local +``` + +3. Initialize the project: ```bash npm run init ``` -3. Install the dependencies: +4. Install the dependencies: ```bash npm ci ``` -4. Run the development server: +5. Run the development server: ```bash npm dev ``` -5. Open [http://localhost:3000](http://localhost:3000) with your browser to see the result. +6. Open [http://localhost:3000](http://localhost:3000) with your browser to see the result. -6. This project uses a git hook to enforce [conventional commits](https://github.com/qoomon/git-conventional-commits). To install the git hook, run the following command in the root directory of the project: +7. This project uses a git hook to enforce [conventional commits](https://github.com/qoomon/git-conventional-commits). To install the git hook, run the following command in the root directory of the project: ```sh brew install pre-commit diff --git a/env.mjs b/env.mjs index 8c19186f..4b8b21a5 100644 --- a/env.mjs +++ b/env.mjs @@ -14,6 +14,8 @@ export const env = createEnv({ NEXTAUTH_URL: z.string().nonempty(), GITHUB_CLIENT_ID: z.string().nonempty(), GITHUB_CLIENT_SECRET: z.string().nonempty(), + AUTH_ADMIN_EMAIL: z.string().nonempty(), + AUTH_ADMIN_PASSWORD: z.string().nonempty(), }, client: {}, runtimeEnv: { @@ -25,5 +27,11 @@ export const env = createEnv({ NEXTAUTH_URL: process.env.NEXTAUTH_URL, GITHUB_CLIENT_ID: process.env.GITHUB_CLIENT_ID, GITHUB_CLIENT_SECRET: process.env.GITHUB_CLIENT_SECRET, + AUTH_ADMIN_EMAIL: process.env.AUTH_ADMIN_EMAIL, + AUTH_ADMIN_PASSWORD: process.env.AUTH_ADMIN_PASSWORD, + }, + onValidationError: (error) => { + console.error(error) + throw error }, }) diff --git a/package-lock.json b/package-lock.json index d23e2567..83bc09a3 100644 --- a/package-lock.json +++ b/package-lock.json @@ -55,8 +55,10 @@ "react": "^18.2.0", "react-dom": "^18.2.0", "react-hook-form": "^7.45.2", + "request-ip": "^3.3.0", "tailwind-merge": "^1.14.0", "tailwindcss-animate": "^1.0.6", + "ua-parser-js": "^1.0.35", "zod": "^3.21.4" }, "devDependencies": { @@ -77,9 +79,10 @@ "@total-typescript/ts-reset": "^0.4.2", "@types/bcrypt": "^5.0.0", "@types/crypto-js": "^4.1.1", - "@types/node": "^18.0.0", + "@types/node": "^18.17.0", "@types/react": "^18.0.37", "@types/react-dom": "^18.0.11", + "@types/request-ip": "^0.0.38", "@typescript-eslint/eslint-plugin": "^6.1.0", "@typescript-eslint/parser": "^6.1.0", "all-contributors-cli": "^6.24.0", @@ -106,8 +109,9 @@ "storybook": "^7.0.5", "tailwindcss": "^3.2.7", "ts-jest": "^29.1.0", + "ts-node": "^10.9.1", "tsc": "^2.0.4", - "tsx": "^3.12.7", + "typescript": "^5.1.6", "vitest": "^0.33.0" }, "engines": { @@ -2466,8 +2470,7 @@ "version": "0.8.1", "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==", - "optional": true, - "peer": true, + "devOptional": true, "dependencies": { "@jridgewell/trace-mapping": "0.3.9" }, @@ -2479,8 +2482,7 @@ "version": "0.3.9", "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz", "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==", - "optional": true, - "peer": true, + "devOptional": true, "dependencies": { "@jridgewell/resolve-uri": "^3.0.3", "@jridgewell/sourcemap-codec": "^1.4.10" @@ -2519,444 +2521,6 @@ "react": ">=16.8.0" } }, - "node_modules/@esbuild-kit/cjs-loader": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/@esbuild-kit/cjs-loader/-/cjs-loader-2.4.2.tgz", - "integrity": "sha512-BDXFbYOJzT/NBEtp71cvsrGPwGAMGRB/349rwKuoxNSiKjPraNNnlK6MIIabViCjqZugu6j+xeMDlEkWdHHJSg==", - "dev": true, - "dependencies": { - "@esbuild-kit/core-utils": "^3.0.0", - "get-tsconfig": "^4.4.0" - } - }, - "node_modules/@esbuild-kit/core-utils": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/@esbuild-kit/core-utils/-/core-utils-3.1.0.tgz", - "integrity": "sha512-Uuk8RpCg/7fdHSceR1M6XbSZFSuMrxcePFuGgyvsBn+u339dk5OeL4jv2EojwTN2st/unJGsVm4qHWjWNmJ/tw==", - "dev": true, - "dependencies": { - "esbuild": "~0.17.6", - "source-map-support": "^0.5.21" - } - }, - "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/android-arm": { - "version": "0.17.19", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.17.19.tgz", - "integrity": "sha512-rIKddzqhmav7MSmoFCmDIb6e2W57geRsM94gV2l38fzhXMwq7hZoClug9USI2pFRGL06f4IOPHHpFNOkWieR8A==", - "cpu": [ - "arm" - ], - "dev": true, - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/android-arm64": { - "version": "0.17.19", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.17.19.tgz", - "integrity": "sha512-KBMWvEZooR7+kzY0BtbTQn0OAYY7CsiydT63pVEaPtVYF0hXbUaOyZog37DKxK7NF3XacBJOpYT4adIJh+avxA==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/android-x64": { - "version": "0.17.19", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.17.19.tgz", - "integrity": "sha512-uUTTc4xGNDT7YSArp/zbtmbhO0uEEK9/ETW29Wk1thYUJBz3IVnvgEiEwEa9IeLyvnpKrWK64Utw2bgUmDveww==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/darwin-arm64": { - "version": "0.17.19", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.17.19.tgz", - "integrity": "sha512-80wEoCfF/hFKM6WE1FyBHc9SfUblloAWx6FJkFWTWiCoht9Mc0ARGEM47e67W9rI09YoUxJL68WHfDRYEAvOhg==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/darwin-x64": { - "version": "0.17.19", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.17.19.tgz", - "integrity": "sha512-IJM4JJsLhRYr9xdtLytPLSH9k/oxR3boaUIYiHkAawtwNOXKE8KoU8tMvryogdcT8AU+Bflmh81Xn6Q0vTZbQw==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/freebsd-arm64": { - "version": "0.17.19", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.17.19.tgz", - "integrity": "sha512-pBwbc7DufluUeGdjSU5Si+P3SoMF5DQ/F/UmTSb8HXO80ZEAJmrykPyzo1IfNbAoaqw48YRpv8shwd1NoI0jcQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/freebsd-x64": { - "version": "0.17.19", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.17.19.tgz", - "integrity": "sha512-4lu+n8Wk0XlajEhbEffdy2xy53dpR06SlzvhGByyg36qJw6Kpfk7cp45DR/62aPH9mtJRmIyrXAS5UWBrJT6TQ==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/linux-arm": { - "version": "0.17.19", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.17.19.tgz", - "integrity": "sha512-cdmT3KxjlOQ/gZ2cjfrQOtmhG4HJs6hhvm3mWSRDPtZ/lP5oe8FWceS10JaSJC13GBd4eH/haHnqf7hhGNLerA==", - "cpu": [ - "arm" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/linux-arm64": { - "version": "0.17.19", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.17.19.tgz", - "integrity": "sha512-ct1Tg3WGwd3P+oZYqic+YZF4snNl2bsnMKRkb3ozHmnM0dGWuxcPTTntAF6bOP0Sp4x0PjSF+4uHQ1xvxfRKqg==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/linux-ia32": { - "version": "0.17.19", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.17.19.tgz", - "integrity": "sha512-w4IRhSy1VbsNxHRQpeGCHEmibqdTUx61Vc38APcsRbuVgK0OPEnQ0YD39Brymn96mOx48Y2laBQGqgZ0j9w6SQ==", - "cpu": [ - "ia32" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/linux-loong64": { - "version": "0.17.19", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.17.19.tgz", - "integrity": "sha512-2iAngUbBPMq439a+z//gE+9WBldoMp1s5GWsUSgqHLzLJ9WoZLZhpwWuym0u0u/4XmZ3gpHmzV84PonE+9IIdQ==", - "cpu": [ - "loong64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/linux-mips64el": { - "version": "0.17.19", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.17.19.tgz", - "integrity": "sha512-LKJltc4LVdMKHsrFe4MGNPp0hqDFA1Wpt3jE1gEyM3nKUvOiO//9PheZZHfYRfYl6AwdTH4aTcXSqBerX0ml4A==", - "cpu": [ - "mips64el" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/linux-ppc64": { - "version": "0.17.19", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.17.19.tgz", - "integrity": "sha512-/c/DGybs95WXNS8y3Ti/ytqETiW7EU44MEKuCAcpPto3YjQbyK3IQVKfF6nbghD7EcLUGl0NbiL5Rt5DMhn5tg==", - "cpu": [ - "ppc64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/linux-riscv64": { - "version": "0.17.19", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.17.19.tgz", - "integrity": "sha512-FC3nUAWhvFoutlhAkgHf8f5HwFWUL6bYdvLc/TTuxKlvLi3+pPzdZiFKSWz/PF30TB1K19SuCxDTI5KcqASJqA==", - "cpu": [ - "riscv64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/linux-s390x": { - "version": "0.17.19", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.17.19.tgz", - "integrity": "sha512-IbFsFbxMWLuKEbH+7sTkKzL6NJmG2vRyy6K7JJo55w+8xDk7RElYn6xvXtDW8HCfoKBFK69f3pgBJSUSQPr+4Q==", - "cpu": [ - "s390x" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/linux-x64": { - "version": "0.17.19", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.17.19.tgz", - "integrity": "sha512-68ngA9lg2H6zkZcyp22tsVt38mlhWde8l3eJLWkyLrp4HwMUr3c1s/M2t7+kHIhvMjglIBrFpncX1SzMckomGw==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/netbsd-x64": { - "version": "0.17.19", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.17.19.tgz", - "integrity": "sha512-CwFq42rXCR8TYIjIfpXCbRX0rp1jo6cPIUPSaWwzbVI4aOfX96OXY8M6KNmtPcg7QjYeDmN+DD0Wp3LaBOLf4Q==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "netbsd" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/openbsd-x64": { - "version": "0.17.19", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.17.19.tgz", - "integrity": "sha512-cnq5brJYrSZ2CF6c35eCmviIN3k3RczmHz8eYaVlNasVqsNY+JKohZU5MKmaOI+KkllCdzOKKdPs762VCPC20g==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "openbsd" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/sunos-x64": { - "version": "0.17.19", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.17.19.tgz", - "integrity": "sha512-vCRT7yP3zX+bKWFeP/zdS6SqdWB8OIpaRq/mbXQxTGHnIxspRtigpkUcDMlSCOejlHowLqII7K2JKevwyRP2rg==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "sunos" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/win32-arm64": { - "version": "0.17.19", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.17.19.tgz", - "integrity": "sha512-yYx+8jwowUstVdorcMdNlzklLYhPxjniHWFKgRqH7IFlUEa0Umu3KuYplf1HUZZ422e3NU9F4LGb+4O0Kdcaag==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/win32-ia32": { - "version": "0.17.19", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.17.19.tgz", - "integrity": "sha512-eggDKanJszUtCdlVs0RB+h35wNlb5v4TWEkq4vZcmVt5u/HiDZrTXe2bWFQUez3RgNHwx/x4sk5++4NSSicKkw==", - "cpu": [ - "ia32" - ], - "dev": true, - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/win32-x64": { - "version": "0.17.19", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.17.19.tgz", - "integrity": "sha512-lAhycmKnVOuRYNtRtatQR1LPQf2oYCkRGkSFnseDAKPl8lu5SOsK/e1sXe5a0Pc5kHIHe6P2I/ilntNv2xf3cA==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild-kit/core-utils/node_modules/esbuild": { - "version": "0.17.19", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.17.19.tgz", - "integrity": "sha512-XQ0jAPFkK/u3LcVRcvVHQcTIqD6E2H1fvZMA5dQPSOWb3suUbWbfbRf94pjc0bNzRYLfIrDRQXr7X+LHIm5oHw==", - "dev": true, - "hasInstallScript": true, - "bin": { - "esbuild": "bin/esbuild" - }, - "engines": { - "node": ">=12" - }, - "optionalDependencies": { - "@esbuild/android-arm": "0.17.19", - "@esbuild/android-arm64": "0.17.19", - "@esbuild/android-x64": "0.17.19", - "@esbuild/darwin-arm64": "0.17.19", - "@esbuild/darwin-x64": "0.17.19", - "@esbuild/freebsd-arm64": "0.17.19", - "@esbuild/freebsd-x64": "0.17.19", - "@esbuild/linux-arm": "0.17.19", - "@esbuild/linux-arm64": "0.17.19", - "@esbuild/linux-ia32": "0.17.19", - "@esbuild/linux-loong64": "0.17.19", - "@esbuild/linux-mips64el": "0.17.19", - "@esbuild/linux-ppc64": "0.17.19", - "@esbuild/linux-riscv64": "0.17.19", - "@esbuild/linux-s390x": "0.17.19", - "@esbuild/linux-x64": "0.17.19", - "@esbuild/netbsd-x64": "0.17.19", - "@esbuild/openbsd-x64": "0.17.19", - "@esbuild/sunos-x64": "0.17.19", - "@esbuild/win32-arm64": "0.17.19", - "@esbuild/win32-ia32": "0.17.19", - "@esbuild/win32-x64": "0.17.19" - } - }, - "node_modules/@esbuild-kit/core-utils/node_modules/source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/@esbuild-kit/core-utils/node_modules/source-map-support": { - "version": "0.5.21", - "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", - "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", - "dev": true, - "dependencies": { - "buffer-from": "^1.0.0", - "source-map": "^0.6.0" - } - }, - "node_modules/@esbuild-kit/esm-loader": { - "version": "2.5.5", - "resolved": "https://registry.npmjs.org/@esbuild-kit/esm-loader/-/esm-loader-2.5.5.tgz", - "integrity": "sha512-Qwfvj/qoPbClxCRNuac1Du01r9gvNOT+pMYtJDapfB1eoGN1YlJ1BixLyL9WVENRx5RXgNLdfYdx/CuswlGhMw==", - "dev": true, - "dependencies": { - "@esbuild-kit/core-utils": "^3.0.0", - "get-tsconfig": "^4.4.0" - } - }, "node_modules/@esbuild/android-arm": { "version": "0.18.14", "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.18.14.tgz", @@ -12586,29 +12150,25 @@ "version": "1.0.9", "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.9.tgz", "integrity": "sha512-jNsYVVxU8v5g43Erja32laIDHXeoNvFEpX33OK4d6hljo3jDhCBDhx5dhCCTMWUojscpAagGiRkBKxpdl9fxqA==", - "optional": true, - "peer": true + "devOptional": true }, "node_modules/@tsconfig/node12": { "version": "1.0.11", "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.11.tgz", "integrity": "sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==", - "optional": true, - "peer": true + "devOptional": true }, "node_modules/@tsconfig/node14": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.3.tgz", "integrity": "sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==", - "optional": true, - "peer": true + "devOptional": true }, "node_modules/@tsconfig/node16": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.4.tgz", "integrity": "sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==", - "optional": true, - "peer": true + "devOptional": true }, "node_modules/@types/aria-query": { "version": "5.0.1", @@ -12946,9 +12506,9 @@ "integrity": "sha512-jhuKLIRrhvCPLqwPcx6INqmKeiA5EWrsCOPhrlFSrbrmU4ZMPjj5Ul/oLCMDO98XRUIwVm78xICz4EPCektzeQ==" }, "node_modules/@types/node": { - "version": "18.16.19", - "resolved": "https://registry.npmjs.org/@types/node/-/node-18.16.19.tgz", - "integrity": "sha512-IXl7o+R9iti9eBW4Wg2hx1xQDig183jj7YLn8F7udNceyfkbn1ZxmzZXuak20gR40D7pIkIY1kYGx5VIGbaHKA==" + "version": "18.17.0", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.17.0.tgz", + "integrity": "sha512-GXZxEtOxYGFchyUzxvKI14iff9KZ2DI+A6a37o6EQevtg6uO9t+aUZKcaC1Te5Ng1OnLM7K9NVVj+FbecD9cJg==" }, "node_modules/@types/node-fetch": { "version": "2.6.4", @@ -13021,6 +12581,15 @@ "@types/react": "*" } }, + "node_modules/@types/request-ip": { + "version": "0.0.38", + "resolved": "https://registry.npmjs.org/@types/request-ip/-/request-ip-0.0.38.tgz", + "integrity": "sha512-1yeq8UuK/tUBqLXRY24gjeFvrSNaGNcOcZLQjHlnuw8iu+qE/vTQ64TUcLWorr607NKLfFakdoYEXXHXrLLKCw==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@types/scheduler": { "version": "0.16.3", "resolved": "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.16.3.tgz", @@ -16886,8 +16455,7 @@ "version": "1.1.1", "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", - "optional": true, - "peer": true + "devOptional": true }, "node_modules/cross-env": { "version": "7.0.3", @@ -17756,8 +17324,7 @@ "version": "4.0.2", "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", - "optional": true, - "peer": true, + "devOptional": true, "engines": { "node": ">=0.3.1" } @@ -34286,6 +33853,11 @@ "entities": "^2.0.0" } }, + "node_modules/request-ip": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/request-ip/-/request-ip-3.3.0.tgz", + "integrity": "sha512-cA6Xh6e0fDBBBwH77SLJaJPBmD3nWVAcF9/XAcsrIHdjhFzFiB5aNQFytdjCGPezU3ROwrR11IddKAM08vohxA==" + }, "node_modules/require-directory": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", @@ -36851,8 +36423,7 @@ "version": "10.9.1", "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.1.tgz", "integrity": "sha512-NtVysVPkxxrwFGUUxGYhfux8k78pQB3JqYBXlLRZgdGUqTO5wU/UyHop5p70iEbGhB7q5KmiZiU0Y3KlJrScEw==", - "optional": true, - "peer": true, + "devOptional": true, "dependencies": { "@cspotcode/source-map-support": "^0.8.0", "@tsconfig/node10": "^1.0.7", @@ -36895,8 +36466,7 @@ "version": "8.10.0", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.10.0.tgz", "integrity": "sha512-F0SAmZ8iUtS//m8DmCTA0jlh6TDKkHQyK6xc6V4KDTyZKA9dnvX9/3sRTVQrWm79glUAZbnmmNcdYwUIHWVybw==", - "optional": true, - "peer": true, + "devOptional": true, "bin": { "acorn": "bin/acorn" }, @@ -36908,8 +36478,7 @@ "version": "8.2.0", "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.2.0.tgz", "integrity": "sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA==", - "optional": true, - "peer": true, + "devOptional": true, "engines": { "node": ">=0.4.0" } @@ -36918,8 +36487,7 @@ "version": "4.1.3", "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", - "optional": true, - "peer": true + "devOptional": true }, "node_modules/ts-pnp": { "version": "1.2.0", @@ -37059,23 +36627,6 @@ "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", "dev": true }, - "node_modules/tsx": { - "version": "3.12.7", - "resolved": "https://registry.npmjs.org/tsx/-/tsx-3.12.7.tgz", - "integrity": "sha512-C2Ip+jPmqKd1GWVQDvz/Eyc6QJbGfE7NrR3fx5BpEHMZsEHoIxHL1j+lKdGobr8ovEyqeNkPLSKp6SCSOt7gmw==", - "dev": true, - "dependencies": { - "@esbuild-kit/cjs-loader": "^2.4.2", - "@esbuild-kit/core-utils": "^3.0.0", - "@esbuild-kit/esm-loader": "^2.5.5" - }, - "bin": { - "tsx": "dist/cli.js" - }, - "optionalDependencies": { - "fsevents": "~2.3.2" - } - }, "node_modules/tty-browserify": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/tty-browserify/-/tty-browserify-0.0.1.tgz", @@ -37160,7 +36711,6 @@ "version": "5.1.6", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.1.6.tgz", "integrity": "sha512-zaWCozRZ6DLEWAWFrVDz1H6FVXzUSfTy5FUMWsQlU8Ym5JP9eO4xkTIROFCQvhQf61z6O/G6ugw3SgAnvvm+HA==", - "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -37169,6 +36719,24 @@ "node": ">=14.17" } }, + "node_modules/ua-parser-js": { + "version": "1.0.35", + "resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-1.0.35.tgz", + "integrity": "sha512-fKnGuqmTBnIE+/KXSzCn4db8RTigUzw1AN0DmdU6hJovUTbYJKyqj+8Mt1c4VfRDnOVJnENmfYkIPZ946UrSAA==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/ua-parser-js" + }, + { + "type": "paypal", + "url": "https://paypal.me/faisalman" + } + ], + "engines": { + "node": "*" + } + }, "node_modules/ufo": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/ufo/-/ufo-1.1.2.tgz", @@ -37544,8 +37112,7 @@ "version": "3.0.1", "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz", "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==", - "optional": true, - "peer": true + "devOptional": true }, "node_modules/v8-to-istanbul": { "version": "9.1.0", @@ -38932,8 +38499,7 @@ "version": "3.1.1", "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", - "optional": true, - "peer": true, + "devOptional": true, "engines": { "node": ">=6" } diff --git a/package.json b/package.json index b73960e9..269444ff 100644 --- a/package.json +++ b/package.json @@ -71,8 +71,10 @@ "react": "^18.2.0", "react-dom": "^18.2.0", "react-hook-form": "^7.45.2", + "request-ip": "^3.3.0", "tailwind-merge": "^1.14.0", "tailwindcss-animate": "^1.0.6", + "ua-parser-js": "^1.0.35", "zod": "^3.21.4" }, "devDependencies": { @@ -93,9 +95,10 @@ "@total-typescript/ts-reset": "^0.4.2", "@types/bcrypt": "^5.0.0", "@types/crypto-js": "^4.1.1", - "@types/node": "^18.0.0", + "@types/node": "^18.17.0", "@types/react": "^18.0.37", "@types/react-dom": "^18.0.11", + "@types/request-ip": "^0.0.38", "@typescript-eslint/eslint-plugin": "^6.1.0", "@typescript-eslint/parser": "^6.1.0", "all-contributors-cli": "^6.24.0", @@ -122,8 +125,9 @@ "storybook": "^7.0.5", "tailwindcss": "^3.2.7", "ts-jest": "^29.1.0", + "ts-node": "^10.9.1", "tsc": "^2.0.4", - "tsx": "^3.12.7", + "typescript": "^5.1.6", "vitest": "^0.33.0" }, "engines": { @@ -131,6 +135,6 @@ }, "prisma": { "schema": "prisma/schema.prisma", - "seed": "tsx prisma/seed.ts --preview-feature" + "seed": "npx ts-node --compiler-options {\"module\":\"CommonJS\"} prisma/seed.ts" } } diff --git a/prisma/migrations/20230723112529_add_ua_to_session/migration.sql b/prisma/migrations/20230723112529_add_ua_to_session/migration.sql deleted file mode 100644 index ee4ab146..00000000 --- a/prisma/migrations/20230723112529_add_ua_to_session/migration.sql +++ /dev/null @@ -1,8 +0,0 @@ -/* - Warnings: - - - Added the required column `ua` to the `Session` table without a default value. This is not possible if the table is not empty. - -*/ --- AlterTable -ALTER TABLE "Session" ADD COLUMN "ua" TEXT NOT NULL; diff --git a/prisma/migrations/20230723113240_nullable_expire/migration.sql b/prisma/migrations/20230723113240_nullable_expire/migration.sql deleted file mode 100644 index 1560a615..00000000 --- a/prisma/migrations/20230723113240_nullable_expire/migration.sql +++ /dev/null @@ -1,2 +0,0 @@ --- AlterTable -ALTER TABLE "Session" ALTER COLUMN "expires" DROP NOT NULL; diff --git a/prisma/migrations/20230723141349_mandatory_expires/migration.sql b/prisma/migrations/20230723141349_mandatory_expires/migration.sql deleted file mode 100644 index 6c8ccedf..00000000 --- a/prisma/migrations/20230723141349_mandatory_expires/migration.sql +++ /dev/null @@ -1,8 +0,0 @@ -/* - Warnings: - - - Made the column `expires` on table `Session` required. This step will fail if there are existing NULL values in that column. - -*/ --- AlterTable -ALTER TABLE "Session" ALTER COLUMN "expires" SET NOT NULL; diff --git a/prisma/migrations/20230722123612_/migration.sql b/prisma/migrations/20230723163732_init/migration.sql similarity index 97% rename from prisma/migrations/20230722123612_/migration.sql rename to prisma/migrations/20230723163732_init/migration.sql index 491e530c..6f1ce7be 100644 --- a/prisma/migrations/20230722123612_/migration.sql +++ b/prisma/migrations/20230723163732_init/migration.sql @@ -22,6 +22,8 @@ CREATE TABLE "Session" ( "sessionToken" TEXT NOT NULL, "userId" TEXT NOT NULL, "expires" TIMESTAMP(3) NOT NULL, + "ua" TEXT NOT NULL, + "ip" TEXT NOT NULL, CONSTRAINT "Session_pkey" PRIMARY KEY ("id") ); diff --git a/prisma/schema.prisma b/prisma/schema.prisma index 11654fdf..b79c9934 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -31,10 +31,11 @@ model Account { model Session { id String @id @default(cuid()) - sessionToken String @unique + sessionToken String @unique // for credentials, it's a uuid userId String expires DateTime ua String // User Agent + ip String user User @relation(fields: [userId], references: [id], onDelete: Cascade) } diff --git a/prisma/seed.ts b/prisma/seed.ts index 0530fc00..6865389a 100644 --- a/prisma/seed.ts +++ b/prisma/seed.ts @@ -1,23 +1,61 @@ import { PrismaClient } from "@prisma/client" +import { PrismaClientKnownRequestError } from "@prisma/client/runtime/library" +import { hash as bhash } from "bcrypt" +import crypto from "crypto-js" +import * as dotenv from "dotenv" + +dotenv.config({ + path: ".env.local", +}) + +const env = { + AUTH_ADMIN_EMAIL: process.env.AUTH_ADMIN_EMAIL, + AUTH_ADMIN_PASSWORD: process.env.AUTH_ADMIN_PASSWORD, + PASSWORD_HASHER_SECRET: process.env.PASSWORD_HASHER_SECRET, +} + +const hash = async (value: string, saltOrRounds: string | number) => { + const preHashed = crypto.HmacSHA256(value, env.PASSWORD_HASHER_SECRET ?? "").toString() + return await bhash(preHashed, saltOrRounds) +} + +if (!env.AUTH_ADMIN_EMAIL || !env.AUTH_ADMIN_PASSWORD || !env.PASSWORD_HASHER_SECRET) { + console.error("Missing AUTH_ADMIN_EMAIL or AUTH_ADMIN_PASSWORD or PASSWORD_HASHER_SECRET") + process.exit(1) +} const prisma = new PrismaClient() -const load = async () => { +async function main() { try { await prisma.user.create({ data: { - email: "louis@huort.com", - name: "HUORT Louis", - image: "https://avatars.githubusercontent.com/u/70844594?v=4", - emailVerified: null, + email: env.AUTH_ADMIN_EMAIL, + password: await hash(env.AUTH_ADMIN_PASSWORD ?? "", 12), + role: "admin", }, }) - console.log("User created") + console.log("Admin created") } catch (e) { + if (e instanceof PrismaClientKnownRequestError) { + if (e.code === "P2002") { + console.error("Admin already exists") + process.exit(0) + } + } console.error(e) process.exit(1) } finally { await prisma.$disconnect() } } -load() + +main() + .then(async () => { + await prisma.$disconnect() + }) + .catch(async (e) => { + console.error(e) + await prisma.$disconnect() + process.exit(1) + }) diff --git a/src/app/(auth)/sign-in/page.tsx b/src/app/(auth)/sign-in/page.tsx index 37ed011b..20138193 100644 --- a/src/app/(auth)/sign-in/page.tsx +++ b/src/app/(auth)/sign-in/page.tsx @@ -3,6 +3,7 @@ import { getProviders } from "next-auth/react" import GithubSignIn from "@/components/auth/github-sign-in" import { LoginUserAuthForm } from "@/components/auth/login-user-auth-form" import { buttonVariants } from "@/components/ui/button" +import { authRoutes } from "@/lib/auth/constants" import { cn } from "@/lib/utils" export default async function SignInPage({ @@ -15,7 +16,7 @@ export default async function SignInPage({ return (
Sign up diff --git a/src/app/(auth)/sign-up/credentials/page.tsx b/src/app/(auth)/sign-up/credentials/page.tsx index 7b2a2b09..b455d298 100644 --- a/src/app/(auth)/sign-up/credentials/page.tsx +++ b/src/app/(auth)/sign-up/credentials/page.tsx @@ -1,6 +1,7 @@ import { redirect } from "next/navigation" import { RegisterUserAuthForm } from "@/components/auth/register-user-auth-form" import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card" +import { authRoutes } from "@/lib/auth/constants" export default function SignupByCredentials({ searchParams, @@ -9,7 +10,7 @@ export default function SignupByCredentials({ }) { //? If there is no email in the search params, redirect to the sign-up page if (!searchParams?.email) { - redirect("/sign-up") + redirect(authRoutes.signUp[0]) } return ( diff --git a/src/app/(auth)/sign-up/page.tsx b/src/app/(auth)/sign-up/page.tsx index 05ccb677..11abe309 100644 --- a/src/app/(auth)/sign-up/page.tsx +++ b/src/app/(auth)/sign-up/page.tsx @@ -3,6 +3,7 @@ import { getProviders } from "next-auth/react" import GithubSignIn from "@/components/auth/github-sign-in" import { RegisterUserAuthForm } from "@/components/auth/register-user-auth-form" import { buttonVariants } from "@/components/ui/button" +import { authRoutes } from "@/lib/auth/constants" import { cn } from "@/lib/utils" export default async function SignUpPage({ @@ -15,7 +16,7 @@ export default async function SignUpPage({ return (
Login diff --git a/src/app/api/auth/[...nextauth]/route.ts b/src/app/api/auth/[...nextauth]/route.ts index 59ed89ef..91829af9 100644 --- a/src/app/api/auth/[...nextauth]/route.ts +++ b/src/app/api/auth/[...nextauth]/route.ts @@ -1,6 +1,6 @@ import NextAuth from "next-auth" - -import { nextAuthOptions } from "@/lib/auth/index" +import { nextAuthOptions } from "@/lib/auth" const handler = NextAuth(nextAuthOptions) + export { handler as GET, handler as POST } diff --git a/src/app/page.tsx b/src/app/page.tsx index d5542905..1a12973e 100644 --- a/src/app/page.tsx +++ b/src/app/page.tsx @@ -1,5 +1,6 @@ import Link from "next/link" import { buttonVariants } from "@/components/ui/button" +import { authRoutes } from "@/lib/auth/constants" export default function Home() { return ( @@ -8,12 +9,12 @@ export default function Home() {