Skip to main content
Glama
rotatesecret.test.ts6.55 kB
// SPDX-FileCopyrightText: Copyright Orangebot, Inc. and Medplum contributors // SPDX-License-Identifier: Apache-2.0 import { ContentType } from '@medplum/core'; import type { ClientApplication, Parameters } from '@medplum/fhirtypes'; import express from 'express'; import request from 'supertest'; import { initApp, shutdownApp } from '../../app'; import { loadTestConfig } from '../../config/loader'; import { createTestProject } from '../../test.setup'; describe('ClientApplication $rotate-secret', () => { const app = express(); beforeAll(async () => { const config = await loadTestConfig(); await initApp(app, config); }); afterAll(async () => { await shutdownApp(); }); test('Secret can be changed directly', async () => { const { client, accessToken } = await createTestProject({ withAccessToken: true, withClient: true, membership: { admin: true }, }); const res = await request(app) .put(`/fhir/R4/ClientApplication/${client.id}`) .set('Authorization', 'Bearer ' + accessToken) .set('Content-Type', ContentType.FHIR_JSON) .set('X-Medplum', 'extended') .send({ ...client, secret: 'foo' } satisfies ClientApplication); expect(res.status).toBe(200); const updated = res.body as ClientApplication; expect(updated.secret).toEqual('foo'); }); test('Secret is changed to new value', async () => { const { client, accessToken } = await createTestProject({ withAccessToken: true, withClient: true, membership: { admin: true }, }); const res = await request(app) .post(`/fhir/R4/ClientApplication/${client.id}/$rotate-secret`) .set('Authorization', 'Bearer ' + accessToken) .set('Content-Type', ContentType.FHIR_JSON) .set('X-Medplum', 'extended') .send({ resourceType: 'Parameters', parameter: [{ name: 'secret', valueString: client.secret }], } satisfies Parameters); expect(res.status).toBe(200); const updated = res.body as ClientApplication; expect(updated.secret).toBeDefined(); expect(updated.secret).not.toStrictEqual(client.secret); expect(updated.retiringSecret).toStrictEqual(client.secret); }); test('Secret parameter must match existing', async () => { const { client, accessToken } = await createTestProject({ withAccessToken: true, withClient: true, membership: { admin: true }, }); const res = await request(app) .post(`/fhir/R4/ClientApplication/${client.id}/$rotate-secret`) .set('Authorization', 'Bearer ' + accessToken) .set('Content-Type', ContentType.FHIR_JSON) .set('X-Medplum', 'extended') .send({ resourceType: 'Parameters', parameter: [{ name: 'secret', valueString: 'incorrect' }], } satisfies Parameters); expect(res.status).toBe(400); expect(res.body.issue?.[0]?.code).toStrictEqual('invalid'); }); test('Remove retired secret', async () => { const { client, accessToken } = await createTestProject({ withAccessToken: true, withClient: true, membership: { admin: true }, }); const res = await request(app) .post(`/fhir/R4/ClientApplication/${client.id}/$rotate-secret`) .set('Authorization', 'Bearer ' + accessToken) .set('Content-Type', ContentType.FHIR_JSON) .set('X-Medplum', 'extended') .send({ resourceType: 'Parameters', parameter: [{ name: 'secret', valueString: client.secret }], } satisfies Parameters); expect(res.status).toBe(200); const updated = res.body as ClientApplication; expect(updated.secret).toBeDefined(); expect(updated.secret).not.toStrictEqual(client.secret); expect(updated.retiringSecret).toStrictEqual(client.secret); const res2 = await request(app) .post(`/fhir/R4/ClientApplication/${client.id}/$rotate-secret`) .set('Authorization', 'Bearer ' + accessToken) .set('Content-Type', ContentType.FHIR_JSON) .set('X-Medplum', 'extended') .send({ resourceType: 'Parameters', parameter: [{ name: 'retiringSecret', valueString: updated.retiringSecret }], } satisfies Parameters); expect(res2.status).toBe(200); const retired = res2.body as ClientApplication; expect(retired.secret).toStrictEqual(updated.secret); expect(retired.retiringSecret).toBeUndefined(); }); test('Access denied for non-admin user', async () => { const { client, accessToken } = await createTestProject({ withAccessToken: true, withClient: true, }); const res = await request(app) .post(`/fhir/R4/ClientApplication/${client.id}/$rotate-secret`) .set('Authorization', 'Bearer ' + accessToken) .set('Content-Type', ContentType.FHIR_JSON) .set('X-Medplum', 'extended') .send({ resourceType: 'Parameters', parameter: [{ name: 'secret', valueString: client.secret }], } satisfies Parameters); expect(res.status).toBe(403); expect(res.body.issue?.[0]?.code).toStrictEqual('forbidden'); }); test('Secret must be provided', async () => { const { client, accessToken } = await createTestProject({ withAccessToken: true, withClient: true, membership: { admin: true }, }); const res = await request(app) .post(`/fhir/R4/ClientApplication/${client.id}/$rotate-secret`) .set('Authorization', 'Bearer ' + accessToken) .set('Content-Type', ContentType.FHIR_JSON) .set('X-Medplum', 'extended') .send({ resourceType: 'Parameters', parameter: [], } satisfies Parameters); expect(res.status).toBe(400); expect(res.body.issue?.[0]?.code).toStrictEqual('invalid'); }); test('Only one secret can be provided', async () => { const { client, accessToken } = await createTestProject({ withAccessToken: true, withClient: true, membership: { admin: true }, }); const res = await request(app) .post(`/fhir/R4/ClientApplication/${client.id}/$rotate-secret`) .set('Authorization', 'Bearer ' + accessToken) .set('Content-Type', ContentType.FHIR_JSON) .set('X-Medplum', 'extended') .send({ resourceType: 'Parameters', parameter: [ { name: 'secret', valueString: client.secret }, { name: 'retiringSecret', valueString: client.secret }, ], } satisfies Parameters); expect(res.status).toBe(400); expect(res.body.issue?.[0]?.code).toStrictEqual('invalid'); }); });

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