Merge branch 'feature/public-api-initial-setup-N8N-3170' into feature/n8n-public-api

This commit is contained in:
ricardo 2022-03-21 10:06:33 -04:00
commit e1bd9fc510
7 changed files with 382 additions and 8 deletions

View file

@ -580,6 +580,15 @@ const config = convict({
},
},
publicApiEndpoints: {
path: {
format: String,
default: 'api',
env: 'N8N_PUBLIC_API_ENDPOINT',
doc: 'Path for the public api endpoints',
},
},
workflowTagsDisabled: {
format: Boolean,
default: false,

View file

@ -19,7 +19,7 @@
"bin": "n8n"
},
"scripts": {
"build": "tsc && cp -r ./src/UserManagement/email/templates ./dist/src/UserManagement/email",
"build": "tsc && cp -r ./src/UserManagement/email/templates ./dist/src/UserManagement/email && rsync -a --include='*/' --include='*.yml' --exclude='*' ./src/PublicApi/ ./dist/src/PublicApi/",
"dev": "concurrently -k -n \"TypeScript,Node\" -c \"yellow.bold,cyan.bold\" \"npm run watch\" \"nodemon\"",
"format": "cd ../.. && node_modules/prettier/bin-prettier.js packages/cli/**/**.ts --write",
"lint": "cd ../.. && node_modules/eslint/bin/eslint.js packages/cli",
@ -114,6 +114,7 @@
"csrf": "^3.1.0",
"dotenv": "^8.0.0",
"express": "^4.16.4",
"express-openapi-validator": "^4.13.6",
"fast-glob": "^3.2.5",
"flatted": "^3.2.4",
"google-timezones-json": "^1.0.2",

View file

@ -0,0 +1,39 @@
import {
Application,
} from 'express';
import * as OpenApiValidator from 'express-openapi-validator';
import path = require('path');
import express = require('express');
export interface N8nApp {
app: Application;
}
const publicApiController = express.Router();
export const getRoutes = (): express.Router => {
publicApiController.use(`/v1`,
OpenApiValidator.middleware({
apiSpec: path.join(__dirname, 'openapi.yml'),
operationHandlers: path.join(__dirname),
validateRequests: true, // (default)
validateApiSpec: true,
}));
//add error handler
//@ts-ignore
publicApiController.use((err, req, res, next) => {
res.status(err.status || 500).json({
message: err.message,
errors: err.errors,
});
});
return publicApiController;
};

View file

@ -0,0 +1,292 @@
---
openapi: 3.0.0
info:
title: Public n8n API
description: n8n Public API
termsOfService: https://n8n.io/legal/terms
contact:
email: hello@n8n.io
license:
name: Apache 2.0 with Commons Clause
url: https://github.com/n8n-io/n8n/blob/master/packages/cli/LICENSE.md
version: 1.0.0
externalDocs:
description: Find out more about Swagger
url: http://swagger.io
servers:
- url: /api/v1
tags:
- name: user
description: Operations about user
externalDocs:
description: Find out more about our store
url: http://swagger.io
paths:
/users:
get:
x-eov-operation-id: getUsers
x-eov-operation-handler: routes/Users
tags:
- users
summary: Retrieve all users
description: Retrieve all users from your instance. Only available for the instance owner.
parameters:
- name: select
in: query
required: false
style: form
explode: true
schema:
type: string
description: Comma-separted list of the properties to return. Use a to return all properties. Dot notation be use for nested properties
example: email,firstName
- name: limit
in: query
description: The maximum number of items to return
required: false
style: form
explode: true
schema:
type: number
example: 100
default: 100
- name: cursor
in: query
description: Paginate through users by setting the cursor parameter to a nextCursor attribute returned by a previous request's response_metadata. Default value fetches the first "page" of the collection. See pagination for more detail.
required: false
style: form
explode: true
schema:
type: string
example: MTIzZTQ1NjctZTg5Yi0xMmQzLWE0NTYtNDI2NjE0MTc0MDA
responses:
"200":
description: Successful operation
content:
application/json:
schema:
$ref: '#/components/schemas/inline_response_200'
"401":
description: Unauthorized
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
post:
x-eov-operation-id: createUsers
x-eov-operation-handler: routes/Users
tags:
- user
summary: Invite a user
description: Invites a user to your instance. Only available for the instance owner.
operationId: createUser
requestBody:
description: Created user object
content:
application/json:
schema:
$ref: '#/components/schemas/Users'
required: true
responses:
"200":
description: A User object
content:
application/json:
schema:
$ref: '#/components/schemas/Users'
"401":
description: Unauthorized
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
"422":
description: Unprocessable Entity
content:
application/json:
schema:
$ref: '#/components/schemas/InputValidationError'
/users/{userId}:
get:
x-eov-operation-id: getUser
x-eov-operation-handler: routes/Users
tags:
- users
summary: Get user by ID/Email
description: Retrieve a user from your instance. Only available for the instance owner.
operationId: getUser
parameters:
- name: userId
in: path
description: The ID or email of the user
required: true
style: simple
explode: false
schema:
type: string
responses:
"200":
description: Successful operation
content:
application/json:
schema:
$ref: '#/components/schemas/User'
"401":
description: Unauthorized
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
delete:
x-eov-operation-id: deleteUser
x-eov-operation-handler: routes/Users
tags:
- users
summary: Delete user by ID/Email
description: Deletes a user from your instance. Only available for the instance owner.
operationId: deleteUser
parameters:
- name: userId
in: path
description: The name that needs to be deleted
required: true
style: simple
explode: false
schema:
type: string
- name: transferId
in: query
description: ID of the user to transfer workflows and credentials to.
required: true
style: form
explode: true
schema:
type: string
responses:
"200":
description: User deleted successfully
"401":
description: Unauthorized
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
"404":
description: User not found
components:
schemas:
InputValidationError:
required:
- code
- description
- message
type: object
properties:
code:
type: string
message:
type: string
errors:
$ref: '#/components/schemas/Errors'
Error:
required:
- code
- description
- message
type: object
properties:
code:
type: string
message:
type: string
description:
type: string
Errors:
type: array
items:
$ref: '#/components/schemas/Error'
Users:
type: array
items:
$ref: '#/components/schemas/User'
User:
required:
- email
type: object
properties:
id:
type: string
readOnly: true
example: 123e4567-e89b-12d3-a456-426614174000
email:
type: string
example: jhon.doe@company.com
firstName:
maxLength: 32
type: string
description: User's first name
example: jhon
lastName:
maxLength: 32
type: string
description: User's last name
example: doe
finishedSetup:
type: boolean
description: Whether the user finished setting up the invitation or not
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 updaded
format: date-time
readOnly: true
inline_response_200:
type: object
properties:
users:
type: array
items:
$ref: '#/components/schemas/User'
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
responses:
NotFound:
description: The specified resource was not found
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
Unauthorized:
description: Unauthorized
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
Forbidden:
description: Forbidden
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
UnprocessableEntity:
description: Unprocessable Entity
content:
application/json:
schema:
$ref: '#/components/schemas/InputValidationError'
securitySchemes:
ApiKeyAuth:
type: apiKey
in: header
name: X-N8N-API-KEY
security:
- ApiKeyAuth: []

View file

@ -0,0 +1,19 @@
import express = require('express');
import { UserRequest } from '../../../../requests';
export = {
createUsers: async (req: UserRequest.Invite, res: express.Response) => {
res.json({ success: true});
},
deleteUser: async (req: UserRequest.Delete, res: express.Response) => {
console.log('aja')
res.json({ success: true });
},
getUser: async (req: UserRequest.Get, res: express.Response) => {
res.json({ success: true });
},
getUsers: async (req: UserRequest.Get, res: express.Response) => {
res.json({ success: true });
},
};

View file

@ -111,9 +111,11 @@ import {
Db,
ExternalHooks,
GenericHelpers,
getCredentialForUser,
ICredentialsDb,
ICredentialsOverwrite,
ICustomRequest,
IDiagnosticInfo,
IExecutionFlattedDb,
IExecutionFlattedResponse,
IExecutionPushResponse,
@ -122,7 +124,6 @@ import {
IExecutionsStopData,
IExecutionsSummary,
IExternalHooksClass,
IDiagnosticInfo,
IN8nUISettings,
IPackageVersions,
ITagWithCountDb,
@ -139,7 +140,6 @@ import {
WorkflowExecuteAdditionalData,
WorkflowHelpers,
WorkflowRunner,
getCredentialForUser,
} from '.';
import * as config from '../config';
@ -158,13 +158,13 @@ import { resolveJwt } from './UserManagement/auth/jwt';
import { User } from './databases/entities/User';
import { CredentialsEntity } from './databases/entities/CredentialsEntity';
import type {
AuthenticatedRequest,
CredentialRequest,
ExecutionRequest,
WorkflowRequest,
NodeParameterOptionsRequest,
OAuthRequest,
AuthenticatedRequest,
TagsRequest,
WorkflowRequest,
} from './requests';
import { DEFAULT_EXECUTIONS_GET_ALL_LIMIT, validateEntity } from './GenericHelpers';
import { ExecutionEntity } from './databases/entities/ExecutionEntity';
@ -172,6 +172,7 @@ import { SharedWorkflow } from './databases/entities/SharedWorkflow';
import { AUTH_COOKIE_NAME, RESPONSE_ERROR_MESSAGES } from './constants';
import { credentialsController } from './api/credentials.api';
import { getInstanceBaseUrl, isEmailSetUp } from './UserManagement/UserManagementHelper';
import * as publicApiv1Routes from './PublicApi/v1';
require('body-parser-xml')(bodyParser);
@ -220,6 +221,8 @@ class App {
restEndpoint: string;
publicApiEndpoint: string;
frontendSettings: IN8nUISettings;
protocol: string;
@ -252,6 +255,7 @@ class App {
this.payloadSizeMax = config.get('endpoints.payloadSizeMax') as number;
this.timezone = config.get('generic.timezone') as string;
this.restEndpoint = config.get('endpoints.rest') as string;
this.publicApiEndpoint = config.get('publicApiEndpoints.path') as string;
this.activeWorkflowRunner = ActiveWorkflowRunner.getInstance();
this.testWebhooks = TestWebhooks.getInstance();
@ -386,6 +390,7 @@ class App {
this.endpointWebhook,
this.endpointWebhookTest,
this.endpointPresetCredentials,
this.publicApiEndpoint,
];
// eslint-disable-next-line prefer-spread
ignoredEndpoints.push.apply(ignoredEndpoints, excludeEndpoints.split(':'));
@ -495,8 +500,9 @@ class App {
// eslint-disable-next-line no-inner-declarations
function isTenantAllowed(decodedToken: object): boolean {
if (jwtNamespace === '' || jwtAllowedTenantKey === '' || jwtAllowedTenant === '')
if (jwtNamespace === '' || jwtAllowedTenantKey === '' || jwtAllowedTenant === '') {
return true;
}
for (const [k, v] of Object.entries(decodedToken)) {
if (k === jwtNamespace) {
@ -554,6 +560,12 @@ class App {
});
}
// ----------------------------------------
// Public API
// ----------------------------------------
this.app.use(`/${this.publicApiEndpoint}`, publicApiv1Routes.getRoutes());
// Parse cookies for easier access
this.app.use(cookieParser());
@ -3081,7 +3093,7 @@ async function getExecutionsCount(
try {
// Get an estimate of rows count.
const estimateRowsNumberSql =
"SELECT n_live_tup FROM pg_stat_all_tables WHERE relname = 'execution_entity';";
'SELECT n_live_tup FROM pg_stat_all_tables WHERE relname = \'execution_entity\';';
const rows: Array<{ n_live_tup: string }> = await Db.collections.Execution!.query(
estimateRowsNumberSql,
);

View file

@ -196,7 +196,9 @@ export declare namespace UserRequest {
{ inviterId?: string; inviteeId?: string }
>;
export type Delete = AuthenticatedRequest<{ id: string }, {}, {}, { transferId?: string }>;
export type Delete = AuthenticatedRequest<{ id: string, email: string }, {}, {}, { transferId?: string }>;
export type Get = AuthenticatedRequest<{ id: string, email: string }, {}, {}, { limit: string, cursor: string }>;
export type Reinvite = AuthenticatedRequest<{ id: string }>;