How To Create REST Endpoints and Remote Methods in Loopback at Runtime Without Restarting Node? - api

We have successfully created REST remote methods in code in Loopback using a boot script which has has allowed us to completely eliminate the JSON schema files. However, our ultimate goal is be able to create a new REST endpoint and remote methods at runtime, on the fly after startup at any time. In the example below the new endpoint ('newEndPoint') should be created by calling /api/example/createNewMethod, but 'newMethod' is not being exposed in the REST API. Here's the code:
// **************
// Initialize 'createNewMethod' in a boot script. (THIS IS WORKING)
// **************
model.createNewMethod = function createNewMethod(data, callback) {
// **************
// Initialize 'newMethod' # runtime by calling createNewMethod
// **************
console.log("Initializing 'newMethod'...");
// This is called by calling /api/example/newMethod
model.newMethod = function newMethod(data, callback) {
// THIS IS NOT WORKING
console.log("'newMethod' works!")
// Return from newMethod()
callback({return: true});
}
model.remoteMethod(
'newMethod',
{
http: { verb: 'get' },
returns: [
{ arg: 'eventinfo', type: 'data' },
]
}
);
// Return from createNewMethod()
callback({return: true});
}
model.remoteMethod(
'createNewMethod',
{
http: { verb: 'get' },
returns: [
{ arg: 'eventinfo', type: 'data' }
]
}
);

This issue has been resolved. We can allow users to add new tables or add new columns to existing tables without restarting node and the exposing the changes via a Loopback REST endpoint by simply recreating the end-point. See

Related

How to handle JWT authentication with RxDB?

I have a local RxDB database and I want to connect it with CouchDB. Everything seems to works fine except for authentication. I have no idea how to add it differently then inserting credentials in database url:
database.tasks.sync({
remote: `http://${username}:${pass}#127.0.0.1:5984/tododb`,
});
I would like to use JWT auth but can't find how to add a token to sync request. I found only some solutions for PouchDB (pouchdb-authentication plugin) but can't get it working with RxDB.
RxDB is tightly coupled with PouchDB and uses its sync implementation under the hood. To my understanding, the only way to add custom headers to a remote PouchDB instance (which is what is created for you when you pass a url as the remote argument in sync), is to intercept the HTTP request:
var db = new PouchDB('http://example.com/dbname', {
fetch: function (url, opts) {
opts.headers.set('X-Some-Special-Header', 'foo');
return PouchDB.fetch(url, opts);
}
});
PouchDB replication documentation (sync) also states that:
The remoteDB can either be a string or a PouchDB object. If you have a fetch override on a remote database, you will want to use PouchDB objects instead of strings, so that the options are used.
Luckily, RxDB's Rx.Collection.sync does not only accept an server url as the remote argument, but also another RxCollection or a PouchDB-instance.
RxDB even reexport the internally used PouchDB module, so you do not have to install PouchDB as a direct dependency.
import { ..., PouchDB } from 'rxdb';
// ...
const remotePouch = new PouchDB('http://27.0.0.1:5984/tododb', {
fetch: function (url, opts) {
opts.headers.set('Authorization', `Bearer ${getYourJWTToken()}`)
return PouchDB.fetch(url, opts);
}
})
database.tasks.sync({
remote: remotePouch,
});

Response to preflight request doesn't pass access control check: It does not have HTTP ok status. GET working POST PUT DELETE not working

Greetings
I have one web application with following architecture:
Web api: ASP.net core 2.1 (Windows Authentication)
UI: angular 8
UI is able to get data but unable to send data.
I mean GET method is working fine but POST, PUT, DELETE options are not working .
And all the methods are working using POSTMAN.
ERROR is:
Access to XMLHttpRequest at 'http://xx.xxx.xxx.xx:xxyy/xxx/xxxxxx/Method' from origin 'http://localhost:xxxx' has been blocked by CORS policy:
Response to preflight request doesn't pass access control check: It does not have HTTP ok status.
Any help will be appreciated .
Thanks in advance :)
That's because your API is on different domain than your SPA angular application.
Please at this at the start of your Configure method in Startup.cs
if (env.IsDevelopment())
{
app.UseCors(opts =>
{
opts.WithOrigins(new string[]
{
"http://localhost:3000",
"http://localhost:3001"
// whatever domain/port u are using
});
opts.AllowAnyHeader();
opts.AllowAnyMethod();
opts.AllowCredentials();
});
}
Please note that this will handle only CORS for local development since you'll probably have same domain in production - if not, you'll need to reconfigure this for production also.
CORS blocking is browser specific and that's why it's working in PostMan but not in browser.
This is what i use and it should work i hope for your case.
My startup.cs ConfigureServices() decorated with:
services.AddCors(feature =>
feature.AddPolicy(
"CorsPolicy",
apiPolicy => apiPolicy
//.AllowAnyOrigin()
//.WithOrigins("http://localhost:4200")
.AllowAnyHeader()
.AllowAnyMethod()
.SetIsOriginAllowed(host => true)
.AllowCredentials()
));
And, Configure() method with:
app.UseCors("CorsPolicy");
Notice the SetIsOriginAllowed() and allowCreds() along with other policy settings, this works for me with POST calls to my api from my angular, which are running on two different port#s.
UPDATE:
Following the questions on the comments, adding additional information on how do we check the logged in user (windows auth) btwn api and the angular (frontend).
You can check the incoming User on a specific route that would only expect the authenticated user using the decoration [Authorize]. In my case, i would have only one method that would expect the windows user in the api:
[HttpGet("UserInfo")]
[Authorize]
public IActionResult GetUserInfo()
{
string defaultCxtUser = HttpContext?.User?.Identity?.Name;
if (defaultCxtUser != null && !string.IsNullOrEmpty(defaultCxtUser))
{
_logger.LogDebug($"START - Get Context user details for {defaultCxtUser}");
ADHelper.logger = _logger;
var userFullName = ADHelper.GetUserIdentityInfo(defaultCxtUser);
_logger.LogInformation($"Context user {defaultCxtUser} with name: {userFullName}");
var userInfo = new { Name = userFullName };
//_logger.LogDebug($"END - GetUserInfo({defaultCxtUser} for {userFullName}");
return Ok(userInfo);
}
else
return Ok(new { Name = defaultCxtUser });
}
then i would call this from my angular with the service call as,
// Get the Logged in user info
GetCurrentUserInfo(): Observable<string> {
const httpOptions = {
headers: new HttpHeaders({
'Content-Type': 'application/json'
}),
withCredentials: true
};
// return this.http.get<string>(`${ApiPath}UserInfo`, httpOptions)
// .pipe(map(v => v as string));
return this.http.get<UserInfo>(`${ApiPath}UserInfo`, httpOptions)
.pipe(map(data => {
// console.log(data, data.Name);
return data.Name;
}))
;
}
Please see the headers with 'withCredentials: true' line that would trigger to pass the current user info, and it would be read and understood only if it has the authorize attr to read the User.Identity object in c# side. The reason we do this on a specific method is that, there should be some other parental method in the api like ApiStatus() or anything that could be, should be called first. This would ensure to also invoke the preflight check with OPTIONS that would require anonymous auth. Like in my case, getting whether the api is available and running, and some other app environment info before i get the userInfo() from my angular app.

Lumen 5.5 Session store not set on request

I use vue-authenticate (https://github.com/dgrubelic/vue-authenticate) to create two kinds of connection on our web service, the first method is the connection to his account, the second method is the addition of account when connected.
I use Lumen (by Laravel) for backend and connection management in PHP.
Only sessions are not available under Lumen, how do I store temporary credentials?
use League\OAuth1\Client\Server\Twitter;
public function login(Request $request)
{
try {
$this->server = new Twitter([
'identifier' => $this->key,
'secret' => $this->secret,
'callback_uri' => $request->get('redirectUri'), // Variable getted from POST
]);
if(empty($request->get('oauth_token'))) {
$temporaryCredentials = $this->server->getTemporaryCredentials();
$request->session()->put('temporary_credentials', serialize($temporaryCredentials)); // Session doesn't works
return response()->json([
'oauth_token' => $temporaryCredentials->getIdentifier(),
'oauth_token_secret' => $temporaryCredentials->getSecret(),
], 200);
} else {
// I must have oauth_token here with session
}
} catch (\Exception $e) {
return response()->json($e->getMessage(), 500);
}
}
I think you just misunderstood the concept of Web Service (API). API is not a stateful application, rather it's a stateless, means no session available for each request. So, in major API framework, session is not supported (officially). To handle your problem, you can store your temporary credentials in a database or maybe in a cache (with TTL, eg: 60 minutes), like this:
$requestIdentifier = $request->getClientIdentifier(); // YOU SHOULD IMPLEMENT THIS METHOD
Cache::put($requestIdentifier, $temporaryCredentials, 60);
To retrieve your cache just use:
$temporaryCredentials = Cache::get($requestIdentifier);
Here I give you some idea, when you implement getClientIdentifier, you can force the client to send a unique key inside your header, like:
axios.post('http://somewhere', {
headers: {
'x-request-identifier': UNIQUE_ID
}
})
In your API:
$requestIdentifier = $request->header('x-request-identifier');

Managing user authenticated state on manual URL navigation

I'm using Angular2 with ASP.NET Core MVC and managing manual URL navigation works fine, the server is loading my Home view with Angular2 successfully.
On user authentication, I'm setting up a session variable like this :
HttpHelper.HttpContext.Session.SetString("isLoggedIn", true.ToString() );
What I want is that after the user is in the application, if somewhat he wants to load a specific route by manually navigating to it, I want my service to call my ASP Controller to check if the user already got authenticated so that my guard allows the routing. Instead, the guard is by default set to false and I obviously get redirected to my login page.
Here is my ASP Controller method I want to call to update my IsLoggedIn value in my auth.service :
[HttpGet]
public IActionResult IsConnectedState()
{
if (!String.IsNullOrWhiteSpace(HttpHelper.HttpContext.Session.GetString("isLoggedIn")))
return Ok(true);
else
return Ok(false);
}
so that my AuthenticationGuard can call the AuthenticationService to update the boolean managing the authenticated state :
alert(this.authService.isLoggedIn);
if (!this.authService.isLoggedIn) {
this.authService.setupLoggedInState().subscribe(() => { });
}
with the following code updating the boolean in my auth.service :
setupLoggedInState() {
alert("Setting Up");
// Setting up the Http call
let lControllerAction: string = "/IsConnectedState";
let lControllerFullURL: string = this.controllerURL + lControllerAction;
let headers = new Headers({ 'Content-Type': 'application/json' });
let options = new RequestOptions({ headers: headers });
// Call my ASP Controller IsConnectedState() method
return this.http.get(lControllerFullURL, options)
.map((res: any) => {
// Réponse reçue du WebService
let data = res.json();
alert(data);
if (data == true) {
this.isLoggedIn = true;
}
}
).catch(this.handleError);
}
When I do the authentication, and then manually navigate to an URL, my guard tells me that the boolean is indeed set to "false", and I also get the "Setting Up" when my service tries to call http.get.
It seems to bug in the middle of the method, because I never get to the breakpoint I set in my ASP Controller. I get the following error which I don't understand :
"platform-browser.umd.js:937 EXCEPTION: Error: Uncaught (in promise): TypeError: Cannot read property 'toString' of null"
Could it be because I don't call the service at the right moment in my auth.guard ? All my others call such as authentication work with http.post without any issue, so I don't really get where the problem is coming from...
Any help would be greatly appreciated.
There's a known defect in Angular 2 RC5 that causes this error when you do a get and provide the 'Content-Type': 'application/json' header.
For a temporary workaround, add an empty string to the body property on your request options:
let options = new RequestOptions({ headers: headers, body: "" });

Enable/Disable logging in Worklight application

We are developing a WL application using WL enterprise 6.2.0.1. We have four environments (Dev/QA/UAT and PROD).
Our application is logging the user credentials on the Server (file:SystemOut.log) which is ok for Dev environment. However, when we need to move the build to QA and UAT we need to disable the logging since it is a security point of view and we can't proceed to PROD.
What we did is we added the following code to the initOptions.js:
var bEnableConsoleLog = false; // Enable Disable the logging
var wlInitOptions = {
...
...
...
logger : {
enabled : bEnableConsoleLog},};
var disableLogging = function() {
WL.Logger.info("##### LOG ENABLED ?? => " + bEnableConsoleLog);
if (bEnableConsoleLog == false)
{
WL.Logger.config({
enabled : false,
level : 'info'
});
console.log = function() {
}.bind(console.log);
console.error = function() {
}.bind(console.error);
}
};
if (window.addEventListener) {
window.addEventListener('load', function() {
WL.Client.init(wlInitOptions);
disableLogging();
}, false);
} else if (window.attachEvent) {
window.attachEvent('onload', function() {
WL.Client.init(wlInitOptions);
disableLogging();
});
}
disableLogging();
WL.Logger
.info("######################## WL.Logger.info ENABLED ############################");
console
.log("######################## console.log ENABLED ############################");
console
.error("######################## console.error ENABLED ############################");
By Setting the value var bEnableConsoleLog = (true/false); we thought we can enable or disable the logging, but it seems still logging the credentials.
Is there a way to resolve this?
I don't think there is an 'enabled' option on WL.Logger.config based on the WL.Logger API reference. There is a 'capture' option which you can set to false, which will disable saving the client logs and sending them to the server.
If your client is logging the user credentials in a log statement then that information should only be sent based on 'capture' being true (the default) and the log statement you use being at the 'level' value or above. Given your WL.Logger.config() above, that means WL.Logger.info() would be sent to the server and WL.Logger.debug() would not. For more information see Configuring the Worklight Logger.
Note that all of this pertains only to WL.Logger calls made by the client. If you are logging the user credentials in your server-side code (for example using Java logger) then what is logged will be based on the log levels configured on the server; the client log configuration will have no effect.