Custom authorizations lambda functions node js [closed] - node.js

Closed. This question needs details or clarity. It is not currently accepting answers.
Want to improve this question? Add details and clarify the problem by editing this post.
Closed 14 days ago.
Improve this question
How to use custom authorizer for lambda functions through https API in AWS using node js
I am a novice to the field so I do not have prior experience regarding this. Any help is highly appreciated.

Create following 3 files under respective file names (serverless.yml, middleware-auth.js, auth.js) and write the code inside those files. This allows custom authorization for AWS lambda functions.
serverless.yml
provider:
name: aws
runtime: nodejs14.x
lambdaHashingVersion: 20201221
# you can overwrite defaults here
stage: dev
region: us-east-1
apiGateway:
binaryMediaTypes:
- "*/*"
httpApi:
cors: true
authorizers:
customAuthorizer:
type: request
functionName: authoriserAnyToken
functions:
authoriserAnyToken:
handler: middleware-auth.handler
signin:
handler: auth.signin
timeout: 15
events:
- httpApi:
path: /auth/{role}/signin
method: post
auth_remember_me:
handler: auth.auth_remember_me
timeout: 15
events:
- httpApi:
path: /auth/remember-me
method: post
authorizer:
name: customAuthorizer
type: request
auth_jwt_token_check:
handler: auth.auth_jwt_token_check
timeout: 15
events:
- httpApi:
path: /auth/jwt/check
method: post
authorizer:
name: customAuthorizer
type: request
middleware-auth.js
const JWT_SECRET = "goK!pusp6ThEdURUtRenOwUhAsSURESHBazl!uJLPlS8EbreWLdrupIwabRAsiBa";
const jwt = require("jsonwebtoken");
exports.handler = async event => {
console.log('event', event);
const token = event.headers.authorization? event.headers.authorization.replace('Bearer ', ''): null;
if (!token) {
console.log('could not find a token on the event');
return generatePolicy({ allow: false });
}
try {
const decoded = jwt.verify(token, JWT_SECRET);
console.log('token_details ', decoded);
if(decoded.user == 'admin'){
return generatePolicy({ allow: true });
}
else if (decoded.user == 'seller')
{
return generatePolicySeller({ allow: true });
}
} catch (error) {
console.log('error ', error);
return generatePolicy({ allow: false });
}
};
const generatePolicy = ({ allow }) => {
return {
principalId: 'token',
policyDocument: {
Version: '2012-10-17',
Statement: {
Action: 'execute-api:Invoke',
Effect: allow ? 'Allow' : 'Deny',
Resource: '*',
// Resource: 'arn:aws:execute-api:us-east-1:*:*/*/*',
// Resource: 'arn:aws:execute-api:us-east-1:*:*/*/auth/*',
},
},
};
};
const generatePolicySeller = ({ allow }) => {
return {
principalId: 'token',
policyDocument: {
Version: '2012-10-17',
Statement: {
Action: 'execute-api:Invoke',
Effect: allow ? 'Allow' : 'Deny',
// Resource: '*',
// Resource: 'arn:aws:execute-api:us-east-1:*:*/*/*',
Resource: ['arn:aws:execute-api:us-east-1:*:*/*/sellers/*','arn:aws:execute-api:us-east-1:*:*/*/auth/*'],
},
},
};
};
auth.js
'use strict';
const jsonwebtoken = require("jsonwebtoken");
const bcrypt = require('bcrypt');
const AWS = require("aws-sdk");
const s3 = new AWS.S3()
const MAX_SIZE = 2097152 // 2MB
const bucket = 'S3_BUCKET_NAME' // Name of your bucket.
const Busboy = require("busboy")
const JWT_SECRET = "goK!pusp6ThEdURUtRenOwUhAsSURESHBazl!uJLPlS8EbreWLdrupIwabRAsiBa";
// Tables
const USERS_TABLE = 'users'
const { Validator } = require('node-input-validator');
s3.config.update({
region: "us-east-1",
accessKeyId: 'S3_ACCESS_KEY_ID', // ACCESS_KEY_ID of your S3 bucket.
secretAccessKey: 'S3_SECRET_ACCESS_KEY' // SECRET_ACCESS_KEY of your S3 bucket.
});
const mysql = require('serverless-mysql')({ // DB configuration
config: {
host: process.env.DB_HOST,
port: process.env.DB_PORT,
database: process.env.DB_NAME,
user: process.env.DB_USER,
password: process.env.DB_PASSWORD,
debug: false
}
});
const sendJSON = (code, message, data) => {
let resData = {
"status": code < 400 ? 'Success' : 'Error',
"message": message,
}
data ? (resData["data"] = data) : null;
return {
statusCode: code,
headers: {
"Content-Type": "application/json"
},
body: JSON.stringify({
...resData
})
};
}
const FORM = {
parse(body, headers) {
return new Promise((resolve, reject) => {
const data = {};
const buffer = Buffer.from(body, 'base64');
const bb = Busboy({
headers: Object.keys(headers).reduce((newHeaders, key) => {
// busboy expects lower-case headers.
newHeaders[key.toLowerCase()] = headers[key];
return newHeaders;
}, {})
});
bb.on('field', (name, val, info) => {
data[name] = val;
});
bb.on('error', (err) => {
reject(err);
});
bb.on('close', () => {
resolve(data);
});
bb.end(buffer);
});
}
};
module.exports.signin = async (event, context) => {
try {
const data = await FORM.parse(event['body'], event['headers']);
const v = new Validator(data, {
email: 'required|email',
password: 'required'
});
const matched = await v.check();
if (!matched)
return {
statusCode: 400,
headers: {
"Content-Type": "application/json"
},
body: JSON.stringify(v.errors),
};
let results = "";
let email = data.email;
let password = data.password;
const role = event.pathParameters.role;
results = await mysql.query(`SELECT * FROM ${USERS_TABLE} WHERE email = ? LIMIT 1`, [email]);
await mysql.end();
if (!results[0]) {
return {
statusCode: 400,
headers: {
"Content-Type": "application/json"
},
body: JSON.stringify({
email: {
"message": "User not found"
}
}),
};
} else {
let user_hash = results[0].password;
user_hash = user_hash.replace(/^\$2y(.+)$/i, '$2a$1');
const match = await bcrypt.compare(password, user_hash);
if (!match) {
return {
statusCode: 400,
body: JSON.stringify({
password: {
"message": "Missing or Invalid password"
}
}),
};
}
const user_info = {
'id' : results[0].id,
'role': results[0].role,
'fname' : results[0].fname,
'lname' : results[0].lname
};
const token = jsonwebtoken.sign({ id: results[0].id, user: results[0].role, fname: results[0].fname, lname: results[0].lname}, JWT_SECRET, { expiresIn: "2h" });
return sendJSON(200, 'Successfully logged.',{
'user' : user_info,
'token': token
});
}
} catch (e) {
return sendJSON(400, e.message);
}
};
module.exports.auth_jwt_token_check = async (event) => {
try {
return sendJSON(200, 'verifyed user');
} catch (e) {
return sendJSON(400, e.message);
}
};
module.exports.auth_remember_me = async (event) => {
try {
const token = event.headers.authorization? event.headers.authorization.replace('Bearer ', ''): null;
const decoded = jsonwebtoken.verify(token, JWT_SECRET);
const new_token = jsonwebtoken.sign({ id: decoded.id, user: decoded.user, fname: decoded.fname, lname: decoded.lname}, JWT_SECRET, { expiresIn: "2h" });
return sendJSON(200, 'Get Renew Token.', {
'token': new_token
});
} catch (e) {
return sendJSON(400, e.message);
}
};

Related

Jest Mock Implementation is not working, instead original function is being called

I am trying to test an API by mocking the database function, but the imported function is being called instead of the mocked one.
Here are the code snippets
const supertest = require('supertest');
const axios = require('axios');
const querystring = require('querystring');
const { app } = require('../app');
const DEF = require('../Definition');
const tripDb = require('../database/trip');
const request = supertest.agent(app); // Agent can store cookies after login
const { logger } = require('../Log');
describe('trips route test', () => {
let token = '';
let companyId = '';
beforeAll(async (done) => {
// do something before anything else runs
logger('Jest starting!');
const body = {
username: process.env.EMAIL,
password: process.env.PASSWORD,
grant_type: 'password',
client_id: process.env.NODE_RESOURCE,
client_secret: process.env.NODE_SECRET,
};
const config = {
method: 'post',
url: `${process.env.AUTH_SERV_URL}/auth/realms/${process.env.REALM}/protocol/openid-connect/token`,
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
},
data: querystring.stringify(body),
};
const res = await axios(config);
token = res.data.access_token;
done();
});
const shutdown = async () => {
await new Promise((resolve) => {
DEF.COM.RCLIENT.quit(() => {
logger('redis quit');
resolve();
});
});
// redis.quit() creates a thread to close the connection.
// We wait until all threads have been run once to ensure the connection closes.
await new Promise(resolve => setImmediate(resolve));
};
afterAll(() => shutdown());
test('post correct data', async (done) => {
const createTripMock = jest.spyOn(tripDb, 'addTrip').mockImplementation(() => Promise.resolve({
pk: `${companyId}_trip`,
uid: '1667561135293773',
lsi1: 'Kotha Yatra',
lsi2: companyId,
name: 'Kotha Yatra',
companyId,
origin: {
address: 'Goa, India',
location: {
lat: 15.2993265,
lng: 74.12399599999999,
},
},
destination: {
address: 'Norway',
location: {
lat: 60.47202399999999,
lng: 8.468945999999999,
},
},
path: [
{
lat: 15.2993265,
lng: 74.12399599999999,
},
{
lat: 60.47202399999999,
lng: 8.468945999999999,
},
],
isDeleted: false,
currentVersion: 1,
geofences: [],
}));
const response = await request.post('/api/trips').set('Authorization', `Bearer ${token}`).send(tripPayload);
expect(createTripMock).toHaveBeenCalled();
expect(response.status).toEqual(200);
expect(response.body.status).toBe('success');
done();
});
});
The database function:
const addTrip = (trip) => {
// const uid = trip.uid ? trip.uid : (Date.now() * 1000) + Math.round(Math.random() * 1000);
const uid = (Date.now() * 1000) + Math.round(Math.random() * 1000);
const item = {
pk: `${trip.companyId}_trip`,
uid: `v${trip.version ? trip.version : 0}#${uid}`,
lsi1: trip.name,
lsi2: trip.companyId,
name: trip.name,
companyId: trip.companyId,
origin: trip.origin,
destination: trip.destination,
path: trip.path,
isDeleted: false,
};
if (!trip.version || trip.version === 0) {
item.currentVersion = 1;
} else {
item.version = trip.version;
}
if (trip.geofences) item.geofences = trip.geofences;
const params = {
TableName: TN,
Item: item,
ConditionExpression: 'attribute_not_exists(uid)',
};
// console.log('params ', params);
return new Promise((resolve, reject) => {
ddb.put(params, (err, result) => {
// console.log('err ', err);
if (err) {
if (err.code === 'ConditionalCheckFailedException') return reject(new Error('Trip id or name already exists'));
return reject(err);
}
if (!trip.version || trip.version === 0) {
const newItem = { ...item };
delete newItem.currentVersion;
newItem.version = 1;
newItem.uid = `v1#${item.uid.split('#')[1]}`;
const newParams = {
TableName: TN,
Item: newItem,
ConditionExpression: 'attribute_not_exists(uid)',
};
// console.log('new params ', newParams);
ddb.put(newParams, (v1Err, v1Result) => {
// console.log('v1 err ', v1Err);
if (v1Err) return reject(v1Err);
item.uid = item.uid.split('#')[1];
return resolve(item);
});
} else {
item.uid = item.uid.split('#')[1];
return resolve(item);
}
});
});
};
module.exports = {
addTrip,
};
I was mocking the above database function when I was making a request to add API, instead, the original function is being called and I was getting the result that I had written in the mock Implementation.
What should I do to just mock the result ,when the function is called and no implementation of the original function should happen.
Even this did not give an error
expect(createTripMock).toHaveBeenCalled();
Still the database function call is happening
I tried using mockReturnValue, mockReturnValueOnce, mockImplemenationOnce but not luck.
Can anyone help me with this?

Keep getting pending request when trying to call endpoint, what's wrong?

I have made an endpoint to get token for paytm payment integration. In backend when i'm calling api i'm getting reqdata json but in frontend when i'm logging await transactionAPI, i'm getting only pending promise. i've tried using then in backend in PaytmChecksum.generateSignature method & in frontend in fetch but nothing is working. Keep getting the same result. Pls help.
Frontend code:
const makePayment = async () => {
const data = {
oid: String(new Date().valueOf()),
amount: '1.00',
email: 'abc#gmail.com',
};
let transactionAPI = fetch('http://localhost:3000/api/pretransact', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(data),
});
console.log(await transactionAPI);
}
Backend code:
const https = require('https');
const PaytmChecksum = require('./PaytmChecksum');
export default async function handler(req, res) {
if (req.method == 'POST') {
const { oid, amount, email } = req.body;
let mid = process.env.PAYTM_MID;
let paytmParams = {};
paytmParams.body = {
requestType: 'Payment',
mid,
websiteName: process.env.WEBSITE,
orderId: oid,
callbackUrl: 'http://localhost:3000/api/callback',
txnAmount: {
value: amount,
currency: 'INR',
},
userInfo: {
custId: email,
},
};
const checksum = await PaytmChecksum.generateSignature(
JSON.stringify(paytmParams.body),
process.env.MERCHANT_KEY
);
paytmParams.head = {
signature: checksum,
};
var post_data = JSON.stringify(paytmParams);
const requestAsync = () => {
return new Promise((resolve, reject) => {
var options = {
hostname: 'securegw-stage.paytm.in',
// hostname: 'securegw.paytm.in',
port: 443,
path: `/theia/api/v1/initiateTransaction?mid=${mid}&orderId=${oid}`,
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Content-Length': post_data.length,
},
};
var response = '';
var post_req = https.request(options, function (post_res) {
post_res.on('data', function (chunk) {
response += chunk;
});
post_res.on('end', function () {
resolve(response);
});
});
post_req.write(post_data);
post_req.end();
});
};
const reqdata = await requestAsync();
res.send(200).json(reqdata);
}
}

Set Mock Response for JWT Module NodeJS

I am writing Test case using JEST in NodeJs inside AzureFunction.
Im trying to mock JWT module outcome inside my index.test.js , however its not working and getting timedout. I wonder is it the return datatype mismatch? How to set response similar to cb of jwt verify method?
Here is my sample code. Please suggest!
Index.js
const JWT = require('./jwtDecoder')
module.exports = function(context, req) {
try {
JWT(req.body, process.env.jwtsecret, function(err, decoded) {
if (err) {
context.log("Invalid JWT::" + req.body);
context.res = {
headers: {
'Content-Type': 'application/json'
},
status: 400,
body: {
"error": err
}
};
context.done();
} else {
context.log("JWT Authentication Successful:");
context.res = {
headers: {
'Content-Type': 'application/json'
},
status: 200,
body: {
"message": "success"
}
};
context.done();
}
});
} catch (err) {
context.log("Exception in main function, PushHttpFunction:" + err);
context.res = {
headers: {
'Content-Type': 'application/json'
},
status: 500,
body: {
"error": err
}
};
context.done();
}
}
jwtDecoder.js
'use strict';
module.exports = (body, secret, cb) => {
console.log('inside jwtDecoder');
if (!body) {
return cb(new Error('invalid jwt data'));
}
require('jsonwebtoken').verify(body.toString('utf8'), secret, { algorithm: 'HS256' }, cb);
};
index.test.js
let indexTest = require('../index')
const { runStubFunctionFromBindings } = require('stub-azure-function-context')
let JWT = require('../jwtDecoder')
jest.mock("../jwtDecoder.js")
/* verify.mockImplementation(() => () => ({
err: new Error('invalid jwt data'),
decoded: 'ok'
})); */
JWT.mockImplementation(() => new Promise(function(resolve, reject) {
resolve('ok');
}));
beforeAll(() => {
process.env = Object.assign(process.env, {
NODE_ENV: "test",
});
});
describe('Simple Testing', () => {
test('return 200 by mocking simpleFunc response" ', async() => {
let request = {
body: "dummy.jwt.zT5p"
};
const context = await runStubFunctionFromBindings(indexTest, [
{ type: 'httpTrigger', name: 'req', direction: 'in', data: request },
{ type: 'http', name: 'res', direction: 'out' },
], new Date());
console.log('mockedResp::', context);
expect(context.res.status).toEqual(200);
}, 30000);
});
Basically you are mocking wrong, you can keep only this line:
jest.mock('./jwtDecoder.js', () => (res, req, cb) => cb(null, 'ok'))
as you need to mock callback
and remove all this part:
jest.mock("../jwtDecoder.js")
JWT.mockImplementation(() => new Promise(function(resolve, reject) {
resolve('ok');
}));

Uploading Files on production server returns either CORS error or POST 400 Bad Request using Apollo-Graphql

I'm having trouble on uploading files to my production server, in a local environment, everything works fine just as expected, but when I try to do a a Mutation containing file uploads (and only on those mutations) it throws me a CORS error. I'm using Spaces to upload the files and save them into my Database.
app.ts (Back-end):
const configureExpress = async () => {
const app: express.Application = express();
app.use(cors());
app.use(express.json());
app.use(express.urlencoded({ extended: false }));
app.use('/services', servicesRoute);
const { typeDefs, resolvers } = await buildSchema;
const schema = makeExecutableSchema({ typeDefs, resolvers });
const server = new ApolloServer({
schema,
playground: true,
introspection: true,
uploads: {
maxFileSize: 1000000000,
maxFiles: 100,
},
context: async ({ req }) => ({
auth: await Auth.getUser(req),
}),
formatError: (err) => ({
message: err.message,
code: err.extensions && err.extensions.code,
locations: err.locations,
path: err.path,
extensions: err.extensions && err.extensions.exception,
}),
});
server.applyMiddleware({ app });
return app;
};
export default () => database.connect().then(configureExpress);
client-auth.ts (On the Front-end):
const errorLink = onError(({ graphQLErrors }: any) => {
if (graphQLErrors) {
console.log(graphQLErrors);
graphQLErrors.map(({ message }: any) => console.log(message));
graphQLErrors.map(({ code }: any) => {
if (code === 'UNAUTHENTICATED') {
persistStore(store)
.purge()
.then(() => {
window.location.reload();
});
}
return true;
});
}
});
const authLink = setContext((_, { headers }) => {
const token = store.getState().auth.user!.token;
return {
headers: {
...headers,
authorization: `Bearer ${token}`,
},
};
});
const uploadLink = createUploadLink({
uri: 'https://api.example.com.br/graphql'
// uri: 'http://localhost:4000/graphql',
});
const client = new ApolloClient({
link: ApolloLink.from([errorLink, authLink, uploadLink]),
cache: new InMemoryCache(),
defaultOptions: {
query: {
fetchPolicy: 'network-only',
},
},
});
resolver.ts
return propertyModel.create({
...data
} as DocumentType<any>).then(async property => {
const user = await userModel.findById(input.userId);
if (!user) throw new UserNotFound();
await ownerModel.findByIdAndUpdate(user.owner, {
$push: {
properties: property.id,
}
});
if (input.images) {
input.images.forEach(async image => {
const uploadedImage = await FileS3.upload(image, {
path: 'images',
id: propertyId.toHexString(),
});
await property.updateOne({$push: {images: uploadedImage}});
});
}
if (input.scripture) {
const uploadedScripture = await FileS3.upload(input.scripture, {
path: 'documents',
id: propertyId.toHexString(),
});
await property.updateOne({scripture: uploadedScripture});
}
if (input.registration) {
const uploadedRegistration = await FileS3.upload(input.registration, {
path: 'documents',
id: propertyId.toHexString(),
})
await property.updateOne({
registration: uploadedRegistration,
});
};
if (input.car) {
const uploadedCar = await FileS3.upload(input.car, {
path: 'documents',
id: propertyId.toHexString(),
});
await property.updateOne({
car: uploadedCar,
});
};
if (input.ccir) {
const uploadedCcir = await FileS3.upload(input.ccir, {
path: 'documents',
id: propertyId.toHexString(),
});
await property.updateOne({
ccir: uploadedCcir,
});
};
if (input.itr) {
const uploadedItr = await FileS3.upload(input.itr, {
path: 'documents',
id: propertyId.toHexString(),
});
await property.updateOne({
itr: uploadedItr,
});
};
if (input.subscription) {
const uploadedSubscription = await FileS3.upload(input.subscription, {
path: 'documents',
id: propertyId.toHexString(),
});
await property.updateOne({
subscription: uploadedSubscription
});
return property;
});
};
I'm really lost regarding this error, is this an actual server error? (Production server is on DigitalOcean in Ubuntu) or something wrong regarding the code?
For CORS, if you are using the latest version of ApolloServer then turn on the CORS:
const server = new ApolloServer({
cors: {
credentials: true,
origin: true
},,
...
});
//also apply it here
server.applyMiddleware({ app,
cors: {
credentials: true,
origin: true
}
});
400 status code is returned for bad request which happens when a client sends a malformed request to server, You need to verify that your client is sending the correct data and headers on via correct HTTP verb (post/get etc)
If any one happens to have this same problem, here's how I solved.
After digging through the code I realized that in the server I was receiving a Maximum call stack size exceeded, upon looking further to this problem I realized that It was an error regarding the Graphql-Upload dependency, I fixed it by removing it as a dependency and added the following on my package.json:
"resolutions": {
"fs-capacitor":"^6.2.0",
"graphql-upload": "^11.0.0"
}
after this I just executed this script: npx npm-force-resolutions. And It worked all fine.

Upload File To Slack From Hapi

I am trying to upload a file that is being uploaded to my hapijs server directly to slack using the web api files.upload.
I get in return an error message {"ok":false,"error":"no_file_data"}.
The same code works if i upload a file that already in the storage using fs.createReadStream
Here is my code:
const Promise = require('bluebird');
const Joi = require('joi');
const Path = require('path');
const Boom = require('boom');
const Utils = require('../utils');
const Request = require('request');
exports.routes = Promise.coroutine(function*(server, options) {
server.route({
method: 'POST',
path: options.prefix,
config: {
tags: ['api', 'v1'],
payload: {
maxBytes: 2097152,
output: 'stream',
parse: true
},
plugins: {
'hapi-swagger': {
payloadType: 'form'
}
},
validate: {
payload: Joi.object({
cv: Joi.object({ pipe: Joi.func().required() }).required().unknown().description('CV file').meta({ swaggerType: 'file' })
})
},
response: {
emptyStatusCode: 204,
schema: false
},
handler: Utils.coHandler(function*(request, reply) {
const supportedFiles = ['docx', 'doc', 'pdf', 'pages'];
const ext = Path.extname(request.payload.cv.hapi.filename).substr(1);
if (!supportedFiles.filter((supExt) => supExt === ext).length) {
return reply(Boom.badRequest('Unsupported file type'));
}
Request.post({
url: 'https://slack.com/api/files.upload',
formData: {
token: 'XXXX',
filename: `${request.payload.name} - CV.${ext}`,
file: request.payload.cv,
channels: 'XXXX'
}
}, (err, res, body) => {
console.log(body);
});
reply();
})
}
});
});

Resources