mirror of
https://github.com/danny-avila/LibreChat.git
synced 2026-10-21 17:44:11 +00:00
📮 fix: Validate Invite Email Claims (#15542)
* 🔒 fix: Require the email getInvite claims to validate `findToken` builds its query from the fields it is given, so an absent email is a lookup by token alone: the invite matches, `checkInviteUser` deletes it, and the registration then fails on the missing field. The invite is consumed and no account is created. * 🎨 style: Sort spec imports by line length --------- Co-authored-by: lailson henrique <dev@iagiliza.com.br> Co-authored-by: Danny Avila <danny@librechat.ai>
This commit is contained in:
parent
bd31ecd129
commit
1c2796b978
2 changed files with 58 additions and 0 deletions
51
packages/api/src/auth/invite.spec.ts
Normal file
51
packages/api/src/auth/invite.spec.ts
Normal file
|
|
@ -0,0 +1,51 @@
|
|||
import mongoose from 'mongoose';
|
||||
import { MongoMemoryServer } from 'mongodb-memory-server';
|
||||
import { createMethods, createModels } from '@librechat/data-schemas';
|
||||
import { createInvite, getInvite } from './invite';
|
||||
|
||||
let mongoServer: MongoMemoryServer;
|
||||
let deps: Parameters<typeof getInvite>[2];
|
||||
|
||||
beforeAll(async () => {
|
||||
mongoServer = await MongoMemoryServer.create();
|
||||
await mongoose.connect(mongoServer.getUri());
|
||||
createModels(mongoose);
|
||||
|
||||
const { createToken, findToken } = createMethods(mongoose);
|
||||
deps = { createToken, findToken } as Parameters<typeof getInvite>[2];
|
||||
});
|
||||
|
||||
afterAll(async () => {
|
||||
await mongoose.disconnect();
|
||||
await mongoServer.stop();
|
||||
});
|
||||
|
||||
beforeEach(async () => {
|
||||
await mongoose.models.Token.deleteMany({});
|
||||
});
|
||||
|
||||
describe('getInvite', () => {
|
||||
it('returns the invite for the address it was issued to', async () => {
|
||||
const token = (await createInvite('pedro@example.com', deps)) as string;
|
||||
|
||||
await expect(getInvite(token, 'pedro@example.com', deps)).resolves.toMatchObject({
|
||||
email: 'pedro@example.com',
|
||||
});
|
||||
});
|
||||
|
||||
it('refuses an address the invite was not issued to', async () => {
|
||||
const token = (await createInvite('pedro@example.com', deps)) as string;
|
||||
|
||||
await expect(getInvite(token, 'someone.else@example.com', deps)).resolves.toMatchObject({
|
||||
error: true,
|
||||
});
|
||||
});
|
||||
|
||||
it('refuses a lookup carrying no address at all', async () => {
|
||||
const token = (await createInvite('pedro@example.com', deps)) as string;
|
||||
|
||||
await expect(getInvite(token, undefined as unknown as string, deps)).resolves.toMatchObject({
|
||||
error: true,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
@ -45,6 +45,13 @@ export async function getInvite(
|
|||
deps: InviteDeps,
|
||||
): Promise<unknown> {
|
||||
try {
|
||||
/** `findToken` builds its query from the fields it is given, so an absent email
|
||||
* is a lookup by token alone — which would match the invite and let the caller
|
||||
* consume it without ever proving the address. */
|
||||
if (!email) {
|
||||
throw new Error('Invite not found or email does not match');
|
||||
}
|
||||
|
||||
const token = decodeURIComponent(encodedToken);
|
||||
const hash = await hashToken(token);
|
||||
const invite = await deps.findToken({ token: hash, email });
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue