fix(services): improve validation PUT /users/current

This commit is contained in:
Divlo 2022-01-29 22:31:37 +01:00
parent 32ac15c831
commit 2405b4951b
No known key found for this signature in database
GPG Key ID: 8F9478F220CE65E9
7 changed files with 890 additions and 1572 deletions

View File

@ -1,15 +1,15 @@
FROM node:16.13.1 AS dependencies FROM node:16.13.2 AS dependencies
WORKDIR /usr/src/app WORKDIR /usr/src/app
COPY ./package*.json ./ COPY ./package*.json ./
RUN npm install RUN npm install
FROM node:16.13.1 AS builder FROM node:16.13.2 AS builder
WORKDIR /usr/src/app WORKDIR /usr/src/app
COPY --from=dependencies /usr/src/app/node_modules ./node_modules COPY --from=dependencies /usr/src/app/node_modules ./node_modules
COPY ./ ./ COPY ./ ./
RUN npm run prisma:generate && npm run build RUN npm run prisma:generate && npm run build
FROM node:16.13.1 AS runner FROM node:16.13.2 AS runner
WORKDIR /usr/src/app WORKDIR /usr/src/app
ENV NODE_ENV=production ENV NODE_ENV=production
COPY --from=builder /usr/src/app/node_modules ./node_modules COPY --from=builder /usr/src/app/node_modules ./node_modules

2370
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -31,17 +31,17 @@
"postinstall": "husky install" "postinstall": "husky install"
}, },
"dependencies": { "dependencies": {
"@prisma/client": "3.8.0", "@prisma/client": "3.8.1",
"@sinclair/typebox": "0.23.2", "@sinclair/typebox": "0.23.2",
"@thream/socketio-jwt": "2.1.1", "@thream/socketio-jwt": "2.1.1",
"axios": "0.24.0", "axios": "0.25.0",
"bcryptjs": "2.4.3", "bcryptjs": "2.4.3",
"dotenv": "11.0.0", "dotenv": "14.2.0",
"ejs": "3.1.6", "ejs": "3.1.6",
"fastify": "3.25.3", "fastify": "3.27.0",
"fastify-cors": "6.0.2", "fastify-cors": "6.0.2",
"fastify-helmet": "6.0.0", "fastify-helmet": "7.0.1",
"fastify-multipart": "5.2.1", "fastify-multipart": "5.3.0",
"fastify-plugin": "3.0.0", "fastify-plugin": "3.0.0",
"fastify-rate-limit": "5.7.0", "fastify-rate-limit": "5.7.0",
"fastify-sensible": "3.1.2", "fastify-sensible": "3.1.2",
@ -56,20 +56,20 @@
"socket.io": "4.4.1" "socket.io": "4.4.1"
}, },
"devDependencies": { "devDependencies": {
"@commitlint/cli": "16.0.2", "@commitlint/cli": "16.1.0",
"@commitlint/config-conventional": "16.0.0", "@commitlint/config-conventional": "16.0.0",
"@saithodev/semantic-release-backmerge": "2.1.0", "@saithodev/semantic-release-backmerge": "2.1.0",
"@swc/cli": "0.1.55", "@swc/cli": "0.1.55",
"@swc/core": "1.2.129", "@swc/core": "1.2.133",
"@swc/jest": "0.2.17", "@swc/jest": "0.2.17",
"@types/bcryptjs": "2.4.2", "@types/bcryptjs": "2.4.2",
"@types/busboy": "1.3.0", "@types/busboy": "1.3.0",
"@types/ejs": "3.1.0", "@types/ejs": "3.1.0",
"@types/http-errors": "1.8.1", "@types/http-errors": "1.8.2",
"@types/jest": "27.4.0", "@types/jest": "27.4.0",
"@types/jsonwebtoken": "8.5.7", "@types/jsonwebtoken": "8.5.8",
"@types/ms": "0.7.31", "@types/ms": "0.7.31",
"@types/node": "17.0.8", "@types/node": "17.0.10",
"@types/nodemailer": "6.4.4", "@types/nodemailer": "6.4.4",
"@typescript-eslint/eslint-plugin": "4.33.0", "@typescript-eslint/eslint-plugin": "4.33.0",
"concurrently": "7.0.0", "concurrently": "7.0.0",
@ -82,19 +82,19 @@
"eslint-plugin-node": "11.1.0", "eslint-plugin-node": "11.1.0",
"eslint-plugin-prettier": "4.0.0", "eslint-plugin-prettier": "4.0.0",
"eslint-plugin-promise": "5.1.1", "eslint-plugin-promise": "5.1.1",
"eslint-plugin-unicorn": "40.0.0", "eslint-plugin-unicorn": "40.1.0",
"husky": "7.0.4", "husky": "7.0.4",
"jest": "27.4.7", "jest": "27.4.7",
"jest-mock-extended": "2.0.4", "jest-mock-extended": "2.0.4",
"jest-ts-webcompat-resolver": "1.0.0", "jest-ts-webcompat-resolver": "1.0.0",
"lint-staged": "12.1.7", "lint-staged": "12.2.2",
"markdownlint-cli": "0.30.0", "markdownlint-cli": "0.30.0",
"nodemon": "2.0.15", "nodemon": "2.0.15",
"plop": "3.0.5", "plop": "3.0.5",
"prettier": "2.5.1", "prettier": "2.5.1",
"prisma": "3.8.0", "prisma": "3.8.1",
"rimraf": "3.0.2", "rimraf": "3.0.2",
"semantic-release": "18.0.1", "semantic-release": "19.0.2",
"typescript": "4.5.4" "typescript": "4.5.5"
} }
} }

View File

@ -8,10 +8,10 @@ export const themes = [Type.Literal('light'), Type.Literal('dark')]
export const userSettingsSchema = { export const userSettingsSchema = {
id, id,
language: Type.Union(languages, { default: 'en' }), language: Type.Union(languages),
theme: Type.Union(themes, { default: 'dark' }), theme: Type.Union(themes),
isPublicEmail: Type.Boolean({ default: false }), isPublicEmail: Type.Boolean(),
isPublicGuilds: Type.Boolean({ default: false }), isPublicGuilds: Type.Boolean(),
createdAt: date.createdAt, createdAt: date.createdAt,
updatedAt: date.updatedAt, updatedAt: date.updatedAt,
userId: id userId: id

View File

@ -82,7 +82,7 @@ describe('PUT /users/current', () => {
expect(response.statusCode).toEqual(400) expect(response.statusCode).toEqual(400)
}) })
it('suceeds with valid website url', async () => { it('succeeds with valid website url', async () => {
const newWebsite = 'https://somerandomwebsite.com' const newWebsite = 'https://somerandomwebsite.com'
const { accessToken, user } = await authenticateUserTest() const { accessToken, user } = await authenticateUserTest()
prismaMock.user.update.mockResolvedValue({ prismaMock.user.update.mockResolvedValue({

View File

@ -1,5 +1,4 @@
import { randomUUID } from 'node:crypto' import { randomUUID } from 'node:crypto'
import { Static, Type } from '@sinclair/typebox' import { Static, Type } from '@sinclair/typebox'
import { FastifyPluginAsync, FastifySchema } from 'fastify' import { FastifyPluginAsync, FastifySchema } from 'fastify'
@ -14,10 +13,10 @@ import { parseStringNullish } from '../../../tools/utils/parseStringNullish.js'
const bodyPutServiceSchema = Type.Object({ const bodyPutServiceSchema = Type.Object({
name: Type.Optional(userSchema.name), name: Type.Optional(userSchema.name),
email: Type.Optional(userSchema.email), email: Type.Optional(Type.Union([userSchema.email, Type.Null()])),
status: Type.Optional(userSchema.status), status: Type.Optional(Type.Union([userSchema.status, Type.Null()])),
biography: Type.Optional(userSchema.biography), biography: Type.Optional(Type.Union([userSchema.biography, Type.Null()])),
website: Type.Optional(userSchema.website) website: Type.Optional(Type.Union([userSchema.website, Type.Null()]))
}) })
type BodyPutServiceSchemaType = Static<typeof bodyPutServiceSchema> type BodyPutServiceSchemaType = Static<typeof bodyPutServiceSchema>
@ -89,7 +88,12 @@ export const putCurrentUser: FastifyPluginAsync = async (fastify) => {
if (request.user.current.password != null) { if (request.user.current.password != null) {
strategies.push('local') strategies.push('local')
} }
if (email != null) { if (email === null && strategies.includes('local')) {
throw fastify.httpErrors.badRequest(
'You must have an email to sign in.'
)
}
if (email != null && email !== request.user.current.email) {
await prisma.refreshToken.deleteMany({ await prisma.refreshToken.deleteMany({
where: { where: {
userId: request.user.current.id userId: request.user.current.id

View File

@ -1,21 +1,25 @@
/** /**
* Parse a nullish string: * Parse a nullish string:
* - if `string === undefined`, it returns `defaultString`
* - if `string === null`, it returns `null`
* - if `string.length === 0`, it returns `null` * - if `string.length === 0`, it returns `null`
* - if `string == null`, it returns `defaultString` * - else, it returns `string`
* - if `string.length > 0`, it returns `string`
* @param defaultString * @param defaultString
* @param string * @param string
* @returns * @returns
*/ */
export const parseStringNullish = ( export const parseStringNullish = (
defaultString: string | null, defaultString: string | null,
string?: string string?: string | null
): string | null => { ): string | null => {
if (string != null) { if (string === undefined) {
if (string.length > 0) {
return string
}
return null
}
return defaultString return defaultString
} }
if (string === null) {
return null
}
if (string.length === 0) {
return null
}
return string
}