Azure Function disabled setting "user" field to HTTP request object on the 26th of April 2022 - node.js

(!) Issue cannot be reproduced locally.
Azure Function Version ~4
Node Version 14.18.1
Creating a simple HTTP triggered Azure Function and setting just two simple properties we get the following code:
module.exports = async function (context, req) {
req.auth = {authField: 'some value'}
req.user = {userField: 'some value'}
context.log(`${JSON.stringify(req,null,2)}`);
context.res = {
body: 'responseMessage'
};
}
The logger prints the following object:
{
"method": "POST",
"url": "xxx",
"originalUrl": "xxx",
"headers": {
/// ...
},
"query": {},
"params": {},
"body": { "name": "Azure" },
"rawBody": "{\"name\":\"Azure\"}",
"auth": { "authField": "some value" }
}
As you see only auth is set and not user.
The same failing behavior can be seen in version 4.2.0.
When I test with Azure Function ~3 the output looks like this:
{
"method": "POST",
"url": "xxx",
"originalUrl": "xxx",
"headers": {
// ...
},
"query": {},
"params": {},
"body": { "name": "Azure" },
"rawBody": "{\"name\":\"Azure\"}",
"auth": { "authField": "some value" },
"user": { "userField": "some value" }
}
As you see the field is set.
The following custom v4 4.1.0-17156 also sets the user field.
The field user was used by us through the express-jwt (v6.1.0) which is using it when no value is provided for requestProperty.
I could not yet reproduce it but in out transpiled from Typescript project, we get the following runtime error:
FailureException: Cannot set property user of [object Object] which has only a getterStack: TypeError: Cannot set property user of [object Object] which has only a getterat Object.run
The issue started at the beginning of the day of 26th of April 2022.
Questions:
what is the reason?
is a quick roll back to the functioning Azure Function custom version possible?

I found the culprit in this PR, which was added for Azure Function runtime v4.2.0.
The user field was added with only a getter.
We took the code and made the minimal example:
class Request {
#cachedUser?: string | null;
constructor() {
}
get user(): string | null {
if (this.#cachedUser === undefined) {
this.#cachedUser = "some value";
}
return this.#cachedUser;
}
}
and got the following transpilied version:
var __classPrivateFieldGet =
(this && this.__classPrivateFieldGet) ||
function (receiver, state, kind, f) {
if (kind === 'a' && !f) throw new TypeError('Private accessor was defined without a getter')
if (typeof state === 'function' ? receiver !== state || !f : !state.has(receiver))
throw new TypeError('Cannot read private member from an object whose class did not declare it')
return kind === 'm' ? f : kind === 'a' ? f.call(receiver) : f ? f.value : state.get(receiver)
}
var __classPrivateFieldSet =
(this && this.__classPrivateFieldSet) ||
function (receiver, state, value, kind, f) {
if (kind === 'm') throw new TypeError('Private method is not writable')
if (kind === 'a' && !f) throw new TypeError('Private accessor was defined without a setter')
if (typeof state === 'function' ? receiver !== state || !f : !state.has(receiver))
throw new TypeError('Cannot write private member to an object whose class did not declare it')
return kind === 'a' ? f.call(receiver, value) : f ? (f.value = value) : state.set(receiver, value), value
}
var _Request_cachedUser
class Request {
constructor() {
_Request_cachedUser.set(this, void 0)
}
get user() {
if (__classPrivateFieldGet(this, _Request_cachedUser, 'f') === undefined) {
__classPrivateFieldSet(this, _Request_cachedUser, 'some value', 'f')
}
return __classPrivateFieldGet(this, _Request_cachedUser, 'f')
}
}
_Request_cachedUser = new WeakMap()
// this section is added for testing
const request = new Request()
request.user = 'new value'
console.log(JSON.stringify(request.user))
So, it always returns the the initial value, which in our example is "some value" but in the original code is simply undefined and does not allow to set it.

Related

How to dynamically assign a name and type to unknown object property in Typescript

I'm using Shopify's rest client for node and its request and response look something like this:
request
client.get({
path: 'orders/count.json',
query: { fulfillment_status: 'unfulfilled' }
})
If there's an error:
{
"errors": "[API] Invalid API key or access...",
"code": 2342,
"statusText": "Authentication Error",
"Headers": "..."
}
If there's no error:
{
"body": { "count": 8 },
"code": 2342,
"statusText": "Authentication Error",
"Headers": "..."
}
I'd like to add some boilerplate over this client library so that I can get the typings of the response. This is what I'm trying to do but it's not working too well:
const customClient = {
get: async <T, K extends string>(params: GetRequestParams) => {
const response = (await client.get(params));
if (response.body.errors) return { errors: response.body.errors };
// somehow index it. obviously not with the type declaration???
return { [K]: response.body[K] as T };
},
}
With the hopes that I can use it as.
const { count, error } = customClient.get<number, "count">({ ... });
Any help would be appreciated. I have an entire file of the Shopify API types that I would like to leverage. A solution to this would be perfect!
A possible implementation can look like this:
const customClient = {
get: async <T, K extends string>(params: GetRequestParams):
Promise<Partial<Record<K, T> & { errors: string }>> =>
{
const response = (await client.get(params));
if (response.body.errors) return { errors: response.body.errors } as any;
return {
[Object.keys(response)[0]]: response[Object.keys(response)[0]]
} as any
},
}
As you correctly noted, we can't use the TypeScript generic types when constructing the returning object. We need to use JavaScript features instead. In this case I just took the first key of the response and used it for the key of the returning object as well as to index the response object.
The return type of the function is a Promise consisting of both a Record with K and T as keys and values and the error type. I used Partial here since they are not both required.
Destructing the response leads to the correct types now:
async function main(){
const { count, errors } = await customClient.get<number, "count">({} as any);
// count: number | undefined
// errors: string | undefined
}
Playground

Typescript check propery for unknow type

I have an api request and for error statement. I want to return message from payload.
But message object can change depending on error. For example, payload object can be
{ message: 'Not Authorized', type: 'service.not_authorized' }
or
{
"errors": [
{
"category": "AUTHENTICATION_ERROR",
"code": "UNAUTHORIZED",
"detail": "Authorization code is expired.
}
]
}
I can't create interfaces for all possible payloads.
In JS, basically I can write this :
if(payload && payload.message){
return payload.message
}
if(payload && payload.errors){
const message = payload.errors.length>0 ? payload.errors[0].detail : 'Error'
return message;
}
Now I can try to do this with Typescript
import axios from "axios";
interface IApiError{
category:string,
code:string,
detail:string
}
const CustomError=<T>(e:T):string=>{
let message="";
if(axios.isAxiosError(e)){
const payload = e.response?.data; // payload:unknown
if(payload){
if(typeof payload === 'object'){
if(payload.hasOwnProperty('message')){
message=payload.message;
// Error : Property 'message' does not exist on type 'object'.
}
else if(payload.hasOwnProperty('errors')){
const errorsArray:IApiError[]=payload.errors;
message=errorsArray[0].detail;
// Error : Property 'errors' does not exist on type 'object'.
}
}
}
}
return message;
}
How can I solve this?
Edit
I found this topic. But according this we should create interfaces and typeguard for every possible payload option.
I guess, I found a solution. Based on this answer I created a function.
// Basically it takes 3 arguments
// o -> Object that we don't know what is look like
// prop -> The name of prop that we are looking for
// type -> A variable with the same type with our prop
// Because I didn't find to send type to method in typescript
// (for example string,number,boolean)
const getProp = (o: unknown, prop: string, type:any): any => {
// Checks whether the object contains the requested prop
const p = (o as any)[prop]
// If this field instance of requested type
if (typeof p === typeof type) {
return p;
}
return undefined;
}
Let's test this for different scenarios.
let obj:unknown={ message: 'Not Authorized', type: 'service.not_authorized' }
let obj2:unknown={
"errors": [
{
"category": "AUTHENTICATION_ERROR",
"code": "UNAUTHORIZED",
"detail": "Authorization code is expired."
}
]
}
// In first object I looking for message field and it should be string
let res=getProp(obj,"message","");
res ? console.log(res) : console.log("Not Found");
// console result -> Not Authorized
// In second object I'm intentionally sending the wrong type
// There is a field with name 'errors' but it's type is array
res=getProp(obj2,"errors","");
res && res.length>0 ? console.log(res) : console.log("Not Found");
// console result -> Not Found
// I send true types
res=getProp(obj2,"errors",[]);
res && res.length>0 ? console.log(res[0].detail) : console.log("Not Found");
// console result -> Authorization code is expired.

Why is the `message` for my Jest custom matcher not being displayed?

I've created a Jest custom matcher. It works (meaning, it passes/fails when it should), but I don't see the message anywhere in Jest's output.
What am I doing wrong? Do I have to do something to "enable" messages? Am I totally misunderstanding where the message is supposed to show up?
Environment: NestJS, Prisma
Execution command: jest --watch
Simplified code:
declare global {
namespace jest {
interface Matchers<R> {
toMatchHash(received: string, expected: string): R;
}
}
}
expect.extend({
toMatchJsonHash(received, expected) {
return {
pass: false,
message: () => `Why doesn't this work?!`,
};
},
});
expect(prisma.name.findMany).toHaveBeenCalledWith(expect.toMatchJsonHash('db0110285c148c77943f996a17cbaf27'));
Output:
● MyService › should pass a test using a custom matcher
expect(jest.fn()).toHaveBeenCalledWith(...expected)
Expected: toMatchJsonHash<db0110285c148c77943f996a17cbaf27>
Received: {<Big ol' object redacted for conciseness>}
Number of calls: 1
178 |
179 | // #ts-ignore
> 180 | expect(prisma.name.findMany).toHaveBeenCalledWith(expect.toMatchJsonHash('db0110285c148c77943f996a17cbaf27'));
| ^
181 | // expect(prisma.name.findMany).toHaveBeenCalledWith({
182 | // select: { type: true, name: true },
183 | // where: {
at Object.<anonymous> (my/my.service.spec.ts:180:32)
I'm expecting to see "Why doesn't this work?!" somewhere in the output, but I don't. What am I missing?
As suggested by #jonsharpe, the reason was that Jest was showing the message from the "outer" matcher, .toHaveBeenCalledWith().
To fix this, I found the source that defines the .toHaveBeenCalledWith() matcher and "merged" its code into my custom matcher.
This enabled my custom matcher to effectively "extend" the functionality of the .toHaveBeenCalledWith() matcher, including my own custom code and messages.
In case it helps someone, the code I ended up with for my specific use case was:
declare global {
namespace jest {
interface Matchers<R> {
toHaveBeenCalledWithObjectMatchingHash(expected: string): CustomMatcherResult;
}
}
}
expect.extend({toHaveBeenCalledWithObjectMatchingHash(received, expected) {
const isSpy = (received: any) =>
received != null &&
received.calls != null &&
typeof received.calls.all === 'function' &&
typeof received.calls.count === 'function';
const receivedIsSpy = isSpy(received);
const receivedName = receivedIsSpy ? 'spy' : received.getMockName();
const calls = receivedIsSpy
? received.calls.all().map((x: any) => x.args)
: received.mock.calls;
if(calls.length === 0) {
return {
pass: false,
message: () => `expected the function to be called with an object that hashes to '${expected}'. Instead, the function was not called.`,
};
}
if(calls[0].length === 0) {
return {
pass: false,
message: () => `expected the function to be called with an object that hashes to '${expected}'. Instead, the function was called, but not with any arguments.`,
};
}
const md5Hash = crypto.createHash('md5');
const receivedHash = md5Hash.update(JSON.stringify(calls[0][0])).digest('hex');
const pass = receivedHash === expected;
if(pass) {
return {
pass: true,
message: () => `expected the function to not be called with an object that hashes to '${expected}'. Instead, the passed object hashes to the same value.`,
};
} else {
return {
pass: false,
message: () => `expected the function to be called with an object that hashes to '${expected}'. Instead, the passed object hashes to '${receivedHash}'.`,
};
}
}});

AWS PUT request met with "Provided key element does not match schema."

(Edited to incorporate comments)
So I apologize in advance for the long question. I don't know how else to ask it.
I'm trying to finish up a full-stack web app using React, Node, and DynamoDB. POST and GET requests are working fine, but I'm stuck on PUT. My mock PUT request works fine, but once I try it from the front end in React, I get the error mentioned in the title. I'll show the back end code first, then the mock update, and then the front end.
import handler from "./libs/handler-lib";
import dynamoDb from "./libs/dynamodb-lib";
export const main = handler(async (event, context) => {
const data = JSON.parse(event.body);
const params = {
TableName: process.env.tableName,
Key: {
userId: event.requestContext.identity.cognitoIdentityId,
activityId: event.pathParameters.activityId
},
UpdateExpression: "SET title = :title, activityType = :activityType, activityRoutine = :activityRoutine, activityComment = :activityComment",
ExpressionAttributeValues: {
":title": data.title || null,
":activityType": data.activityType || null,
// ":activityRoutine": data.activityRoutine == '' ? "None" : data.activityRoutine,
// ":activityComment": data.activityComment == '' ? "None" : data.activityComment
":activityRoutine": data.activityRoutine || null,
":activityComment": data.activityComment || null
},
ReturnValues: "ALL_NEW"
};
await dynamoDb.update(params);
return { status: true };
This mock update event works without issue:
{
"body": "{\"title\":\"test\",\"activityType\":\"testing\",\"activityRoutine\":\"\",\"activityComment\":\"\"}",
"pathParameters": {
"activityId": "long-alphanumeric-id"
},
"requestContext": {
"identity": {
"cognitoIdentityId": "us-east-and-so-on"
}
}
}
But this code, which produces the exact same Javascript object as the mock, is not okay with AWS:
function saveActivity(activity) {
try {
return API.put("activities", `/activities/${id}`, {
body: activity
});
} catch(e) {
console.log("saveActivity error:", e);
}
}
async function handleSubmit(event) {
event.preventDefault();
setIsLoading(true)
try {
await saveActivity({
title: title, activityType: activityType, activityRoutine: activityRoutine, activityComment: activityComment
// "key": {userId: userId, activityId: activityId}
// "pathParameters": {"id": activityId},
// "requestContext": {"identity": {"cognitoIdentityId": userId}}
});
} catch(e) {
console.log(e)
setIsLoading(false)
}
}
If anyone needs to see more of the code, I'm happy to share, but I figured this question is already getting very long. Any code you see commented out has been tried before without success.
I'd also be happy if someone could point me in the right direction as far as the AWS documentation is concerned. I've been going off of a tutorial and modifying it where need be.
Any help is appreciated!

Firebase Flashlight (ElasticSearch) filtering, sorting, pagination

I am using Flashlight Firebase plugin
I am using this example and it's working fine
In the example you can see example.js file have method for query as below
// display search results
function doSearch(index, type, query) {
var ref = database.ref().child(PATH);
var key = ref.child('request').push( { index: index, type: type, query: query } ).key;
ref.child('response/'+key).on('value', showResults);
}
above function returning me the results when I pass values like following JSON
{ index: index, type: type, query: query }
It returning me nothing when i am trying to pass values like following JSON
{ index: index, type: type, query: { "from" : 1, "size" : 5 , "query": query }
but the following ElasticSearch API returning me the result
http://localhost:9200/firebase/user/_search?q=*mani*&pretty&size=5&from=1
and How do i filtering the query using Flashlight like following
{
"query": {
"filtered": {
"query": {
"query_string": {
"query": "drama"
}
},
"filter": {
//Filter to apply to the query
}
}
}
}
I am using following security rules
{
"rules": {
".read": false,
".write": false,
"search": {
"request": {
"$recid": {
// I can only read records assigned to me
".read": "auth.id === data.child('id').val() || auth.uid === data.child('id').val()",
// I can only write new records that don't exist yet
".write": "!data.exists() && (newData.child('id').val() === auth.id || newData.child('id').val() === auth.uid)",
".validate": "newData.hasChildren(['query', 'index', 'type'])",
"index": {
// accepts arrays or strings
".validate": "(newData.isString() && newData.val().length < 1000) || newData.hasChildren()",
"$child": {
".validate": "newData.isString() && newData.val().length < 1000"
}
},
"type": {
// accepts arrays or strings
".validate": "(newData.isString() && newData.val().length < 1000) || newData.hasChildren()",
"$child": {
".validate": "newData.isString() && newData.val().length < 1000"
}
},
"query": {
// structure of the query object is pretty open-ended
".validate": "newData.isString() || newData.hasChildren()"
},
"$other": {
".validate": false
}
}
},
"response": {
"$recid": {
// I can only read/write records assigned to me
".read": "auth.id === data.child('id').val() || auth.uid === data.child('id').val()",
".write": "auth.id === data.child('id').val() || auth.uid === data.child('id').val()",
// Assumes that Flashlight will be writing the records using a secret or a token that has admin: true
// The only thing a logged in user needs to do is delete results after reading them
".validate": false
}
}
}
}
}
Please let me know how to perform complex queries and filtering with Flashlight
Finally I did it myself
here is the solution
You need to update SearchQueue.js
_process: function (snap) {
var dat = snap.val();
var key = snap.key;
if (this._assertValidSearch(key, dat)) {
// get your query string
var q = dat.query.query;
console.log('search', "test", JSON.stringify(dat, null, 2));
// build your ES query
//var q1 = {"query":{"match":{"_all":q}}};
// Perform (a very simple) ElasticSearch query
this.esc.search({
index: dat.index,
type: dat.type,
// add options
from : dat.query.from,
size : dat.query.size,
// add ES Query
//body : q1
q:dat.query.query
}, function (error, response) {
if (error) {
this._reply(key, {error: error, total: 0});
} else {
this._reply(key, response);
}
}.bind(this));
}
}
and update Example.js
// display search results
function doSearch(index, type, query) {
var ref = database.ref().child(PATH);
var jsonOBJ = {
index: index,
type: type,
query: { size:1, from:0, query:query},
};
var key = ref.child('request').push(jsonOBJ).key;
console.log('search', key, JSON.stringify(jsonOBJ, null, 2));
ref.child('response/'+key).on('value', showResults);
}

Resources