Bootstrap static json applications definition
This commit is contained in:
@@ -0,0 +1,42 @@
|
||||
import { Prefix, TdriveService } from "../../core/platform/framework";
|
||||
import WebServerAPI from "../../core/platform/services/webserver/provider";
|
||||
import web from "./web/index";
|
||||
import FastProxy from "fast-proxy";
|
||||
import globalResolver from "../global-resolver";
|
||||
|
||||
@Prefix("/api")
|
||||
export default class ApplicationsApiService extends TdriveService<undefined> {
|
||||
version = "1";
|
||||
name = "applicationsapi";
|
||||
|
||||
public async doInit(): Promise<this> {
|
||||
const fastify = this.context.getProvider<WebServerAPI>("webserver").getServer();
|
||||
fastify.register((instance, _opts, next) => {
|
||||
web(instance, { prefix: this.prefix });
|
||||
next();
|
||||
});
|
||||
|
||||
//Redirect requests from /plugins/* to the plugin server (if installed)
|
||||
const apps = await globalResolver.services.applications.marketplaceApps.list(null);
|
||||
for (const app of apps) {
|
||||
const domain = app.internal_domain;
|
||||
const prefix = app.external_prefix;
|
||||
if (domain && prefix) {
|
||||
const { proxy, close } = FastProxy({
|
||||
base: domain,
|
||||
});
|
||||
fastify.addHook("onClose", close);
|
||||
fastify.all("/" + prefix.replace(/(\/$|^\/)/, "") + "/*", (req, rep) => {
|
||||
proxy(req.raw, rep.raw, req.url, {});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
// TODO: remove
|
||||
api(): undefined {
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
import { Channel } from "../channels/entities";
|
||||
import { Message } from "../messages/entities/messages";
|
||||
import { Thread } from "../messages/entities/threads";
|
||||
|
||||
export type HookType = {
|
||||
type: "message";
|
||||
application_id: string;
|
||||
company_id: string;
|
||||
|
||||
channel?: Channel;
|
||||
thread: Thread;
|
||||
message: Message;
|
||||
};
|
||||
@@ -0,0 +1,181 @@
|
||||
import { FastifyInstance, FastifyReply, FastifyRequest, HTTPMethods } from "fastify";
|
||||
import { ApplicationObject } from "../../../applications/entities/application";
|
||||
import {
|
||||
ApplicationApiExecutionContext,
|
||||
ApplicationLoginRequest,
|
||||
ApplicationLoginResponse,
|
||||
ConfigureRequest,
|
||||
} from "../types";
|
||||
import { ResourceGetResponse } from "../../../../utils/types";
|
||||
import { CrudException } from "../../../../core/platform/framework/api/crud-service";
|
||||
import { localEventBus } from "../../../../core/platform/framework/event-bus";
|
||||
import {
|
||||
RealtimeApplicationEvent,
|
||||
RealtimeBaseBusEvent,
|
||||
} from "../../../../core/platform/services/realtime/types";
|
||||
import gr from "../../../global-resolver";
|
||||
import _ from "lodash";
|
||||
import { v4 } from "uuid";
|
||||
|
||||
export class ApplicationsApiController {
|
||||
async token(
|
||||
request: FastifyRequest<{ Body: ApplicationLoginRequest }>,
|
||||
): Promise<ResourceGetResponse<ApplicationLoginResponse>> {
|
||||
const context = getExecutionContext(request);
|
||||
|
||||
if (!request.body.id || !request.body.secret) {
|
||||
throw CrudException.forbidden("Application not found");
|
||||
}
|
||||
|
||||
const app = await gr.services.applications.marketplaceApps.get(
|
||||
{
|
||||
id: request.body.id,
|
||||
},
|
||||
context,
|
||||
);
|
||||
|
||||
if (!app) {
|
||||
throw CrudException.forbidden("Application not found");
|
||||
}
|
||||
|
||||
if (!app.api.private_key || app.api.private_key !== request.body.secret) {
|
||||
throw CrudException.forbidden("Secret key is not valid");
|
||||
}
|
||||
|
||||
return {
|
||||
resource: {
|
||||
access_token: gr.platformServices.auth.generateJWT(request.body.id, null, {
|
||||
track: false,
|
||||
provider_id: "",
|
||||
application_id: request.body.id,
|
||||
}),
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
async me(
|
||||
request: FastifyRequest,
|
||||
_reply: FastifyReply,
|
||||
): Promise<ResourceGetResponse<ApplicationObject>> {
|
||||
const context = getExecutionContext(request);
|
||||
|
||||
const entity = await gr.services.applications.marketplaceApps.get(
|
||||
{
|
||||
id: context.application_id,
|
||||
},
|
||||
context,
|
||||
);
|
||||
if (!entity) {
|
||||
throw CrudException.notFound("Application not found");
|
||||
}
|
||||
|
||||
return { resource: entity.getApplicationObject() };
|
||||
}
|
||||
|
||||
async configure(
|
||||
request: FastifyRequest<{ Body: ConfigureRequest }>,
|
||||
_reply: FastifyReply,
|
||||
): Promise<Record<string, string>> {
|
||||
const app_id = request.currentUser.application_id;
|
||||
const context = getExecutionContext(request);
|
||||
const application = await gr.services.applications.marketplaceApps.get({ id: app_id }, context);
|
||||
|
||||
if (!application) {
|
||||
throw CrudException.forbidden("Application not found");
|
||||
}
|
||||
|
||||
const body = request.body;
|
||||
|
||||
const data = {
|
||||
action: "configure",
|
||||
application: {
|
||||
id: app_id,
|
||||
identity: application.identity,
|
||||
},
|
||||
form: body.form,
|
||||
connection_id: body.connection_id,
|
||||
hidden_data: {},
|
||||
configurator_id: v4(),
|
||||
};
|
||||
|
||||
localEventBus.publish("realtime:event", {
|
||||
room: "/me/" + body.user_id,
|
||||
type: "application",
|
||||
data,
|
||||
} as RealtimeBaseBusEvent<RealtimeApplicationEvent>);
|
||||
|
||||
return { status: "ok" };
|
||||
}
|
||||
|
||||
async proxy(
|
||||
request: FastifyRequest<{ Params: { company_id: string; service: string; version: string } }>,
|
||||
reply: FastifyReply,
|
||||
fastify: FastifyInstance,
|
||||
): Promise<void> {
|
||||
// Check the application has access to this company
|
||||
const company_id = request.params.company_id;
|
||||
const companyApplication = gr.services.applications.companyApps.get({
|
||||
company_id,
|
||||
application_id: request.currentUser.application_id,
|
||||
id: undefined,
|
||||
});
|
||||
if (!companyApplication) {
|
||||
throw CrudException.forbidden("This application is not installed in the requested company");
|
||||
}
|
||||
|
||||
const context = getExecutionContext(request);
|
||||
const app = await gr.services.applications.marketplaceApps.get(
|
||||
{
|
||||
id: request.currentUser.application_id,
|
||||
},
|
||||
context,
|
||||
);
|
||||
|
||||
// Check call can be done from this IP
|
||||
if (
|
||||
app.api.allowed_ips.trim() &&
|
||||
app.api.allowed_ips !== "*" &&
|
||||
!_.includes(
|
||||
app.api.allowed_ips
|
||||
.split(",")
|
||||
.map(a => a.trim())
|
||||
.filter(a => a),
|
||||
request.ip,
|
||||
)
|
||||
) {
|
||||
throw CrudException.forbidden(
|
||||
`This application is not allowed to access from this IP (${request.ip})`,
|
||||
);
|
||||
}
|
||||
|
||||
//TODO Check application access rights (write, read, remove for each micro services)
|
||||
const _access = app.access;
|
||||
|
||||
//TODO save some statistics about API usage for application and per companies
|
||||
|
||||
const route = request.url.replace("/api/", "/internal/services/");
|
||||
|
||||
fastify.inject(
|
||||
{
|
||||
method: request.method as HTTPMethods,
|
||||
url: route,
|
||||
payload: request.body as any,
|
||||
headers: _.pick(request.headers, "authorization"),
|
||||
},
|
||||
(err, response) => {
|
||||
reply.headers(response.headers);
|
||||
reply.send(response.payload);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
function getExecutionContext(request: FastifyRequest): ApplicationApiExecutionContext {
|
||||
return {
|
||||
application_id: request.currentUser?.application_id,
|
||||
user: request.currentUser,
|
||||
url: request.url,
|
||||
method: request.routerMethod,
|
||||
transport: "http",
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
import { FastifyInstance, FastifyRegisterOptions } from "fastify";
|
||||
import routes from "./routes";
|
||||
|
||||
export default (
|
||||
fastify: FastifyInstance,
|
||||
options: FastifyRegisterOptions<{ prefix: string }>,
|
||||
): void => {
|
||||
fastify.log.debug("Configuring /internal/services/applications/v1 routes");
|
||||
fastify.register(routes, options);
|
||||
};
|
||||
@@ -0,0 +1,54 @@
|
||||
import { FastifyInstance, FastifyPluginCallback, FastifyRequest } from "fastify";
|
||||
|
||||
import { ApplicationsApiController } from "./controllers";
|
||||
import { ApplicationApiBaseRequest } from "./types";
|
||||
import { logger as log } from "../../../core/platform/framework";
|
||||
import { configureRequestSchema } from "./schemas";
|
||||
|
||||
const routes: FastifyPluginCallback = (fastify: FastifyInstance, options, next) => {
|
||||
const controller = new ApplicationsApiController();
|
||||
|
||||
const checkApplication = async (request: FastifyRequest<{ Body: ApplicationApiBaseRequest }>) => {
|
||||
if (!request.currentUser.application_id) {
|
||||
log.debug(request.currentUser);
|
||||
throw fastify.httpErrors.forbidden("You should log in as application");
|
||||
}
|
||||
};
|
||||
|
||||
//Authenticate the application
|
||||
fastify.route({
|
||||
method: "POST",
|
||||
url: "/console/v1/login",
|
||||
handler: controller.token.bind(controller),
|
||||
});
|
||||
|
||||
//Get myself as an application
|
||||
fastify.route({
|
||||
method: "GET",
|
||||
url: "/console/v1/me",
|
||||
preValidation: [fastify.authenticate],
|
||||
preHandler: [checkApplication],
|
||||
handler: controller.me.bind(controller),
|
||||
});
|
||||
|
||||
//Open a configuration popup on the client side
|
||||
fastify.route({
|
||||
method: "POST",
|
||||
url: "/console/v1/configure",
|
||||
preValidation: [fastify.authenticate],
|
||||
schema: configureRequestSchema,
|
||||
handler: controller.configure.bind(controller),
|
||||
});
|
||||
|
||||
//Get myself as an application
|
||||
fastify.route({
|
||||
method: ["POST", "GET", "DELETE", "PUT"],
|
||||
url: "/:service/:version/companies/:company_id/*",
|
||||
preValidation: [fastify.authenticate],
|
||||
handler: (request, reply) => controller.proxy.bind(controller)(request, reply, fastify),
|
||||
});
|
||||
|
||||
next();
|
||||
};
|
||||
|
||||
export default routes;
|
||||
@@ -0,0 +1,16 @@
|
||||
export const applicationsSchema = {
|
||||
type: "object",
|
||||
properties: {},
|
||||
};
|
||||
|
||||
export const configureRequestSchema = {
|
||||
body: {
|
||||
type: "object",
|
||||
properties: {
|
||||
user_id: { type: "string" },
|
||||
connection_id: { type: "string" },
|
||||
form: {},
|
||||
},
|
||||
required: ["user_id", "connection_id"],
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,23 @@
|
||||
import { AccessToken } from "../../../utils/types";
|
||||
import { ExecutionContext } from "../../../core/platform/framework/api/crud-service";
|
||||
|
||||
export interface ApplicationApiBaseRequest {
|
||||
id: string;
|
||||
secret: string;
|
||||
}
|
||||
|
||||
export type ApplicationLoginRequest = ApplicationApiBaseRequest;
|
||||
|
||||
export interface ApplicationLoginResponse {
|
||||
access_token: AccessToken;
|
||||
}
|
||||
|
||||
export interface ApplicationApiExecutionContext extends ExecutionContext {
|
||||
application_id: string;
|
||||
}
|
||||
|
||||
export interface ConfigureRequest {
|
||||
user_id: string;
|
||||
connection_id: string;
|
||||
form?: any;
|
||||
}
|
||||
Reference in New Issue
Block a user