Configure SOAP header with Strongloop Loopback SOAP connector - loopbackjs

I am attempting to connect to a SOAP web service that implements its WS security by means of a SOAP header element using the Loopback SOAP connector.
Unfortunately, documentation regarding how to configure the soap header option of the connector is sparse.
It would be greatly appreciated if you could assist by indicating how the soap header should be constructed in order for the web service to authenticate successfully.
var loopback = require('loopback');
var path = require('path');
var app = module.exports = loopback();
app.set('restApiRoot', '/api');
var myHeader = {
Security:
{
UsernameToken:{
Username: "Staging Integration Store 3",
Password: "WSAUFbw6"
}
}
};
var ds = loopback.createDataSource('soap',
{
connector: require('../index'),
/* security: {
scheme: 'wsse',
created: null,
username: "Staging Integration Store 3",
password: "WSAUFbw6",
passwordType: 'PasswordText'
}, */
soapHeaders: [{
element: myHeader, // The XML element in JSON object format
prefix: 'wsse', // The XML namespace prefix for the header
namespace: 'http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd' // The XML namespace URI for the header
}],
remotingEnabled: true,
// wsdl: 'https://staging.payu.co.za/service/PayUAPI?wsdl' // The url to WSDL
wsdl: path.join(__dirname, './PayUAPI.wsdl')
});
// Unfortunately, the methods from the connector are mixed in asynchronously
// This is a hack to wait for the methods to be injected
ds.once('connected', function () {
// Create the model
// var WeatherService = ds.createModel('WeatherService', {});
var RedirectPaymentService = ds.createModel('RedirectPaymentService', {});
// Refine the methods
RedirectPaymentService.payments = function (api,safekey,transactiontype,additionalInfo,customer,basket,cb) {
RedirectPaymentService.setTransaction({Api: api,Safekey: safekey,TransactionType: transactiontype,AdditionalInformation: additionalInfo,Customer: customer,Basket: basket}, function (err, response) {
console.log('SetTransaction: %j', response);
var result = (!err && response.return.successful.localCompare("true") == 0) ?
response.return.payuReference : response.return.resultMessage;
cb(err, result);
});
};
Response:
SetTransaction: {"statusCode":500,"body":"<soap:Envelope xmlns:soap=\"http://sch
emas.xmlsoap.org/soap/envelope/\"><soap:Body><soap:Fault><faultcode xmlns:ns1=\"
http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xs
d\">ns1:InvalidSecurityToken</faultcode><faultstring>An invalid security token w
as provided (An error happened processing a Username Token)</faultstring></soap:
Fault></soap:Body></soap:Envelope>","headers":{"date":"Tue, 17 Nov 2015 12:15:17
GMT","server":"Apache/2.4.12 (Win64) OpenSSL/1.0.2a mod_jk/1.2.40","x-distribut
ed-by":"AHC","content-length":"388","connection":"close","content-type":"text/xm
l;charset=UTF-8"},"request":{"uri":{"protocol":"https:","slashes":true,"auth":nu
ll,"host":"staging.payu.co.za","port":443,"hostname":"staging.payu.co.za","hash"
:null,"search":null,"query":null,"pathname":"/service/PayUAPI","path":"/service/
PayUAPI","href":"https://staging.payu.co.za/service/PayUAPI"},"method":"POST","h
eaders":{"User-Agent":"loopback-connector-soap/2.3.0","Accept":"text/html,applic
ation/xhtml+xml,application/xml,text/xml;q=0.9,*/*;q=0.8","Accept-Encoding":"non
e","Accept-Charset":"utf-8","Connection":"close","Host":"staging.payu.co.za","Co
ntent-Length":1128,"Content-Type":"text/xml; charset=utf-8","SOAPAction":"\"\""}
}}
events.js:141
Thanks

Setting the security in datasources.json works, but you probably don't want to save username and password there.
There a couple of options:
1) You could use add a vairable which can be pulled from config.json and embed into datasources.json
like so: "security": ${security}.
And variable security is defined in config.env.json.
2) In a model, eg if you have a Product model, then you could use something like:
Product.datasources.settings.security = {'scheme':'WS', 'username':'abc'....}

I managed to resolve this by making use of the the connector's security option which creates the relevant SOAP Security header, provided that the relevant data is supplied.
security: {
scheme: 'WS',
username: "Staging Integration Store 3",
password: "WSAUFbw6",
passwordType: 'PasswordText'
},
XML conversion
<soap:Header>
<wsse:Security xmlns:wsse="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd" xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd">
<wsu:Timestamp wsu:Id="Timestamp-2015-11-20T08:00:46Z">
<wsu:Created>2015-11-20T08:00:46Z</wsu:Created>
<wsu:Expires>2015-11-20T08:10:46Z</wsu:Expires>
</wsu:Timestamp>
<wsse:UsernameToken xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd" wsu:Id="SecurityToken-2015-11-20T08:00:46Z">
<wsse:Username>Staging Integration Store 3</wsse:Username>
<wsse:Password>WSAUFbw6</wsse:Password>
<wsu:Created>2015-11-20T08:00:46Z</wsu:Created>
</wsse:UsernameToken>
</wsse:Security>

Related

Loopback 4 OpenAPI connector: Specify Authorization header value per request

I have set up an OpenAPI connector in Loopback 4 as described here and for unauthorized requests, it is working well; I managed to create the respective datasource, service and controller. My service is similar to the GeocoderProvider example, but, let's say, with the following service interface.
export interface MyExternalService {
search_stuff(params: {query?: string}): Promise<MyExternalServiceResponse>;
}
export interface MyExternalServiceResponse {
text: string;
}
From my controller, I invoke it like this, where this.myExternalService is the injected service (kind of unrelated, but can Loopback also implicitly parse a JSON response from an external API datasource?):
#get('/search')
async searchStuff(#param.query.string('query') query: string): Promise<void> {
return JSON.parse(
(await this.myExternalService.search_stuff({query})).text,
);
}
Now, the external endpoint corresponding to myExternalService.search_stuff needs an Authorization: Bearer <token> header, where the token is sent to Loopback by the client, i.e. it's not a static API key or so. Assuming I added #param.query.string('token') token: string to the parameter list of my searchStuff controller method, how can I forward that token to the OpenAPI connector? This is the relevant part of the underlying OpenAPI YAML definition file:
paths:
/search:
get:
security:
- Authorization: []
responses:
'200':
content:
application/json:
schema:
$ref: '#/components/schemas/SearchResults'
operationId: search-stuff
components:
securitySchemes:
Authorization:
type: http
scheme: Bearer
I am now using the underlying execute function of the OpenAPI connector and manually intercept the request (the object that is passed to requestInterceptor is later passed directly to the http module by Swagger):
return JSON.parse(
(
await this.myExternalService.execute(
'search_stuff',
{query},
{
requestInterceptor: (req: {headers: {Authorization: string}}) => {
req.headers.Authorization = 'Bearer ' + token;
return req;
},
},
)
).text,
);
I also added the following method to the MyExternalService interface, inspired by the connector's actual execute function:
execute(
operationId: string,
parameters: object,
options: object,
): Promise<MyExternalServiceResponse>;
Some things I found:
Loopback internally uses the swagger-client module to do OpenAPI-based requests.
Specifically the securities option of Swagger's execute function expects a Security Definitions Object. There are some quirks with actually passing it to Swagger as well.
Internally, Swagger builds the final HTTP request that is sent out here in its source code. There, the securities key is mentioned, yet is is never actually used for the request. This means that manually specifying it in the third parameter of this.myExternalService.execute will change nothing.
I'll not accept this answer yet and I'm looking forward to finding a more Loopback-like approach.
I configured my service like this, to inject the basic authentication.
import {inject, lifeCycleObserver, LifeCycleObserver} from '#loopback/core';
import {juggler} from '#loopback/repository';
const SwaggerClient = require('swagger-client');
const config = {
name: 'jira',
connector: 'openapi',
spec: 'swagger-v2.json',
validate: false,
httpClient: (request: any) => {
request.headers["Authorization"] = "Basic " + Buffer.from("test:test").toString('base64');
return SwaggerClient.http(request);
},
};
#lifeCycleObserver('datasource')
export class JiraDataSource extends juggler.DataSource
implements LifeCycleObserver {
static dataSourceName = 'jira';
static readonly defaultConfig = config;
constructor(
#inject('datasources.config.jira', {optional: true})
dsConfig: object = config,
) {
super(dsConfig);
}
}

How do I restore the session ID for Express Session Authentication in a NativeScript app?

Buckle up, this one's a little bit complicated. I know that Express sends the browser a connect.sid cookie... and Passport uses this to deserialize the User on web requests. Not only that, but when I log in to my application from my NativeScript app (I'm running on a Pixel 2 emulator on a Windows PC, but I know it also works on iOS), the cookie seems to be correctly set and sent along with future web requests. I also understand how the application-settings API works, and that you can use this to store a user-identifying token for future boots of the application (so that I don't have to log in every time).
So here's where the disconnect occurs. Conceivably I can override the cookie in the request header if I have it stored, but nowhere can I find documentation on how to retrieve a cookie from the successful login request in nativescript.
Here's the code:
TokenSvc
import { Injectable } from "#angular/core";
import { getString, setString } from "application-settings";
export class TokenSvc {
static isLoggedIn(): boolean {
return !!getString("token");
}
static get token(): string {
return getString("token");
}
static set token(token: string) {
setString("token", token);
}
}
Login Component
(Note I am making an embarrassing attempt at getting the cookies from a new HttpHeaders instance... not sure why I thought that would work.)
#Component({
selector: "app-login",
moduleId: module.id,
templateUrl: "./login.component.html",
styleUrls: ["./login.component.scss"]
})
export class LoginComponent {
credentials: ILoginCredentials;
#ViewChild("password") password: ElementRef;
#ViewChild("handle") handle: ElementRef;
#ViewChild("confirmPassword") confirmPassword: ElementRef;
constructor(private page: Page, private router: Router, private AuthSvc: AuthSvc, private _store: Store<AppStore>) {
this.page.actionBarHidden = true;
this.credentials = {
email: "",
password: "",
cPassword: "",
handle: "",
publicName: ""
};
}
login() {
const loginCredentials: ICredentials = {
username: this.credentials.email,
password: this.credentials.password,
rememberMe: false
};
this.AuthSvc.login(loginCredentials).subscribe(
(payload) => {
console.log(payload);
if (payload.failure) {
alert(payload.failure);
} else {
// user!
let cookies = new HttpHeaders().get("Cookie");
console.log(cookies);
TokenSvc.token = cookies;
this._store.dispatch({ type: "SET_USER", payload: payload });
this.router.navigate(["/tabs"]);
}
}, () => alert("Unfortunately we were unable to create your account.")
);
}
}
The essential question here is... how do I persist a cookie-based session in NativeScript application-settings with a Node/Express back-end?
The essential answer is: you don't.
Prefer JWT, OAuth2 or any other token-based authentication method when it comes to mobile development. You can use the same authentication method for web too.
Store the user token using the secure storage and send the token along with any request made by the user.

Setting Up Apollo Server with subscriptions-transport-ws?

It seems like I have my server set up according to the Apollo docs at http://dev.apollodata.com/tools/apollo-server/setup.html. In my server/main.js file:
//SET UP APOLLO INCLUDING APOLLO PUBSUB
const executableSchema = makeExecutableSchema({
typeDefs: Schema,
resolvers: Resolvers,
connectors: Connectors,
logger: console,
});
const GRAPHQL_PORT = 8080;
const graphQLServer = express();
// `context` must be an object and can't be undefined when using connectors
graphQLServer.use('/graphql', bodyParser.json(), apolloExpress({
schema: executableSchema,
context: {}, //at least(!) an empty object
}));
graphQLServer.use('/graphiql', graphiqlExpress({
endpointURL: '/graphql',
}));
graphQLServer.listen(GRAPHQL_PORT, () => console.log(
`GraphQL Server is now running on http://localhost:${GRAPHQL_PORT}/graphql`
));
//SET UP APOLLO INCLUDING APOLLO PUBSUB
It prints out "GraphQL Server is now running on http://localhost:8080/graphql" to the terminal log indicating that the server was successfully initialized.
But at the top of my main_layout component, when I run this code:
import { Client } from 'subscriptions-transport-ws';
const wsClient = new Client('ws://localhost:8080');
...I get this console message:
WebSocket connection to 'ws://localhost:8080/' failed: Connection closed before receiving a handshake response
What am I missing?
You need to create a dedicated websocket server. It will run on a different port and the code to set it up is provided on the subscriptions-transport-ws package.
Take a look on the following code from GitHunt-API example:
https://github.com/apollostack/GitHunt-API/blob/master/api/index.js#L101-L134
Also you would see that this code is dependent on a class called SubscriptionManager. It is a class from a package called graphql-subscriptions also by the apollo team, and you can find an example of how to use it here:
https://github.com/apollostack/GitHunt-API/blob/master/api/subscriptions.js
TL;DR: You can use graphql-up to quickly get a GraphQL server with subscriptions support up and ready. Here's a more detailed tutorial on using this in combination with Apollo and the websocket client subscriptions-transport-ws.
Obtain a GraphQL Server with one click
Let's say you want to build a Twitter clone based on this GraphQL Schema in IDL syntax:
type Tweet {
id: ID!
title: String!
author: User! #relation(name: "Tweets")
}
type User {
id: ID!
name: String!
tweets: [Tweet!]! #relation(name: "Tweets")
}
Click this button to receive your own GraphQL API and then open the Playground, where you can add some tweets, query all tweets and also test out subscriptions.
Simple to use API
First, let's create a user that will be the author for all coming tweets. Run this mutation in the Playground:
mutation createUser {
createUser(name: "Tweety") {
id # copy this id for future mutations!
}
}
Here's how you query all tweets and their authors stored at your GraphQL server:
query allTweets {
allTweets {
id
title
createdAt
author {
id
name
}
}
}
Subscription support using websockets
Let's now subscribe to new tweets from "Tweety". This is the syntax:
subscription createdTweets {
Message(filter: {
mutation_in: [CREATED]
node: {
author: {
name: "Tweety"
}
}
}) {
node {
id
text
createdAt
sentBy {
id
name
}
}
}
}
Now create a new tab in the Playground and create a new Tweet:
mutation createTweet {
createTweet(
title: "#GraphQL Subscriptions are awesome!"
authorId: "<id-from-above>"
) {
id
}
}
You should see a new event popping up in your other tab where you subscribed before.
Here is a demo about using Apollo GraphQL, React & Hapi: https://github.com/evolastech/todo-react. It's less overwhelmed than GitHunt-React & GitHunt-API
Seems like you aren't actually making the websocket server. use SubscriptionServer. Keep in mind that it is absolutely NOT true that you have to have a dedicated websocket port (I thought this once too) as davidyaha says. I have both my normal queries and subs on the same port.
import { createServer } from 'http';
import { SubscriptionServer } from 'subscriptions-transport-ws';
import { execute, subscribe } from 'graphql';
import { schema } from './my-schema';
// All your graphQLServer.use() etc setup goes here, MINUS the graphQLServer.listen(),
// you'll do that with websocketServer:
// Create WebSocket listener server
const websocketServer = createServer(graphQLServer);
// Bind it to port and start listening
websocketServer.listen(3000, () => console.log(
`Server is now running on http://localhost:3000`
));
const subscriptionServer = SubscriptionServer.create(
{
schema,
execute,
subscribe,
},
{
server: websocketServer,
path: '/subscriptions',
},
);

Loopback soap connector not populating the model with the service methods

I am having trouble consuming soap web service using loopback-connector-soap. I followed the instructions from github and here.
I am able to consume the web service provided in the example but when I replace it with my wsdl it doesn't work. There are no errors after connecting but after creating the model, calling any method exposed by the web service fails with the error message
Object function ModelConstructor(data, options) { if (!(this instanceof ModelConstructor)) { return new ModelConstructor(data, options); } if (ModelClass.settings.unresolved) { throw new Error('Model ' + ModelClass.modelName + ' is not defined.'); } ModelBaseClass.apply(this, arguments); } has no method 'GetAgentBalance'
My code snippet is as shown below
var api = {};
var ds = loopback.createDataSource('soap', {
connector: 'loopback-connector-soap',
remotingEnabled: true,
wsdl: 'https://jambopay.com/agencyservices?WSDL',// The url to WSDL
wsdl_options: {
rejectUnauthorized: false,
strictSSL: false,
requestCert: true,
}
});
// Unfortunately, the methods from the connector are mixed in asynchronously
// This is a hack to wait for the methods to be injected
ds.once('connected', function () {
// Create the model
var service = ds.createModel('AgencyService', {});
api.getBalance = function (callback) {
//Displays the parsed WSDL
console.log(ds.connector);
var timestamp = moment().format('YYYY-MM-DD HH:mm:ss');
var pass = crypto.createHash('sha1').update(apiUsername + timestamp + apiKey).digest("hex");
//GetAgentBalance is a method in the web service.
service.GetAgentBalance({ username: apiUsername, timestamp: timestamp, pass: pass }, function (err, response) {
var result = (!err && response.Balance) ? response.Balance : null;
callback(err, result);
});
}
});
module.exports = api;
The console.log(ds.connector) displays the wsdl content plus a flag parsed:true meaning it was parsed successfully.
Kindly help me figure out why the model is not being populated with the methods from the web service which is preventing the consumption of the web service. Please note that this web service works fine in ASP.NET,Java as well as PHP and I guess several other languages and frameworks. I'm stuck at this point and your assistance is highly appreciated.

Saving new record in Ember-cli using POST mehod is not working

I am using ember-cli with ember-data 1.8.1.I have write api in hapijs.
Problem is when i am creating a new record and saving it.then it should be POST request,but it sending method 'OPTIONS', and saying 404 not found.
I find error in console is :-
[Report Only] Refused to connect to 'http://localhost:3000/users' because it violates the following Content Security Policy directive: "connect-src 'self' ws://localhost:35729 ws://0.0.0.0:35729"
OPTIONS http://localhost:3000/users
XMLHttpRequest cannot load http://localhost:3000/users. No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin 'http://localhost:4200' is therefore not allowed access. The response had HTTP status code 404.
SO Here is the code which i write for saving record
var onSuccess = function(post) {
console.log(post);
};
var onFail = function(post) {
console.log("fail");
console.log(post);
};
var user = store.createRecord('user', {
email : "Email",
phone : "phone",
password : 'password',
"isd_code_id" : 1,
slug: "puneet"
});
user.save().then(onSuccess, onFail);
It always goes in fail, I already add cors in hapijs, GET request is working fine.Only problem is saving record.
Please Help what should i do for make it work
CORS configuration has changed in hapi 8.0, so maybe you are using the old configuration. This is how you can enable CORS in hapi 8.0:
var server = new Hapi.Server({
connections: {
routes: {
cors: true
}
}
});