From e27a24bdb99d22a05d56730d2716acdd64c527cd Mon Sep 17 00:00:00 2001 From: Aaron William Po Date: Mon, 27 Nov 2023 11:50:42 -0500 Subject: [PATCH 1/2] Dev: add placeholder avatar, change image urls --- .gitignore | 7 ++--- src/components/Account/UserAvatar.tsx | 15 +++++++-- .../BeerBreweryComments/CommentCardBody.tsx | 2 +- src/pages/404.tsx | 2 -- src/pages/account/index.tsx | 2 +- src/pages/breweries/index.tsx | 31 +++++++++---------- src/pages/breweries/map.tsx | 2 +- src/prisma/seed/util/imageUrls.ts | 14 ++++++--- tailwind.config.js | 8 ++--- 9 files changed, 47 insertions(+), 36 deletions(-) diff --git a/.gitignore b/.gitignore index e336ab1..2306282 100644 --- a/.gitignore +++ b/.gitignore @@ -37,12 +37,11 @@ yarn-error.log* *.tsbuildinfo next-env.d.ts -# http requests +# HTTP playground *.http -# uploaded images -public/uploads - # vscode .vscode +/cloudinary-images + diff --git a/src/components/Account/UserAvatar.tsx b/src/components/Account/UserAvatar.tsx index 949ed03..385082d 100644 --- a/src/components/Account/UserAvatar.tsx +++ b/src/components/Account/UserAvatar.tsx @@ -2,6 +2,7 @@ import { FC } from 'react'; import Image from 'next/image'; import { z } from 'zod'; import GetUserSchema from '@/services/User/schema/GetUserSchema'; +import { FaUser } from 'react-icons/fa'; interface UserAvatarProps { user: { @@ -13,13 +14,23 @@ interface UserAvatarProps { const UserAvatar: FC = ({ user }) => { const { userAvatar } = user; - return !userAvatar ? null : ( + return !userAvatar ? ( +
+ + + +
+ ) : ( user avatar ); }; diff --git a/src/components/BeerBreweryComments/CommentCardBody.tsx b/src/components/BeerBreweryComments/CommentCardBody.tsx index f4887dc..bb4f643 100644 --- a/src/components/BeerBreweryComments/CommentCardBody.tsx +++ b/src/components/BeerBreweryComments/CommentCardBody.tsx @@ -31,7 +31,7 @@ const CommentCardBody: FC = ({ return (
-
+
diff --git a/src/pages/404.tsx b/src/pages/404.tsx index 6584b01..a83cc5c 100644 --- a/src/pages/404.tsx +++ b/src/pages/404.tsx @@ -1,5 +1,3 @@ -// create a 404 next js page using tailwind - import { NextPage } from 'next'; import Head from 'next/head'; diff --git a/src/pages/account/index.tsx b/src/pages/account/index.tsx index f5394f8..4d9405f 100644 --- a/src/pages/account/index.tsx +++ b/src/pages/account/index.tsx @@ -35,7 +35,7 @@ const AccountPage: NextPage = () => {
-
+
diff --git a/src/pages/breweries/index.tsx b/src/pages/breweries/index.tsx index f9aa78a..967de40 100644 --- a/src/pages/breweries/index.tsx +++ b/src/pages/breweries/index.tsx @@ -8,7 +8,7 @@ import { NextPage } from 'next'; import Head from 'next/head'; import { useContext, MutableRefObject, useRef } from 'react'; import Link from 'next/link'; -import { FaPlus, FaArrowUp } from 'react-icons/fa'; +import { FaPlus, FaArrowUp, FaMap } from 'react-icons/fa'; import { useInView } from 'react-intersection-observer'; import { z } from 'zod'; @@ -51,25 +51,24 @@ const BreweryPage: NextPage = () => {

The Biergarten App

Breweries

-
- +
+ {!!user && ( +
- View map + + + +
+ )} +
+ +
- {!!user && ( -
- - - -
- )}
{!!breweryPosts.length && !isLoading && ( diff --git a/src/pages/breweries/map.tsx b/src/pages/breweries/map.tsx index 8ce572a..61b0c20 100644 --- a/src/pages/breweries/map.tsx +++ b/src/pages/breweries/map.tsx @@ -34,7 +34,7 @@ const BreweryMapPage: NextPage = ({ token }) => { const mapStyles: MapStyles = { light: 'mapbox://styles/mapbox/light-v10', - dark: 'mapbox://styles/mapbox/dark-v11', + dark: 'mapbox://styles/mapbox/dark-v10', }; const pins = useMemo( diff --git a/src/prisma/seed/util/imageUrls.ts b/src/prisma/seed/util/imageUrls.ts index 8029245..2325765 100644 --- a/src/prisma/seed/util/imageUrls.ts +++ b/src/prisma/seed/util/imageUrls.ts @@ -1,9 +1,13 @@ const imageUrls = [ - 'https://res.cloudinary.com/dxie9b7na/image/upload/v1683482214/cld-sample.jpg', - 'https://res.cloudinary.com/dxie9b7na/image/upload/v1683482214/cld-sample-2.jpg', - 'https://res.cloudinary.com/dxie9b7na/image/upload/v1683482214/cld-sample-3.jpg', - 'https://res.cloudinary.com/dxie9b7na/image/upload/v1683482214/cld-sample-4.jpg', - 'https://res.cloudinary.com/dxie9b7na/image/upload/v1683482214/cld-sample-5.jpg', + 'https://res.cloudinary.com/dxie9b7na/image/upload/v1701056802/cloudinary-images/pexels-brett-sayles-1551944_era4te.jpg', + 'https://res.cloudinary.com/dxie9b7na/image/upload/v1701056801/cloudinary-images/pexels-ketut-subiyanto-5055809_fwkfoj.jpg', + 'https://res.cloudinary.com/dxie9b7na/image/upload/v1701056801/cloudinary-images/pexels-cottonbro-studio-5537954_zsxs7n.jpg', + 'https://res.cloudinary.com/dxie9b7na/image/upload/v1701056800/cloudinary-images/pexels-tembela-bohle-1089931_f7jcer.jpg', + 'https://res.cloudinary.com/dxie9b7na/image/upload/v1701056798/cloudinary-images/pexels-tembela-bohle-1089930_yzfjlv.jpg', + 'https://res.cloudinary.com/dxie9b7na/image/upload/v1701056797/cloudinary-images/pexels-cottonbro-studio-5529918_e0jlle.jpg', + 'https://res.cloudinary.com/dxie9b7na/image/upload/v1701056797/cloudinary-images/pexels-casalfilmsestudio-2076748_xymlox.jpg', + 'https://res.cloudinary.com/dxie9b7na/image/upload/v1701056795/cloudinary-images/pexels-elevate-3009770_simouh.jpg', + 'https://res.cloudinary.com/dxie9b7na/image/upload/v1701056793/cloudinary-images/pexels-elevate-1267700_jrno3s.jpg', ] as const; export default imageUrls; diff --git a/tailwind.config.js b/tailwind.config.js index 868e468..a48705e 100644 --- a/tailwind.config.js +++ b/tailwind.config.js @@ -12,12 +12,12 @@ const myThemes = { warning: 'hsl(50, 98%, 50%)', 'primary-content': 'hsl(0, 0%, 98%)', 'error-content': 'hsl(0, 0%, 98%)', - 'base-100': 'hsl(227, 20%, 10%)', - 'base-200': 'hsl(227, 20%, 8%)', - 'base-300': 'hsl(227, 20%, 1%)', + 'base-100': 'hsl(227, 20%, 20%)', + 'base-200': 'hsl(227, 20%, 13%)', + 'base-300': 'hsl(227, 20%, 10%)', }, light: { - primary: 'hsl(180, 15%, 60%)', + primary: 'hsl(180, 15%, 70%)', secondary: 'hsl(21, 54%, 83%)', error: 'hsl(4, 87%, 74%)', accent: 'hsl(93, 27%, 73%)', From a435e011f8a2c98f52b67c4b96c3a7931a811541 Mon Sep 17 00:00:00 2001 From: Aaron William Po Date: Mon, 27 Nov 2023 14:41:09 -0500 Subject: [PATCH 2/2] Security fix: update password system for database seeding --- README.md | 6 +++++- package-lock.json | 13 ++++++++++++ package.json | 9 ++++---- src/config/env/index.ts | 13 ++++++++++++ src/prisma/seed/create/createAdminUser.ts | 4 +++- src/prisma/seed/create/createNewUsers.ts | 26 ++++++++++++++++++++--- src/prisma/seed/index.ts | 2 +- 7 files changed, 63 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index 0e1a6d0..3afa8c4 100644 --- a/README.md +++ b/README.md @@ -136,8 +136,10 @@ POSTGRES_PRISMA_URL= POSTGRES_URL_NON_POOLING= SHADOW_DATABASE_URL= +ADMIN_PASSWORD= + MAPBOX_ACCESS_TOKEN= -NEXT_PUBLIC_MAPBOX_ACCESS_TOKEN= + SPARKPOST_API_KEY= SPARKPOST_SENDER_ADDRESS=" > .env ``` @@ -175,6 +177,8 @@ SPARKPOST_SENDER_ADDRESS=" > .env - `SPARKPOST_API_KEY` is the API key for your SparkPost account. - You can create a free account [here](https://www.sparkpost.com/). - `SPARKPOST_SENDER_ADDRESS` is the email address that will be used to send emails. +- `ADMIN_PASSWORD` is the password for the admin account created when seeding the + database. 1. Initialize the database and run the migrations. diff --git a/package-lock.json b/package-lock.json index 13f0e15..b819fe8 100644 --- a/package-lock.json +++ b/package-lock.json @@ -72,6 +72,7 @@ "eslint-config-next": "^13.5.4", "eslint-config-prettier": "^9.0.0", "eslint-plugin-react": "^7.33.2", + "generate-password": "^1.7.1", "onchange": "^7.1.0", "postcss": "^8.4.26", "prettier": "^3.0.0", @@ -5354,6 +5355,12 @@ "node": ">=10" } }, + "node_modules/generate-password": { + "version": "1.7.1", + "resolved": "https://registry.npmjs.org/generate-password/-/generate-password-1.7.1.tgz", + "integrity": "sha512-9bVYY+16m7W7GczRBDqXE+VVuCX+bWNrfYKC/2p2JkZukFb2sKxT6E3zZ3mJGz7GMe5iRK0A/WawSL3jQfJuNQ==", + "dev": true + }, "node_modules/geojson-vt": { "version": "3.2.1", "resolved": "https://registry.npmjs.org/geojson-vt/-/geojson-vt-3.2.1.tgz", @@ -14595,6 +14602,12 @@ "wide-align": "^1.1.2" } }, + "generate-password": { + "version": "1.7.1", + "resolved": "https://registry.npmjs.org/generate-password/-/generate-password-1.7.1.tgz", + "integrity": "sha512-9bVYY+16m7W7GczRBDqXE+VVuCX+bWNrfYKC/2p2JkZukFb2sKxT6E3zZ3mJGz7GMe5iRK0A/WawSL3jQfJuNQ==", + "dev": true + }, "geojson-vt": { "version": "3.2.1", "resolved": "https://registry.npmjs.org/geojson-vt/-/geojson-vt-3.2.1.tgz", diff --git a/package.json b/package.json index 78ce408..e6f4f03 100644 --- a/package.json +++ b/package.json @@ -64,27 +64,28 @@ "@types/multer": "^1.4.7", "@types/node": "^20.4.2", "@types/passport-local": "^1.0.35", - "@types/react": "^18.2.15", "@types/react-dom": "^18.2.7", + "@types/react": "^18.2.15", "@types/sparkpost": "^2.1.5", "@vercel/fetch": "^7.0.0", "autoprefixer": "^10.4.14", "daisyui": "^3.9.2", "dotenv-cli": "^7.2.1", - "eslint": "^8.51.0", "eslint-config-airbnb-base": "15.0.0", "eslint-config-airbnb-typescript": "17.1.0", "eslint-config-next": "^13.5.4", "eslint-config-prettier": "^9.0.0", "eslint-plugin-react": "^7.33.2", + "eslint": "^8.51.0", + "generate-password": "^1.7.1", "onchange": "^7.1.0", "postcss": "^8.4.26", - "prettier": "^3.0.0", "prettier-plugin-jsdoc": "^1.0.2", "prettier-plugin-tailwindcss": "^0.4.1", + "prettier": "^3.0.0", "prisma": "^5.6.0", - "tailwindcss": "^3.3.3", "tailwindcss-animate": "^1.0.6", + "tailwindcss": "^3.3.3", "ts-node": "^10.9.1", "typescript": "^5.3.2" }, diff --git a/src/config/env/index.ts b/src/config/env/index.ts index ecd1a7c..426e828 100644 --- a/src/config/env/index.ts +++ b/src/config/env/index.ts @@ -28,6 +28,8 @@ const envSchema = z.object({ SPARKPOST_API_KEY: z.string(), SPARKPOST_SENDER_ADDRESS: z.string().email(), MAPBOX_ACCESS_TOKEN: z.string(), + + ADMIN_PASSWORD: z.string().regex(/^(?=.*[A-Z])(?=.*[0-9])(?=.*[^a-zA-Z0-9]).{8,}$/), }); const parsed = envSchema.safeParse(env); @@ -194,3 +196,14 @@ export const SPARKPOST_SENDER_ADDRESS = parsed.data.SPARKPOST_SENDER_ADDRESS; * @see https://docs.mapbox.com/help/how-mapbox-works/access-tokens/ */ export const MAPBOX_ACCESS_TOKEN = parsed.data.MAPBOX_ACCESS_TOKEN; + +/** + * Admin password for seeding the database. + * + * @example + * 'abcdefghijklmnopqrstuvwxyz123456'; + * + * @see README.md for instructions on generating a secret key. + */ + +export const ADMIN_PASSWORD = parsed.data.ADMIN_PASSWORD; diff --git a/src/prisma/seed/create/createAdminUser.ts b/src/prisma/seed/create/createAdminUser.ts index eeb3739..07b3d96 100644 --- a/src/prisma/seed/create/createAdminUser.ts +++ b/src/prisma/seed/create/createAdminUser.ts @@ -1,12 +1,14 @@ import { z } from 'zod'; import { hashPassword } from '../../../config/auth/passwordFns'; +import { ADMIN_PASSWORD } from '../../../config/env'; + import DBClient from '../../DBClient'; import GetUserSchema from '../../../services/User/schema/GetUserSchema'; import imageUrls from '../util/imageUrls'; const createAdminUser = async () => { - const hash = await hashPassword('Pas!3word'); + const hash = await hashPassword(ADMIN_PASSWORD); const adminUser: z.infer = await DBClient.instance.user.create({ data: { username: 'admin', diff --git a/src/prisma/seed/create/createNewUsers.ts b/src/prisma/seed/create/createNewUsers.ts index 96f6a2f..87878cd 100644 --- a/src/prisma/seed/create/createNewUsers.ts +++ b/src/prisma/seed/create/createNewUsers.ts @@ -1,8 +1,11 @@ -// eslint-disable-next-line import/no-extraneous-dependencies +/* eslint-disable import/no-extraneous-dependencies */ import { faker } from '@faker-js/faker'; +import generator from 'generate-password'; + import crypto from 'crypto'; import DBClient from '../../DBClient'; import { hashPassword } from '../../../config/auth/passwordFns'; +import logger from '../../../config/pino/logger'; interface CreateNewUsersArgs { numberOfUsers: number; @@ -23,9 +26,25 @@ interface UserData { const createNewUsers = async ({ numberOfUsers }: CreateNewUsersArgs) => { const prisma = DBClient.instance; + await DBClient.instance.$disconnect(); + + const passwords = Array.from({ length: numberOfUsers }, () => + generator.generate({ + length: 20, + symbols: true, + numbers: true, + uppercase: true, + strict: true, + }), + ); + + logger.info('Hashing passwords. This may take a while...'); + const hashedPasswords = await Promise.all( + passwords.map((password) => hashPassword(password)), + ); + + logger.info('Creating new users. This may take a while...'); - const password = 'passwoRd!3'; - const hash = await hashPassword(password); const data: UserData[] = []; const takenUsernames: string[] = []; @@ -41,6 +60,7 @@ const createNewUsers = async ({ numberOfUsers }: CreateNewUsersArgs) => { .email({ firstName, lastName, provider: 'example.com' }) .toLowerCase(); + const hash = hashedPasswords[i]; const userAvailable = !takenUsernames.includes(username) && !takenEmails.includes(email); diff --git a/src/prisma/seed/index.ts b/src/prisma/seed/index.ts index 2b44cbc..059d2c6 100644 --- a/src/prisma/seed/index.ts +++ b/src/prisma/seed/index.ts @@ -32,7 +32,7 @@ import createNewUserFollows from './create/createNewUserFollows'; await createAdminUser(); logger.info('Admin user created successfully.'); - const users = await createNewUsers({ numberOfUsers: 10000 }); + const users = await createNewUsers({ numberOfUsers: 1000 }); logger.info('Users created successfully.'); const userAvatars = await createNewUserAvatars({ joinData: { users } });