Preserving cookies between sessions - selenium

I need to make sure that a browser is trusted every time the session is when performing Selenium script in Node.js (If the browser is not trusted by the server it resets in MFA with SMSes which I would like to avoid).
In Puppeteer it is simple by:
const browser = await puppeteer.launch({headless: false,
userDataDir: "./user_data"});
I would love to stay with puppeteer because of that but I have a dropdown selector on the page accessible only by name or xpath and page and page.select requires CSS selector:-(.
So I moved back to Selenium, but there I have this persistency problem:
At the end of a session I have:
var allCokies = await driver.manage().getCookies(); fs.writeFile("/Users/matnikr/Documents/scrape/selenium/cookies.json",JSON.stringify(allCokies), function(err){
if (err) {return console.log(err)}
console.log('file saved');})
A the begining I have:
driver = await new Builder().forBrowser('chrome').build();
var allCokies = JSON.parse(fs.readFileSync("/Users/matnikr/Documents/angular\ code/Aliorscrape/selenium/cookies.json","utf8"));
for (var key in allCokies){
await driver.manage().addCookie(allCokies[key])
}
await driver.get('https:*******/do/Login');
And every time it feels like "incognito" session is started. Browser is untrusted. Any help appreciated.

Did you try to do the same as you did with Puppeteer? I mean to load a profile. Because it's actually what you did by providing "./user_data" as a userDataDir value.
const { Builder } = require('selenium-webdriver');
const { Options } = require('selenium-webdriver/chrome');
function buildChromeDriver() {
const options = new Options();
options.addArguments('user-data-dir=./user_data');
return new Builder()
.forBrowser('chrome')
.setChromeOptions(options)
.build();
}
You can always verify the loaded profile by opening chrome://version in the browser:
driver.get('chrome://version');

Related

Anyone have a solution for generating server-side tokens for the ESRI JSAPI SDK?

There are a number of solutions to this:
use the build-in dialog provided by esri/IdentityManager (https://developers.arcgis.com/javascript/3/jsapi/identitymanagerbase-amd.html)
use a server-side proxy (https://github.com/Esri/resource-proxy)
use the identity manager initialize() method (https://developers.arcgis.com/javascript/3/jsapi/identitymanagerbase-amd.html#initialize)
But there what is missing is the ability to hook into the request for a token. I am working with ArcGISDynamicMapServiceLayer and there is no way to know if the server return a 498/499, and no way to update the url to update the token.
I started hacking around in the API to try to hook into various events with no real promise of success. What seems to be missing:
a way to detect when a token is needed
a way to update the token
Closes I came up with is listening for "dialog-create" but there is no way to disable the dialog apart from throwing an exception, which disables the layer.
I tried replacing the "_createLoginDialog" method and returning {open: true} as a trick to pause the layers until I had a token ready but since there is no way to update the layer endpoint I did not pursue this hack. It seems the only way this might work is to use the initialize() method on the identity manager.
Does anyone have knowledge of options beyond what I have outlined?
EDIT: The goal is to provide a single-sign-on experience to users of our product.
"User" is already signed in to our application
"User" wishes to access a secure ESRI ArcGIS Server MapServer or FeatureServer services from the ESRI JSAPI
"User" is prompted for user name and password
The desired flow is to acquire a token on the users behalf using a RESTful services in our product and return the appropriate token that will allow the "User" to access the secure services without being prompted.
I do not wish to use a proxy because I do not want all that traffic routed through the proxy.
I do not wish to use initialize() because it is complicated and not clear how that works apart for re-hydrating the credentials.
I do wish for an API that simply allows me to set the token on any layer services that report a 499 (missing token) or 498 (invalid token), but I cannot find any such API. The solution I am focusing on hinges on being able to update the url of an ArcGISImageServiceLayer instance with a new token.
This answer lacks in satisfaction but delivers on my requirements. I will start with the code (client-side typescript):
class TokenProxy {
private tokenAssuranceHash = {} as Dictionary<Promise<{ token: string, expiration: string }>>;
private service = new TokenService();
private timeoutHandle = 0;
watchLayer(esriLayer: ArcGISDynamicMapServiceLayer) {
setInterval(async () => {
const key = esriLayer._url.path;
const token = await this.tokenAssurance(key);
esriLayer._url.query.token = token;
}, 5000);
}
updateRefreshInterval(ticks: number) {
clearTimeout(this.timeoutHandle);
this.timeoutHandle = setTimeout(() => {
Object.keys(this.tokenAssuranceHash).forEach(url => {
this.tokenAssuranceHash[url] = this.service.getMapToken({serviceUrl: url});
});
this.updateRefreshInterval(ticks);
}, ticks);
}
async tokenAssurance(url: string) {
if (!this.tokenAssuranceHash[url]) {
this.tokenAssuranceHash[url] = this.service.getMapToken({serviceUrl: url});
}
try {
const response = await this.tokenAssuranceHash[url];
await this.recomputeRefreshInterval();
return response.token;
} catch (ex) {
console.error(ex, "could not acquire token");
return null;
}
}
async recomputeRefreshInterval() {
const keys = Object.keys(this.tokenAssuranceHash);
if (!keys.length) return;
const values = keys.map(k => this.tokenAssuranceHash[k]);
const tokens = await Promise.all(values);
const min = Math.min(...tokens.map(t => new Date(t.expiration).getTime()));
if (Number.isNaN(min)) return; // error occured, do not update the refresh interval
const nextRefreshInTicks = min - new Date().getTime();
this.updateRefreshInterval(0.90 * nextRefreshInTicks);
}
}
And highlight the hack that makes it work:
const key = esriLayer._url.path;
const token = await this.tokenAssurance(key);
esriLayer._url.query.token = token;
The "_url" is a hidden/private model that I should not be using to update the token but it works.

xhr to service with windows authentication

To access in browser web service using windows authentication I add the following:xhr.withCredentials= true;
Is it enough? Sometimes browser still shows windows asking for user win credentials. More details.
https://localhost:44386/api uses windows auth. Some users are allowed, some not - for test. Here is js code. Is it ok? Sometimes users still get dialog asking for teir windows credentials.
function send() {
const url = 'https://localhost:44386/api/values';
var xhr = new XMLHttpRequest();
xhr.withCredentials = true;
xhr.open("GET", url);
xhr.onload = () => requestComplete(xhr);
xhr.send();
};

Connect to geckodriver with selenium js

I'm not an expert of Selenium, so I may miss something here.
One of the software in the corp starts a firefox with Geckodriver.
I would like to connect / attach to this browser from my JavaScript code.
I know the port where the Webserver starts and the sessions identifier.
I try to connect from JS:
const webdriver = require('selenium-webdriver')
void async function() {
let driver = await new webdriver.Builder().forBrowser('firefox').usingServer('http://localhost:55849/').build();
await driver.get('http://www.google.com/ncr');
await driver.findElement(By.name('q')).sendKeys('webdriver');
await driver.findElement(By.name('btnG')).click();
await driver.wait(until.titleIs('webdriver - Google Search'), 1000);
driver.quit();
}();
The connection is not successful. What I can think is that this code tries to start a new instance.
There is an error message:
SessionNotCreatedError: Session is already started
Any idea how I can connect to the existing one? And control it?
I've tried everything from the docs: https://www.npmjs.com/package/selenium-webdriver
I even tried to connect http://localhost:55849/wd/hub but then I received WebDriverError: HTTP method not allowed error
Use the selenium-webdriver/http
const webdriver = require('selenium-webdriver')
const http= require('selenium-webdriver/http')
let sessionId = '9aad751d-eb9b-4c92-92f3-298c733f6ec7';
let url = 'http://localhost:57538';
let driver = new webdriver.WebDriver(
sessionId,
new http.Executor(Promise.resolve(url)
.then(
url => new http.HttpClient(url, null, null))
)
);

Get a browser cookie, and pass it to the server side rendering on nuxt js

I develop a project with nuxt js. And I get problem like this below.
I get result from restful api of backend with asynchronous ajax
request.
I need add the result as a header to every quest with the
ajax library of axios.
So I save the result on browser cookie.
When I need the result, I get it from cookie, and attach it on axios
request.
Now, the problem is on the server side rendering, I can not get the browser cookie.
What am I gonna do with the problem?
You can store the needed info in both local storage & cookie, e.g.
import Cookie from 'js-cookie'
....
setCookie(state, value) {
if (process.client) {
localStorage.setItem('cookie', value);
}
Cookie.set('cookie', value)
}
To read it (cookies are included in the request automatically)
getCookie(context, req) {
// if server
if (req) {
if (req.headers.cookie) {
const cookie = req.headers.cookie.split(';').find(...)
}
}
// if client
if (process.client) {
const cookie = localStorage.getItem('cookie');
}
}
And to remove
removeCookie(state) {
if (process.client) {
localStorage.removeItem('cookie');
}
Cookie.remove('cookie');
}
Firstly, you can get the req (request) and res (response) from the nuxt context.
You can set a cookie via
// client side
document.cookie = 'sessionId=some-id;';
document.cookie = 'userId=awesome-id;';
// or server side
res.setHeader('Set-Cookie', ['sessionId=some-id;', 'userId=awesome-id;']);
In Nuxt 3 and Nuxt 2 Bridge you can use useCookie
Nuxt provides an SSR-friendly composable to read and write cookies.
const session = useCookie('session')
session.value = ''

ASP.NET Identity authentication signs the user out after browser refresh

I'm using ASP.NET Identity for my Single Page Application authentication, everything works fine, but when I refresh the page the user signs out automatically. what is the solution for this problem? would you please introduce me a complete sample or article?
Finally after much struggle I found the answer. I was needed to store the token to "sessionStorage" like this:
var sessionToken = 'token';
var sessionAuthenticated = 'auth';
var sessionUser = 'user';
var sessionIsARefresh = 'refresh';
window.onbeforeunload = function () {
sessionStorage.setItem(sessionAuthenticated, clientModel.authenticated());
sessionStorage.setItem(sessionUser, clientModel.username());
sessionStorage.setItem(sessionToken, adminModel.accessToken());
sessionStorage.setItem(sessionIsARefresh,true);
}
$(document).ready(function () {
if (sessionStorage.getItem(sessionIsARefresh)) {
adminModel.accessToken(sessionStorage.getItem(sessionToken));
clientModel.authenticated(sessionStorage.getItem(sessionAuthenticated));
clientModel.username(sessionStorage.getItem(sessionUser));
getUser(clientModel.username());
getNotificationTypes();
}
}
As I have found out according to W3School sessionStorage is more secure than cookies.