Nest.JS and Observables - javascript

I am new to Nest.JS and apparently don't understand how to use observables so hopefully ya'll can help.
Basically I have a method that needs to:
first: login to hashicorp vault and return a client_token via an http call.
second: if we got a token back from vault, we then check that the request contained a certification-id, if not we have to request a new certification to be generated. Which requires the client_token from vault.
The problem I am having is that when I call vault to get the client_token, it does not get returned in time for me to be able to use it to generate a new cert via a second api call.
What can I do in order to be able to use the client_token in the next step?
Here is the code for my latest attempt:
Controller:
#Controller('user')
export class UserController {
constructor(private readonly userService: UserService) {}
#Post('getUserCert')
async getUserCert(#Body() loginDto: vaultLoginReqDto) {
return this.userService.getCertificate(loginDto);
}
}
Controller calls the getCertificate method:
getCertificate(loginDto: vaultLoginReqDto) {
this.loginToVault(loginDto);
if (this.vault_token) {
if (loginDto.cert_id) {
this.checkForExistingCert(loginDto);
} else {
this.generateNewCert(this.vault_token);
}
} else {
throw new Error('User is not authorized to access Vault.');
}
}
The logon method:
loginToVault(loginDto: vaultLoginReqDto) {
const url = 'http://vault:8200/v1/auth/jwt/login';
const payload: vaultLoginReqDto = {
jwt: loginDto.jwt,
role: loginDto.role,
};
try {
this.httpService
.post(url, payload)
.subscribe((res: AxiosResponse<vaultLoginResDto>) => {
this.vault_token = res.data.auth.client_token;
});
} catch (e) {
this.throwError(e, url, 'Unable to login to vault');
}
}
the problem method is the generateNewCert method. It is not getting the vault_token in time.
generateNewCert(vault_token: string): Observable<string> {
const url = `http://127.0.0.1:8200/v1/xxxx/xxxx/issue/reader`;
const payload = {
common_name: 'id.xxxx.com',
};
const headers = {
'X-Vault-Token': vault_token,
};
try {
return this.httpService.post(url, payload, { headers: headers }).pipe(
map((res: AxiosResponse<vaultGetCertResDto>) => {
return res.data.data.certificate;
}),
);
} catch (e) {
this.throwError(e, url);
}
}
I appreciate the help!

The easiest way to make it work is the convert to a Promise so you can wait for the result.
loginToVault(loginDto: vaultLoginReqDto) {
const url = 'http://vault:8200/v1/auth/jwt/login';
const payload = {
jwt: loginDto.jwt,
role: loginDto.role,
};
return this.httpService
.post(url, payload)
.pipe(
catchError(() => {/** ...handleError **/}),
map((res) => {
this.vault_token = res.data.auth.client_token;
return this.vault_token;
}),
)
.toPromise()
}
Now, you can use async / await at getCertificate
async getCertificate(loginDto: vaultLoginReqDto) {
await this.loginToVault(loginDto);
// or const vault_token = await this.loginToVault(loginDto)
if (this.vault_token) {
if (loginDto.cert_id) {
this.checkForExistingCert(loginDto);
} else {
this.generateNewCert(this.vault_token);
}
} else {
throw new Error('User is not authorized to access Vault.');
}
}

If you decide to stick with the observables, you can return an observable from the loginToVault method as opposed to subscribing to it
loginToVault(loginDto: vaultLoginReqDto): Observable<string> {
const url = 'http://vault:8200/v1/auth/jwt/login';
const payload = {
jwt: loginDto.jwt,
role: loginDto.role,
};
return this.httpService
.post(url, payload)
.pipe(
catchError(() => { /* handle errors */ }),
map((res) => res.data.auth.client_token)
)
}
Then in getCertificate method, you subscribe to loginToVault and handle the logic
getCertificate(loginDto: vaultLoginReqDto) {
this.loginToVault(loginDto)
.pipe(
tap(vault_token => {
if (!vault_token) {
throw new Error('User is not authorized to access Vault.');
}
})
)
.subscribe(vault_token => loginDto.cert_id ?
this.checkForExistingCert(loginDto) :
this.generateNewCert(vault_token)
)
}
The vault_token is passed from one service to another and thus will be accessible in the generateNewCert method. You do not need to declare it globally

Related

Error: function uses multiple asynchronous interfaces: callback and promise

Error: function uses multiple asynchronous interfaces: callback and
promise
to use the callback interface: do not return a promise
to use the promise interface: remove the last argument to the function
I'm trying to write a cucumber test to one of my GET node API, and keep getting the above, looked at few GitHub and stack-overflow posts, and could not understand the issue, below are my test method details.
App.ts
async getSsoId(refId: any): Promise<string> {
let ssoId = '';
const secrets = await this.vaultService.getClientSecrets();
this.decrypt(refId, secrets.encryption_secret, secrets.encryption_Id).then((value: any) => {
ssoId = value;
});
return ssoId;
}
api.get('/status', async (req, res) => {
let id;
const statusCode = 200;
try {
id = await this.getId(String('123456'));
} catch (err: any) {
throw new ApiError('Error fetching id');
}
try {
const item = await dbService.getItem(id);
if (item) {
statusCode = 201;
} else {
statusCode = 202;
}
} catch (err: any) {
throw new ApiError(
'The API encountered an error while attempting to communicate with the DB service.'
);
}
res.status(statusCode).send(statusCode);
});
Step Definition:
Given('a valid customer', function () {});
When("I call the get-id api", { timeout: 2 * 5000 }, async function (val) {
util.callGetIdAPI().then((response) => {
this.setApiResponseStatus(response.status);
this.setResponseBody(JSON.stringify(response.body));
});
});
Then("the apiResponseStatus should be <apiResponseStatus>", function (status) {
assert.strictEqual(status, this.apiResponseStatus);
});
Then("the responseBody should be {string}", function (responseBody) {
assert.equal(responseBody, this.responseBody);
});
Util Function
callGetIdAPI = async () => {
const headers = {
'Content-Type': 'application/json;v=1',
Accept: 'application/json;v=1'
}
const client = await getClient('url');
const options = {
method: 'GET',
headers: headers,
version: 3
};
let response;
try {
response = await client.get('/status', options);
return {
status: response.statusCode,
body: response.body
};
} catch(error) {
return {
status: error.statusCode,
body: {
error: {
id: error.id,
message: error.message
}
}
}
}
};
I'm new to this and trying to understand how multiple Premisses and Callbacks works in parallel, any thoughts or inputs on what possibly cause the error, or am I missing anything ??

How to make RTK Query createApi's mutation throw rejection?

How to make RTK Query createApi's mutation throw rejection?
The current code execution will enter then, How to get here.
getList({ id }).catch(() => { How to get here });
baseQuery.js
import { fetchBaseQuery, retry } from '#reduxjs/toolkit/query/react';
const baseQueryWithRetry = retry(
fetchBaseQuery({
baseUrl: process.env.REACT_APP_BASE_URL,
}),
{ maxRetries: 6 }
);
export const baseQueryWithReauth = async (args, api, extraOptions) => {
try {
const result = await baseQueryWithRetry(args, api, extraOptions) as { data: { RES: string } };
if (result.data.RES) {
const { body } = JSON.parse(result.data.RES);
switch (body.code) {
case '000000':
return { ...result, data: body }
default:
debugger
throw new Error('body.message');
}
}
return result;
}
catch (err: any) {
debugger
return Promise.reject(err);
}
}
services.js
import { createApi } from '#reduxjs/toolkit/query/react';
import { baseQueryWithReauth } from './baseQuery';
export const quoteApi = createApi({
reducerPath: 'quoteApi',
baseQuery: baseQueryWithReauth,
endpoints: (builder) => ({
getList: builder.mutation<any, object>({
query: (body) => ({
url: 'getPriceList',
method: 'POST',
body,
}),
}),
}),
refetchOnReconnect: true,
});
This is so that your application is not littered with uncaught async exceptions if you are not interested in the result.
You can unwrap the result to get to "throwing" behaviour:
getList({ id }).unwrap().catch(() => { How to get here });
That said, it is very likely that getList should be a query, not a mutation. Mutations are things that change data on your server.

MSAL acquireTokenSilent with httpInterceptor

I am trying to call the MSAL silentTokenrefresh method from Angular authInterceptor whenever the 401 hits. And then i am trying to recall the failed request again with a new token so the service won't be interrupted. I have followed this stackoverflow link (answered by Andrei Ostrovski) and implemented the same in my application.
There are two problem with refreshToken() method.
the catchError expects return variable where i put EMPTY. The code is happy, but it is NOT triggering back the failed request where the total purpose is not achieved. It means that whenever it encounters 401 and it is allowing to acquire the new token and then it is not triggering the failed requests.
To resolve the above one, i just took another approach (instead of pipe, i used subscribed but there the return is not applicable) and then it became completely invalid.
Could you please suggest me here to make it work?
My complete authInterceptor file:
addAuthHeader(request) {
const tokenType = "Bearer";
const authHeader = this.sessionService.getAccessToken();
if (authHeader) {
return request.clone({
setHeaders: {
"Authorization": tokenType + " "+ authHeader
}
});
}
return request;
}
refreshToken(): Observable<any> {
if (this.refreshTokenInProgress) {
return new Observable(observer => {
this.tokenRefreshed$.subscribe(() => {
observer.next();
observer.complete();
});
});
} else {
this.refreshTokenInProgress = true;
// Getting the scope.
const loginRequest: { scopes: any } = {
scopes: ['openId','profile'],
};
return this.msalAuthService.acquireTokenSilent(loginRequest).pipe(
tap((payloadInfo)=>{
this.authServices.setSessions(payloadInfo);
this.refreshTokenInProgress = false;
this.tokenRefreshedSource.next();
}),
catchError(() => {
this.refreshTokenInProgress = false;
this.authServices.logout();
return EMPTY;
})
)
}
}
handleResponseError(error, request?, next?) {
// Invalid token error
if (error.status === 401) {
return this.refreshToken().pipe(
switchMap(() => {
request = this.addAuthHeader(request);
return next.handle(request);
}),
catchError(e => {
if (e.status !== 401) {
return this.handleResponseError(e);
} else {
this.authentiCationService.logout();
this.router.navigate(["logout"], {
replaceUrl: true
});
}
}));
}
}
intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
this.totalRequests++;
req = req.clone({ headers: req.headers.set("Accept", "*/*") });
req = req.clone({
headers: req.headers.set("Content-Type", "application/json")
});
req = this.addAuthHeader(req);
const started = Date.now();
return next.handle(req).pipe(
tap(
(event: HttpEvent<any>) => {
if (event instanceof HttpResponse) {
// some internal logic for the success scenario
}
},
(err: any) => {
this.handleResponseError(err, req, next);
}
)
);
}
The compile time error you are getting is because you are returning the Subscription instead of the Observable. Please take a look at the difference between them: https://angular.io/guide/observables
You have to return the Observable from your code. In catchError return Observable.throw(error.statusText);

AWS Lambda retries despite returning value

I have found out that my and my colleagues lambda doesn't return data we anticipate.
After what I have already found, we used deprecated invokeAsync which returnes only statusCode.
I have upgraded aws-sdk to the newest version and change the invocation code to use invoke with
InvocationType: 'RequestResponse'
as that should give us the returned value.
Lambdas code itself looks good, its an async function without arguments.
Im not using the callback here (3rd argument of handler), as we are doing some async stuff and it would require a small refactor to not use async/await, also i have read that just returning value is ok as well.
Earlier lambda was returning array, but after some investigation and googling I have changed it to
return {
statusCode: 200,
body: JSON.stringify(result)
}
where result is the mentioned array, as this was recurring pattern in search results.
Overall result is in our service where we do the invocation, we get timeout, lambda logs that it is returning value, but then retries itself again and again.
Dumping code
invocation in our service:
const lambda = new AWS.Lambda({ httpOptions: { timeout: 600000 } })
const results = await new Promise((resolve, reject) => {
lambda.invoke(
{
FunctionName: `lambda-name`,
InvocationType: 'RequestResponse',
Payload: '""'
},
(err, data) => {
if (err) {
reject(err)
} else {
resolve(data)
}
}
)
})
lambda handler
import { parallelLimit } from 'async'
const PARALLEL_FETCHERS_NUMBER = 2
export async function runTasksInParallel (
tasks,
limit,
) {
return new Promise(
(
resolve,
reject,
) => {
parallelLimit(tasks, limit, (error, results) => {
if (error === null) {
resolve(results)
} else {
reject(error)
}
})
},
)
}
async function connectToDB(logger) {
const MONGO_URL = process.env.MONGO_URL || 'mongodb://localhost:27017/'
let db
logger.debug('Connecting to mongo')
try {
db = await MongoClient.connect(
MONGO_URL,
{
sslValidate: false,
}
)
} catch (error) {
logger.error('Could not connect to Mongo', { error })
throw error
}
logger.debug('Connected')
return db
}
async function fetchAndStore(
list,
sources,
logger
) {
const results = []
const tasks = sources.map((source) => async (callback) => {
const { fetchAdapter, storageAdapter } = source
try {
const { entries, timestamp } = await fetchAdapter.fetchLatest(list)
await storageAdapter.storeMany(timestamp, entries)
results.push(['fetch.success', 1, [ `fetcher:${fetchAdapter.name}` ] ])
} catch (error) {
const errorMessage = `Failed to fetch and store from adapter ${fetchAdapter.name}`
const { message, name, stack } = error
logger.error(errorMessage, { error: { message, name, stack } })
results.push(['fetch.error', 1, [ `fetcher:${fetchAdapter.name}` ] ])
}
callback && callback(null)
})
await runTasksInParallel(tasks, PARALLEL_FETCHERS_NUMBER)
return results
}
export async function handle() {
const logger = createLambdaLogger() // just a wrapper for console.log to match interface in service
logger.debug('Starting fetching data')
const db: Db = await connectToDB(logger)
const primaryCollection = db.collection(PRIMARY_COLLECTION)
const itemsColletion = db.collection(ITEMS_COLLECTION)
const secondaryCollection = db.collection(SECONDARY_PRICING_COLLECTION)
const primaryStorageAdapter = new StorageAdapter(
Promise.resolve(primaryCollection),
logger
)
const itemsStorageAdapter = new ItemsStorageAdapter(
Promise.resolve(itemsColletion),
logger
)
const secondaryStorageAdapter = new StorageAdapter(
Promise.resolve(secondaryCollection),
logger
)
const primaryFetchAdapter = new PrimaryFetchAdapter(getFetcherCredentials('PRIMARY'), logger)
const secondaryFetchAdapter = new SecondaryFetchAdapter(getFetcherCredentials('SECONDARY'), logger)
const sources = [
{ fetchAdapter: primaryFetchAdapter, storageAdapter: primaryStorageAdapter },
{ fetchAdapter: secondaryFetchAdapter, storageAdapter: secondaryStorageAdapter },
]
try {
const list = await itemsStorageAdapter.getItems()
logger.debug(`Total items to fetch ${list.length}`)
const result = await fetchAndStore(list, sources, logger)
logger.debug('Returning: ', { result })
return {
statusCode: 200,
body: JSON.stringify(result)
}
} catch (error) {
const errorMessage = 'failed to do task'
const { message, name, stack } = error
logger.error(errorMessage, { error: { message, name, stack } })
return {
statusCode: 500,
body: JSON.stringify(new Error(errorMessage))
}
} finally {
await db.close()
}
}
Edit:
I have changed the way i'm invoking the lambda to this below. Basically I tried to listen for every possible event that can say me something. Result is that i get a error event, with response message being Converting circular structure to JSON. Thing is I don't really know about which structure this is, as the value I'm returning is a two elements array of 3 elements (strings and number) arrays.
const lambda = new AWS.Lambda({
httpOptions: { timeout: 660000 },
maxRetries: 0
})
const request = lambda.invoke(
{
FunctionName: `${config.get('env')}-credit-rubric-pricing-fetching-lambda`,
InvocationType: 'RequestResponse',
Payload: '""'
},
(err, data) => {
if (err) {
reject(err)
} else {
resolve(data)
}
}
)
const results = await request
.on('send', () => {
logger.debug('Request sent to lambda')
})
.on('retry', response => {
logger.debug('Retrying.', { response })
})
.on('extractError', response => {
logger.debug('ExtractError', { response })
})
.on('extractData', response => {
logger.debug('ExtractData', { response })
})
.on('error', response => {
logger.debug('error', { response })
})
.on('succes', response => {
logger.debug('success', { response })
})
.on('complete', response => {
logger.debug('complete', { response })
})
.on('httpHeaders', (statusCode, headers, response, statusMessage) => {
logger.debug('httpHeaders', { statusCode, headers, response, statusMessage })
})
.on('httpData', (chunk, response) => {
logger.debug('httpData', { response, chunk })
})
.on('httpError', (error, response) => {
logger.debug('httpError', { response, error })
})
.on('httpDone', response => {
logger.debug('httpDone', { response })
})
.promise()

Facebook graph API: return object from GraphRequest callback

I have a function that successfully makes requests, but am having trouble translating it to the React-Redux data flow because I can't figure out how to return the object that is received in the callback.
What I currently have doesn't work, but I am confident it makes the request successfully because I can see it when I console.log it.
import FBSDK, { AccessToken, GraphRequest, GraphRequestManager } from 'react-native-fbsdk'
export const callback = (error, result) => {
if (error) {
console.log(error);
return error
} else {
console.log(result);
return result;
}
}
export const graphRequestFor = fields => {
AccessToken.getCurrentAccessToken()
.then(token => {
console.log(token);
const request = new GraphRequest(
'/me',
{
accessToken: token.accessToken,
parameters: {
fields: {
string: fields
}
}
}, callback
)
new GraphRequestManager().addRequest(request).start()
})
}
export const login = () => {
graphRequestFor('first_name, last_name')
.then(results => {
return results
}) //undefined
}
Not an answer to my direct question, but a solution to my problem is to make the api call directly inside the action and dispatching the object from the callback
import FBSDK, { AccessToken, GraphRequest, GraphRequestManager } from 'react-native-fbsdk'
export const login = params => dispatch => {
AccessToken.getCurrentAccessToken()
.then(token => {
const request = new GraphRequest(
'/me',
{
accessToken: token.accessToken,
parameters: {
fields: {
string: params
}
}
}, (error, result) => {
if (error) {
console.log(error);
return dispatch(receiveCurrentUser(error))
} else {
return dispatch(receiveCurrentUser(result))
}
}
)
new GraphRequestManager().addRequest(request).start()
})
}

Categories