mirror of
https://github.com/n8n-io/n8n.git
synced 2025-03-05 20:50:17 -08:00
⚡ Refactor POST /users
This commit is contained in:
parent
b8f03b252e
commit
134215d1cc
|
@ -138,6 +138,7 @@
|
||||||
"p-cancelable": "^2.0.0",
|
"p-cancelable": "^2.0.0",
|
||||||
"passport": "^0.5.0",
|
"passport": "^0.5.0",
|
||||||
"passport-cookie": "^1.0.9",
|
"passport-cookie": "^1.0.9",
|
||||||
|
"passport-http-header-strategy": "^1.1.0",
|
||||||
"passport-jwt": "^4.0.0",
|
"passport-jwt": "^4.0.0",
|
||||||
"pg": "^8.3.0",
|
"pg": "^8.3.0",
|
||||||
"prom-client": "^13.1.0",
|
"prom-client": "^13.1.0",
|
||||||
|
|
|
@ -1,12 +1,31 @@
|
||||||
import * as querystring from 'querystring';
|
import * as querystring from 'querystring';
|
||||||
// eslint-disable-next-line import/no-extraneous-dependencies
|
// eslint-disable-next-line import/no-extraneous-dependencies
|
||||||
import { pick } from 'lodash';
|
import { pick } from 'lodash';
|
||||||
|
import express = require('express');
|
||||||
|
import * as SwaggerParser from '@apidevtools/swagger-parser';
|
||||||
|
import { In } from 'typeorm';
|
||||||
|
// eslint-disable-next-line import/no-cycle
|
||||||
import { User } from '../databases/entities/User';
|
import { User } from '../databases/entities/User';
|
||||||
|
import type { Role } from '../databases/entities/Role';
|
||||||
|
// eslint-disable-next-line import/no-cycle
|
||||||
|
import { Db, InternalHooksManager } from '..';
|
||||||
|
// eslint-disable-next-line import/no-cycle
|
||||||
|
import { getInstanceBaseUrl } from '../UserManagement/UserManagementHelper';
|
||||||
|
// eslint-disable-next-line import/no-cycle
|
||||||
|
import * as UserManagementMailer from '../UserManagement/email';
|
||||||
|
|
||||||
interface IPaginationOffsetDecoded {
|
interface IPaginationOffsetDecoded {
|
||||||
offset: number;
|
offset: number;
|
||||||
limit: number;
|
limit: number;
|
||||||
}
|
}
|
||||||
|
export interface IMiddlewares {
|
||||||
|
[key: string]: [IMiddleware];
|
||||||
|
}
|
||||||
|
interface IMiddleware {
|
||||||
|
(req: express.Request, res: express.Response, next: express.NextFunction): void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type OperationID = 'getUsers' | 'getUser';
|
||||||
|
|
||||||
export const decodeCursor = (cursor: string): IPaginationOffsetDecoded => {
|
export const decodeCursor = (cursor: string): IPaginationOffsetDecoded => {
|
||||||
const data = JSON.parse(Buffer.from(cursor, 'base64').toString()) as string;
|
const data = JSON.parse(Buffer.from(cursor, 'base64').toString()) as string;
|
||||||
|
@ -40,7 +59,7 @@ export const getNextCursor = (
|
||||||
|
|
||||||
export const getSelectableProperties = (table: 'user' | 'role'): string[] => {
|
export const getSelectableProperties = (table: 'user' | 'role'): string[] => {
|
||||||
return {
|
return {
|
||||||
user: ['id', 'email', 'firstName', 'lastName', 'createdAt', 'updatedAt'],
|
user: ['id', 'email', 'firstName', 'lastName', 'createdAt', 'updatedAt', 'isPending'],
|
||||||
role: ['id', 'name', 'scope', 'createdAt', 'updatedAt'],
|
role: ['id', 'name', 'scope', 'createdAt', 'updatedAt'],
|
||||||
}[table];
|
}[table];
|
||||||
};
|
};
|
||||||
|
@ -54,3 +73,156 @@ export const clean = (users: User[], keepRole = false): Array<Partial<User>> =>
|
||||||
pick(user, getSelectableProperties('user').concat(keepRole ? ['globalRole'] : [])),
|
pick(user, getSelectableProperties('user').concat(keepRole ? ['globalRole'] : [])),
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const middlewareDefined = (operationId: OperationID, middlewares: IMiddlewares) =>
|
||||||
|
operationId && middlewares[operationId];
|
||||||
|
|
||||||
|
export const addMiddlewares = (
|
||||||
|
router: express.Router,
|
||||||
|
method: string,
|
||||||
|
routePath: string,
|
||||||
|
operationId: OperationID,
|
||||||
|
middlewares: IMiddlewares,
|
||||||
|
): void => {
|
||||||
|
if (middlewareDefined(operationId, middlewares)) {
|
||||||
|
routePath.replace(/\{([^}]+)}/g, ':$1');
|
||||||
|
switch (method) {
|
||||||
|
case 'get':
|
||||||
|
router.get(routePath, ...middlewares[operationId]);
|
||||||
|
break;
|
||||||
|
case 'post':
|
||||||
|
router.post(routePath, ...middlewares[operationId]);
|
||||||
|
break;
|
||||||
|
case 'put':
|
||||||
|
router.post(routePath, ...middlewares[operationId]);
|
||||||
|
break;
|
||||||
|
case 'delete':
|
||||||
|
router.post(routePath, ...middlewares[operationId]);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const addCustomMiddlewares = async (
|
||||||
|
apiController: express.Router,
|
||||||
|
openApiSpec: string,
|
||||||
|
middlewares: IMiddlewares,
|
||||||
|
): Promise<void> => {
|
||||||
|
const { paths = {} } = await SwaggerParser.parse(openApiSpec);
|
||||||
|
Object.entries(paths).forEach(([routePath, methods]) => {
|
||||||
|
Object.entries(methods).forEach(([method, data]) => {
|
||||||
|
const operationId: OperationID = (
|
||||||
|
data as {
|
||||||
|
'x-eov-operation-id': OperationID;
|
||||||
|
}
|
||||||
|
)['x-eov-operation-id'];
|
||||||
|
addMiddlewares(apiController, method, routePath, operationId, middlewares);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
export async function getGlobalMemberRole(): Promise<Role | undefined> {
|
||||||
|
return Db.collections.Role?.findOneOrFail({
|
||||||
|
name: 'member',
|
||||||
|
scope: 'global',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async function getUsersWithEmails(emails: string[]): Promise<User[] | undefined> {
|
||||||
|
return Db.collections.User?.find({
|
||||||
|
where: { email: In(emails) },
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getUsersToSaveAndInvite(
|
||||||
|
emails: string[],
|
||||||
|
): Promise<{ usersToSave: string[]; pendingUsers: User[] }> {
|
||||||
|
const users = await getUsersWithEmails(emails);
|
||||||
|
const usersInBody = emails;
|
||||||
|
const usersInDB = users?.map((user) => user.email);
|
||||||
|
const usersToSave = usersInBody.filter((email) => !usersInDB?.includes(email));
|
||||||
|
const userInDBWithoutPassword = users?.filter((user) => !user.password);
|
||||||
|
const pendingUsers = userInDBWithoutPassword ?? [];
|
||||||
|
return {
|
||||||
|
usersToSave,
|
||||||
|
pendingUsers,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function saveUsersWithRole(
|
||||||
|
users: string[],
|
||||||
|
role: Role,
|
||||||
|
tokenOwnerId: string,
|
||||||
|
): Promise<User[]> {
|
||||||
|
const savedUsers = await Db.transaction(async (transactionManager) => {
|
||||||
|
return Promise.all(
|
||||||
|
users.map(async (email) => {
|
||||||
|
const newUser = Object.assign(new User(), {
|
||||||
|
email,
|
||||||
|
globalRole: role.id,
|
||||||
|
});
|
||||||
|
const savedUser = await transactionManager.save<User>(newUser);
|
||||||
|
return savedUser;
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
void InternalHooksManager.getInstance().onUserInvite({
|
||||||
|
user_id: tokenOwnerId,
|
||||||
|
target_user_id: savedUsers.map((user) => user.id),
|
||||||
|
});
|
||||||
|
|
||||||
|
return savedUsers;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function invite(
|
||||||
|
users: Partial<User[]>,
|
||||||
|
mailer: UserManagementMailer.UserManagementMailer | undefined,
|
||||||
|
apiKeyOwnerId: string,
|
||||||
|
): Promise<Array<{ success?: boolean; id?: string }>> {
|
||||||
|
const baseUrl = getInstanceBaseUrl();
|
||||||
|
return Promise.all(
|
||||||
|
users.map(async (user) => {
|
||||||
|
const resp: { success?: boolean; id?: string } = {};
|
||||||
|
// eslint-disable-next-line @typescript-eslint/restrict-template-expressions
|
||||||
|
const inviteAcceptUrl = `${baseUrl}/signup?inviterId=${apiKeyOwnerId}&inviteeId=${user?.id}`;
|
||||||
|
const sentEmail = await mailer?.invite({
|
||||||
|
email: user!.email,
|
||||||
|
inviteAcceptUrl,
|
||||||
|
domain: baseUrl,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (sentEmail?.success) {
|
||||||
|
resp.success = true;
|
||||||
|
resp.id = user?.id;
|
||||||
|
} else {
|
||||||
|
resp.success = false;
|
||||||
|
resp.id = user?.id;
|
||||||
|
}
|
||||||
|
return resp;
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function inviteUsers(
|
||||||
|
users: Partial<User[]>,
|
||||||
|
mailer: UserManagementMailer.UserManagementMailer | undefined,
|
||||||
|
apiKeyOwnerId: string,
|
||||||
|
): Promise<void> {
|
||||||
|
const invitations = await invite(users, mailer, apiKeyOwnerId);
|
||||||
|
invitations.forEach((invitation) => {
|
||||||
|
if (!invitation.success) {
|
||||||
|
void InternalHooksManager.getInstance().onEmailFailed({
|
||||||
|
user_id: invitation.id as string,
|
||||||
|
message_type: 'New user invite',
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
void InternalHooksManager.getInstance().onUserTransactionalEmail({
|
||||||
|
user_id: invitation.id as string,
|
||||||
|
message_type: 'New user invite',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
|
@ -1,12 +1,65 @@
|
||||||
import { NextFunction, Request, Response } from 'express';
|
/* eslint-disable consistent-return */
|
||||||
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||||
|
import express = require('express');
|
||||||
|
import validator from 'validator';
|
||||||
|
import config = require('../../config');
|
||||||
|
import type { UserRequest } from '../requests';
|
||||||
|
|
||||||
const sayHello = (req: Request, res: Response, next: NextFunction): void => {
|
type Role = 'owner' | 'member';
|
||||||
console.log('hello');
|
|
||||||
console.log('se llamo esta vegra');
|
const instanceOwnerSetup = (
|
||||||
|
req: express.Request,
|
||||||
|
res: express.Response,
|
||||||
|
next: express.NextFunction,
|
||||||
|
): any => {
|
||||||
|
if (config.get('userManagement.isInstanceOwnerSetUp')) {
|
||||||
|
return next();
|
||||||
|
}
|
||||||
|
return res.status(400).json({ message: 'asasas' });
|
||||||
|
};
|
||||||
|
|
||||||
|
const emailSetup = (
|
||||||
|
req: express.Request,
|
||||||
|
res: express.Response,
|
||||||
|
next: express.NextFunction,
|
||||||
|
): any => {
|
||||||
|
if (config.get('userManagement.emails.mode')) {
|
||||||
|
return next();
|
||||||
|
}
|
||||||
|
return res.status(400).json({ message: 'asasas' });
|
||||||
|
};
|
||||||
|
|
||||||
|
const authorize =
|
||||||
|
(role: [Role]) =>
|
||||||
|
(req: express.Request, res: express.Response, next: express.NextFunction): any => {
|
||||||
|
const {
|
||||||
|
globalRole: { name: userRole },
|
||||||
|
} = req.user as { globalRole: { name: Role } };
|
||||||
|
if (role.includes(userRole)) {
|
||||||
|
return next();
|
||||||
|
}
|
||||||
|
return res.status(400).json({
|
||||||
|
message: 'asasas',
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const validEmail = (
|
||||||
|
req: UserRequest.Invite,
|
||||||
|
res: express.Response,
|
||||||
|
next: express.NextFunction,
|
||||||
|
): any => {
|
||||||
|
req.body.forEach((invite) => {
|
||||||
|
if (!validator.isEmail(invite.email)) {
|
||||||
|
return res.status(400).json({
|
||||||
|
message: `Request to send email invite(s) to user(s) failed because of an invalid email address: ${invite.email}`,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
next();
|
next();
|
||||||
};
|
};
|
||||||
|
|
||||||
export const middlewares = {
|
export const middlewares = {
|
||||||
getUsers: [sayHello],
|
createUsers: [instanceOwnerSetup, emailSetup, validEmail, authorize(['owner'])],
|
||||||
getUser: [sayHello],
|
getUsers: [instanceOwnerSetup, authorize(['owner'])],
|
||||||
|
getUser: [instanceOwnerSetup, authorize(['owner'])],
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,110 +1,72 @@
|
||||||
import { Application, Response } from 'express';
|
|
||||||
|
|
||||||
import * as OpenApiValidator from 'express-openapi-validator';
|
import * as OpenApiValidator from 'express-openapi-validator';
|
||||||
|
|
||||||
import path = require('path');
|
import path = require('path');
|
||||||
|
|
||||||
import express = require('express');
|
import express = require('express');
|
||||||
|
|
||||||
import { HttpError, OpenAPIV3 } from 'express-openapi-validator/dist/framework/types';
|
import { HttpError } from 'express-openapi-validator/dist/framework/types';
|
||||||
import * as bodyParser from 'body-parser';
|
import passport = require('passport');
|
||||||
import * as SwaggerParser from '@apidevtools/swagger-parser';
|
import { Strategy } from 'passport-http-header-strategy';
|
||||||
|
import { VerifiedCallback } from 'passport-jwt';
|
||||||
// eslint-disable-next-line import/no-cycle
|
// eslint-disable-next-line import/no-cycle
|
||||||
import { Db } from '../..';
|
import { Db } from '../..';
|
||||||
import config = require('../../../config');
|
|
||||||
|
|
||||||
import { middlewares } from '../middlewares';
|
import { middlewares } from '../middlewares';
|
||||||
|
// eslint-disable-next-line import/no-cycle
|
||||||
|
import { addCustomMiddlewares, IMiddlewares } from '../helpers';
|
||||||
|
|
||||||
export interface N8nApp {
|
export const publicApiController = (async (): Promise<express.Router> => {
|
||||||
app: Application;
|
const openApiSpec = path.join(__dirname, 'openapi.yml');
|
||||||
}
|
|
||||||
|
|
||||||
type OperationID = 'getUsers' | 'getUser' | undefined;
|
const apiController = express.Router();
|
||||||
|
|
||||||
export const publicApiController = express.Router();
|
apiController.use(express.json());
|
||||||
|
|
||||||
publicApiController.use(bodyParser.json());
|
passport.use(
|
||||||
|
new Strategy(
|
||||||
|
{ header: 'X-N8N-API-KEY', passReqToCallback: false },
|
||||||
|
async (token: string, done: VerifiedCallback) => {
|
||||||
|
const user = await Db.collections.User?.findOne({
|
||||||
|
where: {
|
||||||
|
apiKey: token,
|
||||||
|
},
|
||||||
|
relations: ['globalRole'],
|
||||||
|
});
|
||||||
|
|
||||||
void (async () => {
|
if (!user) {
|
||||||
const { paths = {} } = await SwaggerParser.parse(path.join(__dirname, 'openapi.yml'));
|
return done(null, false);
|
||||||
Object.entries(paths).forEach(([routePath, methods]) => {
|
|
||||||
Object.entries(methods).forEach(([method, data]) => {
|
|
||||||
const operationId: OperationID = (
|
|
||||||
data as {
|
|
||||||
'x-eov-operation-id': OperationID;
|
|
||||||
}
|
}
|
||||||
)['x-eov-operation-id'];
|
|
||||||
if (operationId) {
|
|
||||||
if (method === 'get') {
|
|
||||||
publicApiController.get(
|
|
||||||
routePath.replace(/\{([^}]+)}/g, ':$1'),
|
|
||||||
...middlewares[operationId],
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
publicApiController.use(
|
return done(null, user);
|
||||||
'/',
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
// add authentication middlewlares
|
||||||
|
apiController.use('/', passport.authenticate('header', { session: false }));
|
||||||
|
|
||||||
|
await addCustomMiddlewares(apiController, openApiSpec, middlewares as unknown as IMiddlewares);
|
||||||
|
|
||||||
|
apiController.use(
|
||||||
OpenApiValidator.middleware({
|
OpenApiValidator.middleware({
|
||||||
apiSpec: path.join(__dirname, 'openapi.yml'),
|
apiSpec: openApiSpec,
|
||||||
operationHandlers: path.join(__dirname),
|
operationHandlers: path.join(__dirname),
|
||||||
validateRequests: true,
|
validateRequests: true,
|
||||||
validateApiSpec: true,
|
validateApiSpec: true,
|
||||||
validateSecurity: {
|
validateSecurity: false,
|
||||||
handlers: {
|
|
||||||
// eslint-disable-next-line @typescript-eslint/naming-convention
|
|
||||||
ApiKeyAuth: async (req, scopes, schema: OpenAPIV3.ApiKeySecurityScheme) => {
|
|
||||||
const apiKey = req.headers[schema.name.toLowerCase()];
|
|
||||||
|
|
||||||
const user = await Db.collections.User?.find({
|
|
||||||
where: {
|
|
||||||
apiKey,
|
|
||||||
},
|
|
||||||
relations: ['globalRole'],
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!user?.length) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!config.get('userManagement.isInstanceOwnerSetUp')) {
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-throw-literal
|
|
||||||
throw {
|
|
||||||
status: 400,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
if (config.get('userManagement.disabled')) {
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-throw-literal
|
|
||||||
throw {
|
|
||||||
status: 400,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
if (user[0].globalRole.name !== 'owner') {
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-throw-literal
|
|
||||||
throw {
|
|
||||||
status: 403,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
[req.user] = user;
|
|
||||||
|
|
||||||
return true;
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
// add error handler
|
// add error handler
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
publicApiController.use((error: HttpError, req, res: Response) => {
|
apiController.use(
|
||||||
res.status(error.status || 500).json({
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||||
message: error.message,
|
(error: HttpError, req: express.Request, res: express.Response, next: express.NextFunction) => {
|
||||||
errors: error.errors,
|
return res.status(error.status || 500).json({
|
||||||
});
|
message: error.message,
|
||||||
});
|
errors: error.errors,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
return apiController;
|
||||||
})();
|
})();
|
||||||
|
|
|
@ -1,22 +1,25 @@
|
||||||
|
/* eslint-disable @typescript-eslint/no-unused-vars */
|
||||||
import express = require('express');
|
import express = require('express');
|
||||||
import { getConnection, In } from 'typeorm';
|
import { getConnection, In } from 'typeorm';
|
||||||
|
|
||||||
import { validate as uuidValidate } from 'uuid';
|
import { validate as uuidValidate } from 'uuid';
|
||||||
import validator from 'validator';
|
|
||||||
import { UserRequest } from '../../../../requests';
|
import { UserRequest } from '../../../../requests';
|
||||||
|
|
||||||
import { User } from '../../../../databases/entities/User';
|
import { User } from '../../../../databases/entities/User';
|
||||||
|
import { Role } from '../../../../databases/entities/Role';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
clean,
|
clean,
|
||||||
connectionName,
|
connectionName,
|
||||||
decodeCursor,
|
decodeCursor,
|
||||||
|
getGlobalMemberRole,
|
||||||
getNextCursor,
|
getNextCursor,
|
||||||
getSelectableProperties,
|
getSelectableProperties,
|
||||||
|
getUsersToSaveAndInvite,
|
||||||
|
inviteUsers,
|
||||||
|
saveUsersWithRole,
|
||||||
} from '../../../helpers';
|
} from '../../../helpers';
|
||||||
|
|
||||||
import config = require('../../../../../config');
|
|
||||||
|
|
||||||
import * as UserManagementMailer from '../../../../UserManagement/email/UserManagementMailer';
|
import * as UserManagementMailer from '../../../../UserManagement/email/UserManagementMailer';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
|
@ -26,137 +29,55 @@ import {
|
||||||
ActiveWorkflowRunner,
|
ActiveWorkflowRunner,
|
||||||
ITelemetryUserDeletionData,
|
ITelemetryUserDeletionData,
|
||||||
} from '../../../..';
|
} from '../../../..';
|
||||||
import { Role } from '../../../../databases/entities/Role';
|
|
||||||
import { getInstanceBaseUrl } from '../../../../UserManagement/UserManagementHelper';
|
|
||||||
import { SharedWorkflow } from '../../../../databases/entities/SharedWorkflow';
|
import { SharedWorkflow } from '../../../../databases/entities/SharedWorkflow';
|
||||||
import { SharedCredentials } from '../../../../databases/entities/SharedCredentials';
|
import { SharedCredentials } from '../../../../databases/entities/SharedCredentials';
|
||||||
|
|
||||||
export = {
|
export = {
|
||||||
// eslint-disable-next-line consistent-return
|
createUsers: ResponseHelper.send(async (req: UserRequest.Invite, res: express.Response) => {
|
||||||
createUsers: async (req: UserRequest.Invite, res: express.Response): Promise<any> => {
|
const tokenOwnerId = req.user.id;
|
||||||
if (config.get('userManagement.emails.mode') === '') {
|
const emailsInBody = req.body.map((data) => data.email);
|
||||||
return res.status(500).json({
|
|
||||||
message: 'Email sending must be set up in order to request a password reset email',
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
let mailer: UserManagementMailer.UserManagementMailer | undefined;
|
let mailer: UserManagementMailer.UserManagementMailer | undefined;
|
||||||
try {
|
try {
|
||||||
mailer = await UserManagementMailer.getInstance();
|
mailer = await UserManagementMailer.getInstance();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
if (error instanceof Error) {
|
if (error instanceof Error) {
|
||||||
return res.status(500).json({
|
throw new ResponseHelper.ResponseError(
|
||||||
message: `There is a problem with your SMTP setup! ${error.message}`,
|
'Email sending must be set up in order to request a password reset email',
|
||||||
});
|
undefined,
|
||||||
}
|
500,
|
||||||
}
|
|
||||||
|
|
||||||
const createUsers: { [key: string]: string | null } = {};
|
|
||||||
// Validate payload
|
|
||||||
// @ts-ignore
|
|
||||||
// eslint-disable-next-line consistent-return
|
|
||||||
req.body.forEach((invite) => {
|
|
||||||
if (typeof invite !== 'object' || !invite.email) {
|
|
||||||
return res.status(400).json({
|
|
||||||
message:
|
|
||||||
'Request to send email invite(s) to user(s) failed because the payload is not an array shaped Array<{ email: string }>',
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!validator.isEmail(invite.email)) {
|
|
||||||
return res.status(400).json({
|
|
||||||
message: `Request to send email invite(s) to user(s) failed because of an invalid email address: ${invite.email}`,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
createUsers[invite.email] = null;
|
|
||||||
});
|
|
||||||
|
|
||||||
const role = (await Db.collections.Role?.findOne({ scope: 'global', name: 'member' })) as Role;
|
|
||||||
|
|
||||||
if (!role) {
|
|
||||||
return res.status(500).json({
|
|
||||||
message: `Members role not found in database - inconsistent state`,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// remove/exclude existing users from creation
|
|
||||||
const existingUsers = await Db.collections.User?.find({
|
|
||||||
where: { email: In(Object.keys(createUsers)) },
|
|
||||||
});
|
|
||||||
|
|
||||||
existingUsers?.forEach((user) => {
|
|
||||||
if (user.password) {
|
|
||||||
delete createUsers[user.email];
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
createUsers[user.email] = user.id;
|
|
||||||
});
|
|
||||||
|
|
||||||
const usersToSetUp = Object.keys(createUsers).filter((email) => createUsers[email] === null);
|
|
||||||
|
|
||||||
let savedUsers = [];
|
|
||||||
try {
|
|
||||||
savedUsers = await Db.transaction(async (transactionManager) => {
|
|
||||||
return Promise.all(
|
|
||||||
usersToSetUp.map(async (email) => {
|
|
||||||
const newUser = Object.assign(new User(), {
|
|
||||||
email,
|
|
||||||
globalRole: role,
|
|
||||||
});
|
|
||||||
const savedUser = await transactionManager.save<User>(newUser);
|
|
||||||
createUsers[savedUser.email] = savedUser.id;
|
|
||||||
return savedUser;
|
|
||||||
}),
|
|
||||||
);
|
);
|
||||||
});
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void InternalHooksManager.getInstance().onUserInvite({
|
let role: Role | undefined;
|
||||||
user_id: req.user.id,
|
|
||||||
target_user_id: Object.values(createUsers) as string[],
|
try {
|
||||||
});
|
role = await getGlobalMemberRole();
|
||||||
|
} catch (error) {
|
||||||
|
throw new ResponseHelper.ResponseError(
|
||||||
|
'Members role not found in database - inconsistent state',
|
||||||
|
undefined,
|
||||||
|
500,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const { usersToSave, pendingUsers } = await getUsersToSaveAndInvite(emailsInBody);
|
||||||
|
|
||||||
|
let savedUsers;
|
||||||
|
|
||||||
|
try {
|
||||||
|
savedUsers = await saveUsersWithRole(usersToSave, role!, tokenOwnerId);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
throw new ResponseHelper.ResponseError('An error occurred during user creation');
|
throw new ResponseHelper.ResponseError('An error occurred during user creation');
|
||||||
}
|
}
|
||||||
|
|
||||||
const baseUrl = getInstanceBaseUrl();
|
const userstoInvite = [...savedUsers, ...pendingUsers];
|
||||||
|
|
||||||
const usersPendingSetup = Object.entries(createUsers).filter(([email, id]) => id && email);
|
await inviteUsers(userstoInvite, mailer, tokenOwnerId);
|
||||||
|
|
||||||
// send invite email to new or not yet setup users
|
return clean(userstoInvite);
|
||||||
|
}),
|
||||||
await Promise.all(
|
|
||||||
usersPendingSetup.map(async ([email, id]) => {
|
|
||||||
// eslint-disable-next-line @typescript-eslint/restrict-template-expressions
|
|
||||||
const inviteAcceptUrl = `${baseUrl}/signup?inviterId=${req.user.id}&inviteeId=${id}`;
|
|
||||||
const result = await mailer?.invite({
|
|
||||||
email,
|
|
||||||
inviteAcceptUrl,
|
|
||||||
domain: baseUrl,
|
|
||||||
});
|
|
||||||
const resp: { user: { id: string | null; email: string }; error?: string } = {
|
|
||||||
user: {
|
|
||||||
id,
|
|
||||||
email,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
if (result?.success) {
|
|
||||||
void InternalHooksManager.getInstance().onUserTransactionalEmail({
|
|
||||||
user_id: id!,
|
|
||||||
message_type: 'New user invite',
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
void InternalHooksManager.getInstance().onEmailFailed({
|
|
||||||
user_id: req.user.id,
|
|
||||||
message_type: 'New user invite',
|
|
||||||
});
|
|
||||||
resp.error = `Email could not be sent`;
|
|
||||||
}
|
|
||||||
return resp;
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
|
|
||||||
res.json([...clean(existingUsers ?? []), ...clean(savedUsers)]);
|
|
||||||
},
|
|
||||||
// eslint-disable-next-line consistent-return
|
// eslint-disable-next-line consistent-return
|
||||||
deleteUser: async (req: UserRequest.Delete, res: express.Response): Promise<any> => {
|
deleteUser: async (req: UserRequest.Delete, res: express.Response): Promise<any> => {
|
||||||
const { identifier: idToDelete } = req.params;
|
const { identifier: idToDelete } = req.params;
|
||||||
|
@ -282,7 +203,13 @@ export = {
|
||||||
res.json(user);
|
res.json(user);
|
||||||
},
|
},
|
||||||
// eslint-disable-next-line consistent-return
|
// eslint-disable-next-line consistent-return
|
||||||
getUsers: async (req: UserRequest.Get, res: express.Response): Promise<any> => {
|
getUsers: async (
|
||||||
|
req: UserRequest.Get,
|
||||||
|
res: express.Response,
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-shadow
|
||||||
|
next: express.NextFunction,
|
||||||
|
// eslint-disable-next-line consistent-return
|
||||||
|
): Promise<any> => {
|
||||||
let offset = 0;
|
let offset = 0;
|
||||||
let limit = parseInt(req.query.limit, 10) || 10;
|
let limit = parseInt(req.query.limit, 10) || 10;
|
||||||
const includeRole = req.query?.includeRole?.toLowerCase() === 'true' || false;
|
const includeRole = req.query?.includeRole?.toLowerCase() === 'true' || false;
|
||||||
|
|
|
@ -132,7 +132,6 @@ export function sendErrorResponse(res: Response, error: ResponseError, shouldLog
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
response.stack = error.stack;
|
response.stack = error.stack;
|
||||||
}
|
}
|
||||||
console.log('se esta llmando al error');
|
|
||||||
res.status(httpStatusCode).json(response);
|
res.status(httpStatusCode).json(response);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -580,7 +580,7 @@ class App {
|
||||||
return ResponseHelper.sendSuccessResponse(res, {}, true, 204);
|
return ResponseHelper.sendSuccessResponse(res, {}, true, 204);
|
||||||
});
|
});
|
||||||
|
|
||||||
this.app.use(`/${this.publicApiEndpoint}/v1`, publicApiControllerV1);
|
this.app.use(`/${this.publicApiEndpoint}/v1`, await publicApiControllerV1);
|
||||||
|
|
||||||
// Parse cookies for easier access
|
// Parse cookies for easier access
|
||||||
this.app.use(cookieParser());
|
this.app.use(cookieParser());
|
||||||
|
|
|
@ -66,7 +66,7 @@ export function initTestServer({
|
||||||
if (routerEndpoints.length) {
|
if (routerEndpoints.length) {
|
||||||
const map: Record<string, express.Router> = {
|
const map: Record<string, express.Router> = {
|
||||||
credentials: credentialsController,
|
credentials: credentialsController,
|
||||||
publicApi: publicApiController,
|
//publicApi: publicApiController,
|
||||||
};
|
};
|
||||||
|
|
||||||
for (const group of routerEndpoints) {
|
for (const group of routerEndpoints) {
|
||||||
|
|
Loading…
Reference in a new issue