express-session setting new session every time, and does not persist after creating the session - postgresql

I am using express for my backend on localhost:8080
Using react for my frontend on localhost:3000
No proxy is in use. Just simple http://localhost:3000/roster sending a request to http://localhost:8080/
I have set up all the cross origin and header stuff for cors() too
my postgres store is setup, and when I query the sessions table, I can see the session data there.
using axios for fetch on my front end, and I have configured credentials:true there also.
I am console logging my sessionID in the middleware (before its been set) and then I log it again in the request I am using to test my sessions. In the middleware it's unidentified as expected, and then once I log it in the request, I get a real session id as expected. Everything seems to work, but when I send the request for a second time, it send back a whole new sessionID instead of persisting.
As I looked further it said it could have something to do with the cookie not being set? But I have looks all over for configuring the setting correctly like resave and httpOnly, and all those seem to be okay too.
What am I doing wrong, how can I fix this? I have included my package.json for client and server here first....
PACKAGE.JSON CLIENT
{
"name": "client.react",
"version": "0.1.0",
"private": true,
"dependencies": {
"axios": "^0.21.1",
"react": "^17.0.1",
"react-dom": "^17.0.1",
"react-router-dom": "^5.2.0",
"react-scripts": "0.9.5"
},
"devDependencies": {},
"scripts": {
"start": "react-scripts start",
"build": "react-scripts build",
"test": "react-scripts test --env=jsdom",
"eject": "react-scripts eject"
}
}
PACKAGE.JSON SERVER
{
"name": "backend",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [],
"author": "",
"license": "ISC",
"dependencies": {
"bcryptjs": "^2.4.3",
"body-parser": "^1.19.0",
"connect-pg-simple": "^6.2.1",
"cors": "^2.8.5",
"dotenv": "^8.2.0",
"express": "^4.17.1",
"express-session": "^1.17.1",
"jsonwebtoken": "^8.5.1",
"pg": "^8.5.1",
"uuid": "^8.3.2"
}
}
CLIENT
/**
* File: /src/pages/roster.js
* Date: 01-28-2021
* Author: jreyes
*
* Date | Author | Change
* ---------------------------------------------------------------------------------------
* 01-29-2021 | jreyes | initialization
* ---------------------------------------------------------------------------------------
*/
import React from 'react';
import Axios from 'axios';
const Roster = () => {
const [roster, setRoster] = React.useState([]);
/** Fetch the roster. */
const handleRoster = () => {
Axios.get("http://localhost:8080/", {
headers: {
"Content-Type":"application/json"
}
}, { withCredentials: true })
.then((response) => {
console.log(response.data);
})
}
return (
<React.Fragment>
<h1>Roster</h1>
<button onClick={handleRoster}>Get Roster</button>
</React.Fragment>
)
}
export default Roster;
CLIENT CONSOLE LOG FROM CHROME
Notice the two different sessionIDs sent back from the server. From the same roster page and I just click the button once, and then a second time.
{hit: "!/", session: "SessionID: db9af88c-0101-4bf5-82c7-f57fbe9dac1d"}
hit: "!/"
session: "SessionID: db9af88c-0101-4bf5-82c7-f57fbe9dac1d"
__proto__: Object
roster.js:25
{hit: "!/", session: "SessionID: b1a5ffd2-c986-4932-827c-a6ce644a0b3e"}
hit: "!/"
session: "SessionID: b1a5ffd2-c986-4932-827c-a6ce644a0b3e"
__proto__: Object
SERVER
/**
* File: index.js
* Date: 01-20-2021
* Author: Bennm23
*
* Date | Author | Change
* ---------------------------------------------------------------------------------------
* 01-20-2021 | benm23 | initialization
* ---------------------------------------------------------------------------------------
* 01-29-2021 | jreyes | formatted code; fixed cors; added env
* | | functionality; db is outsourced in db.js;
* ---------------------------------------------------------------------------------------
* 01-30-2021 | jreyes | added express sessions; uuid for unique strings;
* | | added request to fetch user profile.
* ---------------------------------------------------------------------------------------
*/
require('dotenv').config();
const express = require("express");
const app = express();
const db = require('./db');
const bcrypt = require("bcryptjs");
const cors = require("cors");
const {v4: uuidv4} = require('uuid');
const session = require('express-session');
const pgSession = require('connect-pg-simple')(session);
app.use(express.json());
app.use(express.urlencoded());
app.use(session({
genid: (req) => {
console.log("Inside middleware, not set yet: ");
console.log(req.sessionID);
return uuidv4();
},
store: new pgSession({
pool: db,
tableName: "session"
}),
secret: process.env.ES_SECRET,
cookie:{
maxAge:36000,
httpOnly: false,
secure: false
},
resave: false,
saveUninitialized: true
}));
app.use(cors({
origin: "http://localhost:3000",
methods: ['GET', 'POST', 'PUT', 'DELETE'],
credentials: true,
}));
/** Set proper headers */
app.use(function(req, res, next) {
res.header("Access-Control-Allow-Credentials", true);
res.header("Access-Control-Allow-Methods", "GET, PUT, POST, DELETE");
res.header("Access-Control-Allow-Origin", "http://localhost:3000");
res.header("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept, Authorization");
next();
});
/**
* Test request for sessions.
*/
app.get("/", (req, res) => {
console.log('Hit detected: defualt home route');
console.log('Session detected: ' + req.sessionID);
res.json({
"hit":"!/",
"session": "SessionID: " + req.sessionID
});
});
/**
* Register a new user.
*
* Errors:
* !email: email has been taken
* !register: database malfunction
* !hashing: hashing malfunction
*
* */
app.post("/signup", async (req, res) => {
const userExistsReq = "SELECT * FROM users WHERE email = $1";
const userExistsRes = await db.query(userExistsReq,[req.body.email]);
// Email already exists in the database.
if(userExistsRes.rowCount > 0){
res.status(200);
res.json({"error":"!email"});
}
else{
try {
// Hash password.
const salt = await bcrypt.genSalt();
const hashedPassword = await bcrypt.hash(req.body.password, salt)
const registerTemplate = "INSERT INTO users (email, password, firstname, lastname) VALUES ($1,$2,$3,$4)";
// Add user to database.
try {
const registerRes = await db.query(registerTemplate,
[
req.body.email,
hashedPassword,
req.body.firstname,
req.body.lastname
]
);
res.status(201);
res.json({"good":"register"});
// Error adding user.
} catch (err) {
res.status(500);
console.error("error: " + err);
res.json({"error":"!register"});
}
}
// Error hashing password.
catch {
res.status(500);
console.error("error: " + err)
res.json({"error":"!hashing"});
}
}
});
/**
* Login an existing user.
*
* Errors:
*
* !email: user does not exist
* !password: user entered wrong password
* !login: database malfunction.
*/
app.post("/login", async (req, res) => {
// Verify user presence in db.
const userExistsReq = "SELECT * FROM users WHERE email = $1";
const userExistsRes = await db.query(userExistsReq, [req.body.email]);
// User does not exits.
if(userExistsRes.rowCount == 0){
res.status(200);
res.json({"error":"!email"});
}
else{
// Test user credentials.
try {
if(await bcrypt.compare(req.body.password, userExistsRes.rows[0].password)){
const email = userExistsRes.rows[0].email;
const firstname = userExistsRes.rows[0].firstname;
res.status(200);
res.json({"good":"login"})
}else{
res.status(200);
res.json({"error":"!password"})
}
// Error finding user.
} catch (err) {
res.status(200);
console.error("Error while running: " + err);
res.json({"error":"!login"});
}
}
});
/**
* Fetch the roster of players.
*
* !roster: database malfunction
*/
app.get("/roster", async (req, res) => {
const fetchRosterTemplate = "SELECT * FROM users";
const response = await db.query(fetchRosterTemplate);
if (response.rowCount == 0) {
res.status(200);
res.json({"error":"!roster"});
} else {
res.status(200);
res.json(response.rows);
}
});
/**
* Start server.
*/
app.set("port", 8080);
app.listen(app.get("port"), () => {
console.log(`Find the server at http://localhost:${ app.get("port") }`);
});
SERVER CONSOLE LOG
This is the console after two requests from the roster page in my client. I click the button twice and these are the two things that are logged.
jreyes#x1carbon:~/Projects/mothers-rfc/server$ node index.js
body-parser deprecated undefined extended: provide extended option index.js:29:17
Find the server at http://localhost:8080
Inside middleware, not set yet:
undefined
Hit detected: default home route
Session detected: db9af88c-0101-4bf5-82c7-f57fbe9dac1d
Inside middleware, not set yet:
undefined
Hit detected: default home route
Session detected: b1a5ffd2-c986-4932-827c-a6ce644a0b3e

Setting httpOnly solved my issue. I had it set to false and it needs to be true. I left the secure option for cookies set to false.
httpOnly: true
solved my problem :)

Related

Cookie does not appear to be sent via fetch or hapi server is unable to receive cookie

So I have a simple backend server created with Hapi API and the frontend I'm using fetch. These are on different ports so I have CORs enabled and all the sweet stuff. I'm currently trying to set a refresh token in the browser using a http only cookie. As far as I can verify, the http only cookie is being set in the browser when login function is completed. I'm currently trying to send the http only cookie back to the server so I can set up the refresh token route and I can't seem to send or even verify that http token is sent back to the server.
Here's the server setting.
"use strict";
require("dotenv").config();
const Hapi = require("#hapi/hapi");
const Jwt = require("#hapi/jwt");
const routes = require("./routes/routes");
exports.init = async () => {
const server = Hapi.server({
port: 3000,
host: "localhost",
routes: {
cors: {
origin: ["*"],
credentials: true,
},
},
});
require("./models");
await server.register(Jwt);
server.auth.strategy("jwt", "jwt", {
keys: { key: process.env.SECRET_KEY, algorithms: ["HS256"] },
verify: { aud: false, iss: false, sub: false, exp: true },
validate: false,
});
server.state("refresh", {
ttl: 1000 * 60 * 60 * 24,
isSecure: true,
isHttpOnly: true,
encoding: "base64json",
clearInvalid: true,
strictHeader: true,
isSameSite: "None",
});
server.route(routes);
return server;
};
process.on("unhandledRejection", (err) => {
console.log(err);
process.exit(1);
});
Here's the login request and returns the http only cookie. This part works, the http cookie is returned and set.
const validateUserAndReturnToken = async (req, h) => {
const user = await User.findOne({
$or: [{ email: req.payload.username }, { username: req.payload.username }],
});
if (user) {
const match = await bcrypt.compare(req.payload.password, user.passwordHash);
if (match) {
const token = await createToken(match);
const refreshToken = await createRefreshToken(match);
h.state("refresh", refreshToken);
return { id_token: token, user: formatUser(user) };
} else {
throw boom.notAcceptable("Username and password did not match.");
}
} else {
throw boom.notAcceptable("Username or email was not found.");
}
};
Here's the fetch request I'm using to test sending a http cookie only back. I have credential: include so I don't know what is problem?
import type { DateInfo } from "#/stores/application";
const api = "http://localhost:3000/report";
let token = localStorage.getItem("user-token");
const headers = new Headers();
headers.append("Authorization", `Bearer ${token}`);
headers.append("Content-Type", "application/json");
export const getJobReport = async (dateFilter: DateInfo) => {
let response = await fetch(
`${api}/${dateFilter.startDate}/${dateFilter.endDate}`,
{
method: "GET",
headers,
credentials: "include",
}
);
return await response.json();
};
I have checked the application tab as well as the network request so I know set cookie is being sent and set on the browser. The problem is I can't seem to get the cookie back from the browser when fetch request is sent back to the server.
Here's the code I'm using to just check the existence of the cookie. According to Hapi Doc , req.state[cookie-name] which in this case is 'refresh' should have the cookie value. Refresh is returning undefined so I went up one level and check for req.state and gets an empty object {}.
route
{
method: "GET",
path: "/report/{startDate}/{endDate}",
options: {
auth: "jwt",
state: {
parse: true,
failAction: "error",
},
validate: {
params: Joi.object({
startDate: Joi.string(),
endDate: Joi.string(),
}),
},
},
handler: handlers.report.getJobApplicationReport,
},
handler
const getJobApplicationReport = async (req, h) => {
console.log("TEST", req.state);
const start = new Date(req.params.startDate);
const end = new Date(req.params.endDate);
try {
const applications = await Application.find({
dateApplied: { $gte: start, $lt: end },
});
// 'Applied', 'In Process', 'Rejected', 'Received Offer'
const total = applications.length;
let rejectedCount = 0;
let inProcessCount = 0;
applications.forEach((app) => {
if (app.status === "Rejected") {
rejectedCount++;
}
if (app.status === "In Process") {
inProcessCount++;
}
});
return {
total,
rejectedCount,
inProcessCount,
};
} catch (error) {
console.log(error);
throw boom.badRequest(error);
}
};
I've looked through all the Hapi documentation, fetch documentation and stackoverflow question/answers but can't seem to find a solution. I can't verify whether it's the fetch request that's not sending the http only cookie or the server setting that's not parsing it. Any help to determine the issue or solution would be greatly appreciated.
I've looked through all the Hapi documentation, fetch documentation and stackoverflow question/answers but can't seem to find a solution. I can't verify whether it's the fetch request that's not sending the http only cookie or the server setting that's not parsing it. Any help to determine the issue or solution would be greatly appreciated.

NextAuth Hasura Refresh token

I am trying to set up NextAuth for Hasura authentication and authorization. Since Hasura needs custom jwt claims I can't use the default access token provided by an OAuth provider. So I am using encode block in [...nextauth].js to encode a custom jwt token and everything works fine. But I don't know how to implement a refresh token for my custom token. Below is my "pages/api/auth/[...nextauth].js"
import * as jwt from "jsonwebtoken";
import NextAuth from "next-auth";
import Providers from "next-auth/providers";
export default NextAuth({
providers: [
Providers.Google({
clientId: process.env.GOOGLE_CLIENT_ID,
clientSecret: process.env.GOOGLE_CLIENT_SECRET,
authorizationUrl:
"https://accounts.google.com/o/oauth2/v2/auth?prompt=consent&access_type=offline&response_type=code",
}),
],
secret: process.env.SECRET,
session: {
jwt: true,
},
jwt: {
secret: process.env.SECRET,
encode: async ({ secret, token, maxAge }) => {
const jwtClaims = {
sub: token.id,
name: token.name,
email: token.email,
picture: token.picture,
iat: Date.now() / 1000,
exp: Math.floor(Date.now() / 1000) + 60,
"https://hasura.io/jwt/claims": {
"x-hasura-allowed-roles": ["user"],
"x-hasura-default-role": "user",
"x-hasura-role": "user",
"x-hasura-user-id": token.id,
},
};
const encodedToken = jwt.sign(jwtClaims, secret, { algorithm: "HS256" });
return encodedToken;
},
decode: async ({ secret, token, maxAge }) => {
const decodedToken = jwt.verify(token, secret, { algorithms: ["HS256"] });
return decodedToken;
},
},
pages: {
// signIn: '/auth/signin', // Displays signin buttons
// signOut: '/auth/signout', // Displays form with sign out button
// error: '/auth/error', // Error code passed in query string as ?error=
// verifyRequest: '/auth/verify-request', // Used for check email page
// newUser: null // If set, new users will be directed here on first sign in
},
// Callbacks are asynchronous functions you can use to control what happens
// when an action is performed.
// https://next-auth.js.org/configuration/callbacks
callbacks: {
// async signIn(user, account, profile) { return true },
// async redirect(url, baseUrl) { return baseUrl },
async session(session, token) {
const encodedToken = jwt.sign(token, process.env.SECRET, {
algorithm: "HS256",
});
session.token = encodedToken;
session.id = token.id;
return Promise.resolve(session);
},
async jwt(token, user, account, profile, isNewUser) {
const isUserSignedIn = user ? true : false;
// make a http call to our graphql api
// store this in postgres
if (isUserSignedIn) {
token.id = profile.id.toString();
}
return Promise.resolve(token);
},
},
// Events are useful for logging
// https://next-auth.js.org/configuration/events
events: {},
// Enable debug messages in the console if you are having problems
debug: true,
});
Can somebody tell me how to handle refresh token with next-auth when using custom jwt tokens?
I'm currently using a method I found on a post here on the next-auth issues page.
Essentially, we're using a combination of the clientMaxAge option you can pass into the provider to refetch the session, which reruns the jwt callback. I'm not sure I'm using the keepAlive property correctly, but this seems to poll correctly at the moment, though you may need to experiment with this.
Inside your JWT callback, you can have your logic that will check your existing expiry time against another, and fetch a new token from your server to assign for the session.
//_app.tsx
const sessionOptions = {
clientMaxAge: 60 * 30, // Re-fetch session if cache is older than 30 minutes
keepAlive: 60 * 30, // Send keepAlive message every hour
};
<Provider options={sessionOptions} session={pageProps.session}>
..
</Provider>
// [...nextauth].ts
const callbacks: CallbacksOptions = {
async jwt(token: any, user: any) {
if (user) {
token.accessToken = user.token;
token.expires = Date.now() + user.config.USER_SESSION_LENGTH_IN_SECONDS * 1000;
}
// Don't access user as it's only available once, access token.accessToken instead
if (token?.accessToken) {
const tokenExpiry = token.expires;
const almostNow = Date.now() + 60 * 1000;
if (tokenExpiry !== undefined && tokenExpiry < almostNow) {
// Token almost expired, refresh
try {
const newToken = await api.renewToken(token.accessToken); // calling external endpoint to get a new token
// re-assign to the token obj that will be passed into the session callback
token.accessToken = newToken.token;
token.expires = Date.now() + user.config.USER_SESSION_LENGTH_IN_SECONDS * 1000;
} catch (error) {
console.error(error, 'Error refreshing access token');
}
}
}
return token;
},
async session(session: any, user: any) {
session.accessToken = user.accessToken;
session.expires = user.expires;
return session;
}
}

Browser is not in sync while debugging the protractor with Type Script and VS Code

I am new to the protractor and trying to debug the code in VS Code. The browser is launched as expected and not in sync with debugging. After clicking on continue the whole scenario is executed on the browser.
Below is my configuration file.
"configurations": [
{
"type": "node",
"request": "launch",
"name": "Launch",
"program": "${workspaceFolder}/node_modules/protractor/bin/protractor",
"stopOnEntry": false,
"args": ["${workspaceRoot}/built/conf.js"]
}
]
code where am trying to debug:
/**
* Enter userName
*/
public enterUserName(): Promise<void> {
try{
return <Promise<void>> element(by.name(this.userNameLocater)).sendKeys("admin#XXX.com");
}catch(err){
console.log("Error while entering the user name " + err);
}
}
/**
* Enter password
*/
public enterPassword(): Promise<void> {
try{
return <Promise<void>> element(by.name(this.passwordLocater)).sendKeys("PWD");
}catch(err){
console.log("Error while entering the password " + err);
}
}
/**
* click on signin button
*/
public clickSignin(): Promise<void> {
try{
return <Promise<void>> element(by.tagName(this.signinbttonLocater)).click();
}catch(err){
console.log("Error while clicking on signin button" + err);
}
}
/**
* Login success fully
*/
public login(): Promise<void> {
try{
this.enterUserName();
this.enterPassword();
return this.clickSignin();
}catch(err){
console.log("Error while signing in" + err);
}
}
Below is the Spec.ts with just a single scenario.
import { browser, element, by } from 'protractor';
import 'jasmine';
import {LoginCls} from '../Pages/loginCls';
let login = new LoginCls('loginEmail','loginPassword','button');
describe("Login to application", function () {
it("Should login Successful", function () {
browser.get("");
login.login().then(() => {
var text = element(by.xpath('/html/body/app-root/div[1]/div/div/div[2]/home-comp/div/div[1]/div[1]/div[2]/p[1]'));
expect(text.getText()).toContain('Welcome, Administrator Super Admin!');
}).catch((err) => {
console.log(err);
});
});
});
You should try to use async/await pattern to handle promise. See below:
import { browser, element, by } from 'protractor';
import 'jasmine';
import {LoginCls} from '../Pages/loginCls';
let login = new LoginCls('loginEmail','loginPassword','button');
describe("Login to application", () => {
it("Should login Successful", async () => {
await browser.get("");
try {
var text = element(by.xpath('/html/body/app-root/div[1]/div/div/div[2]/home-comp/div/div[1]/div[1]/div[2]/p[1]'));
expect(await text.getText()).toContain('Welcome, Administrator Super Admin!');
} catch (err) {
console.log(err);
}
});
});

TypeError: [function] is not a function in Passport local strategy

I'm trying to authenticate users locally with Passport.js while not keeping session and using my own JWTokens.
I was following this tutorial:
Learn using JWT with Passport authentication
While also reading Passport.js documentation. I don't know what went wrong, but passport doesn't seem to notice that some functions are indeed functions.
I've got a Load function to select a User from the DB(mongo) given certain criteria(a user might logIn with e-mail or phone number).
load: function(options, cb) {
options.select = options.select || 'email phone';
return this.findOne(options.criteria)
.select(options.select)
.exec(cb);
}
I'm calling passport.authenticate in my routes:
// Controllers //
const Users = require('../../app/controllers/users');
...
...
app.post('/api/login', passport.authenticate('local', { failureRedirect: '/api/login' }), Users.login);
And here's my local strategy:
const mongoose = require('mongoose');
const User = mongoose.model('User');
const passport = require('passport');
const LocalStrategy = require('passport-local').Strategy;
passport.use(new LocalStrategy(
{
usernameField: 'email',
phoneField: 'phone',
passwordField: 'password',
session: false
},
function(email, phone, password) {//cb == callback
const options = {
criteria: { email: email, phone: phone },
select: 'name username email hashed_password salt'
};
User.load(options, function(err, user) {
if (err || !user){
return res.status(400).json({
type: 'failure',
message: "User creation failed",
data: [err]
});
};
if (!user.authenticate(password)) {
return res.status(400).json({
type: 'failure',
message: "User creation failed",
data: [err]
});
};
req.login(user, {session: false}, (err) => {
if (err) {
res.send(err);
}
// generate a signed son web token with the contents of user object and return it in the response
const token = jwt.sign(user.id, 'your_jwt_secret');
return res.json({user, token});
});
});
}
));
I'm getting the following error:
TypeError: res.status is not a function
Before trying to get stuff back from passport with responde. I was trying to do it with cb(callback), as done in the tutorial, but I keep getting the same error.
Thanks in advance for any help!
There are a few issues with how you are implementing Passport's local strategy that are causing problems.
You are trying to use two fields as the username when Passport's local startegy only accepts one. (see: http://www.passportjs.org/packages/passport-local/)
function(username, password, done){}
If you want to use both as a username, you might want to consider creating your own custom strategy. This is going to be a little more in-depth, but you can start learning on the Passport Github page (https://github.com/jaredhanson/passport-strategy)
The second issue is that you are trying to get Passport to send a response in the local strategy which is not what it is intended to do. Instead, you should be passing the errors and return values to Passport's done() function, which will process them accordingly.
Here is an example of what your local strategy should look like:
passport.use(
new LocalStrategy(async (email, phone, password, done) => {
const options = {
criteria: { email, phone },
select: 'name username email hashed_password salt',
};
try {
const user = await User.load(options);
/**
* If null is returned meaning there was no user found, send the done call
* with the false flag. This tells passport to redirect to the failure URL.
*/
if (!user) {
return done(null, false);
}
/**
* If the user's password is incorrect, also return the done function with the false
* flag. This tells passport to redirect to the failure URL.
*/
if (!user.authenticate(password)) {
return done(null, false);
}
/**
* If a user is found and their password is verified, send the user object to
* the done function. This will tell Passport to call the next middelware attaching
* the user object.
*/
return done(null, user);
} catch (err) {
/**
* If there is an error with the DB call, return generic message
* for security purposes.
*/
return done('There was an internal server error.');
}
})
);
and an example of what your load function should look like:
load: options => {
return new Promise(async (resolve, reject) => {
options.select = options.select || 'email phone';
try {
const user = await this.findOne(options.criteria)
.select(options.select)
.exec();
resolve(user);
} catch (err) {
reject(err);
}
});
};
As a general best practice, I changed your callbacks to the newer method of promises (https://developers.google.com/web/fundamentals/primers/promises).
This should work in the way you are intending to use Passport.

How to get connected clients and client certificate in node-opcua server

I have a node-opcua server setup with 2 clients connected to it with Security Mode set to SignAndEncrypt. Now, I have 2 questions:
Is there a way for the server to find out how many clients are connected to it?
The server application will like to know the identity of a connected client, is there an API to get the client certificate obtained during OpenSecureChannel?
OPCUA Server can expose such information under the RootFolder.Server.ServerDiagnostics node, and the information you need shall be accessible through OPCUA.
This little node-opcua client program will show you how to do.
note:
that certain data such as security diagnostics requires a secure connection and a non-anonymous user
client_extract_server_diagnostic.ts
// this script is typescript and can be run this way
// $ npx ts-node client_extract_server_diagnostic.ts
import {
AttributeIds,
OPCUAClient,
ClientSession,
StatusCodes,
MessageSecurityMode,
SecurityPolicy,
UserIdentityInfoUserName,
UserTokenType
} from "node-opcua";
// the opcua server to connect to
const endpointUrl = "opc.tcp://localhost:48010";
// the credential
const userIdentityToken: UserIdentityInfoUserName = {
password: "secret",
userName: "root",
type: UserTokenType.UserName
};
async function extractServerStatistics(session: ClientSession) {
const nodesToRead = [
{
attributeIds: AttributeIds.Value, nodeId:"Server_ServerDiagnostics_EnabledFlag"
},
{
attributeIds: AttributeIds.Value, nodeId:"Server_ServerDiagnostics_ServerDiagnosticsSummary_CurrentSessionCount" //i=2277
},
{
attributeIds: AttributeIds.Value, nodeId:"Server_ServerDiagnostics_ServerDiagnosticsSummary_CurrentSubscriptionCount" // i=2285
},
{
attributeIds: AttributeIds.Value, nodeId: "Server_ServerDiagnostics_ServerDiagnosticsSummary_CumulatedSessionCount" // i=2278
},
{
attributeIds: AttributeIds.Value, nodeId: "Server_ServerDiagnostics_ServerDiagnosticsSummary_CumulatedSubscriptionCount" // i=2278
},
{
attributeIds: AttributeIds.Value, nodeId: "Server_ServerDiagnostics_SessionsDiagnosticsSummary_SessionSecurityDiagnosticsArray" // i=3708
}
];
const dataValues = await session.read(nodesToRead);
console.log("Diagnostic enabled ? = ", dataValues[0].value.value);
console.log("Current Session Count = ", dataValues[1].value.value);
console.log("Current Subscription Count = ", dataValues[2].value.value);
console.log("Cumulated Session Count = ", dataValues[3].value.value);
console.log("Cumulated Subscription Count = ", dataValues[4].value.value);
// note reading SessionSecurityDiagnotiscArray may requires authenticated session to succeed
console.log("SessionSecurityDiagnotiscArray = ");
if (dataValues[5].statusCode === StatusCodes.Good) {
const sessionSecurityDiagnosticArray = dataValues[5].value.value;
// console.log(dataValues[5].value.value.toString());
for (const sessionSecurityDiagnostic of sessionSecurityDiagnosticArray) {
console.log(" session client certificate ", sessionSecurityDiagnostic.clientCertificate.toString("base64"));
console.log();
}
} else {
console.log(dataValues[5].toString());
}
}
( async () => {
try {
const client = OPCUAClient.create({
endpoint_must_exist: false,
securityMode: MessageSecurityMode.SignAndEncrypt,
securityPolicy: SecurityPolicy.Basic256Sha256,
});
client.on("backoff",() => console.log("still trying to connec to ", endpointUrl));
await client.connect(endpointUrl);
const session = await client.createSession(userIdentityToken);
await extractServerStatistics(session);
await session.close();
await client.disconnect();
console.log("done");
} catch(err) {
console.log("Err" , err.message);
process.exit(1);
}
})();