mirror of
https://github.com/hexastack/hexabot
synced 2025-06-26 18:27:28 +00:00
Merge branch 'main' of https://github.com/Hexastack/Hexabot into 256-request-move-blocks-between-categories
This commit is contained in:
@@ -6,21 +6,13 @@
|
||||
* 2. All derivative works must include clear attribution to the original creator and software, Hexastack and Hexabot, in a prominent location (e.g., in the software's "About" section, documentation, and README file).
|
||||
*/
|
||||
|
||||
import {
|
||||
BadRequestException,
|
||||
Controller,
|
||||
Get,
|
||||
Req,
|
||||
Res,
|
||||
Session,
|
||||
} from '@nestjs/common';
|
||||
import { Controller, Get, Req, Session } from '@nestjs/common';
|
||||
import { CsrfCheck, CsrfGenAuth } from '@tekuconcept/nestjs-csrf';
|
||||
import { CsrfGenerator } from '@tekuconcept/nestjs-csrf/dist/csrf.generator';
|
||||
import { Request, Response } from 'express';
|
||||
import { Request } from 'express';
|
||||
import { Session as ExpressSession } from 'express-session';
|
||||
|
||||
import { AppService } from './app.service';
|
||||
import { config } from './config';
|
||||
import { LoggerService } from './logger/logger.service';
|
||||
import { Roles } from './utils/decorators/roles.decorator';
|
||||
|
||||
@@ -53,20 +45,6 @@ export class AppController {
|
||||
@Get('__getcookie')
|
||||
cookies(@Req() req: Request): string {
|
||||
req.session.anonymous = true;
|
||||
return '_sailsIoJSConnect();';
|
||||
}
|
||||
|
||||
// @TODO : remove once old frontend is abandoned
|
||||
@Get('logout')
|
||||
logout(@Req() req: Request, @Res({ passthrough: true }) res: Response) {
|
||||
res.clearCookie(config.session.name);
|
||||
|
||||
req.session.destroy((error) => {
|
||||
if (error) {
|
||||
this.logger.error(error);
|
||||
throw new BadRequestException();
|
||||
}
|
||||
});
|
||||
return { status: 'ok' };
|
||||
return '';
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
{
|
||||
"verification_token": "Verification Token",
|
||||
"allowed_domains": "Allowed Domains",
|
||||
"start_button": "Enable `Get Started`",
|
||||
"input_disabled": "Disable Input",
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
{
|
||||
"verification_token": "Jeton de vérification",
|
||||
"allowed_domains": "Domaines autorisés",
|
||||
"start_button": "Activer `Démarrer`",
|
||||
"input_disabled": "Désactiver la saisie",
|
||||
|
||||
@@ -17,12 +17,6 @@ export const CONSOLE_CHANNEL_NAME = 'console-channel';
|
||||
export const CONSOLE_CHANNEL_NAMESPACE = 'console_channel';
|
||||
|
||||
export default [
|
||||
{
|
||||
group: CONSOLE_CHANNEL_NAMESPACE,
|
||||
label: Web.SettingLabel.verification_token,
|
||||
value: 'test',
|
||||
type: SettingType.text,
|
||||
},
|
||||
{
|
||||
group: CONSOLE_CHANNEL_NAMESPACE,
|
||||
label: Web.SettingLabel.allowed_domains,
|
||||
|
||||
@@ -77,7 +77,7 @@ export default abstract class BaseWebChannelHandler<
|
||||
protected readonly attachmentService: AttachmentService,
|
||||
protected readonly messageService: MessageService,
|
||||
protected readonly menuService: MenuService,
|
||||
private readonly websocketGateway: WebsocketGateway,
|
||||
protected readonly websocketGateway: WebsocketGateway,
|
||||
) {
|
||||
super(name, settingService, channelService, logger);
|
||||
}
|
||||
@@ -98,42 +98,32 @@ export default abstract class BaseWebChannelHandler<
|
||||
*/
|
||||
@OnEvent('hook:websocket:connection', { async: true })
|
||||
async onWebSocketConnection(client: Socket) {
|
||||
const settings = await this.getSettings();
|
||||
const handshake = client.handshake;
|
||||
const { channel } = handshake.query;
|
||||
if (channel !== this.getName()) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
const { verification_token } = client.handshake.query;
|
||||
await this.verifyToken(verification_token.toString());
|
||||
try {
|
||||
this.logger.debug(
|
||||
'Web Channel Handler : WS connected .. sending settings',
|
||||
);
|
||||
try {
|
||||
const menu = await this.menuService.getTree();
|
||||
return client.emit('settings', { menu, ...settings });
|
||||
} catch (err) {
|
||||
this.logger.warn(
|
||||
'Web Channel Handler : Unable to retrieve menu ',
|
||||
err,
|
||||
);
|
||||
return client.emit('settings', settings);
|
||||
}
|
||||
} catch (err) {
|
||||
this.logger.warn(
|
||||
'Web Channel Handler : Unable to verify token, disconnecting ...',
|
||||
err,
|
||||
);
|
||||
client.disconnect();
|
||||
const settings = await this.getSettings();
|
||||
const handshake = client.handshake;
|
||||
const { channel } = handshake.query;
|
||||
|
||||
if (channel !== this.getName()) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.logger.debug(
|
||||
'Web Channel Handler : WS connected .. sending settings',
|
||||
);
|
||||
|
||||
try {
|
||||
const menu = await this.menuService.getTree();
|
||||
return client.emit('settings', { menu, ...settings });
|
||||
} catch (err) {
|
||||
this.logger.warn('Web Channel Handler : Unable to retrieve menu ', err);
|
||||
return client.emit('settings', settings);
|
||||
}
|
||||
} catch (err) {
|
||||
this.logger.error(
|
||||
'Web Channel Handler : Unable to initiate websocket connection',
|
||||
err,
|
||||
);
|
||||
client.disconnect();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -218,7 +208,7 @@ export default abstract class BaseWebChannelHandler<
|
||||
*
|
||||
* @returns Formatted message
|
||||
*/
|
||||
private formatHistoryMessages(messages: AnyMessage[]): Web.Message[] {
|
||||
protected formatMessages(messages: AnyMessage[]): Web.Message[] {
|
||||
return messages.map((anyMessage: AnyMessage) => {
|
||||
if ('sender' in anyMessage && anyMessage.sender) {
|
||||
return {
|
||||
@@ -262,7 +252,7 @@ export default abstract class BaseWebChannelHandler<
|
||||
until,
|
||||
n,
|
||||
);
|
||||
return this.formatHistoryMessages(messages.reverse());
|
||||
return this.formatMessages(messages.reverse());
|
||||
}
|
||||
return [];
|
||||
}
|
||||
@@ -287,35 +277,11 @@ export default abstract class BaseWebChannelHandler<
|
||||
since,
|
||||
n,
|
||||
);
|
||||
return this.formatHistoryMessages(messages);
|
||||
return this.formatMessages(messages);
|
||||
}
|
||||
return [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify the received token.
|
||||
*
|
||||
* @param verificationToken - Verification Token
|
||||
*/
|
||||
private async verifyToken(verificationToken: string) {
|
||||
const settings =
|
||||
(await this.getSettings()) as unknown as Settings[typeof WEB_CHANNEL_NAMESPACE];
|
||||
const verifyToken = settings.verification_token;
|
||||
|
||||
if (!verifyToken) {
|
||||
throw new Error('You need to specify a verifyToken in your config.');
|
||||
}
|
||||
if (!verificationToken) {
|
||||
throw new Error('Did not recieve any verification token.');
|
||||
}
|
||||
if (verificationToken !== verifyToken) {
|
||||
throw new Error('Make sure the validation tokens match.');
|
||||
}
|
||||
this.logger.log(
|
||||
'Web Channel Handler : Token has been verified successfully!',
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify the origin against whitelisted domains.
|
||||
*
|
||||
@@ -405,20 +371,12 @@ export default abstract class BaseWebChannelHandler<
|
||||
* @param req
|
||||
* @param res
|
||||
*/
|
||||
private async checkRequest(
|
||||
protected async checkRequest(
|
||||
req: Request | SocketRequest,
|
||||
res: Response | SocketResponse,
|
||||
) {
|
||||
try {
|
||||
await this.validateCors(req, res);
|
||||
try {
|
||||
const { verification_token } =
|
||||
'verification_token' in req.query ? req.query : req.body;
|
||||
await this.verifyToken(verification_token);
|
||||
} catch (err) {
|
||||
this.logger.warn('Web Channel Handler : Unable to verify token ', err);
|
||||
throw new Error('Unauthorized, invalid token!');
|
||||
}
|
||||
} catch (err) {
|
||||
this.logger.warn(
|
||||
'Web Channel Handler : Attempt to access from an unauthorized origin',
|
||||
@@ -741,7 +699,7 @@ export default abstract class BaseWebChannelHandler<
|
||||
*
|
||||
* @returns IP Address
|
||||
*/
|
||||
private getIpAddress(req: Request | SocketRequest): string {
|
||||
protected getIpAddress(req: Request | SocketRequest): string {
|
||||
if ('isSocket' in req && req.isSocket) {
|
||||
return req.socket.handshake.address;
|
||||
} else if (Array.isArray(req.ips) && req.ips.length > 0) {
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
{
|
||||
"verification_token": "Verification Token",
|
||||
"allowed_domains": "Allowed Domains",
|
||||
"start_button": "Enable `Get Started`",
|
||||
"input_disabled": "Disable Input",
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
{
|
||||
"verification_token": "Jeton de vérification",
|
||||
"allowed_domains": "Domaines autorisés",
|
||||
"start_button": "Activer `Démarrer`",
|
||||
"input_disabled": "Désactiver la saisie",
|
||||
|
||||
@@ -16,12 +16,6 @@ export const WEB_CHANNEL_NAME = 'web-channel' as const;
|
||||
export const WEB_CHANNEL_NAMESPACE = 'web_channel';
|
||||
|
||||
export default [
|
||||
{
|
||||
group: WEB_CHANNEL_NAMESPACE,
|
||||
label: Web.SettingLabel.verification_token,
|
||||
value: 'token123',
|
||||
type: SettingType.secret,
|
||||
},
|
||||
{
|
||||
group: WEB_CHANNEL_NAMESPACE,
|
||||
label: Web.SettingLabel.allowed_domains,
|
||||
@@ -68,7 +62,7 @@ export default [
|
||||
{
|
||||
group: WEB_CHANNEL_NAMESPACE,
|
||||
label: Web.SettingLabel.avatar_url,
|
||||
value: 'https://eu.ui-avatars.com/api/?name=Hexa+Bot&size=64',
|
||||
value: '',
|
||||
type: SettingType.text,
|
||||
},
|
||||
{
|
||||
|
||||
@@ -13,8 +13,6 @@ import { StdQuickReply } from '@/chat/schemas/types/quick-reply';
|
||||
|
||||
export namespace Web {
|
||||
export enum SettingLabel {
|
||||
secret = 'secret',
|
||||
verification_token = 'verification_token',
|
||||
allowed_domains = 'allowed_domains',
|
||||
start_button = 'start_button',
|
||||
input_disabled = 'input_disabled',
|
||||
|
||||
5
api/src/global.d.ts
vendored
5
api/src/global.d.ts
vendored
@@ -6,6 +6,9 @@
|
||||
* 2. All derivative works must include clear attribution to the original creator and software, Hexastack and Hexabot, in a prominent location (e.g., in the software's "About" section, documentation, and README file).
|
||||
*/
|
||||
|
||||
import '../types/event-emitter';
|
||||
import '../types/express-session';
|
||||
|
||||
declare global {
|
||||
type HyphenToUnderscore<S extends string> = S extends `${infer P}-${infer Q}`
|
||||
? `${P}_${HyphenToUnderscore<Q>}`
|
||||
@@ -13,4 +16,4 @@ declare global {
|
||||
}
|
||||
|
||||
// eslint-disable-next-line prettier/prettier
|
||||
export { };
|
||||
export {};
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
* 2. All derivative works must include clear attribution to the original creator and software, Hexastack and Hexabot, in a prominent location (e.g., in the software's "About" section, documentation, and README file).
|
||||
*/
|
||||
|
||||
import { HttpModule } from '@nestjs/axios';
|
||||
import { Global, Module } from '@nestjs/common';
|
||||
import { MongooseModule } from '@nestjs/mongoose';
|
||||
import { InjectDynamicProviders } from 'nestjs-dynamic-providers';
|
||||
@@ -40,6 +41,7 @@ import { PluginService } from './plugins.service';
|
||||
CmsModule,
|
||||
AttachmentModule,
|
||||
ChatModule,
|
||||
HttpModule,
|
||||
],
|
||||
providers: [PluginService],
|
||||
exports: [PluginService],
|
||||
|
||||
@@ -83,8 +83,8 @@ export class ReadOnlyUserController extends BaseController<
|
||||
*/
|
||||
@Roles('public')
|
||||
@Get('bot/profile_pic')
|
||||
async botProfilePic() {
|
||||
return getBotAvatar();
|
||||
async botProfilePic(@Query('color') color: string) {
|
||||
return getBotAvatar(color);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -39,8 +39,8 @@ export const generateInitialsAvatar = async (name: {
|
||||
return await generateAvatarSvg(svg);
|
||||
};
|
||||
|
||||
export const getBotAvatar = async () => {
|
||||
const svg = generateBotAvatarSvg({});
|
||||
export const getBotAvatar = async (color: string) => {
|
||||
const svg = generateBotAvatarSvg({ bgColor: color });
|
||||
return await generateAvatarSvg(svg);
|
||||
};
|
||||
|
||||
|
||||
@@ -43,17 +43,17 @@ export function generateUIAvatarSvg({
|
||||
|
||||
export function generateBotAvatarSvg({
|
||||
size = 64,
|
||||
bgColor = '#d1d1d1',
|
||||
bgColor = '#000',
|
||||
textColor = '#fff',
|
||||
}: UIAvatarSvgParams): string {
|
||||
return `
|
||||
<svg width="${size}px" height="${size}px" viewBox="-3.36 -3.36 30.72 30.72" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" fill="#4f4f4f" stroke="#4f4f4f" transform="rotate(0)">
|
||||
<svg width="${size}px" height="${size}px" viewBox="-3.36 -3.36 30.72 30.72" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<g stroke-width="0" transform="translate(0,0), scale(1)">
|
||||
<rect x="-3.36" y="-3.36" width="30.72" height="30.72" rx="15.36" fill="${bgColor}" strokewidth="0"></rect>
|
||||
</g>
|
||||
<g stroke-linecap="round" stroke-linejoin="round" stroke="#CCCCCC" stroke-width="0.336"></g>
|
||||
<g>
|
||||
<g stroke-width="0.00024000000000000003" fill="none" fill-rule="evenodd">
|
||||
<g fill="#000000" fill-rule="nonzero">
|
||||
<g stroke-width="0.00024000000000000003" fill="${textColor}" fill-rule="evenodd">
|
||||
<g fill-rule="nonzero">
|
||||
<path d="M17.7530511,13.999921 C18.9956918,13.999921 20.0030511,15.0072804 20.0030511,16.249921 L20.0030511,17.1550008 C20.0030511,18.2486786 19.5255957,19.2878579 18.6957793,20.0002733 C17.1303315,21.344244 14.8899962,22.0010712 12,22.0010712 C9.11050247,22.0010712 6.87168436,21.3444691 5.30881727,20.0007885 C4.48019625,19.2883988 4.00354153,18.2500002 4.00354153,17.1572408 L4.00354153,16.249921 C4.00354153,15.0072804 5.01090084,13.999921 6.25354153,13.999921 L17.7530511,13.999921 Z M17.7530511,15.499921 L6.25354153,15.499921 C5.83932796,15.499921 5.50354153,15.8357075 5.50354153,16.249921 L5.50354153,17.1572408 C5.50354153,17.8128951 5.78953221,18.4359296 6.28670709,18.8633654 C7.5447918,19.9450082 9.44080155,20.5010712 12,20.5010712 C14.5599799,20.5010712 16.4578003,19.9446634 17.7186879,18.8621641 C18.2165778,18.4347149 18.5030511,17.8112072 18.5030511,17.1550005 L18.5030511,16.249921 C18.5030511,15.8357075 18.1672647,15.499921 17.7530511,15.499921 Z M11.8985607,2.00734093 L12.0003312,2.00049432 C12.380027,2.00049432 12.6938222,2.2826482 12.7434846,2.64872376 L12.7503312,2.75049432 L12.7495415,3.49949432 L16.25,3.5 C17.4926407,3.5 18.5,4.50735931 18.5,5.75 L18.5,10.254591 C18.5,11.4972317 17.4926407,12.504591 16.25,12.504591 L7.75,12.504591 C6.50735931,12.504591 5.5,11.4972317 5.5,10.254591 L5.5,5.75 C5.5,4.50735931 6.50735931,3.5 7.75,3.5 L11.2495415,3.49949432 L11.2503312,2.75049432 C11.2503312,2.37079855 11.5324851,2.05700336 11.8985607,2.00734093 L12.0003312,2.00049432 L11.8985607,2.00734093 Z M16.25,5 L7.75,5 C7.33578644,5 7,5.33578644 7,5.75 L7,10.254591 C7,10.6688046 7.33578644,11.004591 7.75,11.004591 L16.25,11.004591 C16.6642136,11.004591 17,10.6688046 17,10.254591 L17,5.75 C17,5.33578644 16.6642136,5 16.25,5 Z M9.74928905,6.5 C10.4392523,6.5 10.9985781,7.05932576 10.9985781,7.74928905 C10.9985781,8.43925235 10.4392523,8.99857811 9.74928905,8.99857811 C9.05932576,8.99857811 8.5,8.43925235 8.5,7.74928905 C8.5,7.05932576 9.05932576,6.5 9.74928905,6.5 Z M14.2420255,6.5 C14.9319888,6.5 15.4913145,7.05932576 15.4913145,7.74928905 C15.4913145,8.43925235 14.9319888,8.99857811 14.2420255,8.99857811 C13.5520622,8.99857811 12.9927364,8.43925235 12.9927364,7.74928905 C12.9927364,7.05932576 13.5520622,6.5 14.2420255,6.5 Z"></path>
|
||||
</g>
|
||||
</g>
|
||||
|
||||
@@ -16,11 +16,14 @@ export type TFilterKeysOfNeverType<T> = Omit<T, TFilterKeysOfType<T, []>>;
|
||||
|
||||
export type NestedKeys<T> = T extends object
|
||||
? {
|
||||
[K in keyof T]: Array<any> extends T[K]
|
||||
? Exclude<K, symbol>
|
||||
: K extends symbol
|
||||
// eslint-disable-next-line @typescript-eslint/ban-types
|
||||
[K in keyof T]: T[K] extends Function
|
||||
? never
|
||||
: Array<any> extends T[K]
|
||||
? Exclude<K, symbol>
|
||||
: `${Exclude<K, symbol>}${'' | `.${NestedKeys<T[K]>}`}`;
|
||||
: K extends symbol
|
||||
? Exclude<K, symbol>
|
||||
: `${Exclude<K, symbol>}${'' | `.${NestedKeys<T[K]>}`}`;
|
||||
}[keyof T]
|
||||
: never;
|
||||
|
||||
|
||||
@@ -224,7 +224,7 @@ export class WebsocketGateway
|
||||
'Unable to load session, creating a new one ...',
|
||||
err,
|
||||
);
|
||||
if (searchParams.get('channel') === 'web-channel') {
|
||||
if (searchParams.get('channel') !== 'console-channel') {
|
||||
return this.createAndStoreSession(client, next);
|
||||
} else {
|
||||
return next(new Error('Unauthorized: Unknown session ID'));
|
||||
|
||||
Reference in New Issue
Block a user