Add server side ownership enforcement for profile editing

This commit is contained in:
Kurt Hutten
2020-11-06 20:12:46 +11:00
parent c0cd79f48b
commit 9ab61924dc
4 changed files with 66 additions and 6 deletions

View File

@@ -1,4 +1,5 @@
// import { createUserInsecure } from 'src/services/users/users.js' import { createUserInsecure } from 'src/services/users/users.js'
import { db } from 'src/lib/db'
export const handler = async (req, _context) => { export const handler = async (req, _context) => {
const body = JSON.parse(req.body) const body = JSON.parse(req.body)
@@ -61,12 +62,27 @@ export const handler = async (req, _context) => {
// image: '', // image: '',
// bio: '' // bio: ''
// } // }
const generateUniqueUserName = async (seed, count = 0) => {
const isUnique = !(await db.user.findOne({
where: { userName: seed },
}))
if(isUnique) {
return seed
}
count += 1
const newSeed = count === 1 ? `${seed}_${count}` : seed.slice(0,-1) + count
return generateUniqueUserName(newSeed, count)
}
const userNameSeed = email.split('@')[0]
const userName = await generateUniqueUserName(userNameSeed) // TODO maybe come up with a better default userName?
const input = { const input = {
email, email,
bio: 'default bio' userName,
// full_name: user.user_metadata.full_name name: user.user_metadata && user.user_metadata.full_name,
id: user.id,
} }
// await createUserInsecure({input}) await createUserInsecure({input})
return { return {
statusCode: 200, statusCode: 200,

31
api/src/lib/owner.js Normal file
View File

@@ -0,0 +1,31 @@
import { AuthenticationError, ForbiddenError, parseJWT } from '@redwoodjs/api'
import { db } from 'src/lib/db'
export const requireOwnership = async ({ id, userName } = {}) => {
if (!context.currentUser) {
throw new AuthenticationError("You don't have permission to do that.")
}
if(!id && !userName) {
throw new ForbiddenError("You don't have access to do that.")
}
const netlifyUserId = context.currentUser?.sub
if(id && id !== netlifyUserId) {
throw new ForbiddenError("You don't own this resource.")
}
if(context.currentUser.roles?.includes('admin')) {
return
}
const user = await db.user.findOne({
where: { userName },
})
console.log(user, 'USER')
if(!user) {
throw new ForbiddenError("You don't own this resource.")
}
}

View File

@@ -1,35 +1,47 @@
import { db } from 'src/lib/db' import { db } from 'src/lib/db'
import { requireAuth } from 'src/lib/auth'
import { requireOwnership } from 'src/lib/owner'
export const users = () => { export const users = () => {
requireAuth({ role: 'admin' })
return db.user.findMany() return db.user.findMany()
} }
export const user = ({ id }) => { export const user = ({ id }) => {
requireAuth()
return db.user.findOne({ return db.user.findOne({
where: { id }, where: { id },
}) })
} }
export const userName = ({ userName }) => { export const userName = ({ userName }) => {
requireAuth()
return db.user.findOne({ return db.user.findOne({
where: { userName }, where: { userName },
}) })
} }
export const createUser = ({ input }) => { export const createUser = ({ input }) => {
requireAuth({ role: 'admin' })
createUserInsecure({input})
}
export const createUserInsecure = ({ input }) => {
return db.user.create({ return db.user.create({
data: input, data: input,
}) })
} }
export const updateUser = ({ id, input }) => { export const updateUser = ({ id, input }) => {
requireAuth()
return db.user.update({ return db.user.update({
data: input, data: input,
where: { id }, where: { id },
}) })
} }
export const updateUserByUserName = ({ userName, input }) => { export const updateUserByUserName = async ({ userName, input }) => {
requireAuth()
await requireOwnership({userName})
return db.user.update({ return db.user.update({
data: input, data: input,
where: { userName }, where: { userName },
@@ -37,6 +49,7 @@ export const updateUserByUserName = ({ userName, input }) => {
} }
export const deleteUser = ({ id }) => { export const deleteUser = ({ id }) => {
requireAuth({ role: 'admin' })
return db.user.delete({ return db.user.delete({
where: { id }, where: { id },
}) })

View File

@@ -47,7 +47,7 @@ const UserProfile = ({user, isEditable, loading, onSave, error}) => {
<h3 className="text-3xl text-gray-500 font-ropa-sans">Bio:</h3> <h3 className="text-3xl text-gray-500 font-ropa-sans">Bio:</h3>
<div name="description" className="markdown-overrides rounded-lg shadow-md bg-white p-12 my-6 min-h-md"> <div name="description" className="markdown-overrides rounded-lg shadow-md bg-white p-12 my-6 min-h-md">
<Editor <Editor
defaultValue={user.bio} defaultValue={user.bio || ''}
readOnly={!isEditable} readOnly={!isEditable}
onChange={(bioFn) => setInput({ onChange={(bioFn) => setInput({
...input, ...input,