Merge pull request #3019 from n8n-io/n8n-3171-add-authentication-handler

 Add authentication handler - public api
This commit is contained in:
Ricardo Espinoza 2022-03-21 15:20:41 -04:00 committed by GitHub
commit 53d88b33ce
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 70 additions and 3 deletions

View file

@ -9,6 +9,9 @@ import path = require('path');
import express = require('express'); import express = require('express');
import { OpenAPIV3 } from 'express-openapi-validator/dist/framework/types';
import { Db } from '../..';
export interface N8nApp { export interface N8nApp {
app: Application; app: Application;
} }
@ -23,6 +26,29 @@ export const getRoutes = (): express.Router => {
operationHandlers: path.join(__dirname), operationHandlers: path.join(__dirname),
validateRequests: true, // (default) validateRequests: true, // (default)
validateApiSpec: true, validateApiSpec: true,
validateSecurity: {
handlers: {
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;
}
req.user = user[0];
return true;
},
},
},
})); }));
//add error handler //add error handler

View file

@ -7,7 +7,6 @@ export = {
res.json({ success: true}); res.json({ success: true});
}, },
deleteUser: async (req: UserRequest.Delete, res: express.Response) => { deleteUser: async (req: UserRequest.Delete, res: express.Response) => {
console.log('aja')
res.json({ success: true }); res.json({ success: true });
}, },
getUser: async (req: UserRequest.Get, res: express.Response) => { getUser: async (req: UserRequest.Get, res: express.Response) => {

View file

@ -57,7 +57,7 @@ import * as clientOAuth1 from 'oauth-1.0a';
import { RequestOptions } from 'oauth-1.0a'; import { RequestOptions } from 'oauth-1.0a';
import * as csrf from 'csrf'; import * as csrf from 'csrf';
import * as requestPromise from 'request-promise-native'; import * as requestPromise from 'request-promise-native';
import { createHmac } from 'crypto'; import { createHmac, randomBytes } from 'crypto';
// IMPORTANT! Do not switch to anther bcrypt library unless really necessary and // IMPORTANT! Do not switch to anther bcrypt library unless really necessary and
// tested with all possible systems like Windows, Alpine on ARM, FreeBSD, ... // tested with all possible systems like Windows, Alpine on ARM, FreeBSD, ...
import { compare } from 'bcryptjs'; import { compare } from 'bcryptjs';
@ -564,6 +564,24 @@ class App {
// Public API // Public API
// ---------------------------------------- // ----------------------------------------
//test routes to create/regenerate/delete token
//NOTE: Only works with admin role
//This should be within the user's management user scope
this.app.post('/token', async (req: express.Request, res: express.Response) => {
const ramdonToken = randomBytes(20).toString('hex');
//@ts-ignore
await Db.collections.User!.update({ globalRole: 1 }, { apiKey: ramdonToken });
return ResponseHelper.sendSuccessResponse(res, { token: ramdonToken }, true, 200);
});
this.app.delete('/token', async (req: express.Request, res: express.Response) => {
//@ts-ignore
await Db.collections.User!.update({ globalRole: 1 }, { apiKey: null });
return ResponseHelper.sendSuccessResponse(res, {}, true, 204);
});
this.app.use(`/${this.publicApiEndpoint}`, publicApiv1Routes.getRoutes()); this.app.use(`/${this.publicApiEndpoint}`, publicApiv1Routes.getRoutes());
// Parse cookies for easier access // Parse cookies for easier access

View file

@ -6,7 +6,7 @@ import { Response } from 'express';
import { createHash } from 'crypto'; import { createHash } from 'crypto';
import { Db } from '../..'; import { Db } from '../..';
import { AUTH_COOKIE_NAME } from '../../constants'; import { AUTH_COOKIE_NAME } from '../../constants';
import { JwtToken, JwtPayload } from '../Interfaces'; import { JwtPayload, JwtToken } from '../Interfaces';
import { User } from '../../databases/entities/User'; import { User } from '../../databases/entities/User';
import config = require('../../../config'); import config = require('../../../config');

View file

@ -124,6 +124,9 @@ export class User {
this.updatedAt = new Date(); this.updatedAt = new Date();
} }
@Column({ type: String, nullable: true })
apiKey?: string | null;
/** /**
* Whether the user is pending setup completion. * Whether the user is pending setup completion.
*/ */

View file

@ -0,0 +1,19 @@
import {MigrationInterface, QueryRunner} from 'typeorm';
import * as config from '../../../../config';
export class AddAPIKeyColumn1647888658687 implements MigrationInterface {
name = 'AddAPIKeyColumn1647888658687';
async up(queryRunner: QueryRunner): Promise<void> {
const tablePrefix = config.get('database.tablePrefix');
await queryRunner.query(`ALTER TABLE "${tablePrefix}user" ADD apiKey varchar`);
}
async down(queryRunner: QueryRunner): Promise<void> {
const tablePrefix = config.get('database.tablePrefix');
await queryRunner.query(`ALTER TABLE "${tablePrefix}user" RENAME TO "temporary_user"`);
await queryRunner.query(`CREATE TABLE "${tablePrefix}user" ("id" varchar PRIMARY KEY NOT NULL, "email" varchar(255), "firstName" varchar(32), "lastName" varchar(32), "password" varchar, "resetPasswordToken" varchar, "resetPasswordTokenExpiration" integer DEFAULT (NULL), "personalizationAnswers" text, "createdAt" datetime(3) NOT NULL DEFAULT (STRFTIME('%Y-%m-%d %H:%M:%f', 'NOW')), "updatedAt" datetime(3) NOT NULL DEFAULT (STRFTIME('%Y-%m-%d %H:%M:%f', 'NOW')), "globalRoleId" integer NOT NULL, CONSTRAINT "FK_f0609be844f9200ff4365b1bb3d" FOREIGN KEY ("globalRoleId") REFERENCES "role" ("id") ON DELETE NO ACTION ON UPDATE NO ACTION)`);
await queryRunner.query(`INSERT INTO "${tablePrefix}user"("id", "email", "firstName", "lastName", "password", "resetPasswordToken", "resetPasswordTokenExpiration", "personalizationAnswers", "createdAt", "updatedAt", "globalRoleId") SELECT "id", "email", "firstName", "lastName", "password", "resetPasswordToken", "resetPasswordTokenExpiration", "personalizationAnswers", "createdAt", "updatedAt", "globalRoleId" FROM "temporary_user"`);
await queryRunner.query(`DROP TABLE "temporary_user"`);
}
}

View file

@ -11,6 +11,7 @@ import { AddWaitColumn1621707690587 } from './1621707690587-AddWaitColumn';
import { UpdateWorkflowCredentials1630330987096 } from './1630330987096-UpdateWorkflowCredentials'; import { UpdateWorkflowCredentials1630330987096 } from './1630330987096-UpdateWorkflowCredentials';
import { AddExecutionEntityIndexes1644421939510 } from './1644421939510-AddExecutionEntityIndexes'; import { AddExecutionEntityIndexes1644421939510 } from './1644421939510-AddExecutionEntityIndexes';
import { CreateUserManagement1646992772331 } from './1646992772331-CreateUserManagement'; import { CreateUserManagement1646992772331 } from './1646992772331-CreateUserManagement';
import { AddAPIKeyColumn1647888658687 } from './1647888658687-AddAPIKeyColumn';
const sqliteMigrations = [ const sqliteMigrations = [
InitialMigration1588102412422, InitialMigration1588102412422,
@ -24,6 +25,7 @@ const sqliteMigrations = [
UpdateWorkflowCredentials1630330987096, UpdateWorkflowCredentials1630330987096,
AddExecutionEntityIndexes1644421939510, AddExecutionEntityIndexes1644421939510,
CreateUserManagement1646992772331, CreateUserManagement1646992772331,
AddAPIKeyColumn1647888658687,
]; ];
export { sqliteMigrations }; export { sqliteMigrations };