feat(users): added validation to the POST route
This commit is contained in:
parent
5ecbe73cd7
commit
2c5037a491
183
package-lock.json
generated
183
package-lock.json
generated
@ -13,7 +13,9 @@
|
||||
"dotenv": "^16.4.7",
|
||||
"express": "^4.21.2",
|
||||
"i18n-iso-countries": "^7.14.0",
|
||||
"mongodb": "^6.15.0"
|
||||
"joi": "^17.13.3",
|
||||
"mongodb": "^6.15.0",
|
||||
"nostr-tools": "^2.11.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@eslint/js": "^9.23.0",
|
||||
@ -301,6 +303,21 @@
|
||||
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@hapi/hoek": {
|
||||
"version": "9.3.0",
|
||||
"resolved": "https://registry.npmjs.org/@hapi/hoek/-/hoek-9.3.0.tgz",
|
||||
"integrity": "sha512-/c6rf4UJlmHlC9b5BaNvzAcFv7HZ2QHaV0D4/HNlBdvFnvQq8RI4kYdhyPCl7Xj+oWvTWQ8ujhqS53LIgAe6KQ==",
|
||||
"license": "BSD-3-Clause"
|
||||
},
|
||||
"node_modules/@hapi/topo": {
|
||||
"version": "5.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@hapi/topo/-/topo-5.1.0.tgz",
|
||||
"integrity": "sha512-foQZKJig7Ob0BMAYBfcJk8d77QtOe7Wo4ox7ff1lQYoNNAb6jwcY1ncdoy2e9wQZzvNy7ODZCYJkK8kzmcAnAg==",
|
||||
"license": "BSD-3-Clause",
|
||||
"dependencies": {
|
||||
"@hapi/hoek": "^9.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@humanfs/core": {
|
||||
"version": "0.19.1",
|
||||
"resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz",
|
||||
@ -404,6 +421,51 @@
|
||||
"sparse-bitfield": "^3.0.3"
|
||||
}
|
||||
},
|
||||
"node_modules/@noble/ciphers": {
|
||||
"version": "0.5.3",
|
||||
"resolved": "https://registry.npmjs.org/@noble/ciphers/-/ciphers-0.5.3.tgz",
|
||||
"integrity": "sha512-B0+6IIHiqEs3BPMT0hcRmHvEj2QHOLu+uwt+tqDDeVd0oyVzh7BPrDcPjRnV1PV/5LaknXJJQvOuRGR0zQJz+w==",
|
||||
"license": "MIT",
|
||||
"funding": {
|
||||
"url": "https://paulmillr.com/funding/"
|
||||
}
|
||||
},
|
||||
"node_modules/@noble/curves": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.2.0.tgz",
|
||||
"integrity": "sha512-oYclrNgRaM9SsBUBVbb8M6DTV7ZHRTKugureoYEncY5c65HOmRzvSiTE3y5CYaPYJA/GVkrhXEoF0M3Ya9PMnw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@noble/hashes": "1.3.2"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://paulmillr.com/funding/"
|
||||
}
|
||||
},
|
||||
"node_modules/@noble/curves/node_modules/@noble/hashes": {
|
||||
"version": "1.3.2",
|
||||
"resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.3.2.tgz",
|
||||
"integrity": "sha512-MVC8EAQp7MvEcm30KWENFjgR+Mkmf+D189XJTkFIlwohU5hcBbn1ZkKq7KVTi2Hme3PMGF390DaL52beVrIihQ==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 16"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://paulmillr.com/funding/"
|
||||
}
|
||||
},
|
||||
"node_modules/@noble/hashes": {
|
||||
"version": "1.3.1",
|
||||
"resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.3.1.tgz",
|
||||
"integrity": "sha512-EbqwksQwz9xDRGfDST86whPBgM65E0OH/pCgqW0GBVzO22bNE+NuIbeTb714+IfSjU3aRk47EUvXIb5bTsenKA==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 16"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://paulmillr.com/funding/"
|
||||
}
|
||||
},
|
||||
"node_modules/@nodelib/fs.scandir": {
|
||||
"version": "2.1.5",
|
||||
"resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz",
|
||||
@ -702,6 +764,57 @@
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@scure/base": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/@scure/base/-/base-1.1.1.tgz",
|
||||
"integrity": "sha512-ZxOhsSyxYwLJj3pLZCefNitxsj093tb2vq90mp2txoYeBqbcjDjqFhyM8eUjq/uFm6zJ+mUuqxlS2FkuSY1MTA==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "individual",
|
||||
"url": "https://paulmillr.com/funding/"
|
||||
}
|
||||
],
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@scure/bip32": {
|
||||
"version": "1.3.1",
|
||||
"resolved": "https://registry.npmjs.org/@scure/bip32/-/bip32-1.3.1.tgz",
|
||||
"integrity": "sha512-osvveYtyzdEVbt3OfwwXFr4P2iVBL5u1Q3q4ONBfDY/UpOuXmOlbgwc1xECEboY8wIays8Yt6onaWMUdUbfl0A==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@noble/curves": "~1.1.0",
|
||||
"@noble/hashes": "~1.3.1",
|
||||
"@scure/base": "~1.1.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://paulmillr.com/funding/"
|
||||
}
|
||||
},
|
||||
"node_modules/@scure/bip32/node_modules/@noble/curves": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.1.0.tgz",
|
||||
"integrity": "sha512-091oBExgENk/kGj3AZmtBDMpxQPDtxQABR2B9lb1JbVTs6ytdzZNwvhxQ4MWasRNEzlbEH8jCWFCwhF/Obj5AA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@noble/hashes": "1.3.1"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://paulmillr.com/funding/"
|
||||
}
|
||||
},
|
||||
"node_modules/@scure/bip39": {
|
||||
"version": "1.2.1",
|
||||
"resolved": "https://registry.npmjs.org/@scure/bip39/-/bip39-1.2.1.tgz",
|
||||
"integrity": "sha512-Z3/Fsz1yr904dduJD0NpiyRHhRYHdcnyh73FZWiV+/qhWi83wNJ3NWolYqCEN+ZWsUz2TWwajJggcRE9r1zUYg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@noble/hashes": "~1.3.0",
|
||||
"@scure/base": "~1.1.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://paulmillr.com/funding/"
|
||||
}
|
||||
},
|
||||
"node_modules/@sec-ant/readable-stream": {
|
||||
"version": "0.4.1",
|
||||
"resolved": "https://registry.npmjs.org/@sec-ant/readable-stream/-/readable-stream-0.4.1.tgz",
|
||||
@ -1435,6 +1548,27 @@
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@sideway/address": {
|
||||
"version": "4.1.5",
|
||||
"resolved": "https://registry.npmjs.org/@sideway/address/-/address-4.1.5.tgz",
|
||||
"integrity": "sha512-IqO/DUQHUkPeixNQ8n0JA6102hT9CmaljNTPmQ1u8MEhBo/R4Q8eKLN/vGZxuebwOroDB4cbpjheD4+/sKFK4Q==",
|
||||
"license": "BSD-3-Clause",
|
||||
"dependencies": {
|
||||
"@hapi/hoek": "^9.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@sideway/formula": {
|
||||
"version": "3.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@sideway/formula/-/formula-3.0.1.tgz",
|
||||
"integrity": "sha512-/poHZJJVjx3L+zVD6g9KgHfYnb443oi7wLu/XKojDviHy6HOEOA6z1Trk5aR1dGcmPenJEgb2sK2I80LeS3MIg==",
|
||||
"license": "BSD-3-Clause"
|
||||
},
|
||||
"node_modules/@sideway/pinpoint": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@sideway/pinpoint/-/pinpoint-2.0.0.tgz",
|
||||
"integrity": "sha512-RNiOoTPkptFtSVzQevY/yWtZwf/RxyVnPy/OcA9HBM3MlGDnBEYL5B41H0MTn0Uec8Hi+2qUtTfG2WWZBmMejQ==",
|
||||
"license": "BSD-3-Clause"
|
||||
},
|
||||
"node_modules/@sindresorhus/is": {
|
||||
"version": "2.1.1",
|
||||
"resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-2.1.1.tgz",
|
||||
@ -4947,6 +5081,19 @@
|
||||
"node": ">= 0.6.0"
|
||||
}
|
||||
},
|
||||
"node_modules/joi": {
|
||||
"version": "17.13.3",
|
||||
"resolved": "https://registry.npmjs.org/joi/-/joi-17.13.3.tgz",
|
||||
"integrity": "sha512-otDA4ldcIx+ZXsKHWmp0YizCweVRZG96J10b0FevjfuncLO1oX59THoAmHkNubYJ+9gWsYsp5k8v4ib6oDv1fA==",
|
||||
"license": "BSD-3-Clause",
|
||||
"dependencies": {
|
||||
"@hapi/hoek": "^9.3.0",
|
||||
"@hapi/topo": "^5.1.0",
|
||||
"@sideway/address": "^4.1.5",
|
||||
"@sideway/formula": "^3.0.1",
|
||||
"@sideway/pinpoint": "^2.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/js-tokens": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
|
||||
@ -6262,6 +6409,38 @@
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/nostr-tools": {
|
||||
"version": "2.11.1",
|
||||
"resolved": "https://registry.npmjs.org/nostr-tools/-/nostr-tools-2.11.1.tgz",
|
||||
"integrity": "sha512-+Oj5t+behIkU9kh3go5wg8Aa5oR7euBU9gOItUNapJe5Gaa+KPzMuTIN+rMRK3DaZ4Zt6RM4kR/ddwstzGKf7g==",
|
||||
"license": "Unlicense",
|
||||
"dependencies": {
|
||||
"@noble/ciphers": "^0.5.1",
|
||||
"@noble/curves": "1.2.0",
|
||||
"@noble/hashes": "1.3.1",
|
||||
"@scure/base": "1.1.1",
|
||||
"@scure/bip32": "1.3.1",
|
||||
"@scure/bip39": "1.2.1"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"nostr-wasm": "0.1.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"typescript": ">=5.0.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"typescript": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/nostr-wasm": {
|
||||
"version": "0.1.0",
|
||||
"resolved": "https://registry.npmjs.org/nostr-wasm/-/nostr-wasm-0.1.0.tgz",
|
||||
"integrity": "sha512-78BTryCLcLYv96ONU8Ws3Q1JzjlAt+43pWQhIl86xZmWeegYCNLPml7yQ+gG3vR6V5h4XGj+TxO+SS5dsThQIA==",
|
||||
"license": "MIT",
|
||||
"optional": true
|
||||
},
|
||||
"node_modules/npm": {
|
||||
"version": "10.9.2",
|
||||
"resolved": "https://registry.npmjs.org/npm/-/npm-10.9.2.tgz",
|
||||
@ -11277,7 +11456,7 @@
|
||||
"version": "5.8.2",
|
||||
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.2.tgz",
|
||||
"integrity": "sha512-aJn6wq13/afZp/jT9QZmwEjDqqvSGp1VT5GVg+f/t6/oVyrgXM6BY1h9BRh/O5p3PlUPAe+WuiEZOmb/49RqoQ==",
|
||||
"dev": true,
|
||||
"devOptional": true,
|
||||
"license": "Apache-2.0",
|
||||
"bin": {
|
||||
"tsc": "bin/tsc",
|
||||
|
@ -30,7 +30,9 @@
|
||||
"dotenv": "^16.4.7",
|
||||
"express": "^4.21.2",
|
||||
"i18n-iso-countries": "^7.14.0",
|
||||
"mongodb": "^6.15.0"
|
||||
"joi": "^17.13.3",
|
||||
"mongodb": "^6.15.0",
|
||||
"nostr-tools": "^2.11.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@eslint/js": "^9.23.0",
|
||||
|
@ -4,7 +4,7 @@ import { UserRole } from '../types'
|
||||
export class User {
|
||||
constructor(
|
||||
public name: string,
|
||||
public npub: string[],
|
||||
public npub: string | string[],
|
||||
public role: UserRole,
|
||||
public id?: ObjectId
|
||||
) {}
|
||||
|
@ -2,6 +2,8 @@
|
||||
import express, { Request, Response } from 'express'
|
||||
import { collections } from '../services/database.service'
|
||||
import { User } from '../models'
|
||||
import { userValidation } from '../utils'
|
||||
import Joi from 'joi'
|
||||
|
||||
// Global Config
|
||||
export const usersRouter = express.Router()
|
||||
@ -26,7 +28,35 @@ usersRouter.get('/', async (_req: Request, res: Response) => {
|
||||
// POST
|
||||
usersRouter.post('/', async (req: Request, res: Response) => {
|
||||
try {
|
||||
const newUser = req.body as User
|
||||
const {
|
||||
error,
|
||||
value: newUser
|
||||
}: { error: Joi.ValidationError | undefined; value: User } = userValidation(
|
||||
req.body
|
||||
)
|
||||
|
||||
if (error) {
|
||||
throw error.details[0].message
|
||||
}
|
||||
|
||||
if (typeof newUser.npub === 'string') {
|
||||
const users = await collections.users
|
||||
?.find({ npub: newUser.npub })
|
||||
.toArray()
|
||||
|
||||
if (users?.length) {
|
||||
throw new Error('user with provided "npub" exists')
|
||||
}
|
||||
} else {
|
||||
for (const npub of newUser.npub) {
|
||||
const users = await collections.users?.find({ npub }).toArray()
|
||||
|
||||
if (users?.length) {
|
||||
throw new Error('user with provided "npub" exists')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const result = await collections.users?.insertOne(newUser)
|
||||
|
||||
if (result) {
|
||||
@ -41,6 +71,8 @@ usersRouter.post('/', async (req: Request, res: Response) => {
|
||||
|
||||
if (error instanceof Error) {
|
||||
res.status(400).send(error.message)
|
||||
} else if (typeof error === 'string') {
|
||||
res.status(400).send(error)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
2
src/utils/index.ts
Normal file
2
src/utils/index.ts
Normal file
@ -0,0 +1,2 @@
|
||||
export * from './validation'
|
||||
export * from './nostr'
|
34
src/utils/nostr.ts
Normal file
34
src/utils/nostr.ts
Normal file
@ -0,0 +1,34 @@
|
||||
import { nip19 } from 'nostr-tools'
|
||||
|
||||
/**
|
||||
* NPUB provided - it will convert NPUB to HEX
|
||||
* HEX provided - it will return HEX
|
||||
*
|
||||
* @param pubKey in NPUB, HEX format
|
||||
* @returns HEX format
|
||||
*/
|
||||
export const npubToHex = (pubKey: string): string | null => {
|
||||
// If key is NPUB
|
||||
if (pubKey.startsWith('npub1')) {
|
||||
try {
|
||||
return nip19.decode(pubKey).data as string
|
||||
} catch (err) {
|
||||
console.log(Error(`Error converting npub to hex. Error: ${err}`))
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
// valid hex key
|
||||
if (validateHex(pubKey)) return pubKey
|
||||
|
||||
// Not a valid hex key
|
||||
return null
|
||||
}
|
||||
|
||||
/**
|
||||
* @param hexKey hex private or public key
|
||||
* @returns whether or not is key valid
|
||||
*/
|
||||
export const validateHex = (hexKey: string) => {
|
||||
return hexKey.match(/^[a-f0-9]{64}$/)
|
||||
}
|
1
src/utils/validation/index.ts
Normal file
1
src/utils/validation/index.ts
Normal file
@ -0,0 +1 @@
|
||||
export * from './user'
|
29
src/utils/validation/user.ts
Normal file
29
src/utils/validation/user.ts
Normal file
@ -0,0 +1,29 @@
|
||||
import Joi from 'joi'
|
||||
import { UserRole } from '../../types'
|
||||
import { npubToHex, validateHex } from '../nostr'
|
||||
|
||||
const npubValidation = (value: unknown, helper: Joi.CustomHelpers<unknown>) => {
|
||||
const hex = npubToHex(value as string)
|
||||
|
||||
if (!hex || !validateHex(hex)) {
|
||||
return helper.message({
|
||||
custom: Joi.expression('"npub" contains an invalid value')
|
||||
})
|
||||
}
|
||||
|
||||
return hex
|
||||
}
|
||||
|
||||
export const userValidation = (data: unknown): Joi.ValidationResult =>
|
||||
Joi.object({
|
||||
name: Joi.string().not('').required(),
|
||||
npub: Joi.alternatives()
|
||||
.try(
|
||||
Joi.array().items(Joi.string().custom(npubValidation)),
|
||||
Joi.string().not('').custom(npubValidation)
|
||||
)
|
||||
.required(),
|
||||
role: Joi.string()
|
||||
.valid(...Object.values(UserRole))
|
||||
.required()
|
||||
}).validate(data)
|
Loading…
x
Reference in New Issue
Block a user