I'm trying to authenticate users using the following oAuth prompt:
private oauthPrompt = new OAuthPrompt("sign-in", {
connectionName: this.oauthSigninAzureId,
title: "Login"
});
Then I create the waterfall steps:
private waterfall = new WaterfallDialog(INTENTS.GREETING, [
(step: WaterfallStepContext) => {
return step.prompt(
INTENTS.SIGN_IN,
"👋 Hello and welcome."
)
}, async (step: WaterfallStepContext) => {
const token = step.result.token;
...
},
]);
Then I run it:
const dialogContext = await this.dialogs.createContext(context); // activeDialog is ALWAYS empty
if (!dialogContext.activeDialog) await dialogContext.beginDialog("sign-in");
else dialogContext.continueDialog();
The first step runs fine. But then when trying to see if there is an active dialog, I check dialogContext.activeDialog only to find out it's undefined.
Related
I'm creating a custom template to verify email. For this i have created a lambda function in node.js. I need user groups to get role but unable to find in events. Below is my code. I have tried to find a solution but could not get it.
'use strict';
exports.handler = async (event, context, callback) => {
const email = event.request.userAttributes.email;
console.log(context);
console.log(event.request.userAttributes.email);
console.log(event);
const name = event.request.userAttributes.name;
const template = (name) => `<html> </html>`;
if (event.triggerSource === "CustomMessage_SignUp") {
event.response = {
emailSubject: "Activate: Confirm LAGO Account Email",
emailMessage: template(name, link)
};
}
console.log(event.response);
callback(null, event);
};
You will need to call another method from the sdk to do so in your lambda function. Group name is not available in the event. Change your function to add this before your custom message.
const AWS = require('aws-sdk')
const cognito = new AWS.CognitoIdentityServiceProvider()
exports.handler = async event => {
// Params for user group list
const params = {
UserPoolId: userPoolId, /* required */
Username: userName, /* required */
}
// Check if user is in a group and list Groups as array
try {
const userGroup = await cognito.adminListGroupsForUser(params).promise()
console.log(userGroup)
}catch (err) {
console.log('Error', err)
}
// Handle your custom message after
...
};
You will need to add policy to this lambda as well
[
{
"Action": [
"cognito-identity:Describe*",
"cognito-identity:Get*",
"cognito-identity:List*",
"cognito-idp:Describe*",
"cognito-idp:AdminGetDevice",
"cognito-idp:AdminGetUser",
"cognito-idp:AdminList*",
"cognito-idp:List*",
"cognito-sync:Describe*",
"cognito-sync:Get*",
"cognito-sync:List*",
"cognito-idp:AdminListGroupsForUser"
],
"Resource": [
"arn:aws:xxxxx" //Cognito pool arn
]
}
]
I was learning some cypress from this video: https://www.youtube.com/watch?v=03kG2rdJYtc
I'm interested with he's saying at 29:33: "programatic login"
But he's using vue2 and Vuex.
My project is created with Vite and the state management is Pinia.
So how can I do a programatic login using the pinia action?
For example the welcome logged in user should see dashboard:
describe('Welcome', () => {
it('logged in user should visit dashboard', () => {
// login
cy.visit('/')
cy.url().should('contain', '/dashboard')
})
})
And my userStore:
export const useUserStore = defineStore({
id: 'user',
state: () => ({
username: ref(useLocalStorage('username', null)),
}),
getters: {
isLoggedIn: (state) => state.username !== null,
},
actions: {
login(username, password) {
return useAuthLoginService(username, password)
.then((response) => {
this.username = response.username
})
.catch((error) => {
return Promise.reject(new Error(error))
})
},
},
})
How can I call the login action on the cypress test?
For now as a workaround I'm writing on a localstorage like:
localStorage.setItem('username', 'user')
And it works fine, because userStore catch this item from localstorage and passes like it's logged in... But I don't like this solution, seems fragile, and I'd like to use the action which is made for login users.
Another thing I tried is adding the app variable inside window but it doesn't work for me... don't understand why...
on main.js
The video shows that code:
const vue = new Vue({...})
if(window.Cypress){
window.app = app
}
In my case it's:
const app = createApp(App)
if(window.Cypress){
window.app = app
}
But in cypress tests the window.app it's undefined... I don't know how I would access to userStore using this... like it was vuex.
Using the Pinia demo app as an example:
The store is initialized in App.vue. Add a reference to the newly created store(s) for Cypress to use
export default defineComponent({
components: { Layout, PiniaLogo },
setup() {
const user = useUserStore()
const cart = useCartStore()
if (window.Cypress) {
window.store = {user, cart) // test can see window.store
}
...
In the test
let store;
describe('Pinia demo with counters', () => {
beforeEach(() => {
cy.viewport(1000, 1000)
cy.visit(`http://localhost:${PORT}`)
.then(win => store = win.store) // get app's store object
})
it('works', () => {
cy.wait(500) // wait for the JS to load
.then(() => store.cart.addItem('Cypress test item')) // invoke action
.then(() => {
const item1 = store.cart.items[0] // invoke getter
cy.wrap(item1)
.should('have.property', 'name', 'Cypress test item') // passes
})
The login action is asynchronous, so return the promise to allow Cypress to wait.
// user.js
async login(user, password) {
const userData = await apiLogin(user, password)
this.$patch({
name: user,
...userData,
})
return userData // this returns a promise which can awaited
},
// main.spec.js
describe('Pinia demo with counters', () => {
beforeEach(() => {
cy.viewport(1000, 1000)
cy.visit(`http://localhost:${PORT}`).then(win => {
store = win.store
// default name in store before login
cy.wrap(store.user.name).should('eq', 'Eduardo')
// logging in
store.user.login('ed', 'ed').then(() => { // wait for API call
cy.wrap(store.user.name).should('eq', 'ed')
})
})
})
Alternatively, wait for the name to change on the page
// main.spec.js
cy.visit(`http://localhost:${PORT}`).then(win => {
store = win.store
// default name in store
cy.wrap(store.user.name).should('eq', 'Eduardo')
// logging on
store.user.login('ed', 'ed')
cy.contains('Hello ed') // waits for name on page to change
.then(() => {
cy.wrap(store.user.name).should('eq', 'ed')
})
})
I set a cookie and I usually access it like this when I need to test a "protected" request.
beforeAll(async () => {
await db.connect();
//sign my user and get the token
const response = await request(app).post("/gettoken").send(credentials);
TOKEN = response.headers["set-cookie"].pop().split(";")[0];
})
//test exemple
it("exemple", async () => {
const result = await request(app).post("/path").set(`Cookie`, TOKEN).send(data);
});
So far I had no problem with it but in one of my function that I want to test I have this line:
user = await getUser(req.cookies.token);
the function getUser is pretty simple:
const userToken = jwt.verify(token, process.env.JWTPRIVATEKEY);
user = await User.findOne({ _id: userToken.payload._id });
return user;
Seems like supertest does not work with req.cookies. Is there any way to set "req.cookies.token" during my test ?
I'm going to answer my own question but please, be aware that I have no idea why it works. Actually I was a bit desperate to not find any solution so I did try something without any hope but, surprisely, it worked.
so basically after restarting docker my console.log were all similars (npm run test vs test with postman)
module.exports.getUser = async (token) => {
console.log(token);
console.log(process.env.JWTPRIVATEKEY);
const userToken = jwt.verify(token, process.env.JWTPRIVATEKEY);
console.log(userToken.payload._id);
const user = await User.find({ _id: userToken.payload._id });
return user;
};
it("with success", async () => {
const question = {
title: "title",
content: "content",
};
const result = await request(app).post("/api/forum/question").set("Cookie", TOKEN).send(question);
console.log(result.body);
expect(result.status).toBe(200);
});
And I had this error:
{ error: { message: "Cannot read property '_id' of null" } }
I did change
const user = await User.findOne({ _id: userToken.payload._id });
by
const user = await User.find({ _id: userToken.payload._id });
And now it works as expected.
If someone can explain me why findOne was not working with test (but was ok with postman) I would be happy to understand... because to me, as beginner, it does not make any sense.
I'm currently taking this udemy course on developing networks on Ethereum. The course was made two years ago so I sort of have to use old versions of everything to follow through. My code would run just fine when performing "npm run test" but after adding the statement in line 29 (the assert.ok statement), I started getting this error. I don't understand what is the terminal error. Help?
inbox.test.js:
//inbox.test.js
const assert = require('assert'); // used for ganache assertion
const ganache = require('ganache-cli'); // local ethereum testing netwrok
const Web3 = require('web3'); // Web3 is a constructor function (that's why it is capatalized)
const { interface, bytecode } = require('../compile'); // descructors - going up the directory tree
// creating an instance of Web3
const provider = ganache.provider();
const web3 = new Web3(provider);
let accounts; // to be accessed globally
let inbox; // holds the deployed contract
beforeEach( async () => {
// Get a list of all accounts
accounts = await web3.eth.getAccounts(); //eth is a module that has a lot of functions used for development
// Use one of the accounts to deploy the contract
inbox = await new web3.eth.Contract (JSON.parse(interface)) // instance of a contract
.deploy({data: bytecode, arguments: ['Hi There!'] })
.send ({from: accounts[0], gas:'1000000'});
inbox.setProvider(provider);
});
describe('Inbox', ()=> {
it('deployes a contract', () => {
assert.ok(inbox.options.address); // to check if the contract is successfuly depolyed
});
it('has a default message', async () => {
const message = await inbox.methods.message().call();
assert.equal(message, 'Hi there!');
});
it('can change the message', async () => {
await inbox.methods.setMessage('bye').send( {from: accounts[0]} );const message = await inbox.methods.message().call();
assert.equal(message, 'bye');
});
});
Terminal error:
Terminal Error 1
Terminal Error 2
Package.json:
{
"name": "inbox",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "mocha"
},
"author": "Eiman",
"license": "ISC",
"dependencies": {
"ganache-cli": "^6.9.1",
"mocha": "^8.1.0",
"solc": "^0.4.17",
"web3": "^1.0.0-beta.26"
}
}
compile.js:
// Modules
const path = require ('path'); // module used to help build a path from compile.js to inbox.sol - guaranteed to get compatibility with OS used
const fs = require ('fs');
const solc = require ('solc');
const inboxPath = path.resolve(__dirname, 'contracts', 'inbox.sol' );
const source = fs.readFileSync(inboxPath, 'utf8'); // to read the content of the inbox.sol file
module.exports = solc.compile(source, 1).contracts[':Inbox']; // compile statement
inbox.sol:
pragma solidity ^0.4.17;
contract Inbox {
string public message;
function Inbox (string initalMessage) public {
message = initalMessage;
}
function setMessage(string newMessage) public {
message = newMessage;
}
}
Your code runs smoothly on my Mac (the strings are actually different):
Inbox
✓ deployes a contract
1) has a default message
✓ can change the message (74ms)
2 passing (405ms)
1 failing
1) Inbox
has a default message:
AssertionError [ERR_ASSERTION]: 'Hi There!' == 'Hi there!'
+ expected - actual
-Hi There!
+Hi there!
Looks like some problem with your node_modules, try a fresh install.
I have developed a chat bot using Microsoft Bot Framework V4, and have used BotFramework-WebChat for providing the user to chat from website using DirectLine Token,
I am able to set the bot avatar and the user avatar by assigning the static public image URL. The problem is that I want to set the user avatar dynamically in the WebChat using below steps
Fetch the user icon using the Microsoft graph API after OAuthCard login
Set the signed in user image in the Webchat styleSetOptions dynamically.
I have gone through the Demo for setting the bot framework server and the webchat for the bot by following the samples provided
bot server == https://github.com/Microsoft/BotBuilder-Samples
webchat == https://github.com/Microsoft/BotFramework-WebChat
but there is no proper example or documentation on how to set the user image after the user has signed in. using the signed user object.
can any one point on the right direction on how can it be achieved.
Thanks in advance
You can achieve this by wrapping the Graph API call and result into the result of the AAD login process. The following code is based off of the BotBuilder-Samples 24.bot-authentication-msgraph sample and BotFramework-WebChat 17.chat-send-history sample using React.Component.
(Please be aware that the Graph sample currently located in the master branch does not include obtaining the AAD login user's photo. I have a PR which adds this feature into the sample, however I have included it here, as well. I used the WebChat sample as a reference for building the below.)
WebChat
You will need these files from sample #17, followed by the App.js file that needs altering:
public [folder]
favicon.ico
index.html
manifest.json
src [folder]
App.js
index.css
index.js
.env
package.json
App.js:
Note: I generate the direct line token locally in a separate project. This assumes an AAD profile has a photo.
import React from 'react';
import ReactWebChat, { createDirectLine, createStore} from 'botframework-webchat';
export default class extends React.Component {
constructor(props) {
super(props);
this.state = {
directLine: null,
avatarState: false, // Sets value to false; Is updated to true after login
// Sets the default styleOptions used during rendering
styleOptions: {
botAvatarImage: 'https://learn.microsoft.com/en-us/azure/bot-service/v4sdk/media/logo_bot.svg?view=azure-bot-service-4.0',
botAvatarInitials: 'BF',
userAvatarImage: 'https://github.com/compulim.png?size=64',
userAvatarInitials: 'WC'
}
};
// Creates the listener filtering for a new avatar image and applies to styleOptions
this.store = createStore(
{},
() => next => action => {
if (action.type === 'DIRECT_LINE/INCOMING_ACTIVITY') {
}
if (action.type === 'DIRECT_LINE/INCOMING_ACTIVITY'
&& action.payload.activity.attachments
&& 0 in action.payload.activity.attachments
&& this.state.avatarState === false) {
let attachments = action.payload.activity.attachments;
if ('content' in attachments[0] && 'images' in attachments[0].content) {
this.setState(() => ({
styleOptions: {
userAvatarImage: attachments[0].content.images[0].contentUrl
},
avatarState: true
}));
}
}
return next(action);
}
)
}
componentDidMount() {
this.fetchToken();
}
async fetchToken() {
const res = await fetch('http://localhost:3979/directline/token', { method: 'POST' });
const { token } = await res.json();
this.setState(() => ({
directLine: createDirectLine({ token })
}));
}
render() {
return (
this.state.directLine ?
<ReactWebChat
className="chat"
directLine={ this.state.directLine }
styleOptions={ this.state.styleOptions }
store={ this.store }
{ ...this.props }
/>
:
<div>Connecting to bot…</div>
);
}
}
package.json
{
"name": "change-avatar",
"version": "0.1.0",
"private": true,
"homepage": "",
"dependencies": {
"botframework-webchat": ">= 0.0.0-0",
"react": "^16.6.3",
"react-dom": "^16.6.3",
"react-scripts": "2.1.1"
},
"scripts": {
"start": "react-scripts start",
"build": "react-scripts build",
"eject": "react-scripts eject"
},
"browserslist": [
">0.2%",
"not dead",
"not ie <= 11",
"not op_mini all"
]
}
MS Graph Bot
Update the following files in sample #24:
bot.js:
Replace async processStep with:
async processStep(step) {
// We do not need to store the token in the bot. When we need the token we can
// send another prompt. If the token is valid the user will not need to log back in.
// The token will be available in the Result property of the task.
const tokenResponse = step.result;
// If the user is authenticated the bot can use the token to make API calls.
if (tokenResponse !== undefined) {
let parts = await this.commandState.get(step.context);
if (!parts) {
parts = step.context.activity.text;
}
const command = parts.split(' ')[0].toLowerCase();
if (command === 'me') {
await OAuthHelpers.listMe(step.context, tokenResponse);
} else if (command === 'send') {
await OAuthHelpers.sendMail(step.context, tokenResponse, parts.split(' ')[1].toLowerCase());
} else if (command === 'recent') {
await OAuthHelpers.listRecentMail(step.context, tokenResponse);
} else {
let photoResponse = await OAuthHelpers.loginData(step.context, tokenResponse);
const card = CardFactory.heroCard(
`Welcome ${ photoResponse.displayName }, you are now logged in.`,
[photoResponse],
[]
);
const reply = ({ type: ActivityTypes.Message });
reply.attachments = [card];
await step.context.sendActivity(reply);
}
} else {
// Ask the user to try logging in later as they are not logged in.
await step.context.sendActivity(`We couldn't log you in. Please try again later.`);
}
return await step.endDialog();
};
oauth-helpers.js:
Add static async loginData:
/**
* Displays information about the user in the bot.
* #param {TurnContext} turnContext A TurnContext instance containing all the data needed for processing this conversation turn.
* #param {TokenResponse} tokenResponse A response that includes a user token.
*/
static async loginData(turnContext, tokenResponse) {
if (!turnContext) {
throw new Error('OAuthHelpers.loginData(): `turnContext` cannot be undefined.');
}
if (!tokenResponse) {
throw new Error('OAuthHelpers.loginData(): `tokenResponse` cannot be undefined.');
}
try {
// Pull in the data from Microsoft Graph.
const client = new SimpleGraphClient(tokenResponse.token);
const me = await client.getMe();
const photoResponse = await client.getPhoto();
// Attaches user's profile photo to the reply activity.
if (photoResponse != null) {
let replyAttachment;
const base64 = Buffer.from(photoResponse, 'binary').toString('base64');
replyAttachment = {
contentType: 'image/jpeg',
contentUrl: `data:image/jpeg;base64,${ base64 }`
};
replyAttachment.displayName = me.displayName;
return (replyAttachment);
}
} catch (error) {
throw error;
}
}
simple-graph-client.js:
Add async getPhoto:
/**
* Collects the user's photo.
*/
async getPhoto() {
return await this.graphClient
.api('/me/photo/$value')
.responseType('ArrayBuffer')
.version('beta')
.get()
.then((res) => {
return res;
})
.catch((err) => {
console.log(err);
});
}
package.json:
Be sure the #microsoft/microsoft-graph-client installs version 1.0.0 due to breaking changes around AAD 'displayName' acquisition in subsequent versions.
Once the above code was implemented, I was able to login which, upon success, immediately updated the user avatar.
Hope of help!