Angular2: PUT request to Node-Server doesn't work - authentication lost - javascript

I'm using Angular2 on the client side and a node-express server as my backend. The node-server works as an API-middleware and also as my authentication service. The user-requests must contain a valid JWT token to perform requests on the node-server.
All of my GET functions and other PUT functions are working properly. I wrote a new one, which just should delete an ID on a third-party API, doesn't.
Furthermore, my node-express server sends custom error messages at some points to the client. This comes to my problem, whenever I run my latest PUT-function, my server responds with "No token provided". This happens when the user isn't logged in on the client side.
As I said, all my other functions working. this.createAuthenticationHeaders(); is necessary to perform valid request on the server side. But it's implemented.
In other words, the authentication gets lost between client and server and I get my own error message: "No token provided".
Appointment-Detail.Component.ts
cancelAppointment() {
this.authService.getProfile().subscribe(profile => {
this.username = profile.user.username; // Set username
this.email = profile.user.email; // Set e-mail
if (profile.user.email) {
this.apiService.cancelUserAppointment(this.id).subscribe(data => {
console.log(this.id);
if (!data.success) {
this.messageClass = 'alert alert-danger'; // Set error bootstrap class
this.message = data.message; // Set error message
} else {
this.messageClass = 'alert alert-success'; // Set success bootstrap class
this.message = data.message; // Set success message
// After two seconds, navigate back to blog page
}
});
}
});
}
API Service
cancelUserAppointment(id) {
this.createAuthenticationHeaders();
console.log('API SERVICE ' + id);
return this.http
.put(this.domain + 'api/appointments/' + id + '/cancel', this.options)
.map(res => res.json());
}
An API Service functions that works
getCertificatesByUser(email) {
this.createAuthenticationHeaders();
return this.http
.get(this.domain + 'api/user/' + email + '/certificates', this.options)
.map(res => res.json());
}
Server route to the third party API
router.put('/appointments/:id/cancel', (req, res) => {
console.log('hi');
var id = req.params.id;
const url = process.env.acuityUri + '/appointments/' + id + '/cancel';
console.log(id);
});
Authentication middleware
router.use((req, res, next) => {
const token = req.headers['authorization']; // Create token found in headers
// Check if token was found in headers
if (!token) {
res.json({
success: false,
message: 'No token provided'
}); // Return error
} else {
// Verify the token is valid
jwt.verify(token, config.secret, (err, decoded) => {
// Check if error is expired or invalid
if (err) {
res.json({
success: false,
message: 'Token invalid: ' + err
}); // Return error for token validation
} else {
req.decoded = decoded; // Create global variable to use in any request beyond
next(); // Exit middleware
}
});
}
});

Without doing too much of a deep dive into your auth headers, I see a pretty glaring issue that I think may be the cause of your troubles.
HTTP REST verbs carry different "intents", the intent we specifically care about in this case is wether or not your request should have a body.
GET requests do not carry a body with them.
PUT requests do carry a body.
Because of this, angular's HttpClient request methods (http.get, http.post, etc.) have different method signatures.
To cut to the chase, http.put's method signature accepts 3 parameters: url, body, and options, whereas http.get's method signature only accepts 2: url and options.
If you look at your example, for http.put you are providing this.httpOptions as the second parameter instead of the third, so Angular is packaging up your options object as the PUT request body. This is why you have a working example and a non-working example; the working example is a GET!
The solution? Simply put something else as the request body in the second parameter and shift this.options down to the third parameter slot. If you don't care what it is, just use the empty object: {}.
So your request should look like this:
return this.http
.put(this.domain + 'api/appointments/' + id + '/cancel', {}, this.options)
At the very least, this should send whatever is in this.options to the server correctly. Now wether what's in this.options is correct or not is another story.
Example PUT call from Angular's docs: https://angular.io/guide/http#making-a-put-request

Related

Twilio in nodejs is giving "To: undefined error" and is not able to understand data from front end

I just started using Twilio services and there have been a few challenges. The phone number I send from the front end, I'm not able to make out if its actually being sent to the backend route. Whenever the GET request is done, it throws an error
": Required parameter "opts['to']" missing. twilio".
The first error is what I am getting now.
My form sends this to the backend:
case 2:
const Phoneno = {
phone:countryCode+PhoneNumber
};
axios.post('http://localhost:4000/app/otp', { data :Phoneno });
console.log(Phoneno)
my route for sending otp:
router.post('/otp', async(req, res)=>{
client.verify.v2.services("VERIFY_SERVICE_SID")
.verifications
.create({to:req.body.phone, channel: 'sms'})
.then((verification) => {
console.log(verification.status);
return callback(null);
}).catch((e) => {
console.log(e);
return callback(e);
});
});
In that console.log, I get this,
"{phone: '+91**********'}
phone: "+91**********"(my actual number)
[[Prototype]]: Object"
Also, when I hardcode the phone number, the route works perfectly fine. And when i send an HTTP request to the route, then also it sends an otp to my phone number. But is not able to recognize the data sent from the frontend.
My http request:
POST http://localhost:4000/app/otp
Content-Type: application/json
{
"Phoneno":"+9199********"
}
Please help me out. Thanks a lot for looking into it.
You are making a GET request to your back-end, but you are trying to get the data from the request body. GET requests do not have a body.
You're also trying to send the data by passing it in an object as the second argument to axios.get, but that argument should be a config object. To send the data in the query for the GET request it should be under the params key for that object:
const Phoneno = {
phone:countryCode+PhoneNumber
};
axios.get('http://localhost:4000/app/otp', { params: Phoneno });
Then on the back-end, read the data from the query, using req.query.phone:
router.get('/otp', async(req, res)=>{
client.verify.v2.services(VERIFY_SERVICE_SID)
.verifications
.create({to:req.query.phone, channel: 'sms'})
.then((verification) => {
console.log(verification.status);
return callback(null);
}).catch((e) => {
console.log(e);
return callback(e);
});
However, this might not be a good idea because an attacker can easily create a URL to your site and add the phone number in the query parameters and use it to perform SMS pumping.
I'd actually recommend you change your back-end to a POST request and send the data from the front end in a POST request with the data in the body of the request:
const Phoneno = {
phone:countryCode+PhoneNumber
};
axios.post('http://localhost:4000/app/otp', { data: Phoneno });
router.post('/otp', async(req, res)=>{
client.verify.v2.services(VERIFY_SERVICE_SID)
.verifications
.create({to:req.body.data.phone, channel: 'sms'})
.then((verification) => {
console.log(verification.status);
return callback(null);
}).catch((e) => {
console.log(e);
return callback(e);
});

Different headers used in Axios patch

I spent an hour looking in the Chrome console and I cannot see where this bug comes from.
I am finishing an update of OAuth implementation in my Vue app.
The story begins when socialLink.js finds out that a new user must be created. Vue component Vue-authentication depends on the presence of access_token in a response so I return some dummy text:
return api.sendResponse(res, { email, name, socialId, access_token: 'abcd' });
The library stores this value in localStorage:
After a redirect, the SignUp.vue is rendered and I complete the form. The first communication with the server is a Vuex call to create a new user:
response = await this.$store.dispatch('CREATE_USER_PROFILE', payload);
Which returns a real short lived JWT token:
const token = auth.createToken(userId, nickname, new Date(), null, false, '1m');
return api.sendCreated(res, api.createResponse(token));
Which I store in the Vue page afterwards:
const { data } = response;
const token = data.data;
if (token === undefined) {
this.error = this.$t('sign-up.something-went-wrong');
return false;
}
I checked that the token contains what the server returned:
Request URL: https://beta.mezinamiridici.cz/api/v1/users
Request Method: POST
Status Code: 201 Created
{"success":true,"data":"eyJhbGciOiJIUzI1NiIs...Tl8JFw2HZ3VMXJk"}
Then I call another Vuex method and pass the current JWT token:
await this.$store.dispatch('UPDATE_USER_PROFILE', {
I checked in the Vuex devtools that there really is the correct JWT token. I then pass it further to api.js.
Here I create an Axios configuration holding an Authorization header:
function getAuthHeader(context, jwt = undefined, upload) {
const config = { headers: { } };
if (jwt || (context && context.rootState.users.userToken)) {
config.headers.Authorization = `bearer ${jwt || context.rootState.users.userToken}`;
}
Again, I checked that the correct JWT token is used there.
Finally, I pass all data to Axios:
function patch(endpoint, url, body, context, jwt) {
const headers = getAuthHeader(context, jwt);
console.log(headers);
if (endpoint === 'BFF') {
return axios.patch(`${VUE_APP_BFF_ENDPOINT}${url}`, body, headers);
} else {
return axios.patch(`${VUE_APP_API_ENDPOINT}${url}`, body, headers);
}
}
Which I log and can confirm the correct JWT is still there:
bearer eyJhbGciOiJIUzI1N....8JFw2HZ3VMXJk
There is nothing that could change the header now to abcd, but, the 'Network' tab shows it:
And the server fails with a parse error.
Has anybody got an idea why Axios uses the Authorization header with a different value than I pass it?
Ok, mystery solved. vue-authenticate is the reason, because, it creates Axios interceptors and handles the Authorization header itself.
vue-authenticate.common.js:
var defaultOptions = {
bindRequestInterceptor: function ($auth) {
var tokenHeader = $auth.options.tokenHeader;
$auth.$http.interceptors.request.use(function (config) {
if ($auth.isAuthenticated()) {
config.headers[tokenHeader] = [
$auth.options.tokenType, $auth.getToken()
].join(' ');
} else {
delete config.headers[tokenHeader];
}
return config
});
},
My code is more complex and it supports internal accounts with email/password so this code is breaking mine. The interceptor must be present and be a function, so the solution was:
Vue.use(VueAuthenticate, {
tokenName: 'jwt',
baseUrl: process.env.VUE_APP_API_ENDPOINT,
storageType: 'localStorage',
bindRequestInterceptor() {},
bindResponseInterceptor() {},
providers: {
facebook: {
clientId: process.env.VUE_APP_FACEBOOK_CLIENT_ID,
redirectUri: process.env.VUE_APP_FACEBOOK_REDIRECT_URI,
},

How to display 404 page if a back-end GET request to an API fails because user doesn't exists? Separated front-end and back-end

I have an application that uses JavaScript with Vue.js for the front-end and PHP with Laravel for the back-end.
Right now, when I make a GET request from my front-end to my back-end on URL /getSummoner/{summonerName}, I make another GET request from my back-end to a third party API in order to get the details for a user with a certain summoner name like this:
public function getSummoner($summonerName){
$summoner = Summoner::where('summoner_name', $summonerName)->first();
if ($summoner === null) {
$apiKey = env("RIOT_API_KEY");
$region = env("EUW");
$getSummonerInfo = file_get_contents($region . "/lol/summoner/v4/summoners/by-name/" . $summonerName . "?api_key=" . $apiKey);
$summonerInfo = json_decode($getSummonerInfo);
$summoner = new Summoner();
$summoner->summoner_name = $summonerName;
$summoner->summoner_info = json_encode($summonerInfo);
$summoner->save();
} else {
$summonerInfo = json_decode($summoner->summoner_info);
}
return response()->json([
'summonerInfo' => $summonerInfo,
], 201);
}
And then I return a JSON response to my front-end with the summoner info. This all works fine and dandy as long as a user with that summoner name exists. If he doesn't exists, the GET request fails so the rest of my function fails and in return I get an error on my front-end.
So I am wondering what am I supposed to do to get a 404 page on the front-end if my back-end GET request doesn't go through? Both on the front and back-end. I assume I need to return some sort of response from the back-end and then based on that response do something on the front-end?
Here's my front-end:
<template>
<div>{{ summonerInfo }}</div>
</template>
<script>
import axios from 'axios'
import router from '../router'
export default {
data(){
return {
summoner: this.$route.params.summonerName,
summonerInfo: '',
}
},
methods: {
user(action){
let trimmedSummoner = this.summoner.replace(/\s+/g, '');
axios.get('/' + action + 'Summoner/' + trimmedSummoner)
.then((response) => {
this.summonerInfo = response.data.summonerInfo
})
.catch(function (error) {
console.log(error);
})
}
},
watch:{
$route (to, from){
this.summoner = this.$route.params.summonerName
this.user('get')
}
},
mounted(){
this.user('get')
}
}
</script>
One poor mans way of doing this would be to wrap your request in a try / catch. This way, when you request fails, you have the opportunity to catch it and redirect. Downside to this method is that it doesn't give you any info on what the status code is (4xx vs 5xx, etc...).
However, a proper solution would be to use Http Interceptors to handle this.
How can you use axios interceptors?
Here is another example using try / catch approach:
https://gist.github.com/fgilio/230ccd514e9381fafa51608fcf137253
They've also got quite a few examples on this within their GitHub Docs:
https://github.com/axios/axios
Interceptor Example:
axios.interceptors.response.use((response) => {
if(response.status === 401) {
alert("You are not authorized");
}
return response;
}, (error) => {
if (error.response && error.response.data) {
return Promise.reject(error.response.data);
}
return Promise.reject(error.message);
});

Cloud Functions for Firebase HTTP timeout

I'm so close with this one.
I have written a Cloud Function that takes information sent from an Azure token to custom mint a Firebase token and send this token back to the client.
The token is created correctly, but isn't returned on my HTTP-request.
Unfortunately my Firebase app causes a timeout.
Function execution took 60002 ms, finished with status: 'timeout'
I can't really wrap my head around why that is, hence this post. Is there something wrong with my code, or is it me that's calling the HTTP-request wrong?
Here is the log I get from the Firebase Functions console.
Here's my code
// Create a Firebase token from any UID
exports.createFirebaseToken = functions.https.onRequest((req, res) => {
// The UID and other things we'll assign to the user.
const uid = req.body.uid;
const additionalClaims = {
name: req.body.name,
email: req.body.email
};
// Create or update the user account.
const userCreationTask = admin.auth().updateUser(uid, additionalClaims).catch(error => {
// If user does not exists we create it.
if (error.code === 'auth/user-not-found') {
console.log(`Created user with UID:${uid}, Name: ${additionalClaims.name} and e-mail: ${additionalClaims.email}`);
return admin.auth().createUser({
uid: uid,
displayName: displayName,
email: email,
});
}
throw error;
console.log('Error!');
});
// Wait for all async tasks to complete, then generate and return a custom auth token.
return Promise.all([userCreationTask]).then(() => {
console.log('Function create token triggered');
// Create a Firebase custom auth token.
return admin.auth().createCustomToken(uid, additionalClaims).then((token) => {
console.log('Created Custom token for UID "', uid, '" Token:', token);
return token;
});
});
});
When I'm making this HTTP-request, all i'm sending in is a JSON that looks like this:
parameters = [
"uid" : id,
"email" : mail,
"name" : name
]
Cloud Functions triggered by HTTP requests need to be terminated by ending them with a send(), redirect(), or end(), otherwise they will continue running and reach the timeout.
From the terminate HTTP functions section of the documentation on HTTP triggers:
Always end an HTTP function with send(), redirect(), or end(). Otherwise, your function might to continue to run and be forcibly terminated by the system. See also Sync, Async and Promises.
After retrieving and formatting the server time using the Node.js moment module, the date() function concludes by sending the result in the HTTP response:
const formattedDate = moment().format(format);
console.log('Sending Formatted date:', formattedDate);
res.status(200).send(formattedDate);
So, within your code, you could send the token back in the response with send(), for example:
// ...
// Create a Firebase custom auth token.
return admin.auth().createCustomToken(uid, additionalClaims).then((token) => {
console.log('Created Custom token for UID "', uid, '" Token:', token);
res.status(200).send(token);
return token;
});
// ...

Send POST parameters to Voice URL for incoming calls with Twilio

I'm allowing my site to accept incoming calls with python and javascript. I want to make sure that whoever answers the call has their username logged correctly by Twilio. As posted in their docs you need to create a capability token to accept incoming calls:
capability.allow_client_outgoing(application_sid)
capability.allow_client_incoming("jenny")
There could be up to 20 different users on the site at once that could answer the call so I would want the "jenny" string to be replaced to allow a dynamic username based upon who is logged in.
I'm using Django as my framework and generate the token through a view that request the username from request.user.username
def token(request):
capability = TwilioCapability(TWILIO_ACCOUNT_SID, TWILIO_AUTH_TOKEN)
capability.allow_client_outgoing(APP_ID)
capability.allow_client_incoming(request.user.username)
token = capability.generate()
data = json.dumps({'token': token})
return HttpResponse(data, content_type='application/json')
My Twilio Voice URL gets directed to this function. If it does not see a phone_number parameter then it can assume it is a incoming call. I somehow want to send the 'call_rep' parameter through this post which will match up the usernames to allow the incoming call with the correct user information:
#twilio_view
def make_call(request):
resp = Response()
if 'phone_number' in request.POST:
phone_number = request.POST['phone_number']
resp.say("Making the call now")
resp.dial(number=phone_number, callerId=TWILIO_PHONE_NUMBER)
else:
resp.say("Incoming call")
with resp.dial(callerId=TWILIO_PHONE_NUMBER) as r:
r.client(request.POST['call_rep'])
return resp
Is there somewhere in my JavaScript I need to put the call_rep's username in?
Twilio.Device.setup(token);
Twilio.Device.ready(function (device) {
});
Twilio.Device.error(function (error) {
console.log(error);
$("#log").text("Error: " + error.message);
});
Twilio.Device.offline(function(device) {
// Called on network connection lost.
});
Twilio.Device.connect(function (conn) {
console.log("Successfully established call");
});
Twilio.Device.disconnect(function (conn) {
// Called for all disconnections
console.log('DISCONNECT: ' + conn.status);
});
/* Listen for incoming connections */
Twilio.Device.incoming(function (conn) {
connection = conn
connection.accept()
});
For outgoing calls I can pass extra parameters no problems. How do I do this with incoming?
Twilio.Device.connect({
call_rep: '{{ request.user.username }}',
phone_number: phone_number
});

Categories

Resources