diff --git a/src/auth/operations/me.ts b/src/auth/operations/me.ts index 5947ac9aa74..996a7b07733 100644 --- a/src/auth/operations/me.ts +++ b/src/auth/operations/me.ts @@ -1,3 +1,4 @@ +import url from 'url'; import jwt from 'jsonwebtoken'; import { PayloadRequest } from '../../express/types'; import getExtractJWT from '../getExtractJWT'; @@ -26,16 +27,24 @@ async function me({ }; if (req.user) { - const user = { ...req.user }; + const parsedURL = url.parse(req.originalUrl); + const isGraphQL = parsedURL.pathname === `/api${req.payload.config.routes.graphQL}`; - if (user.collection !== collection.config.slug) { + const user = await req.payload.findByID({ + id: req.user.id, + collection: collection.config.slug, + req, + depth: isGraphQL ? 0 : collection.config.auth.depth, + overrideAccess: false, + showHiddenFields: false, + }) as User; + + if (req.user.collection !== collection.config.slug) { return { user: null, }; } - delete user.collection; - response = { user, collection: req.user.collection, diff --git a/src/auth/strategies/jwt.ts b/src/auth/strategies/jwt.ts index ecd32db9b0a..10c2aa6d42f 100644 --- a/src/auth/strategies/jwt.ts +++ b/src/auth/strategies/jwt.ts @@ -21,8 +21,8 @@ export default ({ secret, config, collections }: Payload): PassportStrategy => { try { const collection = collections[token.collection]; - const parsedURL = url.parse(req.url); - const isGraphQL = parsedURL.pathname === config.routes.graphQL; + const parsedURL = url.parse(req.originalUrl); + const isGraphQL = parsedURL.pathname === `/api${req.payload.config.routes.graphQL}`; const user = await req.payload.findByID({ id: token.id, diff --git a/test/auth/config.ts b/test/auth/config.ts index 2c4d3727139..278bb26ff92 100644 --- a/test/auth/config.ts +++ b/test/auth/config.ts @@ -36,6 +36,15 @@ export default buildConfigWithDefaults({ }, }, fields: [ + { + name: 'adminOnlyField', + type: 'text', + access: { + read: ({ req: { user: { roles = [] } } }) => { + return roles.includes('admin'); + }, + }, + }, { name: 'roles', label: 'Role', diff --git a/test/auth/int.spec.ts b/test/auth/int.spec.ts index 75877094a2d..cc10629d9cb 100644 --- a/test/auth/int.spec.ts +++ b/test/auth/int.spec.ts @@ -451,6 +451,51 @@ describe('Auth', () => { expect(result).toBeTruthy(); }); + + + it('should enforce access control on the me route', async () => { + const user = await payload.create({ + collection: slug, + data: { + email: 'insecure@me.com', + password: 'test', + roles: ['admin'], + adminOnlyField: 'admin secret', + }, + }); + + const response = await fetch(`${apiUrl}/${slug}/login`, { + body: JSON.stringify({ + email: 'insecure@me.com', + password: 'test', + }), + headers, + method: 'post', + }); + + const data = await response.json(); + const adminMe = await fetch(`${apiUrl}/${slug}/me`, { + headers: { + Authorization: `JWT ${data.token}`, + }, + }).then((res) => res.json()); + expect(adminMe.user.adminOnlyField).toEqual('admin secret'); + + await payload.update({ + collection: slug, + id: user?.id || '', + data: { + roles: ['editor'], + }, + }); + + const editorMe = await fetch(`${apiUrl}/${slug}/me`, { + headers: { + Authorization: `JWT ${adminMe?.token}`, + }, + }).then((res) => res.json()); + expect(editorMe.user.adminOnlyField).toBeUndefined(); + }); }); describe('API Key', () => { diff --git a/test/auth/payload-types.ts b/test/auth/payload-types.ts index 5f1feda915d..abf92ad4b4c 100644 --- a/test/auth/payload-types.ts +++ b/test/auth/payload-types.ts @@ -1,26 +1,59 @@ /* tslint:disable */ +/* eslint-disable */ /** * This file was automatically generated by Payload. * DO NOT MODIFY IT BY HAND. Instead, modify your source Payload config, * and re-run `payload generate:types` to regenerate this file. */ -export interface Config {} -/** - * This interface was referenced by `Config`'s JSON-Schema - * via the `definition` "users". - */ +export interface Config { + collections: { + users: User; + 'api-keys': ApiKey; + }; + globals: {}; +} export interface User { id: string; + adminOnlyField?: string; roles: ('admin' | 'editor' | 'moderator' | 'user' | 'viewer')[]; + namedSaveToJWT?: string; + group?: { + liftedSaveToJWT?: string; + }; + groupSaveToJWT?: { + saveToJWTString?: string; + saveToJWTFalse?: string; + }; + saveToJWTTab: { + test?: string; + }; + tabSaveToJWTString: { + includedByDefault?: string; + }; + tabLiftedSaveToJWT?: string; + unnamedTabSaveToJWTString?: string; + unnamedTabSaveToJWTFalse?: string; + custom?: string; + updatedAt: string; + createdAt: string; enableAPIKey?: boolean; apiKey?: string; apiKeyIndex?: string; - email?: string; + email: string; resetPasswordToken?: string; resetPasswordExpiration?: string; + salt?: string; + hash?: string; loginAttempts?: number; lockUntil?: string; - createdAt: string; + password?: string; +} +export interface ApiKey { + id: string; updatedAt: string; + createdAt: string; + enableAPIKey?: boolean; + apiKey?: string; + apiKeyIndex?: string; }