Skip to main content
Glama
setpassword.test.ts9.61 kB
// SPDX-FileCopyrightText: Copyright Orangebot, Inc. and Medplum contributors // SPDX-License-Identifier: Apache-2.0 import { SendEmailCommand, SESv2Client } from '@aws-sdk/client-sesv2'; import { badRequest, createReference } from '@medplum/core'; import type { UserSecurityRequest } from '@medplum/fhirtypes'; import { randomUUID } from 'crypto'; import express from 'express'; import { pwnedPassword } from 'hibp'; import { simpleParser } from 'mailparser'; import fetch from 'node-fetch'; import request from 'supertest'; import { initApp, shutdownApp } from '../app'; import { loadTestConfig } from '../config/loader'; import { getSystemRepo } from '../fhir/repo'; import { generateSecret } from '../oauth/keys'; import { setupPwnedPasswordMock, setupRecaptchaMock, withTestContext } from '../test.setup'; import { registerNew } from './register'; jest.mock('@aws-sdk/client-sesv2'); jest.mock('hibp'); jest.mock('node-fetch'); const app = express(); describe('Set Password', () => { beforeAll(async () => { const config = await loadTestConfig(); config.emailProvider = 'awsses'; await initApp(app, config); }); afterAll(async () => { await shutdownApp(); }); beforeEach(() => { (SESv2Client as unknown as jest.Mock).mockClear(); (SendEmailCommand as unknown as jest.Mock).mockClear(); (fetch as unknown as jest.Mock).mockClear(); (pwnedPassword as unknown as jest.Mock).mockClear(); setupPwnedPasswordMock(pwnedPassword as unknown as jest.Mock, 0); setupRecaptchaMock(fetch as unknown as jest.Mock, true); }); test('Success', async () => { const email = `george${randomUUID()}@example.com`; const res = await withTestContext(() => registerNew({ projectName: 'Set Password Project', firstName: 'George', lastName: 'Washington', email, password: 'password!@#', scope: 'openid profile email', }) ); expect(res).toBeDefined(); const res2 = await request(app).post('/auth/resetpassword').type('json').send({ email, recaptchaToken: 'xyz', }); expect(res2.status).toBe(200); expect(SESv2Client).toHaveBeenCalledTimes(1); expect(SendEmailCommand).toHaveBeenCalledTimes(1); const userInfoRes1 = await request(app).get('/oauth2/userinfo').set('Authorization', `Bearer ${res.accessToken}`); expect(userInfoRes1.status).toBe(200); expect(userInfoRes1.body).toMatchObject({ email, email_verified: false, }); const args = (SendEmailCommand as unknown as jest.Mock).mock.calls[0][0]; const parsed = await simpleParser(args.Content.Raw.Data); const content = parsed.text as string; const url = /(https?:\/\/[^\s]+)/g.exec(content)?.[0] as string; const paths = url.split('/'); const id = paths.at(-2); const secret = paths.at(-1); const res3 = await request(app).post('/auth/setpassword').type('json').send({ id, secret, password: 'my-new-password', }); expect(res3.status).toBe(200); // Make sure that the user can login with the new password const res4 = await request(app).post('/auth/login').type('json').send({ email: email, password: 'my-new-password', scope: 'openid', }); expect(res4.status).toBe(200); // Make sure that the PCR cannot be used again const res5 = await request(app).post('/auth/setpassword').type('json').send({ id, secret, password: 'bad-guys-trying-to-reuse-code', }); expect(res5.status).toBe(400); const userInfoRes2 = await request(app).get('/oauth2/userinfo').set('Authorization', `Bearer ${res.accessToken}`); expect(userInfoRes2.status).toBe(200); expect(userInfoRes2.body).toMatchObject({ email, email_verified: true, }); }); test('UserSecurityRequest', async () => { const email = `george${randomUUID()}@example.com`; const { user, project } = await withTestContext(() => registerNew({ projectName: 'Set Password Project', firstName: 'George', lastName: 'Washington', email, password: 'password!@#', scope: 'openid profile email', }) ); const usr = await withTestContext(async () => getSystemRepo().createResource<UserSecurityRequest>({ resourceType: 'UserSecurityRequest', meta: { project: project.id, }, type: 'reset', user: createReference(user), secret: generateSecret(16), }) ); const res3 = await request(app).post('/auth/setpassword').type('json').send({ id: usr.id, secret: usr.secret, password: 'my-new-password', }); expect(res3.status).toBe(200); // Make sure that the user can login with the new password const res4 = await request(app).post('/auth/login').type('json').send({ email: email, password: 'my-new-password', scope: 'openid', }); expect(res4.status).toBe(200); }); test('UserSecurityRequest invalid type', async () => { const email = `george${randomUUID()}@example.com`; const { user, project } = await withTestContext(() => registerNew({ projectName: 'Set Password Project', firstName: 'George', lastName: 'Washington', email, password: 'password!@#', scope: 'openid profile email', }) ); const usr = await withTestContext(async () => getSystemRepo().createResource<UserSecurityRequest>({ resourceType: 'UserSecurityRequest', meta: { project: project.id, }, type: 'verify-email', // Invalid type user: createReference(user), secret: generateSecret(16), }) ); const res3 = await request(app).post('/auth/setpassword').type('json').send({ id: usr.id, secret: usr.secret, password: 'my-new-password', }); expect(res3.status).toBe(400); }); test('Wrong secret', async () => { const email = `george${randomUUID()}@example.com`; const res = await request(app).post('/auth/newuser').type('json').send({ firstName: 'George', lastName: 'Washington', email, password: 'password!@#', recaptchaToken: 'xyz', }); expect(res.status).toBe(200); const res2 = await request(app).post('/auth/resetpassword').type('json').send({ email, recaptchaToken: 'xyz', }); expect(res2.status).toBe(200); expect(SESv2Client).toHaveBeenCalledTimes(1); expect(SendEmailCommand).toHaveBeenCalledTimes(1); const args = (SendEmailCommand as unknown as jest.Mock).mock.calls[0][0]; const parsed = await simpleParser(args.Content.Raw.Data); const content = parsed.text as string; const url = /(https?:\/\/[^\s]+)/g.exec(content)?.[0] as string; const paths = url.split('/'); const id = paths[paths.length - 2]; const res3 = await request(app).post('/auth/setpassword').type('json').send({ id, secret: 'WRONG!', password: 'my-new-password', }); expect(res3.status).toBe(400); }); test('Breached password', async () => { const email = `george${randomUUID()}@example.com`; const res = await request(app).post('/auth/newuser').type('json').send({ firstName: 'George', lastName: 'Washington', email, password: 'password!@#', recaptchaToken: 'xyz', }); expect(res.status).toBe(200); const res2 = await request(app).post('/auth/resetpassword').type('json').send({ email, recaptchaToken: 'xyz', }); expect(res2.status).toBe(200); expect(SESv2Client).toHaveBeenCalledTimes(1); expect(SendEmailCommand).toHaveBeenCalledTimes(1); const args = (SendEmailCommand as unknown as jest.Mock).mock.calls[0][0]; const parsed = await simpleParser(args.Content.Raw.Data); const content = parsed.text as string; const url = /(https?:\/\/[^\s]+)/g.exec(content)?.[0] as string; const paths = url.split('/'); const id = paths.at(-2); const secret = paths.at(-1); // Mock the pwnedPassword function to return "1", meaning the password is breached. setupPwnedPasswordMock(pwnedPassword as unknown as jest.Mock, 1); const res3 = await request(app).post('/auth/setpassword').type('json').send({ id, secret, password: 'breached', }); expect(res3.status).toBe(400); expect(res3.body).toMatchObject(badRequest('Password found in breach database', 'password')); }); test('Missing id', async () => { const res = await request(app) .post('/auth/setpassword') .type('json') .send({ id: '', secret: generateSecret(16), password: 'my-new-password', }); expect(res.status).toBe(400); }); test('Missing secret', async () => { const res = await request(app).post('/auth/setpassword').type('json').send({ id: randomUUID(), secret: '', password: 'my-new-password', }); expect(res.status).toBe(400); }); test('Missing password', async () => { const res = await request(app) .post('/auth/setpassword') .type('json') .send({ id: randomUUID(), secret: generateSecret(16), password: '', }); expect(res.status).toBe(400); }); test('Not found', async () => { const res = await request(app) .post('/auth/setpassword') .type('json') .send({ id: randomUUID(), secret: generateSecret(16), password: 'my-new-password', }); expect(res.status).toBe(404); }); });

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/medplum/medplum'

If you have feedback or need assistance with the MCP directory API, please join our Discord server