mirror of
https://github.com/hexastack/hexabot
synced 2025-06-26 18:27:28 +00:00
Merge pull request #323 from Hexastack/refactor/remove-widget-token
refactor: remove widget token
This commit is contained in:
commit
72db79a838
@ -1,5 +1,4 @@
|
|||||||
{
|
{
|
||||||
"verification_token": "Verification Token",
|
|
||||||
"allowed_domains": "Allowed Domains",
|
"allowed_domains": "Allowed Domains",
|
||||||
"start_button": "Enable `Get Started`",
|
"start_button": "Enable `Get Started`",
|
||||||
"input_disabled": "Disable Input",
|
"input_disabled": "Disable Input",
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
{
|
{
|
||||||
"verification_token": "Jeton de vérification",
|
|
||||||
"allowed_domains": "Domaines autorisés",
|
"allowed_domains": "Domaines autorisés",
|
||||||
"start_button": "Activer `Démarrer`",
|
"start_button": "Activer `Démarrer`",
|
||||||
"input_disabled": "Désactiver la saisie",
|
"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 const CONSOLE_CHANNEL_NAMESPACE = 'console_channel';
|
||||||
|
|
||||||
export default [
|
export default [
|
||||||
{
|
|
||||||
group: CONSOLE_CHANNEL_NAMESPACE,
|
|
||||||
label: Web.SettingLabel.verification_token,
|
|
||||||
value: 'test',
|
|
||||||
type: SettingType.text,
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
group: CONSOLE_CHANNEL_NAMESPACE,
|
group: CONSOLE_CHANNEL_NAMESPACE,
|
||||||
label: Web.SettingLabel.allowed_domains,
|
label: Web.SettingLabel.allowed_domains,
|
||||||
|
@ -77,7 +77,7 @@ export default abstract class BaseWebChannelHandler<
|
|||||||
protected readonly attachmentService: AttachmentService,
|
protected readonly attachmentService: AttachmentService,
|
||||||
protected readonly messageService: MessageService,
|
protected readonly messageService: MessageService,
|
||||||
protected readonly menuService: MenuService,
|
protected readonly menuService: MenuService,
|
||||||
private readonly websocketGateway: WebsocketGateway,
|
protected readonly websocketGateway: WebsocketGateway,
|
||||||
) {
|
) {
|
||||||
super(name, settingService, channelService, logger);
|
super(name, settingService, channelService, logger);
|
||||||
}
|
}
|
||||||
@ -98,42 +98,32 @@ export default abstract class BaseWebChannelHandler<
|
|||||||
*/
|
*/
|
||||||
@OnEvent('hook:websocket:connection', { async: true })
|
@OnEvent('hook:websocket:connection', { async: true })
|
||||||
async onWebSocketConnection(client: Socket) {
|
async onWebSocketConnection(client: Socket) {
|
||||||
|
try {
|
||||||
const settings = await this.getSettings();
|
const settings = await this.getSettings();
|
||||||
const handshake = client.handshake;
|
const handshake = client.handshake;
|
||||||
const { channel } = handshake.query;
|
const { channel } = handshake.query;
|
||||||
|
|
||||||
if (channel !== this.getName()) {
|
if (channel !== this.getName()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
try {
|
|
||||||
const { verification_token } = client.handshake.query;
|
|
||||||
await this.verifyToken(verification_token.toString());
|
|
||||||
try {
|
|
||||||
this.logger.debug(
|
this.logger.debug(
|
||||||
'Web Channel Handler : WS connected .. sending settings',
|
'Web Channel Handler : WS connected .. sending settings',
|
||||||
);
|
);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const menu = await this.menuService.getTree();
|
const menu = await this.menuService.getTree();
|
||||||
return client.emit('settings', { menu, ...settings });
|
return client.emit('settings', { menu, ...settings });
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
this.logger.warn(
|
this.logger.warn('Web Channel Handler : Unable to retrieve menu ', err);
|
||||||
'Web Channel Handler : Unable to retrieve menu ',
|
|
||||||
err,
|
|
||||||
);
|
|
||||||
return client.emit('settings', settings);
|
return client.emit('settings', settings);
|
||||||
}
|
}
|
||||||
} catch (err) {
|
|
||||||
this.logger.warn(
|
|
||||||
'Web Channel Handler : Unable to verify token, disconnecting ...',
|
|
||||||
err,
|
|
||||||
);
|
|
||||||
client.disconnect();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
this.logger.error(
|
this.logger.error(
|
||||||
'Web Channel Handler : Unable to initiate websocket connection',
|
'Web Channel Handler : Unable to initiate websocket connection',
|
||||||
err,
|
err,
|
||||||
);
|
);
|
||||||
|
client.disconnect();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -218,7 +208,7 @@ export default abstract class BaseWebChannelHandler<
|
|||||||
*
|
*
|
||||||
* @returns Formatted message
|
* @returns Formatted message
|
||||||
*/
|
*/
|
||||||
private formatHistoryMessages(messages: AnyMessage[]): Web.Message[] {
|
protected formatMessages(messages: AnyMessage[]): Web.Message[] {
|
||||||
return messages.map((anyMessage: AnyMessage) => {
|
return messages.map((anyMessage: AnyMessage) => {
|
||||||
if ('sender' in anyMessage && anyMessage.sender) {
|
if ('sender' in anyMessage && anyMessage.sender) {
|
||||||
return {
|
return {
|
||||||
@ -262,7 +252,7 @@ export default abstract class BaseWebChannelHandler<
|
|||||||
until,
|
until,
|
||||||
n,
|
n,
|
||||||
);
|
);
|
||||||
return this.formatHistoryMessages(messages.reverse());
|
return this.formatMessages(messages.reverse());
|
||||||
}
|
}
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
@ -287,35 +277,11 @@ export default abstract class BaseWebChannelHandler<
|
|||||||
since,
|
since,
|
||||||
n,
|
n,
|
||||||
);
|
);
|
||||||
return this.formatHistoryMessages(messages);
|
return this.formatMessages(messages);
|
||||||
}
|
}
|
||||||
return [];
|
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.
|
* Verify the origin against whitelisted domains.
|
||||||
*
|
*
|
||||||
@ -405,20 +371,12 @@ export default abstract class BaseWebChannelHandler<
|
|||||||
* @param req
|
* @param req
|
||||||
* @param res
|
* @param res
|
||||||
*/
|
*/
|
||||||
private async checkRequest(
|
protected async checkRequest(
|
||||||
req: Request | SocketRequest,
|
req: Request | SocketRequest,
|
||||||
res: Response | SocketResponse,
|
res: Response | SocketResponse,
|
||||||
) {
|
) {
|
||||||
try {
|
try {
|
||||||
await this.validateCors(req, res);
|
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) {
|
} catch (err) {
|
||||||
this.logger.warn(
|
this.logger.warn(
|
||||||
'Web Channel Handler : Attempt to access from an unauthorized origin',
|
'Web Channel Handler : Attempt to access from an unauthorized origin',
|
||||||
@ -741,7 +699,7 @@ export default abstract class BaseWebChannelHandler<
|
|||||||
*
|
*
|
||||||
* @returns IP Address
|
* @returns IP Address
|
||||||
*/
|
*/
|
||||||
private getIpAddress(req: Request | SocketRequest): string {
|
protected getIpAddress(req: Request | SocketRequest): string {
|
||||||
if ('isSocket' in req && req.isSocket) {
|
if ('isSocket' in req && req.isSocket) {
|
||||||
return req.socket.handshake.address;
|
return req.socket.handshake.address;
|
||||||
} else if (Array.isArray(req.ips) && req.ips.length > 0) {
|
} else if (Array.isArray(req.ips) && req.ips.length > 0) {
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
{
|
{
|
||||||
"verification_token": "Verification Token",
|
|
||||||
"allowed_domains": "Allowed Domains",
|
"allowed_domains": "Allowed Domains",
|
||||||
"start_button": "Enable `Get Started`",
|
"start_button": "Enable `Get Started`",
|
||||||
"input_disabled": "Disable Input",
|
"input_disabled": "Disable Input",
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
{
|
{
|
||||||
"verification_token": "Jeton de vérification",
|
|
||||||
"allowed_domains": "Domaines autorisés",
|
"allowed_domains": "Domaines autorisés",
|
||||||
"start_button": "Activer `Démarrer`",
|
"start_button": "Activer `Démarrer`",
|
||||||
"input_disabled": "Désactiver la saisie",
|
"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 const WEB_CHANNEL_NAMESPACE = 'web_channel';
|
||||||
|
|
||||||
export default [
|
export default [
|
||||||
{
|
|
||||||
group: WEB_CHANNEL_NAMESPACE,
|
|
||||||
label: Web.SettingLabel.verification_token,
|
|
||||||
value: 'token123',
|
|
||||||
type: SettingType.secret,
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
group: WEB_CHANNEL_NAMESPACE,
|
group: WEB_CHANNEL_NAMESPACE,
|
||||||
label: Web.SettingLabel.allowed_domains,
|
label: Web.SettingLabel.allowed_domains,
|
||||||
|
@ -13,8 +13,6 @@ import { StdQuickReply } from '@/chat/schemas/types/quick-reply';
|
|||||||
|
|
||||||
export namespace Web {
|
export namespace Web {
|
||||||
export enum SettingLabel {
|
export enum SettingLabel {
|
||||||
secret = 'secret',
|
|
||||||
verification_token = 'verification_token',
|
|
||||||
allowed_domains = 'allowed_domains',
|
allowed_domains = 'allowed_domains',
|
||||||
start_button = 'start_button',
|
start_button = 'start_button',
|
||||||
input_disabled = 'input_disabled',
|
input_disabled = 'input_disabled',
|
||||||
|
@ -224,7 +224,7 @@ export class WebsocketGateway
|
|||||||
'Unable to load session, creating a new one ...',
|
'Unable to load session, creating a new one ...',
|
||||||
err,
|
err,
|
||||||
);
|
);
|
||||||
if (searchParams.get('channel') === 'web-channel') {
|
if (searchParams.get('channel') !== 'console-channel') {
|
||||||
return this.createAndStoreSession(client, next);
|
return this.createAndStoreSession(client, next);
|
||||||
} else {
|
} else {
|
||||||
return next(new Error('Unauthorized: Unknown session ID'));
|
return next(new Error('Unauthorized: Unknown session ID'));
|
||||||
|
@ -58,4 +58,3 @@ NEXT_PUBLIC_SSO_ENABLED=false
|
|||||||
APP_WIDGET_PORT=5173
|
APP_WIDGET_PORT=5173
|
||||||
REACT_APP_WIDGET_API_URL=http://${APP_DOMAIN}:${API_PORT}
|
REACT_APP_WIDGET_API_URL=http://${APP_DOMAIN}:${API_PORT}
|
||||||
REACT_APP_WIDGET_CHANNEL=web-channel
|
REACT_APP_WIDGET_CHANNEL=web-channel
|
||||||
REACT_APP_WIDGET_TOKEN=token123
|
|
||||||
|
@ -34,7 +34,6 @@ export const ChatWidget = () => {
|
|||||||
config={{
|
config={{
|
||||||
apiUrl,
|
apiUrl,
|
||||||
channel: "console-channel",
|
channel: "console-channel",
|
||||||
token: "test",
|
|
||||||
language: i18n.language,
|
language: i18n.language,
|
||||||
}}
|
}}
|
||||||
CustomHeader={ChatWidgetHeader}
|
CustomHeader={ChatWidgetHeader}
|
||||||
|
@ -7,11 +7,9 @@ COPY package*.json ./
|
|||||||
# Set the environment variables
|
# Set the environment variables
|
||||||
ARG REACT_APP_WIDGET_API_URL
|
ARG REACT_APP_WIDGET_API_URL
|
||||||
ARG REACT_APP_WIDGET_CHANNEL
|
ARG REACT_APP_WIDGET_CHANNEL
|
||||||
ARG REACT_APP_WIDGET_TOKEN
|
|
||||||
|
|
||||||
ENV REACT_APP_WIDGET_API_URL=${REACT_APP_WIDGET_API_URL}
|
ENV REACT_APP_WIDGET_API_URL=${REACT_APP_WIDGET_API_URL}
|
||||||
ENV REACT_APP_WIDGET_CHANNEL=${REACT_APP_WIDGET_CHANNEL}
|
ENV REACT_APP_WIDGET_CHANNEL=${REACT_APP_WIDGET_CHANNEL}
|
||||||
ENV REACT_APP_WIDGET_TOKEN=${REACT_APP_WIDGET_TOKEN}
|
|
||||||
|
|
||||||
# Installer stage: Installs dependencies
|
# Installer stage: Installs dependencies
|
||||||
FROM base AS installer
|
FROM base AS installer
|
||||||
|
@ -20,7 +20,6 @@ import { useColors } from '../providers/ColorProvider';
|
|||||||
import { useConfig } from '../providers/ConfigProvider';
|
import { useConfig } from '../providers/ConfigProvider';
|
||||||
import { useSettings } from '../providers/SettingsProvider';
|
import { useSettings } from '../providers/SettingsProvider';
|
||||||
import { useSocket } from '../providers/SocketProvider';
|
import { useSocket } from '../providers/SocketProvider';
|
||||||
import './UserSubscription.scss';
|
|
||||||
import { useWidget } from '../providers/WidgetProvider';
|
import { useWidget } from '../providers/WidgetProvider';
|
||||||
import {
|
import {
|
||||||
Direction,
|
Direction,
|
||||||
@ -28,6 +27,7 @@ import {
|
|||||||
TMessage,
|
TMessage,
|
||||||
TOutgoingMessageType,
|
TOutgoingMessageType,
|
||||||
} from '../types/message.types';
|
} from '../types/message.types';
|
||||||
|
import './UserSubscription.scss';
|
||||||
|
|
||||||
const UserSubscription: React.FC = () => {
|
const UserSubscription: React.FC = () => {
|
||||||
const config = useConfig();
|
const config = useConfig();
|
||||||
@ -55,7 +55,7 @@ const UserSubscription: React.FC = () => {
|
|||||||
messages: TMessage[];
|
messages: TMessage[];
|
||||||
profile: ISubscriber;
|
profile: ISubscriber;
|
||||||
}>(
|
}>(
|
||||||
`/webhook/${config.channel}/?verification_token=${config.token}&first_name=${firstName}&last_name=${lastName}`,
|
`/webhook/${config.channel}/?first_name=${firstName}&last_name=${lastName}`,
|
||||||
);
|
);
|
||||||
const { messages, profile } = body;
|
const { messages, profile } = body;
|
||||||
|
|
||||||
|
@ -9,6 +9,5 @@
|
|||||||
export const DEFAULT_CONFIG = {
|
export const DEFAULT_CONFIG = {
|
||||||
apiUrl: process.env.REACT_APP_WIDGET_API_URL || 'http://localhost:4000',
|
apiUrl: process.env.REACT_APP_WIDGET_API_URL || 'http://localhost:4000',
|
||||||
channel: process.env.REACT_APP_WIDGET_CHANNEL || 'console-channel',
|
channel: process.env.REACT_APP_WIDGET_CHANNEL || 'console-channel',
|
||||||
token: process.env.REACT_APP_WIDGET_TOKEN || 'test',
|
|
||||||
language: 'en',
|
language: 'en',
|
||||||
};
|
};
|
||||||
|
@ -19,7 +19,6 @@ ReactDOM.createRoot(document.getElementById('root')!).render(
|
|||||||
{...{
|
{...{
|
||||||
apiUrl: process.env.REACT_APP_WIDGET_API_URL || 'http://localhost:4000',
|
apiUrl: process.env.REACT_APP_WIDGET_API_URL || 'http://localhost:4000',
|
||||||
channel: process.env.REACT_APP_WIDGET_CHANNEL || 'web-channel',
|
channel: process.env.REACT_APP_WIDGET_CHANNEL || 'web-channel',
|
||||||
token: process.env.REACT_APP_WIDGET_TOKEN || 'token123',
|
|
||||||
language: 'en',
|
language: 'en',
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
@ -289,7 +289,7 @@ const ChatProvider: React.FC<{
|
|||||||
);
|
);
|
||||||
setMessage('');
|
setMessage('');
|
||||||
const sentMessage = await socketCtx.socket.post<TMessage>(
|
const sentMessage = await socketCtx.socket.post<TMessage>(
|
||||||
`/webhook/${config.channel}/?verification_token=${config.token}`,
|
`/webhook/${config.channel}/`,
|
||||||
{
|
{
|
||||||
data: {
|
data: {
|
||||||
...data,
|
...data,
|
||||||
@ -308,7 +308,7 @@ const ChatProvider: React.FC<{
|
|||||||
messages: TMessage[];
|
messages: TMessage[];
|
||||||
profile: ISubscriber;
|
profile: ISubscriber;
|
||||||
}>(
|
}>(
|
||||||
`/webhook/${config.channel}/?verification_token=${config.token}&first_name=${firstName}&last_name=${lastName}`,
|
`/webhook/${config.channel}/?first_name=${firstName}&last_name=${lastName}`,
|
||||||
);
|
);
|
||||||
|
|
||||||
localStorage.setItem('profile', JSON.stringify(body.profile));
|
localStorage.setItem('profile', JSON.stringify(body.profile));
|
||||||
|
@ -13,7 +13,6 @@ import { DEFAULT_CONFIG } from '../constants/defaultConfig';
|
|||||||
// Define the type for your config, including all possible properties
|
// Define the type for your config, including all possible properties
|
||||||
export type Config = {
|
export type Config = {
|
||||||
apiUrl: string;
|
apiUrl: string;
|
||||||
token: string;
|
|
||||||
channel: string;
|
channel: string;
|
||||||
language: string;
|
language: string;
|
||||||
};
|
};
|
||||||
@ -23,7 +22,6 @@ const ConfigContext = createContext<Config>(DEFAULT_CONFIG);
|
|||||||
|
|
||||||
export const ConfigProvider: React.FC<{
|
export const ConfigProvider: React.FC<{
|
||||||
apiUrl?: string;
|
apiUrl?: string;
|
||||||
token?: string;
|
|
||||||
channel?: string;
|
channel?: string;
|
||||||
language?: string;
|
language?: string;
|
||||||
children: ReactNode;
|
children: ReactNode;
|
||||||
|
@ -23,7 +23,6 @@ import { useSubscribe } from './SocketProvider';
|
|||||||
type ChannelSettings = {
|
type ChannelSettings = {
|
||||||
menu: IMenuNode[];
|
menu: IMenuNode[];
|
||||||
secret: string;
|
secret: string;
|
||||||
verification_token: string;
|
|
||||||
allowed_domains: string;
|
allowed_domains: string;
|
||||||
start_button: boolean;
|
start_button: boolean;
|
||||||
input_disabled: boolean;
|
input_disabled: boolean;
|
||||||
|
@ -6,7 +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).
|
* 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 { io, Socket, ManagerOptions, SocketOptions } from 'socket.io-client';
|
import { io, ManagerOptions, Socket, SocketOptions } from 'socket.io-client';
|
||||||
|
|
||||||
import { Config } from '../providers/ConfigProvider';
|
import { Config } from '../providers/ConfigProvider';
|
||||||
import {
|
import {
|
||||||
@ -195,14 +195,16 @@ let socketIoClient: SocketIoClient;
|
|||||||
* @param handlers Event handlers
|
* @param handlers Event handlers
|
||||||
* @returns Socket io client instance
|
* @returns Socket io client instance
|
||||||
*/
|
*/
|
||||||
export const getSocketIoClient = (config: Config, handlers: SocketIoEventHandlers) => {
|
export const getSocketIoClient = (
|
||||||
|
config: Config,
|
||||||
|
handlers: SocketIoEventHandlers,
|
||||||
|
) => {
|
||||||
if (!socketIoClient) {
|
if (!socketIoClient) {
|
||||||
socketIoClient = new SocketIoClient(
|
socketIoClient = new SocketIoClient(
|
||||||
config.apiUrl,
|
config.apiUrl,
|
||||||
{
|
{
|
||||||
query: {
|
query: {
|
||||||
channel: config.channel,
|
channel: config.channel,
|
||||||
verification_token: config.token,
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
handlers,
|
handlers,
|
||||||
|
Loading…
Reference in New Issue
Block a user