Websocket authentication.
This commit is contained in:
parent
8c1b0bc1e0
commit
fcc8ba6a3d
|
|
@ -5,7 +5,6 @@ public interface IConversationService
|
|||
IConversationStateService States { get; }
|
||||
string ConversationId { get; }
|
||||
Task<Conversation> NewConversation(Conversation conversation);
|
||||
Task MarkConnectionReady(string conversationId);
|
||||
void SetConversationId(string conversationId, List<string> states);
|
||||
Task<Conversation> GetConversation(string id);
|
||||
Task<List<Conversation>> GetConversations();
|
||||
|
|
|
|||
|
|
@ -118,15 +118,4 @@ public partial class ConversationService : IConversationService
|
|||
_state.Load(_conversationId);
|
||||
states.ForEach(x => _state.SetState(x.Split('=')[0], x.Split('=')[1]));
|
||||
}
|
||||
|
||||
public async Task MarkConnectionReady(string conversationId)
|
||||
{
|
||||
var hooks = _services.GetServices<IConversationHook>();
|
||||
var conv = await GetConversation(conversationId);
|
||||
foreach (var hook in hooks)
|
||||
{
|
||||
// Need to check if user connected with agent is the first time.
|
||||
await hook.OnUserAgentConnectedInitially(conv);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -41,13 +41,6 @@ public class ConversationController : ControllerBase, IApiAdapter
|
|||
return ConversationViewModel.FromSession(conv);
|
||||
}
|
||||
|
||||
[HttpPatch("/conversation/{conversationId}/ready")]
|
||||
public async Task ReadyToInteractive([FromRoute] string conversationId)
|
||||
{
|
||||
var service = _services.GetRequiredService<IConversationService>();
|
||||
await service.MarkConnectionReady(conversationId);
|
||||
}
|
||||
|
||||
[HttpGet("/conversations/{agentId}")]
|
||||
public async Task<IEnumerable<ConversationViewModel>> GetConversations()
|
||||
{
|
||||
|
|
|
|||
|
|
@ -1,4 +1,3 @@
|
|||
using BotSharp.Abstraction.Messaging;
|
||||
using BotSharp.Abstraction.Messaging.Models.RichContent;
|
||||
using Microsoft.AspNetCore.SignalR;
|
||||
|
||||
|
|
@ -8,12 +7,15 @@ public class ChatHubConversationHook : ConversationHookBase
|
|||
{
|
||||
private readonly IServiceProvider _services;
|
||||
private readonly IHubContext<SignalRHub> _chatHub;
|
||||
private readonly IUserIdentity _user;
|
||||
|
||||
public ChatHubConversationHook(IServiceProvider services,
|
||||
IHubContext<SignalRHub> chatHub)
|
||||
IHubContext<SignalRHub> chatHub,
|
||||
IUserIdentity user)
|
||||
{
|
||||
_services = services;
|
||||
_chatHub = chatHub;
|
||||
_user = user;
|
||||
}
|
||||
|
||||
public override async Task OnUserAgentConnectedInitially(Conversation conversation)
|
||||
|
|
@ -33,7 +35,18 @@ public class ChatHubConversationHook : ConversationHookBase
|
|||
foreach (var message in messages)
|
||||
{
|
||||
await Task.Delay(300);
|
||||
await OnResponseGenerated(new RoleDialogModel(AgentRole.Assistant, message.Text));
|
||||
|
||||
await _chatHub.Clients.User(_user.Id).SendAsync("OnMessageReceivedFromAssistant", new ChatResponseModel()
|
||||
{
|
||||
ConversationId = conversation.Id,
|
||||
Text = message.Text,
|
||||
Sender = new UserViewModel()
|
||||
{
|
||||
FirstName = "AI",
|
||||
LastName = "Assistant",
|
||||
Role = AgentRole.Assistant
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -75,7 +88,7 @@ public class ChatHubConversationHook : ConversationHookBase
|
|||
{
|
||||
var conv = _services.GetRequiredService<IConversationService>();
|
||||
|
||||
await _chatHub.Clients.All.SendAsync("OnMessageReceivedFromAssistant", new ChatResponseModel()
|
||||
await _chatHub.Clients.User(_user.Id).SendAsync("OnMessageReceivedFromAssistant", new ChatResponseModel()
|
||||
{
|
||||
ConversationId = conv.ConversationId,
|
||||
MessageId = message.MessageId,
|
||||
|
|
|
|||
|
|
@ -1,25 +1,47 @@
|
|||
using BotSharp.Abstraction.Conversations.Models;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.SignalR;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace BotSharp.Plugin.ChatHub;
|
||||
|
||||
[Authorize]
|
||||
public class SignalRHub : Hub
|
||||
{
|
||||
private readonly IServiceProvider _services;
|
||||
private readonly ILogger _logger;
|
||||
private readonly IUserIdentity _user;
|
||||
private readonly IHttpContextAccessor _context;
|
||||
|
||||
public SignalRHub(IServiceProvider services,
|
||||
ILogger<SignalRHub> logger,
|
||||
IUserIdentity user)
|
||||
IUserIdentity user,
|
||||
IHttpContextAccessor context)
|
||||
{
|
||||
_services = services;
|
||||
_logger = logger;
|
||||
_user = user;
|
||||
_context = context;
|
||||
}
|
||||
|
||||
public override Task OnConnectedAsync()
|
||||
public override async Task OnConnectedAsync()
|
||||
{
|
||||
Console.WriteLine($"SignalR Hub: {Context.UserIdentifier} connected in {Context.ConnectionId} [{DateTime.Now}]");
|
||||
return base.OnConnectedAsync();
|
||||
_logger.LogInformation($"SignalR Hub: {_user.FirstName} {_user.LastName} ({Context.UserIdentifier}) connected in {Context.ConnectionId} [{DateTime.Now}]");
|
||||
|
||||
var hooks = _services.GetServices<IConversationHook>();
|
||||
var convService = _services.GetRequiredService<IConversationService>();
|
||||
_context.HttpContext.Request.Query.TryGetValue("conversationId", out var conversationId);
|
||||
var conv = await convService.GetConversation(conversationId);
|
||||
|
||||
foreach (var hook in hooks)
|
||||
{
|
||||
// Check if user connected with agent is the first time.
|
||||
if (!conv.Dialogs.Any())
|
||||
{
|
||||
await hook.OnUserAgentConnectedInitially(conv);
|
||||
}
|
||||
}
|
||||
|
||||
await base.OnConnectedAsync();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
28
src/Plugins/BotSharp.Plugin.ChatHub/WebSocketsMiddleware.cs
Normal file
28
src/Plugins/BotSharp.Plugin.ChatHub/WebSocketsMiddleware.cs
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
using Microsoft.AspNetCore.Http;
|
||||
|
||||
namespace BotSharp.Plugin.ChatHub;
|
||||
|
||||
public class WebSocketsMiddleware
|
||||
{
|
||||
private readonly RequestDelegate _next;
|
||||
|
||||
public WebSocketsMiddleware(RequestDelegate next)
|
||||
{
|
||||
_next = next;
|
||||
}
|
||||
|
||||
public async Task Invoke(HttpContext httpContext)
|
||||
{
|
||||
var request = httpContext.Request;
|
||||
|
||||
// web sockets cannot pass headers so we must take the access token from query param and
|
||||
// add it to the header before authentication middleware runs
|
||||
if (request.Path.StartsWithSegments("/chatHub", StringComparison.OrdinalIgnoreCase) &&
|
||||
request.Query.TryGetValue("access_token", out var accessToken))
|
||||
{
|
||||
request.Headers.Add("Authorization", $"Bearer {accessToken}");
|
||||
}
|
||||
|
||||
await _next(httpContext);
|
||||
}
|
||||
}
|
||||
|
|
@ -68,6 +68,9 @@ if (app.Environment.IsDevelopment())
|
|||
app.UseSwaggerUI();
|
||||
}
|
||||
|
||||
app.MapHub<SignalRHub>("/chatHub");
|
||||
app.UseMiddleware<WebSocketsMiddleware>();
|
||||
|
||||
app.UseAuthentication();
|
||||
app.UseAuthorization();
|
||||
|
||||
|
|
@ -80,6 +83,4 @@ app.UseBotSharp();
|
|||
app.UseCors("MyCorsPolicy");
|
||||
#endif
|
||||
|
||||
app.MapHub<SignalRHub>("/chatHub");
|
||||
|
||||
app.Run();
|
||||
|
|
|
|||
|
|
@ -1,10 +1,11 @@
|
|||
import axios from 'axios';
|
||||
import { getUserStore } from '$lib/helpers/store.js';
|
||||
|
||||
/**
|
||||
* Set axios http headers globally
|
||||
* @param {string} token
|
||||
*/
|
||||
export function setAuthorization(token) {
|
||||
export function setAuthorization() {
|
||||
let user = getUserStore();
|
||||
let headers = axios.defaults.headers;
|
||||
headers.common['Authorization'] = `Bearer ${token}`;
|
||||
headers.common['Authorization'] = `Bearer ${user.token}`;
|
||||
}
|
||||
|
|
@ -13,11 +13,10 @@ export const agentListUrl = `${host}/agents`;
|
|||
export const agentDetailUrl = `${host}/agent/{id}`;
|
||||
|
||||
// conversation
|
||||
export const conversationInitUrl = `${host}/conversation/{agentId}`
|
||||
export const conversationReadyUrl = `${host}/conversation/{conversationId}/ready`
|
||||
export const conversationMessageUrl = `${host}/conversation/{agentId}/{conversationId}`
|
||||
export const conversationsUrl = `${host}/conversations/{agentId}`
|
||||
export const dialogsUrl = `${host}/conversation/{conversationId}/dialogs`
|
||||
export const conversationInitUrl = `${host}/conversation/{agentId}`;
|
||||
export const conversationMessageUrl = `${host}/conversation/{agentId}/{conversationId}`;
|
||||
export const conversationsUrl = `${host}/conversations/{agentId}`;
|
||||
export const dialogsUrl = `${host}/conversation/{conversationId}/dialogs`;
|
||||
|
||||
// chathub
|
||||
export const chatHubUrl = `${host}/chatHub`;
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
import { userStore, getUserStore } from '$lib/helpers/store.js';
|
||||
import { tokenUrl, myInfoUrl, usrCreationUrl } from './api-endpoints.js';
|
||||
import axios from 'axios';
|
||||
|
||||
/**
|
||||
* This callback type is called `requestCallback` and is displayed as a global symbol.
|
||||
|
|
@ -37,31 +38,20 @@ export async function getToken(email, password, onSucceed) {
|
|||
}
|
||||
|
||||
/**
|
||||
* Set token from exteranl
|
||||
* @param {string} token
|
||||
*/
|
||||
export function setToken(token) {
|
||||
let user = getUserStore();
|
||||
userStore.set({ ...user, init: false, loggedIn: true, token: token });
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {Promise<import('$typedefs').UserModel>}
|
||||
*/
|
||||
export async function myInfo(token) {
|
||||
const headers = {
|
||||
Authorization: `Bearer ${token}`,
|
||||
};
|
||||
|
||||
const info = await fetch(myInfoUrl, {
|
||||
method: 'GET',
|
||||
headers: headers,
|
||||
}).then(response => {
|
||||
if (response.ok) {
|
||||
return response.json();
|
||||
} else {
|
||||
alert(response.statusText);
|
||||
}
|
||||
}).then(result => {
|
||||
let user = getUserStore();
|
||||
userStore.set({ ...user, init: false, id: result.id });
|
||||
return result;
|
||||
})
|
||||
.catch(error => alert(error.message));
|
||||
|
||||
return info;
|
||||
export async function myInfo() {
|
||||
const response = await axios.get(myInfoUrl);
|
||||
return response.data;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -1,10 +1,9 @@
|
|||
import {
|
||||
conversationInitUrl,
|
||||
conversationMessageUrl,
|
||||
dialogsUrl,
|
||||
conversationReadyUrl,
|
||||
dialogsUrl,
|
||||
} from './api-endpoints.js';
|
||||
|
||||
import { setAuthorization } from '$lib/helpers/http';
|
||||
import axios from 'axios';
|
||||
|
||||
/**
|
||||
|
|
@ -13,27 +12,19 @@ import axios from 'axios';
|
|||
* @returns {Promise<import('$typedefs').ConversationModel>}
|
||||
*/
|
||||
export async function newConversation(agentId) {
|
||||
setAuthorization();
|
||||
let url = conversationInitUrl.replace("{agentId}", agentId);
|
||||
const response = await axios.post(url, {});
|
||||
return response.data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Coversation is ready
|
||||
* @param {string} conversationId
|
||||
*/
|
||||
export async function conversationReady(conversationId) {
|
||||
let url = conversationReadyUrl.replace("{conversationId}", conversationId);
|
||||
const response = await axios.patch(url, {});
|
||||
return response.data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get dialog history
|
||||
* @param {string} conversationId
|
||||
* @returns {Promise<import('$typedefs').ChatResponseModel[]>}
|
||||
*/
|
||||
export async function GetDialogs(conversationId) {
|
||||
setAuthorization();
|
||||
let url = dialogsUrl.replace("{conversationId}", conversationId);
|
||||
const response = await axios.get(url);
|
||||
return response.data;
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
import { HubConnection, HubConnectionBuilder, LogLevel } from '@microsoft/signalr';
|
||||
import { chatHubUrl } from '$lib/services/api-endpoints.js';
|
||||
import { getUserStore } from '$lib/helpers/store.js';
|
||||
|
||||
// create a writable store to store the connection object
|
||||
/** @type {HubConnection} */
|
||||
|
|
@ -21,10 +22,12 @@ export const signalr = {
|
|||
onMessageReceivedFromAssistant: () => {},
|
||||
|
||||
// start the connection
|
||||
async start() {
|
||||
/** @param {string} conversationId */
|
||||
async start(conversationId) {
|
||||
// create a new connection object with the hub URL and some options
|
||||
let user = getUserStore();
|
||||
connection = new HubConnectionBuilder()
|
||||
.withUrl(chatHubUrl) // the hub URL, change it according to your server
|
||||
.withUrl(chatHubUrl + `?conversationId=${conversationId}&access_token=${user.token}`) // the hub URL, change it according to your server
|
||||
.withAutomaticReconnect() // enable automatic reconnection
|
||||
.configureLogging(LogLevel.Information) // configure the logging level
|
||||
.build();
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@
|
|||
import { page } from '$app/stores';
|
||||
import { onMount } from 'svelte';
|
||||
import { newConversation } from '$lib/services/conversation-service.js';
|
||||
import { getToken } from '$lib/services/auth-service.js'
|
||||
import { getToken, setToken } from '$lib/services/auth-service.js'
|
||||
import { setAuthorization } from '$lib/helpers/http';
|
||||
|
||||
const params = $page.params;
|
||||
|
|
@ -17,21 +17,19 @@
|
|||
let agentId = params.agentId;
|
||||
|
||||
onMount(async () => {
|
||||
let userToken = "";
|
||||
if(!$page.url.searchParams.has('token')) {
|
||||
await getToken("guest@gmail.com", "123456", (token) => {
|
||||
userToken = token;
|
||||
});
|
||||
} else {
|
||||
userToken = $page.url.searchParams.get('token') ?? "unauthorized";
|
||||
let token = $page.url.searchParams.get('token') ?? "unauthorized";
|
||||
setToken(token);
|
||||
}
|
||||
setAuthorization(userToken);
|
||||
|
||||
// new conversation
|
||||
conversation = await newConversation(agentId);
|
||||
conversationId = conversation.id;
|
||||
|
||||
window.location.href = `/chat/${agentId}/${conversationId}?token=${userToken}`;
|
||||
window.location.href = `/chat/${agentId}/${conversationId}`;
|
||||
});
|
||||
</script>
|
||||
|
||||
|
|
|
|||
|
|
@ -2,7 +2,6 @@
|
|||
import Chat from './chat-box.svelte';
|
||||
import { page } from '$app/stores';
|
||||
import { onMount } from 'svelte';
|
||||
import { setAuthorization } from '$lib/helpers/http';
|
||||
import { myInfo } from '$lib/services/auth-service.js';
|
||||
import { getAgent } from '$lib/services/agent-service.js';
|
||||
|
||||
|
|
@ -15,10 +14,7 @@
|
|||
let currentUser;
|
||||
|
||||
onMount(async () => {
|
||||
const token = $page.url.searchParams.get('token') ?? "unauthorized";
|
||||
setAuthorization(token);
|
||||
|
||||
currentUser = await myInfo(token);
|
||||
currentUser = await myInfo();
|
||||
|
||||
// get agent profile
|
||||
let agentId = params.agentId;
|
||||
|
|
|
|||
|
|
@ -13,9 +13,8 @@
|
|||
import { page } from '$app/stores';
|
||||
import { onMount } from 'svelte';
|
||||
import Link from 'svelte-link';
|
||||
import { setAuthorization } from '$lib/helpers/http';
|
||||
import { signalr } from '$lib/services/signalr-service.js';
|
||||
import { sendMessageToHub, GetDialogs, conversationReady } from '$lib/services/conversation-service.js';
|
||||
import { sendMessageToHub, GetDialogs } from '$lib/services/conversation-service.js';
|
||||
|
||||
const options = {
|
||||
scrollbars: {
|
||||
|
|
@ -45,17 +44,12 @@
|
|||
let dialogs = [];
|
||||
|
||||
onMount(async () => {
|
||||
const token = $page.url.searchParams.get('token') ?? "unauthorized";
|
||||
setAuthorization(token);
|
||||
|
||||
dialogs = await GetDialogs(params.conversationId);
|
||||
|
||||
signalr.onMessageReceivedFromClient = onMessageReceivedFromClient;
|
||||
signalr.onMessageReceivedFromCsr = onMessageReceivedFromCsr;
|
||||
signalr.onMessageReceivedFromAssistant = onMessageReceivedFromAssistant;
|
||||
await signalr.start();
|
||||
|
||||
await conversationReady(params.conversationId);
|
||||
await signalr.start(params.conversationId);
|
||||
|
||||
const scrollElements = document.querySelectorAll('.scrollbar');
|
||||
scrollElements.forEach((item) => {
|
||||
|
|
|
|||
|
|
@ -15,7 +15,6 @@
|
|||
import Headtitle from '$lib/common/HeadTitle.svelte';
|
||||
import { getToken } from '$lib/services/auth-service.js'
|
||||
import { goto } from '$app/navigation';
|
||||
import { setAuthorization } from '$lib/helpers/http';
|
||||
|
||||
let username = 'guest@gmail.com';
|
||||
let password = '';
|
||||
|
|
@ -29,8 +28,7 @@
|
|||
isOpen = true;
|
||||
msg = 'Authentication success';
|
||||
status = 'success';
|
||||
setAuthorization(token);
|
||||
goto(`/chat/01fcc3e5-9af7-49e6-ad7a-a760bd12dc4a?token=${token}`);
|
||||
goto(`/chat/01fcc3e5-9af7-49e6-ad7a-a760bd12dc4a`);
|
||||
});
|
||||
}
|
||||
</script>
|
||||
|
|
|
|||
Loading…
Reference in a new issue