Skip to main content
Glama
newuser.ts4.59 kB
// SPDX-FileCopyrightText: Copyright Orangebot, Inc. and Medplum contributors // SPDX-License-Identifier: Apache-2.0 import type { NewUserRequest, WithId } from '@medplum/core'; import { badRequest, normalizeOperationOutcome } from '@medplum/core'; import type { ClientApplication, User } from '@medplum/fhirtypes'; import type { Request, Response } from 'express'; import { body } from 'express-validator'; import { pwnedPassword } from 'hibp'; import { randomUUID } from 'node:crypto'; import { getConfig } from '../config/loader'; import { sendOutcome } from '../fhir/outcomes'; import { getSystemRepo } from '../fhir/repo'; import { globalLogger } from '../logger'; import { getUserByEmailInProject, getUserByEmailWithoutProject, tryLogin } from '../oauth/utils'; import { makeValidationMiddleware } from '../util/validator'; import { bcryptHashPassword } from './utils'; export const newUserValidator = makeValidationMiddleware([ body('firstName').isLength({ min: 1, max: 128 }).withMessage('First name must be between 1 and 128 characters'), body('lastName').isLength({ min: 1, max: 128 }).withMessage('Last name must be between 1 and 128 characters'), body('email') .isEmail() .withMessage('Valid email address between 3 and 72 characters is required') .isLength({ min: 3, max: 72 }) .withMessage('Valid email address between 3 and 72 characters is required'), body('password').isByteLength({ min: 8, max: 72 }).withMessage('Password must be between 8 and 72 characters'), ]); /** * Handles a HTTP request to /auth/newuser. * @param req - The HTTP request. * @param res - The HTTP response. */ export async function newUserHandler(req: Request, res: Response): Promise<void> { const config = getConfig(); if (config.registerEnabled === false) { // Explicitly check for "false" because the config value may be undefined sendOutcome(res, badRequest('Registration is disabled')); return; } const systemRepo = getSystemRepo(); let projectId = req.body.projectId as string | undefined; // If the user specifies a client ID, then make sure it is compatible with the project const clientId = req.body.clientId; let client: ClientApplication | undefined = undefined; if (clientId) { client = await systemRepo.readResource<ClientApplication>('ClientApplication', clientId); if (projectId) { if (client.meta?.project !== projectId) { sendOutcome(res, badRequest('Client and project do not match')); return; } } else { projectId = client.meta?.project; } } // If the user is a practitioner, then projectId should be undefined // If the user is a patient, then projectId must be set const email = req.body.email.toLowerCase(); let existingUser = undefined; if (req.body.projectId && req.body.projectId !== 'new') { existingUser = await getUserByEmailInProject(email, req.body.projectId); } else { existingUser = await getUserByEmailWithoutProject(email); } if (existingUser) { sendOutcome(res, badRequest('Email already registered', 'email')); return; } try { await createUser({ ...req.body, email } as NewUserRequest); const login = await tryLogin({ authMethod: 'password', clientId, projectId, scope: req.body.scope || 'openid', nonce: req.body.nonce || randomUUID(), codeChallenge: req.body.codeChallenge, codeChallengeMethod: req.body.codeChallengeMethod, email, password: req.body.password, remember: req.body.remember, remoteAddress: req.ip, userAgent: req.get('User-Agent'), allowNoMembership: true, }); res.status(200).json({ login: login.id }); } catch (err) { sendOutcome(res, normalizeOperationOutcome(err)); } } export async function createUser(request: Omit<NewUserRequest, 'recaptchaToken'>): Promise<WithId<User>> { const { firstName, lastName, email, password, projectId } = request; const numPwns = await pwnedPassword(password); if (numPwns > 0) { throw badRequest('Password found in breach database', 'password'); } globalLogger.info('User creation request received', { email }); const passwordHash = await bcryptHashPassword(password); const systemRepo = getSystemRepo(); const result = await systemRepo.createResource<User>({ resourceType: 'User', firstName, lastName, email, passwordHash, project: projectId && projectId !== 'new' ? { reference: `Project/${projectId}` } : undefined, }); globalLogger.info('User created', { id: result.id, email }); return result; }

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