fix: stricter ajv validation
This commit is contained in:
		| @@ -1,6 +1,7 @@ | ||||
| COMPOSE_PROJECT_NAME='thream-api' | ||||
| HOST='0.0.0.0' | ||||
| PORT='8080' | ||||
| NODE_ENV='development' | ||||
| API_URL='http://localhost:8080' | ||||
| DATABASE_URL='postgresql://user:password@thream-database:5432/thream' | ||||
| JWT_ACCESS_EXPIRES_IN='15 minutes' | ||||
|   | ||||
							
								
								
									
										2024
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										2024
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
							
								
								
									
										38
									
								
								package.json
									
									
									
									
									
								
							
							
						
						
									
										38
									
								
								package.json
									
									
									
									
									
								
							| @@ -25,7 +25,7 @@ | ||||
|     "lint:typescript": "eslint \"**/*.{js,jsx,ts,tsx}\" --ignore-path \".gitignore\"", | ||||
|     "lint:prettier": "prettier \".\" --check", | ||||
|     "lint:staged": "lint-staged", | ||||
|     "test": "c8 tap", | ||||
|     "test": "cross-env NODE_ENV=test c8 tap", | ||||
|     "prisma:generate": "prisma generate", | ||||
|     "prisma:studio": "prisma studio", | ||||
|     "prisma:migrate:dev": "prisma migrate dev", | ||||
| @@ -34,14 +34,14 @@ | ||||
|     "postinstall": "husky install" | ||||
|   }, | ||||
|   "dependencies": { | ||||
|     "@prisma/client": "3.11.0", | ||||
|     "@prisma/client": "3.12.0", | ||||
|     "@sinclair/typebox": "0.23.4", | ||||
|     "@thream/socketio-jwt": "2.2.1", | ||||
|     "@thream/socketio-jwt": "3.0.0", | ||||
|     "axios": "0.26.1", | ||||
|     "bcryptjs": "2.4.3", | ||||
|     "dotenv": "16.0.0", | ||||
|     "ejs": "3.1.6", | ||||
|     "fastify": "3.27.4", | ||||
|     "fastify": "3.28.0", | ||||
|     "fastify-cors": "6.0.3", | ||||
|     "fastify-helmet": "7.0.1", | ||||
|     "fastify-multipart": "5.3.1", | ||||
| @@ -49,7 +49,7 @@ | ||||
|     "fastify-rate-limit": "5.8.0", | ||||
|     "fastify-sensible": "3.1.2", | ||||
|     "fastify-static": "4.6.1", | ||||
|     "fastify-swagger": "5.0.0", | ||||
|     "fastify-swagger": "5.1.0", | ||||
|     "fastify-url-data": "3.0.3", | ||||
|     "http-errors": "2.0.0", | ||||
|     "jsonwebtoken": "8.5.1", | ||||
| @@ -62,41 +62,41 @@ | ||||
|     "@commitlint/cli": "16.2.3", | ||||
|     "@commitlint/config-conventional": "16.2.1", | ||||
|     "@saithodev/semantic-release-backmerge": "2.1.2", | ||||
|     "@swc/cli": "0.1.56", | ||||
|     "@swc/core": "1.2.159", | ||||
|     "@swc/cli": "0.1.57", | ||||
|     "@swc/core": "1.2.164", | ||||
|     "@types/bcryptjs": "2.4.2", | ||||
|     "@types/busboy": "1.3.0", | ||||
|     "@types/busboy": "1.5.0", | ||||
|     "@types/ejs": "3.1.0", | ||||
|     "@types/http-errors": "1.8.2", | ||||
|     "@types/jsonwebtoken": "8.5.8", | ||||
|     "@types/ms": "0.7.31", | ||||
|     "@types/node": "17.0.21", | ||||
|     "@types/node": "17.0.23", | ||||
|     "@types/nodemailer": "6.4.4", | ||||
|     "@types/sinon": "10.0.11", | ||||
|     "@types/tap": "15.0.6", | ||||
|     "@typescript-eslint/eslint-plugin": "5.15.0", | ||||
|     "@typescript-eslint/eslint-plugin": "5.18.0", | ||||
|     "c8": "7.11.0", | ||||
|     "concurrently": "7.0.0", | ||||
|     "concurrently": "7.1.0", | ||||
|     "cross-env": "7.0.3", | ||||
|     "editorconfig-checker": "4.0.2", | ||||
|     "eslint": "8.11.0", | ||||
|     "eslint-config-conventions": "1.1.2", | ||||
|     "eslint": "8.12.0", | ||||
|     "eslint-config-conventions": "2.0.0", | ||||
|     "eslint-config-prettier": "8.5.0", | ||||
|     "eslint-plugin-import": "2.25.4", | ||||
|     "eslint-plugin-import": "2.26.0", | ||||
|     "eslint-plugin-prettier": "4.0.0", | ||||
|     "eslint-plugin-promise": "6.0.0", | ||||
|     "eslint-plugin-unicorn": "41.0.1", | ||||
|     "eslint-plugin-unicorn": "42.0.0", | ||||
|     "husky": "7.0.4", | ||||
|     "lint-staged": "12.3.7", | ||||
|     "markdownlint-cli": "0.31.1", | ||||
|     "nodemon": "2.0.15", | ||||
|     "plop": "3.0.5", | ||||
|     "prettier": "2.6.0", | ||||
|     "prisma": "3.11.0", | ||||
|     "prettier": "2.6.2", | ||||
|     "prisma": "3.12.0", | ||||
|     "rimraf": "3.0.2", | ||||
|     "semantic-release": "19.0.2", | ||||
|     "sinon": "13.0.1", | ||||
|     "tap": "16.0.0", | ||||
|     "typescript": "4.6.2" | ||||
|     "tap": "16.0.1", | ||||
|     "typescript": "4.6.3" | ||||
|   } | ||||
| } | ||||
|   | ||||
| @@ -44,7 +44,7 @@ export const authenticateUserTest = async (): Promise<{ | ||||
|   sinon.stub(prisma, 'oAuth').value(oAuthStubValue) | ||||
|   sinon.stub(prisma, 'refreshToken').value(refreshTokenStubValue) | ||||
|   const userJWT: UserJWT = { | ||||
|     currentStrategy: 'local', | ||||
|     currentStrategy: 'Local', | ||||
|     id: 1 | ||||
|   } | ||||
|   const accessToken = generateAccessToken(userJWT) | ||||
|   | ||||
| @@ -17,7 +17,12 @@ import { UPLOADS_URL } from './tools/configurations/index.js' | ||||
|  | ||||
| dotenv.config() | ||||
| export const application = fastify({ | ||||
|   logger: process.env.NODE_ENV === 'development' | ||||
|   logger: process.env.NODE_ENV === 'development', | ||||
|   ajv: { | ||||
|     customOptions: { | ||||
|       format: 'full' | ||||
|     } | ||||
|   } | ||||
| }) | ||||
|  | ||||
| await application.register(fastifyCors) | ||||
| @@ -33,7 +38,7 @@ await application.register(fastifySocketIo, { | ||||
| }) | ||||
| await application.register(fastifyHelmet) | ||||
| await application.register(fastifyRateLimit, { | ||||
|   max: 150, | ||||
|   max: 200, | ||||
|   timeWindow: '1 minute' | ||||
| }) | ||||
| await application.register(fastifyStatic, { | ||||
|   | ||||
| @@ -2,8 +2,8 @@ import { Type } from '@sinclair/typebox' | ||||
|  | ||||
| import { date, id } from './utils.js' | ||||
|  | ||||
| export const providers = ['google', 'github', 'discord'] as const | ||||
| export const strategies = [...providers, 'local'] as const | ||||
| export const providers = ['Google', 'GitHub', 'Discord'] as const | ||||
| export const strategies = [...providers, 'Local'] as const | ||||
|  | ||||
| export const strategiesTypebox = strategies.map((strategy) => | ||||
|   Type.Literal(strategy) | ||||
|   | ||||
| @@ -27,7 +27,7 @@ export const userSchema = { | ||||
|   website: Type.String({ | ||||
|     minLength: 1, | ||||
|     maxLength: 255, | ||||
|     format: 'uri-reference' | ||||
|     format: 'uri' | ||||
|   }), | ||||
|   isConfirmed: Type.Boolean({ default: false }), | ||||
|   temporaryToken: Type.String(), | ||||
|   | ||||
| @@ -21,7 +21,7 @@ await tap.test('GET /users/current', async (t) => { | ||||
|     const responseJson = response.json() | ||||
|     t.equal(response.statusCode, 200) | ||||
|     t.equal(responseJson.user.name, user.name) | ||||
|     t.strictSame(responseJson.user.strategies, ['local']) | ||||
|     t.strictSame(responseJson.user.strategies, ['Local']) | ||||
|   }) | ||||
|  | ||||
|   await t.test('fails with unauthenticated user', async (t) => { | ||||
|   | ||||
| @@ -44,7 +44,7 @@ export const getCurrentUser: FastifyPluginAsync = async (fastify) => { | ||||
|         return oauth.provider | ||||
|       }) | ||||
|       if (user.current.password != null) { | ||||
|         strategies.push('local') | ||||
|         strategies.push('Local') | ||||
|       } | ||||
|       reply.statusCode = 200 | ||||
|       return { | ||||
|   | ||||
| @@ -52,7 +52,7 @@ export const putCurrentUser: FastifyPluginAsync = async (fastify) => { | ||||
|  | ||||
|   fastify.route<{ | ||||
|     Body: BodyPutServiceSchemaType | ||||
|     Params: QueryPutCurrentUserSchemaType | ||||
|     Querystring: QueryPutCurrentUserSchemaType | ||||
|   }>({ | ||||
|     method: 'PUT', | ||||
|     url: '/users/current', | ||||
| @@ -62,7 +62,7 @@ export const putCurrentUser: FastifyPluginAsync = async (fastify) => { | ||||
|         throw fastify.httpErrors.forbidden() | ||||
|       } | ||||
|       const { name, email, status, biography, website } = request.body | ||||
|       const { redirectURI } = request.params | ||||
|       const { redirectURI } = request.query | ||||
|       const userValidation = await prisma.user.findFirst({ | ||||
|         where: { | ||||
|           OR: [ | ||||
| @@ -90,9 +90,9 @@ export const putCurrentUser: FastifyPluginAsync = async (fastify) => { | ||||
|         return oauth.provider | ||||
|       }) | ||||
|       if (request.user.current.password != null) { | ||||
|         strategies.push('local') | ||||
|         strategies.push('Local') | ||||
|       } | ||||
|       if (email === null && strategies.includes('local')) { | ||||
|       if (email === null && strategies.includes('Local')) { | ||||
|         throw fastify.httpErrors.badRequest( | ||||
|           'You must have an email to sign in.' | ||||
|         ) | ||||
|   | ||||
| @@ -53,7 +53,7 @@ export const deleteProviderService: FastifyPluginAsync = async (fastify) => { | ||||
|         return oauth.provider | ||||
|       }) | ||||
|       if (user.current.password != null) { | ||||
|         strategies.push('local') | ||||
|         strategies.push('Local') | ||||
|       } | ||||
|       const oauthProvider = OAuths.find((oauth) => oauth.provider === provider) | ||||
|       if (oauthProvider == null) { | ||||
|   | ||||
| @@ -4,8 +4,8 @@ import axios from 'axios' | ||||
|  | ||||
| import { OAuthStrategy } from '../../../../../tools/utils/OAuthStrategy.js' | ||||
|  | ||||
| export const DISCORD_PROVIDER = 'discord' | ||||
| export const DISCORD_BASE_URL = 'https://discord.com/api/v6' | ||||
| export const DISCORD_PROVIDER = 'Discord' | ||||
| export const DISCORD_BASE_URL = 'https://discord.com/api/v10' | ||||
| export const DISCORD_CLIENT_ID = | ||||
|   process.env.DISCORD_CLIENT_ID ?? 'DISCORD_CLIENT_ID' | ||||
| export const DISCORD_CLIENT_SECRET = | ||||
|   | ||||
| @@ -4,7 +4,7 @@ import axios from 'axios' | ||||
|  | ||||
| import { OAuthStrategy } from '../../../../../tools/utils/OAuthStrategy.js' | ||||
|  | ||||
| export const GITHUB_PROVIDER = 'github' | ||||
| export const GITHUB_PROVIDER = 'GitHub' | ||||
| export const GITHUB_BASE_URL = 'https://github.com' | ||||
| export const GITHUB_API_BASE_URL = 'https://api.github.com' | ||||
| export const GITHUB_CLIENT_ID = | ||||
|   | ||||
| @@ -4,7 +4,7 @@ import axios from 'axios' | ||||
|  | ||||
| import { OAuthStrategy } from '../../../../../tools/utils/OAuthStrategy.js' | ||||
|  | ||||
| export const GOOGLE_PROVIDER = 'google' | ||||
| export const GOOGLE_PROVIDER = 'Google' | ||||
| export const GOOGLE_BASE_URL = 'https://accounts.google.com/o/oauth2/v2/auth' | ||||
| export const GOOGLE_OAUTH2_TOKEN = 'https://oauth2.googleapis.com/token' | ||||
| export const GOOGLE_USERINFO = | ||||
|   | ||||
| @@ -53,11 +53,11 @@ export const postSigninUser: FastifyPluginAsync = async (fastify) => { | ||||
|         throw fastify.httpErrors.badRequest('Invalid credentials.') | ||||
|       } | ||||
|       const accessToken = generateAccessToken({ | ||||
|         currentStrategy: 'local', | ||||
|         currentStrategy: 'Local', | ||||
|         id: user.id | ||||
|       }) | ||||
|       const refreshToken = await generateRefreshToken({ | ||||
|         currentStrategy: 'local', | ||||
|         currentStrategy: 'Local', | ||||
|         id: user.id | ||||
|       }) | ||||
|       reply.statusCode = 200 | ||||
|   | ||||
| @@ -58,7 +58,7 @@ await tap.test('POST /users/signup', async (t) => { | ||||
|       url: '/users/signup', | ||||
|       payload: { | ||||
|         ...payload, | ||||
|         email: 'incorrect-email' | ||||
|         email: 'incorrect-email@abc' | ||||
|       } | ||||
|     }) | ||||
|     t.equal(response.statusCode, 400) | ||||
|   | ||||
| @@ -22,7 +22,7 @@ await tap.test( | ||||
|           return userExample | ||||
|         } | ||||
|       }) | ||||
|       const currentStrategy = 'local' | ||||
|       const currentStrategy = 'Local' | ||||
|       sinon.stub(jwt, 'verify').value(() => { | ||||
|         return { id: userExample.id, currentStrategy } | ||||
|       }) | ||||
| @@ -89,7 +89,7 @@ await tap.test( | ||||
|           } | ||||
|         }) | ||||
|         sinon.stub(jwt, 'verify').value(() => { | ||||
|           return { id: userExample.id, currentStrategy: 'local' } | ||||
|           return { id: userExample.id, currentStrategy: 'Local' } | ||||
|         }) | ||||
|         await t.rejects(getUserWithBearerToken('Bearer token'), BadRequest) | ||||
|       } | ||||
|   | ||||
| @@ -33,7 +33,7 @@ export const getUserWithBearerToken = async ( | ||||
|     throw new Forbidden() | ||||
|   } | ||||
|  | ||||
|   if (!user.isConfirmed && payload.currentStrategy === 'local') { | ||||
|   if (!user.isConfirmed && payload.currentStrategy === 'Local') { | ||||
|     throw new BadRequest( | ||||
|       'You should have a confirmed account, please check your email and follow the instructions to verify your account' | ||||
|     ) | ||||
|   | ||||
| @@ -7,7 +7,7 @@ import { OAuthStrategy } from '../OAuthStrategy.js' | ||||
| import prisma from '../../database/prisma.js' | ||||
| import { refreshTokenExample } from '../../../models/RefreshToken.js' | ||||
|  | ||||
| const oauthStrategy = new OAuthStrategy('discord') | ||||
| const oauthStrategy = new OAuthStrategy('Discord') | ||||
|  | ||||
| await tap.test('tools/utils/OAuthStrategy', async (t) => { | ||||
|   await t.test('callbackSignin', async (t) => { | ||||
| @@ -47,7 +47,7 @@ await tap.test('tools/utils/OAuthStrategy', async (t) => { | ||||
|           return { | ||||
|             id: 1, | ||||
|             userId: userExample.id, | ||||
|             provider: 'discord', | ||||
|             provider: 'Discord', | ||||
|             providerId: id, | ||||
|             updatedAt: new Date(), | ||||
|             createdAt: new Date() | ||||
| @@ -64,7 +64,7 @@ await tap.test('tools/utils/OAuthStrategy', async (t) => { | ||||
|         oAuthCreateSpy.calledWith({ | ||||
|           data: { | ||||
|             userId: userExample.id, | ||||
|             provider: 'discord', | ||||
|             provider: 'Discord', | ||||
|             providerId: id | ||||
|           } | ||||
|         }), | ||||
| @@ -73,7 +73,7 @@ await tap.test('tools/utils/OAuthStrategy', async (t) => { | ||||
|       t.equal( | ||||
|         oAuthFindFirstSpy.calledWith({ | ||||
|           where: { | ||||
|             provider: 'discord', | ||||
|             provider: 'Discord', | ||||
|             providerId: id | ||||
|           } | ||||
|         }), | ||||
| @@ -108,7 +108,7 @@ await tap.test('tools/utils/OAuthStrategy', async (t) => { | ||||
|           return { | ||||
|             id: 1, | ||||
|             userId: userExample.id, | ||||
|             provider: 'discord', | ||||
|             provider: 'Discord', | ||||
|             providerId: id, | ||||
|             updatedAt: new Date(), | ||||
|             createdAt: new Date() | ||||
| @@ -119,14 +119,14 @@ await tap.test('tools/utils/OAuthStrategy', async (t) => { | ||||
|       const oAuthFindFirstSpy = sinon.spy(prisma.oAuth, 'findFirst') | ||||
|       const result = await oauthStrategy.callbackAddStrategy( | ||||
|         { id, name }, | ||||
|         { accessToken: '123', current: userExample, currentStrategy: 'local' } | ||||
|         { accessToken: '123', current: userExample, currentStrategy: 'Local' } | ||||
|       ) | ||||
|       t.equal(result, 'success') | ||||
|       t.equal( | ||||
|         oAuthCreateSpy.calledWith({ | ||||
|           data: { | ||||
|             userId: userExample.id, | ||||
|             provider: 'discord', | ||||
|             provider: 'Discord', | ||||
|             providerId: id | ||||
|           } | ||||
|         }), | ||||
| @@ -135,7 +135,7 @@ await tap.test('tools/utils/OAuthStrategy', async (t) => { | ||||
|       t.equal( | ||||
|         oAuthFindFirstSpy.calledWith({ | ||||
|           where: { | ||||
|             provider: 'discord', | ||||
|             provider: 'Discord', | ||||
|             providerId: id | ||||
|           } | ||||
|         }), | ||||
| @@ -153,7 +153,7 @@ await tap.test('tools/utils/OAuthStrategy', async (t) => { | ||||
|             return { | ||||
|               id: 1, | ||||
|               userId: 2, | ||||
|               provider: 'discord', | ||||
|               provider: 'Discord', | ||||
|               providerId: id, | ||||
|               updatedAt: new Date(), | ||||
|               createdAt: new Date() | ||||
| @@ -163,13 +163,13 @@ await tap.test('tools/utils/OAuthStrategy', async (t) => { | ||||
|         const oAuthFindFirstSpy = sinon.spy(prisma.oAuth, 'findFirst') | ||||
|         const result = await oauthStrategy.callbackAddStrategy( | ||||
|           { id, name }, | ||||
|           { accessToken: '123', current: userExample, currentStrategy: 'local' } | ||||
|           { accessToken: '123', current: userExample, currentStrategy: 'Local' } | ||||
|         ) | ||||
|         t.equal(result, 'This account is already used by someone else') | ||||
|         t.equal( | ||||
|           oAuthFindFirstSpy.calledWith({ | ||||
|             where: { | ||||
|               provider: 'discord', | ||||
|               provider: 'Discord', | ||||
|               providerId: id | ||||
|             } | ||||
|           }), | ||||
| @@ -188,7 +188,7 @@ await tap.test('tools/utils/OAuthStrategy', async (t) => { | ||||
|             return { | ||||
|               id: 1, | ||||
|               userId: userExample.id, | ||||
|               provider: 'discord', | ||||
|               provider: 'Discord', | ||||
|               providerId: id, | ||||
|               updatedAt: new Date(), | ||||
|               createdAt: new Date() | ||||
| @@ -198,13 +198,13 @@ await tap.test('tools/utils/OAuthStrategy', async (t) => { | ||||
|         const oAuthFindFirstSpy = sinon.spy(prisma.oAuth, 'findFirst') | ||||
|         const result = await oauthStrategy.callbackAddStrategy( | ||||
|           { id, name }, | ||||
|           { accessToken: '123', current: userExample, currentStrategy: 'local' } | ||||
|           { accessToken: '123', current: userExample, currentStrategy: 'Local' } | ||||
|         ) | ||||
|         t.equal(result, 'You are already using this account') | ||||
|         t.equal( | ||||
|           oAuthFindFirstSpy.calledWith({ | ||||
|             where: { | ||||
|               provider: 'discord', | ||||
|               provider: 'Discord', | ||||
|               providerId: id | ||||
|             } | ||||
|           }), | ||||
|   | ||||
		Reference in New Issue
	
	Block a user