refresh Directline token after it got expired - botframework

I'm applying a token based authentication to bot webchat along with the chat being persisted but facing an issue after the token got expired. Unable to connect to bot.
At first I'm generating a token and refreshing for 15 mins and every thing working fine till then. But, when user went offline with no internet connectivity, suppose for around 6-7 hours, due to offline the refresh token post call don't happen and there will be an expired token in the session storage. later than he wanted to chat with the same conversation he did before. but was going to FailedToConnect or ExpiredToken issue. As the token got expired due to inactivity unable to connect to bot again.
My main intention is how to connect user with the previous converstion.
Thanks in advance.
(async function() {
'use strict';
const {
hooks: { usePostActivity },
hooks: { useDirection },
ReactWebChat
} = window.WebChat;
let { token, conversation_Id } = sessionStorage;
if ( !token ) {
const res = await fetch( 'https:/localhost/api/generateToken', { method: 'POST' } );
const { token: directLineToken, conversationId: conversationId } = await res.json();
sessionStorage[ 'token' ] = directLineToken;
sessionStorage[ 'conversation_Id' ] = conversationId;
token = directLineToken;
conversation_Id = conversationId;
}
if (token) {
await setInterval(async () => {
var myHeaders = new Headers();
myHeaders.append("Authorization","Bearer "+ sessionStorage[ 'token' ]);
let res = await fetch( 'https://directline.botframework.com/v3/directline/tokens/refresh', {
method: 'POST',
headers: myHeaders,
});
const { token: directLineToken, conversationId } = await res.json();
sessionStorage[ 'token' ] = directLineToken;
sessionStorage[ 'conversation_Id' ] = conversationId;
token = directLineToken;
conversation_Id = conversationId;
}, 1000*60*15)}
const store = window.WebChat.createStore({}, ({ dispatch }) => next => action => {
if(action.payload && action.payload.directLine) {
const subscription = action.payload.directLine.connectionStatus$.subscribe({
error: error => console.log( error ),
next: value => {
if ( value === 0 ) {console.log('Uninitialized')}
else if ( value === 1 ) {console.log('Connecting')}
else if ( value === 2 ) {console.log('Online')}
else if ( value === 3 ) {console.log('Expire Token')}
else if ( value === 4 ) {console.log('FailedToConnect')}
else if ( value === 5 ) {console.log('Ended')}
}
});
}
if (action.type === 'DIRECT_LINE/CONNECT_FULFILLED') {
dispatch({
type: 'WEB_CHAT/SEND_EVENT',
payload: {
name: 'Welcome',
value: { language: window.navigator.language }
}
});
}
if (action.type === 'DIRECT_LINE/POST_ACTIVITY') {
action = window.simpleUpdateIn(action, ['payload', 'activity', 'channelData', 'CustomChannel'], () =>"webchat");
}
return next(action);
});
const botconnection = createDirectLine( {token,webSockets: true,watermark: "0" });
window.ReactDOM.render(
<ReactWebChat directLine={botconnection}
store={store}
/>,
document.getElementById('webchat'));
document.querySelector('#webchat > *').focus();
})().catch(err => console.error(err));

Unfortunately, once a token is expired there is no way to obtain a refreshed token in order to continue a conversation. The original token must still be valid when requesting a refreshed token.
Regarding maintaining a connection, there's no great option, but here is an idea that may be worth trying:
Create a service that, when a token is generated, it is sent to this service along with the user's online status.
The service would manage the token refreshes and would return the refreshed token to the original client to be consumed.
If a user goes offline, the call to return the refreshed token would fail, the service updates the user's online status, and follows some condition on when to stop refreshing (e.g. after 4 hours).
If a user comes back online, state is again updated, the refreshed token is consumed, and the conversation continues.
Hope of help!

Related

NestJs Timeout issue with HttpService

I am facing a timeout issue with nestJs Httpservice.
The error number is -60 and error code is 'ETIMEDOUT'.
I am basically trying to call one api after the previous one is successfully.
Here is the first api
getUaaToken(): Observable<any> {
//uaaUrlForClient is defined
return this.httpService
.post(
uaaUrlForClient,
{ withCredentials: true },
{
auth: {
username: this.configService.get('AUTH_USERNAME'),
password: this.configService.get('AUTH_PASSWORD'),
},
},
)
.pipe(
map((axiosResponse: AxiosResponse) => {
console.log(axiosResponse);
return this.getJwtToken(axiosResponse.data.access_token).subscribe();
}),
catchError((err) => {
throw new UnauthorizedException('failed to login to uaa');
}),
);
}
Here is the second api
getJwtToken(uaaToken: string): Observable<any> {
console.log('inside jwt method', uaaToken);
const jwtSignInUrl = `${awsBaseUrl}/api/v1/auth`;
return this.httpService
.post(
jwtSignInUrl,
{ token: uaaToken },
{
headers: {
'Access-Control-Allow-Origin': '*',
'Content-type': 'Application/json',
},
},
)
.pipe(
map((axiosResponse: AxiosResponse) => {
console.log('SUCUSUCSCUSS', axiosResponse);
return axiosResponse.data;
}),
catchError((err) => {
console.log('ERRRORRRORROR', err);
// return err;
throw new UnauthorizedException('failed to login for');
}),
);
}
Both files are in the same service file. Strangely, when i call the second api through the controller like below. It works fine
#Post('/signin')
#Grafana('Get JWT', '[POST] /v1/api/auth')
signin(#Body() tokenBody: { token: string }) {
return this.authService.getJwtToken(tokenBody.token);
}
When the two api's are called, however, the first one works, the second one that is chained is giving me the timeout issue.
Any ideas?
Two things that made it work: changed the http proxy settings and used switchMap.

Inject token from auth0 to React-admin Hasura data provider

This example demonstrates how to use Auth0 with react-admin. It is working as expected.
We are trying to adjust it so it will use the hasura data provider. We've created a new file dataProvider.js that will construct the data-provider:
import buildHasuraProvider from "ra-data-hasura";
import { ApolloClient, InMemoryCache } from "#apollo/client";
export const initDataProvider = async (token) => {
const client = new ApolloClient({
uri: process.env.REACT_APP_GRAPHQL_URI,
headers: {
Authorization: `Bearer ${token}`,
},
cache: new InMemoryCache(),
});
const dataProvider = await buildHasuraProvider({ client });
return dataProvider;
};
However, we are missing the JWT token which is created as part of the Auth0 authentication process. We do not know how to get the token in order to initialize the data provider with it. Does react-admin know how to do it on its own? if not, how do we access the JWT token to do it ourselves manually?
This is the authProvider source-code:
import authConfig from "./authConfig";
import {Auth0Client} from '#auth0/auth0-spa-js';
const auth0 = new Auth0Client({
domain: authConfig.domain,
client_id: authConfig.clientID,
redirect_uri: authConfig.redirectURI,
cacheLocation: 'localstorage',
useRefreshTokens: true
});
export default {
// called when the user attempts to log in
login: (url) => {
if (typeof url === 'undefined') {
return auth0.loginWithRedirect()
}
return auth0.handleRedirectCallback(url.location);
},
// called when the user clicks on the logout button
logout: () => {
return auth0.isAuthenticated().then(function (isAuthenticated) {
if (isAuthenticated) { // need to check for this as react-admin calls logout in case checkAuth failed
return auth0.logout({
redirect_uri: window.location.origin,
federated: true // have to be enabled to invalidate refresh token
});
}
return Promise.resolve()
})
},
// called when the API returns an error
checkError: ({status}) => {
if (status === 401 || status === 403) {
return Promise.reject();
}
return Promise.resolve();
},
// called when the user navigates to a new location, to check for authentication
checkAuth: () => {
return auth0.isAuthenticated().then(function (isAuthenticated) {
if (isAuthenticated) {
return Promise.resolve();
}
return auth0.getTokenSilently()
})
},
// called when the user navigates to a new location, to check for permissions / roles
getPermissions: () => {
return Promise.resolve()
},
};
It is unclear to us if there is a point where we can extract the token from.
getTokenSilently should give you back the token.
You'll have to structure your React app such that you have access to the result of this method before you construct your data provider.

Test GraphQl passport Context

Okey, this is the repo
What I want to do: Test my protected routes.
Currently, the security of the app is handle by passport, with this strategy: graphql-passport.
I am running my rests with supertest (for the request) and jest
When I build the Apollo Server, i use it to create the context:
import { buildContext } from 'graphql-passport';
const server = new ApolloServer({
typeDefs,
resolvers,
context: ({ req, res }) => {
return buildContext({ req, res, User })
},
playground: {
settings: {
'request.credentials': 'same-origin',
},
},
});
This allows me to get the user from the request. Like any authentication with passport works.
passport.use(
new GraphQLLocalStrategy((email, password, next) => {
console.log(`šŸŽ« GraphQLLocalStrategy ${email} šŸš” šŸ‘®ā€ā™‚`)
User.findOne({ email })
.then(user => !user
? next(null, false, 'Invalid email or password')
: user.checkPassword(password) //bcrypt
.then(match => !match
? next(null, false, 'Invalid email or password')
: next(null, user)
)
)
.catch(error => next(error))
}),
);
So far, it works good enough. For every test that i run, I can see my šŸŽ« GraphQLLocalStrategy ${email} šŸš” šŸ‘®ā€ā™‚ being called. Good!.
For some mutations, like login and update user profile, i am able to do this:
user.mutations.test.js
// * Login for add the user in the context
agent
.post("/graphql")
.send({ query: ` mutation {
${loginQuery(email)}
${updateFirstName}
}`})
.set("Accept", "application/json")
.end((err, {body:{data, errors}}) => {
if (err) return done(err);
const {updateUser} = data;
expect(updateUser).toBeInstanceOf(Object);
expect(updateUser.email).toBe(email);
expect(updateUser.firstName).toBe(newName);
expect(updateUser.rol).toBe("patron");
UserFields.map(checkFields(updateUser));
done();
})
So, in one query, I can send the login mutation and then run the update the first name mutation. Both, works good enough, and according to passport I am logged and I can update the user profile.
What is the issue?? I want to run a loging mutation and after that run a query to get all users.
But, ofcourse, I can not run both at the same time in the request(app).post("/graphql").send() It has to be a one or multiple mutations or a queries... but not both.
The other idea, who doesnt work, is run one, and in the response, run the second one, like this:
const agent = request(app);
agent
.post("/graphql")
.send({ query: `mutation { ${loginQuery(email)} }`})
.end((err, {body:{data}}) => {
if (err) return done(err);
agent
.post("/graphql")
.send({ query: `query { getGuestsQuery() }`})
...
If I try to ask in a second request for a protected route, there is not a way to know that i was authenticated, at least not automatically... Can I make an authenticated request here with supertest
**How can I tell to my tested application that I am authenticated???? **
test("fetch all Guests", async (done) => {
const userAdmin = await User.findOne({rol:"admin"}).exec();
if(!userAdmin) return done('no admin users for testing');
const agent = request.agent(app);
agent
.post('/graphql')
.send({ query: ` mutation { ${loginQuery(userAdmin.email)} }`})
.expect(200)
.end((err, res) => {
if (err) return done(err);
agent
.post("/graphql")
.send({query: `{ getGuests { ${GuestInput.join(' ')} } }`})
.set("Accept", "application/json")
.expect("Content-Type", /json/)
.expect(200)
.end((err, {body:{data}}) => {
if (err) return done(err);
expect(data).toBeInstanceOf(Object);
const {getGuests} = data;
expect(getGuests).toBeInstanceOf(Array);
getGuests.map(user => GuestInput.map(checkFields(user)))
done();
});
});
});

"Network interruption occurred. Reconnectingā€¦" , How to potentially automatically reconnect when i minimize or maximize chabot

We have implemented bot framework-webchat to create a bot. Currently, we handle the minimize and maximize with the event passed in component (code shown below) but the challenge occurs when I minimize and then maximize the chatbot I am seeing 'Unable to connect' message and then it flashes away and if after an hour-long later if we minimize and maximize I am getting 'Network interruption occurred, Reconnecting...' How do I keep webchat potentially automatically reconnect when I minimize and maximize Chabot.
MaximizeChatWndow () {
if (this.state.token === null &&
this.state.productService === null) {
return
}
this.setState({
directLine: this.createDirectLine()
}, () => {
this.setState({
minimized: false,
newMessage: false,
userId: 'User_' + Math.random
})
})
this.checkExpandFlag = true
}
The component:
renderĀ ()Ā {
constĀ {
state:Ā {Ā minimized, storeĀ }
}Ā =Ā thisĀ 
returnĀ (
<Row>
<ColĀ md={12}>
<div>
{minimizedĀ ?Ā (
<ChatDon
handleMaximizeButtonClick={this.handleMaximizeButtonClick}
/>
)Ā :Ā (
<ChatWin
handleMinimizeButtonClick={this.handleMinimizeButtonClick}
directLine={this.state.directLine}
userId={this.state.userId}
store={store}
/>
)}
</div>
</Col>
</Row>
)
}
It looks like you creating your directLine object in "MaximizeChatWndow()" which I think is the problem. In "MaximizeChatWndow()", you should be fetching your token and passing that to your web chat component. It is in the web chat component that you should use the token to call createDirectLine().
It appears that there have been various updates to the 06.recomposing-us/a.minimizable-web-chat sample. (The docs also look like they are out of date and no longer match the code). However, if comparing to the available sample code, you will want to do something like the following. Please look at the full code in the above link as I am only including the most relevant parts here.
When I tested, I had no issues with the conversation resetting or the network disconnecting/reconnecting.
MinimizableWebChat.js
import WebChat from './WebChat';
const MinimizableWebChat = () => {
[...]
const [token, setToken] = useState();
const handleFetchToken = useCallback(async () => {
if (!token) {
const res = await fetch('http://localhost:3500/directline/token', { method: 'POST' });
const { token } = await res.json();
setToken(token);
}
}, [setToken, token]);
[...]
return (
[...]
<WebChat
className="react-web-chat"
onFetchToken={handleFetchToken}
store={store}
styleSet={styleSet}
token={token}
/>
)
WebChat.js
const WebChat = ({ className, onFetchToken, store, token }) => {
const directLine = useMemo(() => createDirectLine({ token }), [token]);
[...]
useEffect(() => {
onFetchToken();
}, [onFetchToken]);
return token ? (
<ReactWebChat ...
);
};
Hope of help!

Cannot connect Ember Simple Auth and DRF Token Auth

I have a trouble with Ember Simple Auth.
I'm trying to connect my server-side application, which working on Django 1.9 with DRF, and client-side which working on Ember 2.2.
On server side I'm obtaining token on 'http://localhost:8000/api-token-auth/'. Function requires two args from request: "username" and "password". But Ember Simple Auth send POST request with args: "username[identification]" and "password[password]", and server returns "400". I think that problem with arguments keys.
POST request
Responce
I tried to change .authenticate method in oauth2-password-grant.js(i can't write custom authenticator because i'm newbee in javascript), but nothing changed.
Manually POST request returns expected answer.
Please tell me the way to solve this problem.
And please forgive me for my english.
authenticate(identification, password, scope = []) {
return new RSVP.Promise((resolve, reject) => {
const data = { 'grant_type': 'password', username: identification, password };
const serverTokenEndpoint = this.get('serverTokenEndpoint');
const scopesString = Ember.makeArray(scope).join(' ');
if (!Ember.isEmpty(scopesString)) {
data.scope = scopesString;
}
this.makeRequest(serverTokenEndpoint, data).then((response) => {
run(() => {
const expiresAt = this._absolutizeExpirationTime(response['expires_in']);
this._scheduleAccessTokenRefresh(response['expires_in'], expiresAt, response['refresh_token']);
if (!isEmpty(expiresAt)) {
response = Ember.merge(response, { 'expires_at': expiresAt });
}
resolve(response);
});
}, (xhr) => {
run(null, reject, xhr.responseJSON || xhr.responseText);
});
});
},
My variant:
const data = { 'grant_type': 'password', 'username': identification, 'password': password };
authenticate: function () {
// var username = this.getProperties('username');
// var password = this.getProperties('password');
const {username, password} = this.getProperties('username', 'password');
this.get('session').authenticate('authenticator:oauth2', username, password).catch((reason) => {
this.set('errorMessage', reason.error || reason);
});
}
It was my mistake.

Resources