Excuse me if this question seems obvious, but I am new to Express, Node, and Swagger.
I have written a Swagger specification for an API.
Are there tools in which you can pass a request to a Swagger documented API along with the Swagger.json file to validate that the required parameters are there, that the enum values for those parameters are correct, etc.?
Something akin to:
validator.validate ("./swagger.json", req, function (req, res, err) {
if (err) {
res.status('400').send(err.message());
}
else {
// Call my controller
swaggerController(req, res);
}
});
I believe there is, but it's difficult to find or I'm not looking for the correct thing.
Yes, you can do this.
There is a generator project that does exactly this, express-no-stress. Get it here:
When using it, each API request will be validated against your Swagger API description, provided by you, in Api.yaml.
For example, to validate the body of a POST request to /examples, you can do the following:
Edit Api.yaml
...
definitions:
# define the example body i.e. require the property name
ExampleBody:
type: object
title: example
required:
- name
properties:
name:
type: string
description: The example name
paths:
# define your /examples POST endpoint
# reference the ExamplesBody definition from above
/examples:
post:
tags:
- Examples
description: Create a new example
parameters:
- name: example
in: body
description: number of items to skip
required: true
schema:
$ref: "#/definitions/ExampleBody"
responses:
200:
description: Returns all examples
...
Next, in the Node.js code create a route handler for POSTs to /examples
e.g.
app.post('/examples', function (req, res) {
/* your handler logic here */
/* no need to validate the request payload. it will be done automatically */
});
Note: include only your handler logic. Body validation will be handled automatically.
Here's an example of middleware for validating incoming responses against swagger json-schema. Just a proof of concept but it may point you in the right direction:
swagger-json-schema-middleware-example
Related
Using this example RESTlet code below, is there any way to get information about the HTTP request's calling URL? I know my deployment URL is this (shown below), but how can I get that information in my get, delete, post, or put functions?
https://123123-abc.restlets.api.netsuite.com/app/site/hosting/restlet.nl?script=123&deploy=1
I call the above URL in Postman or other client apps and I pass in header info and a JSON payload. I only have access to the JSON payload inside my RESTlet code below. I would like to get access to the URL that was called.
/**
*#NApiVersion 2.x
*#NScriptType Restlet
*/
define(['N/record', 'N/error'],
function(record, error) {
// Get a standard NetSuite record
function _get(context) {
// Is there a way to get information about the request URL at this point?
return JSON.stringify(record.load({
type: context.recordtype,
id: context.id
}));
}
return {
get: _get,
delete: _delete, // not shown
post: post, // not shown
put: put, // not shown
};
});
I am answering my own question. This is one way I found to get the URL navigation paths within Netsuite. Using the N/url module provided by Netsuite, I can do the following. If there are better ways, let me know.
require(['N/url'], function(url) {
var host = url.resolveDomain({
hostType: url.HostType.APPLICATION
});
console.log(host)
// result = 123123-abc.app.netsuite.com
})
I have a simple node.js app using express static and nodemailer with a POST form that emails the filled fields to myself. My problems are very simple, I'm just quite new to Node so I can't find a way to do them.
My first problem is that I can't find a way to put all the form data into the email's text. Below, I am trying to store my form data in a JSON and call it in the email text, but it doesn't work. It has only correctly worked for me when I only used one variable (ex. just req.body.name) for the text. How can I format my data together in the email?
My second problem is that I can find a way to handle the app after the email is sent. I want it to redirect to a success page, as shown in the marked line, but it does not work. Instead, the page goes to /reqform and displays an error message saying success.html doesn't exist (it is in the same public folder as my html file). I believe the problem lies in my sendFile usage, but I'm not sure.
app.post('/reqform', urlencodedParser, function (req, res) {
response = {
name: req.body.name,
email: req.body.email,
phone: req.body.phone
};
var mailContent = {
from: 'myemail#gmail.com',
to: 'myemail#gmail.com',
subject: 'Service Request From req.body.name',
text: response //*** Problem #1
};
transporter.sendMail(mailClient, function (error, info) {
if (error) {
console.log(error);
} else {
res.sendFile("success.html"); //** Problem #2
}
});
})
Any help is greatly appreciated. Thanks!
The default string value for a JavaScript Object is just "[object Object]". If you want anything else, you'll have to be specific with how you want it represented.
For example, JSON is a text/string format that represents values like Objects. It's separate from the Object itself, so you'll need to convert to use it:
var mailContent = {
// ...
text: JSON.stringify(response)
}
Or, provide your own formatting:
var mailContent = {
// ...
text: `Name: ${response.name}\nEmail: ${response.email}\nPhone: ${response.phone}`
}
It might be better to use res.redirect() in this case, allowing the client/browser to request success.html via your application's static() middleware:
res.redirect('/success.html');
res.sendFile() doesn't collaborate with any static() middleware to know about your public folder. It expects you to provide a full path either directly or with its root option:
res.sendFile(path.join(__dirname, 'public/success.html'));
I have a generic SendMail route which I want to create multiple remote methods to handle multiple request templates. Any ideas on how to return a Email_Type from the remote method back to the base route. I know I could add a default with a code in it, but would like a more elegant solution.
Mail.genericSendMail = function genericEmail(response, callback) {
console.log(response);
let templateId=0;
//PROBLEM: HOW TO KNOW WHICH REMOTE WAS USED
switch (response.emailType) {
case "Template-1":
templateId= 1234;
break;
case "Template-2":
tempalteId = 456;
break;
default:
templateId = 789l
} //switch
console.log(templateId);
};
//Want multiple routes like this to support various templates
Mail.remoteMethod("genericEmail", {
http: {
path: "/emailTemplate1",
verb: "POST"
},
accepts [
{arg: "request", type:"object",http: {source:"body"},
default: {firstName:"", lastName:"",emailAddress:""}
}],
returns: RESTResponseStatic.loopbackAdapterCommonRestResponseDefinition()
});
//Want multiple routes like this to support various templates
Mail.remoteMethod("genericEmail", {
http: {
path: "/emailTemplate2",
verb: "POST"
},
accepts [
{arg: "request", type:"object",http: {source:"body"},
default: {emailAddress:"", promoCode:""}
}],
returns: RESTResponseStatic.loopbackAdapterCommonRestResponseDefinition()
});
There are a couple of different ways to do this. Since it happens to be a POST request, I usually go with attaching data to the body using a before remote hook.
Let's say you have a model method for logging in users.
Say we have a multi realm platform, so we need to know what platform we are logging in. If you don't use realms or don't know what they are, don't worry. This just shows you how to populate the data to the model method.
User.login = function(data, cb) {
if (data.realm == 'platform1) {
return logUserIntoPlatform1(data, cb);
}
return logUserIntoDefaultPlatform(data, cb);
}
Now let's say you don't want the client/frontend to send the realm and you don't want to do the lookup for realm in the model. We can add a beforeRemote hook like so:
User.beforeRemote('login', function (context, user, next) {
context.args.data.realm = lookUpRealmSync(context); // 1
next();
});
This will be called before the login method. Note the next() call: this is how you could do error detection before actually hitting the model method. Something like next({ status: 422, message: 'Parameter missing: password }); would return an error and not execute the User.login method.
You may have to look carefully at your context object (i.e. the line marked with 1 may not work exactly as I've shown for you).
If you want to read more about this stuff, I LoopBack's docs are pretty good. It seems they've been updated since I've last used them so I can't link you to the more useful pages. I found the remote method documentation here though.
Edit: I took a closer look at your question. You should be able to retrieve the path from the context object and pass data accordingly. I'm not going to try to code that since I don't know where it would actually be within the object.
guys
on my exisiting api i already have user authhication using Bearer security. Using http header api_key and later tokens.
My problem seems to be i have diffefrent end point that are only need to be consumed based on roles.
For example to post a new user :
POST user should only be authenticated to user with admin role.
I have looked at the swagger spec here but nothing i could find on thier docuemation and google as well.
Please could give me some brain stroming idea ? below is my access verifaction code in nodejs and express.
swaggerTools.initializeMiddleware(swaggerDoc, function (middleware) {
// Interpret Swagger resources and attach metadata to request - must be first in swagger-tools middleware chain
app.use(middleware.swaggerMetadata());
app.use(middleware.swaggerSecurity({
Bearer: function(req,def,apiKey,next){
apiKey= apiKey.slice(7)
debug("token check",def,apiKey)
var ok=checkToken(apiKey)
if(ok) {
req.user=ok
debug('Token is ok')
return next()
}
debug("Invalid token",apiKey)
var err=Error("Invalid token")
err.statusCode=403
next(err)
}
}));
As of this writing, the solution is still homebrewing. Swagger does not, save through oAuth scopes or using a "hacky" api-key security definition (https://stackoverflow.com/a/40222161/3736937), have a built in RBAC mechanism.
Fortunately, we can create some pretty basic middleware to handle the problem because swagger does allow us to add x-swagger-* members to the swagger definition.
So here's what I did:
Add x-swagger-roles to each endpoint that requires RBAC (Role-based Access Control)
paths:
"/":
x-swagger-router-controller: getStatus
get:
operationId: getStatus
x-swagger-roles:
- admin
tags:
- "users"
summary: "Returns message: 'working'"
description: "default endpoint for testing"
responses:
$ref: "#/definitions/AnyResponse"
Place middleware before swagger-node-runner is registered with the application. In our case we're using expressjs, so the connect middleware is used.
var findOne = function (haystack, arr) {
return arr.some(function (v) {
return haystack.indexOf(v) >= 0;
});
};
app.use(function(req, res, next) {
var operation = runner.getOperation(req);
if(operation && operation.definition) {
var definition = operation.definition;
var requiredRoles = definition['x-swagger-roles'];
// if the endpoint has no required roles then go to the next route
if(!requiredRoles) return next();
// get the users roles
var userRoles = req.session.roles; // this may differ for you
// if any roles match then go to the next route
if(findOne(userRoles, requiredRoles)) return next();
// if no roles match then assert that this endpoint is forbidden
else return res.sendStatus(403);
}
next();
})
// it's important to register the middleware after the role check
runner.expressMiddleware().register(app);
Notes:
This code has not been tested in production, and should be reviewed by a security professional.
x-swagger-roles will not appear in your swagger-ui without altering it, which is beyond the scope of this answer.
Has anyone been able to get the google-api-nodejs-client to successfully insert a moment?
Whatever I try, I get a generic 400 "Invalid value" error but am unable to narrow down the invalid value because the API Explorer doesn't work either.
Would it be because of the missing data-requestvisibleactions parameter? I'm using passport.js's require('passport-google-oauth').OAuth2Strategy for handling oauth access, and that part is working fine, but I have no idea how to incorporate requestvisibleactions into the oauth request flow since this is definitely not originating from a clientside form.
Here's a snippet of what I'm trying to do (using the latest version of googleapis, v1.0.2):
var google = require('googleapis')
var auth = new google.auth.OAuth2()
auth.setCredentials({
'access_token': user.token
})
google.plus('v1').moments.insert({
collection: 'vault',
userId: 'me',
debug: true,
resource: {
type: "http://schemas.google.com/AddActivity",
target: {
type: "http://schema.org/CreativeWork",
url: "...omitted...",
image: "...omitted...",
description: "test",
name: "test"
}
},
auth: auth
}, function (err, response) {
if (err) {
console.error(err)
res.send(err.code, err)
} else {
console.log(response)
res.send(200)
}
})
ref 1 (out-of-date w.r.t. an older version of googleapis)
ref 2 (client-side, where the use of data-requestvisibleactions is more obvious)
As you speculated, you need the request_visible_actions parameter as part of the URL calling the oauth endpoint.
It looks like the current version of passport-google-oauth doesn't support this parameter. Judging by several of the open issues and pull requests, it isn't clear that the author will respond to requests to add it either. You have two possible options:
Switch to using the OAuth support that is included in google-api-nodejs-client
Patch the passport-google-oauth code. (And possibly submit a pull request in the hopes it will be useful to someone else.)
I don't use passport.js or the passport module in question, so I can't test this, but based on the github repository, I think you can insert the following in lib/passport-google-oauth/oauth2.js after line 136 and before the return statement:
if (options.requestVisibleActions) {
// Space separated list of allowed app actions
// as documented at:
// https://developers.google.com/+/web/app-activities/#writing_an_app_activity_using_the_google_apis_client_libraries
// https://developers.google.com/+/api/moment-types/
params['request_visible_actions'] = options.requestVisibleActions;
}