mirror of
https://github.com/n8n-io/n8n.git
synced 2024-12-24 04:04:06 -08:00
feat(core): Add GET /users endpoints to public API (#6360)
This commit is contained in:
parent
25b92169ae
commit
6ab350209d
|
@ -186,6 +186,10 @@ export class License {
|
|||
return (this.getFeatureValue(LICENSE_QUOTAS.VARIABLES_LIMIT) ?? -1) as number;
|
||||
}
|
||||
|
||||
getUsersLimit(): number {
|
||||
return this.getFeatureValue(LICENSE_QUOTAS.USERS_LIMIT) as number;
|
||||
}
|
||||
|
||||
getPlanName(): string {
|
||||
return (this.getFeatureValue('planName') ?? 'Community') as string;
|
||||
}
|
||||
|
|
|
@ -0,0 +1,19 @@
|
|||
get:
|
||||
x-eov-operation-id: getUser
|
||||
x-eov-operation-handler: v1/handlers/users/users.handler.ee
|
||||
tags:
|
||||
- User
|
||||
summary: Get user by ID/Email
|
||||
description: Retrieve a user from your instance. Only available for the instance owner.
|
||||
parameters:
|
||||
- $ref: '../schemas/parameters/userIdentifier.yml'
|
||||
- $ref: '../schemas/parameters/includeRole.yml'
|
||||
responses:
|
||||
'200':
|
||||
description: Operation successful.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '../schemas/user.yml'
|
||||
'401':
|
||||
$ref: '../../../../shared/spec/responses/unauthorized.yml'
|
|
@ -0,0 +1,20 @@
|
|||
get:
|
||||
x-eov-operation-id: getUsers
|
||||
x-eov-operation-handler: v1/handlers/users/users.handler.ee
|
||||
tags:
|
||||
- User
|
||||
summary: Retrieve all users
|
||||
description: Retrieve all users from your instance. Only available for the instance owner.
|
||||
parameters:
|
||||
- $ref: '../../../../shared/spec/parameters/limit.yml'
|
||||
- $ref: '../../../../shared/spec/parameters/cursor.yml'
|
||||
- $ref: '../schemas/parameters/includeRole.yml'
|
||||
responses:
|
||||
'200':
|
||||
description: Operation successful.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '../schemas/userList.yml'
|
||||
'401':
|
||||
$ref: '../../../../shared/spec/responses/unauthorized.yml'
|
|
@ -0,0 +1,8 @@
|
|||
name: includeRole
|
||||
in: query
|
||||
description: Whether to include the user's role or not.
|
||||
required: false
|
||||
schema:
|
||||
type: boolean
|
||||
example: true
|
||||
default: false
|
|
@ -0,0 +1,7 @@
|
|||
name: id
|
||||
in: path
|
||||
description: The ID or email of the user.
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
format: identifier
|
|
@ -0,0 +1,25 @@
|
|||
readOnly: true
|
||||
type: object
|
||||
properties:
|
||||
id:
|
||||
type: number
|
||||
readOnly: true
|
||||
example: 1
|
||||
name:
|
||||
type: string
|
||||
example: owner
|
||||
readOnly: true
|
||||
scope:
|
||||
type: string
|
||||
readOnly: true
|
||||
example: global
|
||||
createdAt:
|
||||
type: string
|
||||
description: Time the role was created.
|
||||
format: date-time
|
||||
readOnly: true
|
||||
updatedAt:
|
||||
type: string
|
||||
description: Last time the role was updated.
|
||||
format: date-time
|
||||
readOnly: true
|
|
@ -0,0 +1,40 @@
|
|||
required:
|
||||
- email
|
||||
type: object
|
||||
properties:
|
||||
id:
|
||||
type: string
|
||||
readOnly: true
|
||||
example: 123e4567-e89b-12d3-a456-426614174000
|
||||
email:
|
||||
type: string
|
||||
format: email
|
||||
example: john.doe@company.com
|
||||
firstName:
|
||||
maxLength: 32
|
||||
type: string
|
||||
description: User's first name
|
||||
readOnly: true
|
||||
example: john
|
||||
lastName:
|
||||
maxLength: 32
|
||||
type: string
|
||||
description: User's last name
|
||||
readOnly: true
|
||||
example: Doe
|
||||
isPending:
|
||||
type: boolean
|
||||
description: Whether the user finished setting up their account in response to the invitation (true) or not (false).
|
||||
readOnly: true
|
||||
createdAt:
|
||||
type: string
|
||||
description: Time the user was created.
|
||||
format: date-time
|
||||
readOnly: true
|
||||
updatedAt:
|
||||
type: string
|
||||
description: Last time the user was updated.
|
||||
format: date-time
|
||||
readOnly: true
|
||||
globalRole:
|
||||
$ref: './role.yml'
|
|
@ -0,0 +1,11 @@
|
|||
type: object
|
||||
properties:
|
||||
data:
|
||||
type: array
|
||||
items:
|
||||
$ref: './user.yml'
|
||||
nextCursor:
|
||||
type: string
|
||||
description: Paginate through users by setting the cursor parameter to a nextCursor attribute returned by a previous request. Default value fetches the first "page" of the collection.
|
||||
nullable: true
|
||||
example: MTIzZTQ1NjctZTg5Yi0xMmQzLWE0NTYtNDI2NjE0MTc0MDA
|
|
@ -0,0 +1,71 @@
|
|||
import type express from 'express';
|
||||
|
||||
import { clean, getAllUsersAndCount, getUser } from './users.service.ee';
|
||||
|
||||
import { encodeNextCursor } from '../../shared/services/pagination.service';
|
||||
import {
|
||||
authorize,
|
||||
validCursor,
|
||||
validLicenseWithUserQuota,
|
||||
} from '../../shared/middlewares/global.middleware';
|
||||
import type { UserRequest } from '@/requests';
|
||||
import { InternalHooks } from '@/InternalHooks';
|
||||
import Container from 'typedi';
|
||||
|
||||
export = {
|
||||
getUser: [
|
||||
validLicenseWithUserQuota,
|
||||
authorize(['owner']),
|
||||
async (req: UserRequest.Get, res: express.Response) => {
|
||||
const { includeRole = false } = req.query;
|
||||
const { id } = req.params;
|
||||
|
||||
const user = await getUser({ withIdentifier: id, includeRole });
|
||||
|
||||
if (!user) {
|
||||
return res.status(404).json({
|
||||
message: `Could not find user with id: ${id}`,
|
||||
});
|
||||
}
|
||||
|
||||
const telemetryData = {
|
||||
user_id: req.user.id,
|
||||
public_api: true,
|
||||
};
|
||||
|
||||
void Container.get(InternalHooks).onUserRetrievedUser(telemetryData);
|
||||
|
||||
return res.json(clean(user, { includeRole }));
|
||||
},
|
||||
],
|
||||
getUsers: [
|
||||
validLicenseWithUserQuota,
|
||||
validCursor,
|
||||
authorize(['owner']),
|
||||
async (req: UserRequest.Get, res: express.Response) => {
|
||||
const { offset = 0, limit = 100, includeRole = false } = req.query;
|
||||
|
||||
const [users, count] = await getAllUsersAndCount({
|
||||
includeRole,
|
||||
limit,
|
||||
offset,
|
||||
});
|
||||
|
||||
const telemetryData = {
|
||||
user_id: req.user.id,
|
||||
public_api: true,
|
||||
};
|
||||
|
||||
void Container.get(InternalHooks).onUserRetrievedAllUsers(telemetryData);
|
||||
|
||||
return res.json({
|
||||
data: clean(users, { includeRole }),
|
||||
nextCursor: encodeNextCursor({
|
||||
offset,
|
||||
limit,
|
||||
numberOfTotalRecords: count,
|
||||
}),
|
||||
});
|
||||
},
|
||||
],
|
||||
};
|
|
@ -0,0 +1,68 @@
|
|||
import { Container } from 'typedi';
|
||||
import { RoleRepository, UserRepository } from '@db/repositories';
|
||||
import type { Role } from '@db/entities/Role';
|
||||
import type { User } from '@db/entities/User';
|
||||
import pick from 'lodash/pick';
|
||||
import { validate as uuidValidate } from 'uuid';
|
||||
|
||||
export function isInstanceOwner(user: User): boolean {
|
||||
return user.globalRole.name === 'owner';
|
||||
}
|
||||
|
||||
export async function getWorkflowOwnerRole(): Promise<Role> {
|
||||
return Container.get(RoleRepository).findWorkflowOwnerRoleOrFail();
|
||||
}
|
||||
|
||||
export const getSelectableProperties = (table: 'user' | 'role'): string[] => {
|
||||
return {
|
||||
user: ['id', 'email', 'firstName', 'lastName', 'createdAt', 'updatedAt', 'isPending'],
|
||||
role: ['id', 'name', 'scope', 'createdAt', 'updatedAt'],
|
||||
}[table];
|
||||
};
|
||||
|
||||
export async function getUser(data: {
|
||||
withIdentifier: string;
|
||||
includeRole?: boolean;
|
||||
}): Promise<User | null> {
|
||||
return Container.get(UserRepository).findOne({
|
||||
where: {
|
||||
...(uuidValidate(data.withIdentifier) && { id: data.withIdentifier }),
|
||||
...(!uuidValidate(data.withIdentifier) && { email: data.withIdentifier }),
|
||||
},
|
||||
relations: data?.includeRole ? ['globalRole'] : undefined,
|
||||
});
|
||||
}
|
||||
|
||||
export async function getAllUsersAndCount(data: {
|
||||
includeRole?: boolean;
|
||||
limit?: number;
|
||||
offset?: number;
|
||||
}): Promise<[User[], number]> {
|
||||
const users = await Container.get(UserRepository).find({
|
||||
where: {},
|
||||
relations: data?.includeRole ? ['globalRole'] : undefined,
|
||||
skip: data.offset,
|
||||
take: data.limit,
|
||||
});
|
||||
const count = await Container.get(UserRepository).count();
|
||||
return [users, count];
|
||||
}
|
||||
|
||||
function pickUserSelectableProperties(user: User, options?: { includeRole: boolean }) {
|
||||
return pick(
|
||||
user,
|
||||
getSelectableProperties('user').concat(options?.includeRole ? ['globalRole'] : []),
|
||||
);
|
||||
}
|
||||
|
||||
export function clean(user: User, options?: { includeRole: boolean }): Partial<User>;
|
||||
export function clean(users: User[], options?: { includeRole: boolean }): Array<Partial<User>>;
|
||||
|
||||
export function clean(
|
||||
users: User[] | User,
|
||||
options?: { includeRole: boolean },
|
||||
): Array<Partial<User>> | Partial<User> {
|
||||
return Array.isArray(users)
|
||||
? users.map((user) => pickUserSelectableProperties(user, options))
|
||||
: pickUserSelectableProperties(users, options);
|
||||
}
|
|
@ -1,12 +0,0 @@
|
|||
import { Container } from 'typedi';
|
||||
import { RoleRepository } from '@db/repositories';
|
||||
import type { Role } from '@db/entities/Role';
|
||||
import type { User } from '@db/entities/User';
|
||||
|
||||
export function isInstanceOwner(user: User): boolean {
|
||||
return user.globalRole.name === 'owner';
|
||||
}
|
||||
|
||||
export async function getWorkflowOwnerRole(): Promise<Role> {
|
||||
return Container.get(RoleRepository).findWorkflowOwnerRoleOrFail();
|
||||
}
|
|
@ -11,7 +11,7 @@ import { addNodeIds, replaceInvalidCredentials } from '@/WorkflowHelpers';
|
|||
import type { WorkflowRequest } from '../../../types';
|
||||
import { authorize, validCursor } from '../../shared/middlewares/global.middleware';
|
||||
import { encodeNextCursor } from '../../shared/services/pagination.service';
|
||||
import { getWorkflowOwnerRole, isInstanceOwner } from '../users/users.service';
|
||||
import { getWorkflowOwnerRole, isInstanceOwner } from '../users/users.service.ee';
|
||||
import {
|
||||
getWorkflowById,
|
||||
getSharedWorkflow,
|
||||
|
|
|
@ -8,7 +8,7 @@ import * as Db from '@/Db';
|
|||
import type { User } from '@db/entities/User';
|
||||
import { WorkflowEntity } from '@db/entities/WorkflowEntity';
|
||||
import { SharedWorkflow } from '@db/entities/SharedWorkflow';
|
||||
import { isInstanceOwner } from '../users/users.service';
|
||||
import { isInstanceOwner } from '../users/users.service.ee';
|
||||
import type { Role } from '@db/entities/Role';
|
||||
import config from '@/config';
|
||||
import { START_NODES } from '@/constants';
|
||||
|
|
|
@ -16,6 +16,8 @@ externalDocs:
|
|||
servers:
|
||||
- url: /api/v1
|
||||
tags:
|
||||
- name: User
|
||||
description: Operations about users
|
||||
- name: Audit
|
||||
description: Operations about security audit
|
||||
- name: Execution
|
||||
|
@ -29,34 +31,38 @@ tags:
|
|||
|
||||
paths:
|
||||
/audit:
|
||||
$ref: "./handlers/audit/spec/paths/audit.yml"
|
||||
$ref: './handlers/audit/spec/paths/audit.yml'
|
||||
/credentials:
|
||||
$ref: "./handlers/credentials/spec/paths/credentials.yml"
|
||||
$ref: './handlers/credentials/spec/paths/credentials.yml'
|
||||
/credentials/{id}:
|
||||
$ref: "./handlers/credentials/spec/paths/credentials.id.yml"
|
||||
$ref: './handlers/credentials/spec/paths/credentials.id.yml'
|
||||
/credentials/schema/{credentialTypeName}:
|
||||
$ref: "./handlers/credentials/spec/paths/credentials.schema.id.yml"
|
||||
$ref: './handlers/credentials/spec/paths/credentials.schema.id.yml'
|
||||
/executions:
|
||||
$ref: "./handlers/executions/spec/paths/executions.yml"
|
||||
$ref: './handlers/executions/spec/paths/executions.yml'
|
||||
/executions/{id}:
|
||||
$ref: "./handlers/executions/spec/paths/executions.id.yml"
|
||||
$ref: './handlers/executions/spec/paths/executions.id.yml'
|
||||
/workflows:
|
||||
$ref: "./handlers/workflows/spec/paths/workflows.yml"
|
||||
$ref: './handlers/workflows/spec/paths/workflows.yml'
|
||||
/workflows/{id}:
|
||||
$ref: "./handlers/workflows/spec/paths/workflows.id.yml"
|
||||
$ref: './handlers/workflows/spec/paths/workflows.id.yml'
|
||||
/workflows/{id}/activate:
|
||||
$ref: "./handlers/workflows/spec/paths/workflows.id.activate.yml"
|
||||
$ref: './handlers/workflows/spec/paths/workflows.id.activate.yml'
|
||||
/workflows/{id}/deactivate:
|
||||
$ref: "./handlers/workflows/spec/paths/workflows.id.deactivate.yml"
|
||||
$ref: './handlers/workflows/spec/paths/workflows.id.deactivate.yml'
|
||||
/users:
|
||||
$ref: './handlers/users/spec/paths/users.yml'
|
||||
/users/{id}:
|
||||
$ref: './handlers/users/spec/paths/users.id.yml'
|
||||
/source-control/pull:
|
||||
$ref: "./handlers/sourceControl/spec/paths/sourceControl.yml"
|
||||
components:
|
||||
schemas:
|
||||
$ref: "./shared/spec/schemas/_index.yml"
|
||||
$ref: './shared/spec/schemas/_index.yml'
|
||||
responses:
|
||||
$ref: "./shared/spec/responses/_index.yml"
|
||||
$ref: './shared/spec/responses/_index.yml'
|
||||
parameters:
|
||||
$ref: "./shared/spec/parameters/_index.yml"
|
||||
$ref: './shared/spec/parameters/_index.yml'
|
||||
securitySchemes:
|
||||
ApiKeyAuth:
|
||||
type: apiKey
|
||||
|
|
|
@ -1,9 +1,13 @@
|
|||
/* eslint-disable @typescript-eslint/no-invalid-void-type */
|
||||
|
||||
import type express from 'express';
|
||||
import { Container } from 'typedi';
|
||||
|
||||
import type { AuthenticatedRequest, PaginatedRequest } from '../../../types';
|
||||
import { decodeCursor } from '../services/pagination.service';
|
||||
import { License } from '@/License';
|
||||
|
||||
const UNLIMITED_USERS_QUOTA = -1;
|
||||
|
||||
export const authorize =
|
||||
(authorizedRoles: readonly string[]) =>
|
||||
|
@ -46,3 +50,18 @@ export const validCursor = (
|
|||
|
||||
return next();
|
||||
};
|
||||
|
||||
export const validLicenseWithUserQuota = (
|
||||
req: express.Request,
|
||||
res: express.Response,
|
||||
next: express.NextFunction,
|
||||
): express.Response | void => {
|
||||
const license = Container.get(License);
|
||||
if (license.getUsersLimit() !== UNLIMITED_USERS_QUOTA) {
|
||||
return res.status(403).json({
|
||||
message: '/users path can only be used with a valid license. See https://n8n.io/pricing/',
|
||||
});
|
||||
}
|
||||
|
||||
return next();
|
||||
};
|
||||
|
|
|
@ -8,3 +8,7 @@ WorkflowId:
|
|||
$ref: '../../../handlers/workflows/spec/schemas/parameters/workflowId.yml'
|
||||
IncludeData:
|
||||
$ref: '../../../handlers/executions/spec/schemas/parameters/includeData.yml'
|
||||
UserIdentifier:
|
||||
$ref: '../../../handlers/users/spec/schemas/parameters/userIdentifier.yml'
|
||||
IncludeRole:
|
||||
$ref: '../../../handlers/users/spec/schemas/parameters/includeRole.yml'
|
||||
|
|
|
@ -1,26 +1,32 @@
|
|||
Error:
|
||||
$ref: "./error.yml"
|
||||
$ref: './error.yml'
|
||||
Role:
|
||||
$ref: './../../../handlers/users/spec/schemas/role.yml'
|
||||
Execution:
|
||||
$ref: "./../../../handlers/executions/spec/schemas/execution.yml"
|
||||
$ref: './../../../handlers/executions/spec/schemas/execution.yml'
|
||||
Node:
|
||||
$ref: "./../../../handlers/workflows/spec/schemas/node.yml"
|
||||
$ref: './../../../handlers/workflows/spec/schemas/node.yml'
|
||||
Tag:
|
||||
$ref: "./../../../handlers/workflows/spec/schemas/tag.yml"
|
||||
$ref: './../../../handlers/workflows/spec/schemas/tag.yml'
|
||||
Workflow:
|
||||
$ref: "./../../../handlers/workflows/spec/schemas/workflow.yml"
|
||||
$ref: './../../../handlers/workflows/spec/schemas/workflow.yml'
|
||||
WorkflowSettings:
|
||||
$ref: "./../../../handlers/workflows/spec/schemas/workflowSettings.yml"
|
||||
$ref: './../../../handlers/workflows/spec/schemas/workflowSettings.yml'
|
||||
ExecutionList:
|
||||
$ref: "./../../../handlers/executions/spec/schemas/executionList.yml"
|
||||
$ref: './../../../handlers/executions/spec/schemas/executionList.yml'
|
||||
WorkflowList:
|
||||
$ref: "./../../../handlers/workflows/spec/schemas/workflowList.yml"
|
||||
$ref: './../../../handlers/workflows/spec/schemas/workflowList.yml'
|
||||
Credential:
|
||||
$ref: "./../../../handlers/credentials/spec/schemas/credential.yml"
|
||||
$ref: './../../../handlers/credentials/spec/schemas/credential.yml'
|
||||
CredentialType:
|
||||
$ref: "./../../../handlers/credentials/spec/schemas/credentialType.yml"
|
||||
$ref: './../../../handlers/credentials/spec/schemas/credentialType.yml'
|
||||
Audit:
|
||||
$ref: "./../../../handlers/audit/spec/schemas/audit.yml"
|
||||
$ref: './../../../handlers/audit/spec/schemas/audit.yml'
|
||||
Pull:
|
||||
$ref: "./../../../handlers/sourceControl/spec/schemas/pull.yml"
|
||||
ImportResult:
|
||||
$ref: "./../../../handlers/sourceControl/spec/schemas/importResult.yml"
|
||||
UserList:
|
||||
$ref: './../../../handlers/users/spec/schemas/userList.yml'
|
||||
User:
|
||||
$ref: './../../../handlers/users/spec/schemas/user.yml'
|
||||
|
|
|
@ -84,6 +84,7 @@ export const enum LICENSE_FEATURES {
|
|||
export const enum LICENSE_QUOTAS {
|
||||
TRIGGER_LIMIT = 'quota:activeWorkflows',
|
||||
VARIABLES_LIMIT = 'quota:maxVariables',
|
||||
USERS_LIMIT = 'quota:users',
|
||||
}
|
||||
|
||||
export const CREDENTIAL_BLANKING_VALUE = '__n8n_BLANK_VALUE_e5362baf-c777-4d57-a609-6eaf1f9e87f6';
|
||||
|
|
|
@ -9,7 +9,7 @@ import type { ILicensePostResponse, ILicenseReadResponse } from '@/Interfaces';
|
|||
import { LicenseService } from './License.service';
|
||||
import { License } from '@/License';
|
||||
import type { AuthenticatedRequest, LicenseRequest } from '@/requests';
|
||||
import { isInstanceOwner } from '@/PublicApi/v1/handlers/users/users.service';
|
||||
import { isInstanceOwner } from '@/PublicApi/v1/handlers/users/users.service.ee';
|
||||
import { Container } from 'typedi';
|
||||
import { InternalHooks } from '@/InternalHooks';
|
||||
|
||||
|
|
343
packages/cli/test/integration/publicApi/users.ee.test.ts
Normal file
343
packages/cli/test/integration/publicApi/users.ee.test.ts
Normal file
|
@ -0,0 +1,343 @@
|
|||
import type express from 'express';
|
||||
import validator from 'validator';
|
||||
import { v4 as uuid } from 'uuid';
|
||||
|
||||
import config from '@/config';
|
||||
import type { Role } from '@/databases/entities/Role';
|
||||
import { randomApiKey } from '../shared/random';
|
||||
|
||||
import * as utils from '../shared/utils';
|
||||
import * as testDb from '../shared/testDb';
|
||||
|
||||
import { License } from '@/License';
|
||||
|
||||
let app: express.Application;
|
||||
let globalOwnerRole: Role;
|
||||
let globalMemberRole: Role;
|
||||
|
||||
const licenseLike = {
|
||||
getUsersLimit: jest.fn().mockReturnValue(-1),
|
||||
};
|
||||
|
||||
utils.mockInstance(License, licenseLike);
|
||||
|
||||
beforeAll(async () => {
|
||||
app = await utils.initTestServer({
|
||||
endpointGroups: ['publicApi'],
|
||||
applyAuth: false,
|
||||
enablePublicAPI: true,
|
||||
});
|
||||
|
||||
await testDb.init();
|
||||
|
||||
const [fetchedGlobalOwnerRole, fetchedGlobalMemberRole] = await testDb.getAllRoles();
|
||||
|
||||
globalOwnerRole = fetchedGlobalOwnerRole;
|
||||
globalMemberRole = fetchedGlobalMemberRole;
|
||||
});
|
||||
|
||||
beforeEach(async () => {
|
||||
// do not combine calls - shared tables must be cleared first and separately
|
||||
await testDb.truncate(['SharedCredentials', 'SharedWorkflow']);
|
||||
await testDb.truncate(['User', 'Workflow', 'Credentials']);
|
||||
|
||||
config.set('userManagement.disabled', false);
|
||||
config.set('userManagement.isInstanceOwnerSetUp', true);
|
||||
config.set('userManagement.emails.mode', 'smtp');
|
||||
});
|
||||
|
||||
afterAll(async () => {
|
||||
await testDb.terminate();
|
||||
});
|
||||
|
||||
describe('With license unlimited quota:users', () => {
|
||||
test('GET /users should fail due to missing API Key', async () => {
|
||||
const owner = await testDb.createUser({ globalRole: globalOwnerRole });
|
||||
|
||||
const authOwnerAgent = utils.createAgent(app, {
|
||||
apiPath: 'public',
|
||||
version: 1,
|
||||
auth: true,
|
||||
user: owner,
|
||||
});
|
||||
|
||||
await testDb.createUser();
|
||||
|
||||
const response = await authOwnerAgent.get('/users');
|
||||
|
||||
expect(response.statusCode).toBe(401);
|
||||
});
|
||||
|
||||
test('GET /users should fail due to invalid API Key', async () => {
|
||||
const owner = await testDb.createUser({ globalRole: globalOwnerRole, apiKey: randomApiKey() });
|
||||
|
||||
owner.apiKey = null;
|
||||
|
||||
const authOwnerAgent = utils.createAgent(app, {
|
||||
apiPath: 'public',
|
||||
version: 1,
|
||||
auth: true,
|
||||
user: owner,
|
||||
});
|
||||
|
||||
const response = await authOwnerAgent.get('/users');
|
||||
|
||||
expect(response.statusCode).toBe(401);
|
||||
});
|
||||
|
||||
test('GET /users should fail due to member trying to access owner only endpoint', async () => {
|
||||
const member = await testDb.createUser({ apiKey: randomApiKey() });
|
||||
|
||||
const authOwnerAgent = utils.createAgent(app, {
|
||||
apiPath: 'public',
|
||||
version: 1,
|
||||
auth: true,
|
||||
user: member,
|
||||
});
|
||||
|
||||
const response = await authOwnerAgent.get('/users');
|
||||
|
||||
expect(response.statusCode).toBe(403);
|
||||
});
|
||||
|
||||
test('GET /users should return all users', async () => {
|
||||
const owner = await testDb.createUser({ globalRole: globalOwnerRole, apiKey: randomApiKey() });
|
||||
|
||||
const authOwnerAgent = utils.createAgent(app, {
|
||||
apiPath: 'public',
|
||||
version: 1,
|
||||
auth: true,
|
||||
user: owner,
|
||||
});
|
||||
|
||||
await testDb.createUser();
|
||||
|
||||
const response = await authOwnerAgent.get('/users');
|
||||
|
||||
expect(response.statusCode).toBe(200);
|
||||
expect(response.body.data.length).toBe(2);
|
||||
expect(response.body.nextCursor).toBeNull();
|
||||
|
||||
for (const user of response.body.data) {
|
||||
const {
|
||||
id,
|
||||
email,
|
||||
firstName,
|
||||
lastName,
|
||||
personalizationAnswers,
|
||||
globalRole,
|
||||
password,
|
||||
resetPasswordToken,
|
||||
isPending,
|
||||
createdAt,
|
||||
updatedAt,
|
||||
} = user;
|
||||
|
||||
expect(validator.isUUID(id)).toBe(true);
|
||||
expect(email).toBeDefined();
|
||||
expect(firstName).toBeDefined();
|
||||
expect(lastName).toBeDefined();
|
||||
expect(personalizationAnswers).toBeUndefined();
|
||||
expect(password).toBeUndefined();
|
||||
expect(resetPasswordToken).toBeUndefined();
|
||||
expect(isPending).toBe(false);
|
||||
expect(globalRole).toBeUndefined();
|
||||
expect(createdAt).toBeDefined();
|
||||
expect(updatedAt).toBeDefined();
|
||||
}
|
||||
});
|
||||
|
||||
test('GET /users/:identifier should fail due to missing API Key', async () => {
|
||||
const owner = await testDb.createUser({ globalRole: globalOwnerRole });
|
||||
|
||||
const authOwnerAgent = utils.createAgent(app, {
|
||||
apiPath: 'public',
|
||||
version: 1,
|
||||
auth: true,
|
||||
user: owner,
|
||||
});
|
||||
|
||||
await testDb.createUser();
|
||||
|
||||
const response = await authOwnerAgent.get(`/users/${owner.id}`);
|
||||
|
||||
expect(response.statusCode).toBe(401);
|
||||
});
|
||||
|
||||
test('GET /users/:identifier should fail due to invalid API Key', async () => {
|
||||
const owner = await testDb.createUser({ globalRole: globalOwnerRole, apiKey: randomApiKey() });
|
||||
|
||||
owner.apiKey = null;
|
||||
|
||||
const authOwnerAgent = utils.createAgent(app, {
|
||||
apiPath: 'public',
|
||||
version: 1,
|
||||
auth: true,
|
||||
user: owner,
|
||||
});
|
||||
|
||||
const response = await authOwnerAgent.get(`/users/${owner.id}`);
|
||||
|
||||
expect(response.statusCode).toBe(401);
|
||||
});
|
||||
|
||||
test('GET /users/:identifier should fail due to member trying to access owner only endpoint', async () => {
|
||||
const member = await testDb.createUser({ apiKey: randomApiKey() });
|
||||
|
||||
const authOwnerAgent = utils.createAgent(app, {
|
||||
apiPath: 'public',
|
||||
version: 1,
|
||||
auth: true,
|
||||
user: member,
|
||||
});
|
||||
|
||||
const response = await authOwnerAgent.get(`/users/${member.id}`);
|
||||
|
||||
expect(response.statusCode).toBe(403);
|
||||
});
|
||||
|
||||
test('GET /users/:email with non-existing email should return 404', async () => {
|
||||
const owner = await testDb.createUser({ globalRole: globalOwnerRole, apiKey: randomApiKey() });
|
||||
|
||||
const authOwnerAgent = utils.createAgent(app, {
|
||||
apiPath: 'public',
|
||||
version: 1,
|
||||
auth: true,
|
||||
user: owner,
|
||||
});
|
||||
|
||||
const response = await authOwnerAgent.get('/users/jhondoe@gmail.com');
|
||||
|
||||
expect(response.statusCode).toBe(404);
|
||||
});
|
||||
|
||||
test('GET /users/:id with non-existing id should return 404', async () => {
|
||||
const owner = await testDb.createUser({ globalRole: globalOwnerRole, apiKey: randomApiKey() });
|
||||
|
||||
const authOwnerAgent = utils.createAgent(app, {
|
||||
apiPath: 'public',
|
||||
version: 1,
|
||||
auth: true,
|
||||
user: owner,
|
||||
});
|
||||
|
||||
const response = await authOwnerAgent.get(`/users/${uuid()}`);
|
||||
|
||||
expect(response.statusCode).toBe(404);
|
||||
});
|
||||
|
||||
test('GET /users/:email should return a user', async () => {
|
||||
const owner = await testDb.createUser({ globalRole: globalOwnerRole, apiKey: randomApiKey() });
|
||||
|
||||
const authOwnerAgent = utils.createAgent(app, {
|
||||
apiPath: 'public',
|
||||
version: 1,
|
||||
auth: true,
|
||||
user: owner,
|
||||
});
|
||||
|
||||
const response = await authOwnerAgent.get(`/users/${owner.email}`);
|
||||
|
||||
expect(response.statusCode).toBe(200);
|
||||
|
||||
const {
|
||||
id,
|
||||
email,
|
||||
firstName,
|
||||
lastName,
|
||||
personalizationAnswers,
|
||||
globalRole,
|
||||
password,
|
||||
resetPasswordToken,
|
||||
isPending,
|
||||
createdAt,
|
||||
updatedAt,
|
||||
} = response.body;
|
||||
|
||||
expect(validator.isUUID(id)).toBe(true);
|
||||
expect(email).toBeDefined();
|
||||
expect(firstName).toBeDefined();
|
||||
expect(lastName).toBeDefined();
|
||||
expect(personalizationAnswers).toBeUndefined();
|
||||
expect(password).toBeUndefined();
|
||||
expect(resetPasswordToken).toBeUndefined();
|
||||
expect(isPending).toBe(false);
|
||||
expect(globalRole).toBeUndefined();
|
||||
expect(createdAt).toBeDefined();
|
||||
expect(updatedAt).toBeDefined();
|
||||
});
|
||||
|
||||
test('GET /users/:id should return a pending user', async () => {
|
||||
const owner = await testDb.createUser({ globalRole: globalOwnerRole, apiKey: randomApiKey() });
|
||||
|
||||
const { id: memberId } = await testDb.createUserShell(globalMemberRole);
|
||||
|
||||
const authOwnerAgent = utils.createAgent(app, {
|
||||
apiPath: 'public',
|
||||
version: 1,
|
||||
auth: true,
|
||||
user: owner,
|
||||
});
|
||||
|
||||
const response = await authOwnerAgent.get(`/users/${memberId}`);
|
||||
|
||||
expect(response.statusCode).toBe(200);
|
||||
|
||||
const {
|
||||
id,
|
||||
email,
|
||||
firstName,
|
||||
lastName,
|
||||
personalizationAnswers,
|
||||
globalRole,
|
||||
password,
|
||||
resetPasswordToken,
|
||||
isPending,
|
||||
createdAt,
|
||||
updatedAt,
|
||||
} = response.body;
|
||||
|
||||
expect(validator.isUUID(id)).toBe(true);
|
||||
expect(email).toBeDefined();
|
||||
expect(firstName).toBeDefined();
|
||||
expect(lastName).toBeDefined();
|
||||
expect(personalizationAnswers).toBeUndefined();
|
||||
expect(password).toBeUndefined();
|
||||
expect(resetPasswordToken).toBeUndefined();
|
||||
expect(globalRole).toBeUndefined();
|
||||
expect(createdAt).toBeDefined();
|
||||
expect(isPending).toBeDefined();
|
||||
expect(isPending).toBeTruthy();
|
||||
expect(updatedAt).toBeDefined();
|
||||
});
|
||||
});
|
||||
|
||||
describe('With license without quota:users', () => {
|
||||
beforeEach(async () => {
|
||||
utils.mockInstance(License, { getUsersLimit: jest.fn().mockReturnValue(null) });
|
||||
});
|
||||
|
||||
test('GET /users should fail due to invalid license', async () => {
|
||||
const member = await testDb.createUser({ apiKey: randomApiKey() });
|
||||
const authOwnerAgent = utils.createAgent(app, {
|
||||
apiPath: 'public',
|
||||
version: 1,
|
||||
auth: true,
|
||||
user: member,
|
||||
});
|
||||
const response = await authOwnerAgent.get('/users');
|
||||
expect(response.statusCode).toBe(403);
|
||||
});
|
||||
|
||||
test('GET /users/:id should fail due to invalid license', async () => {
|
||||
const member = await testDb.createUser({ apiKey: randomApiKey() });
|
||||
const authOwnerAgent = utils.createAgent(app, {
|
||||
apiPath: 'public',
|
||||
version: 1,
|
||||
auth: true,
|
||||
user: member,
|
||||
});
|
||||
const response = await authOwnerAgent.get(`/users/${member.id}`);
|
||||
expect(response.statusCode).toBe(403);
|
||||
});
|
||||
});
|
Loading…
Reference in a new issue