I'm having trouble when I'm fetching my APIs that are created on the backend.
Mostly, I'm handling everything right on the good side but not when there is an error because the catch(error) method only catches the errors that are in the front-end server while my errors are coming from my back-end. My stack is mostly focused on VueJS and Spring Boot. I can give an example to clarify things.
For example, in the login. I have a controller in my backend that contains JWT Creation
JwtUtils jwtUtils;
#PostMapping("/signin")
public ResponseEntity<?> authenticateUser(#Valid #RequestBody LoginRequest loginRequest) {
Authentication authentication = authenticationManager.authenticate(
new UsernamePasswordAuthenticationToken(loginRequest.getEmail(), loginRequest.getPassword()));
SecurityContextHolder.getContext().setAuthentication(authentication);
String jwt = jwtUtils.generateJwtToken(authentication);
UserDetailsImpl userDetails = (UserDetailsImpl) authentication.getPrincipal();
List<String> roles = userDetails.getAuthorities().stream()
.map(item -> item.getAuthority())
.collect(Collectors.toList());
return ResponseEntity.ok(new JwtResponse(jwt,
userDetails.getId(),
userDetails.getEmail(),
roles));
}
on the front-end side I just consume this API.
So, I created a service on VueJS and that service is consumed by one of my views on a button click.
My service :
class AuthService{
login(user){
return fetch("http://localhost:100/api/v1/auth/signin",
{method : "POST",
headers:{
"Content-type" : "application/json"
},
body: JSON.stringify({
email:user.email,
password: user.password,
}),
}).then( response => response.json()).then(data =>{
if (data.accessToken){
localStorage.setItem('user',JSON.stringify(data))
}
return data})
}
logout(){
localStorage.removeItem('user')
}
}
export default new AuthService();
That service is called by my module (Vuex) in the localstore:
import AuthService from '../services/auth.service';
const user = JSON.parse(localStorage.getItem('user'));
const initialState = user
? { status: { loggedIn: true }, user }
: { status: { loggedIn: false }, user: null };
export const auth = {
namespaced: true,
state: initialState,
actions: {
login({ commit }, user) {
return AuthService.login(user).then(
user => {
commit('loginSuccess', user);
return Promise.resolve(user);
},
error => {
commit('loginFailure');
return Promise.reject(error);
}
);
},
logout({ commit }) {
AuthService.logout();
commit('logout');
},
register({ commit }, user) {
return AuthService.register(user).then(
response => {
commit('registerSuccess');
return Promise.resolve(response.data);
},
error => {
commit('registerFailure');
return Promise.reject(error);
}
);
}
},
mutations: {
loginSuccess(state, user) {
state.status.loggedIn = true;
state.user = user;
},
loginFailure(state) {
state.status.loggedIn = false;
state.user = null;
},
logout(state) {
state.status.loggedIn = false;
state.user = null;
},
registerSuccess(state) {
state.status.loggedIn = false;
},
registerFailure(state) {
state.status.loggedIn = false;
}
}
};
And finally I have a view that would call the action, its basically a form that contains 2 input fields that are going to be passed to the backend for the post method.
Login.vue :
<template>
<div id="nav">
<NavBar/>
<Loader v-if="loading"/>
<Form #clicked="onClickValue" :error="message" />
</div>
</template>
<script>
import NavBar from '../components/NavBar.vue'
import Form from '../components/Form.vue'
import Loader from '../components/Loader.vue'
export default {
components:{
NavBar,Form,Loader
},
data(){
return{
err:null,
loading:false,
message: '',
}
},
methods : {
onClickValue(user) {
this.loading = true;
this.$store.dispatch("auth/login", user).then(
() => {
this.$router.push("/token");
},
(error) => {
this.loading = false;
this.message =
(error.response &&
error.data &&
error.data.message) ||
error.message ||
error.toString();
}
);
},
},
}
</script>
I know that's alot of code but i'd like to explain my problem, basically I have some data on my server and when I fetch my API I want the user to login depending on his credentials. I know how to handle the errors but I don't know if that's the conventional way to do so. Generally I'd do this for example :
if res.status == 403 then (show error message on screen saying that the user is forbidden)
But is this how people handle errors that come from the backend ?? how should I do to handle my errors that are coming from the backend, is it an if statement or something else that should handle it (is what i'm doing even secure? )?
basically here is an image of my page.
Form
Its generally a good idea for the backend to handle the error messages, whenever they are generic or something specific, then on the frontend we just show that error message.
With this approach you wont struggle on the frontend in order to determinate which error code example: if res.status == 403 || res.status === 404.
The only thing you care about is error.message
Related
I have created a Authentication service in Angular with function SignUp that sends the API Request to Firebase, As Firebase returns the User ID, I am saving the userid into my personal MongoDB Database with its Role. Now the problem here is i am sending two request which i want to further Subscribed in Register.component.ts, I am not able to understand how to achieve this. Below are the sample code that i have tried.
auth.service.ts
signUp(email: string, password: string) {
return this.http.post<AuthResponse>(`https://identitytoolkit.googleapis.com/v1/accounts:signUp?key=${config.API_KEY}`, {
email: email,
password: password,
returnSecureToken: true
}).pipe(
switchMap(data => {
return this.http.post<any>(`${config.BASE_URL}/api/ezusers/`,{useruid: data.idToken, 'isRegistered':false}); // or this.userId
})
).map(response => {
this.authenticatedUser(response.email, response.localId, response.idToken, +response.expiresIn);
// this.userOrders = response;
return
});
}
Register.component.ts
onSubmit() {
this.loading = true;
if (this.registerForm.valid) {
this._authService.signUp(this.registerForm.value.email, this.registerForm.value.password).subscribe(
res => {
console.log(res);
this.loading = false;
this.registerForm.reset();
this.success = true;
this.error = false;
},
err => {
console.log(err);
this.loading = false;
this.success = false;
this.error = this.errMsgs[err.error.error.message];
})
}
else {
}
}
Any help would be really Appreciated.
Thanks in Advance!
I'm not totally understand what you want to achieve in register component, but what's I've noticed there always response will be falsy, as you return undefined in service. Not sure what method authenticatedUser returns, but try it.
signUp(email: string, password: string) {
return this.http.post<AuthResponse>(`https://identitytoolkit.googleapis.com/v1/accounts:signUp?key=${config.API_KEY}`, {
email: email,
password: password,
returnSecureToken: true
}).pipe(
switchMap(data => {
return this.http.post<any>(`${config.BASE_URL}/api/ezusers/`,{useruid: data.idToken, 'isRegistered':false}); // or this.userId
})
).map(response =>
this.authenticatedUser(response.email, response.localId, response.idToken, +response.expiresIn)
);
I have a Vue application that I am trying to have it that when the user has not paid and trial is over they are redirected to /billing and displayed an error message. I also would like it so that if they have paid or are still in their trial period that they can use the application.
storeUser code in my store.js
storeUser(state, user) {
state.user = user
state.isPaid = user.isPaid
state.isTrial = user.isTrial
},
data passed into storeUser as 'user'
{
name: "Joe",
isPaid: false,
isTrial: false
}
Data showing in my vuex store using the chrome vui extention
{
name: "Joe",
isPaid: null,
isTrial: null
}
Not sure why the data is being input wrong since I can console.log the correct data in the storeUser function. However if I look into the user portion I can see it as the correct false values. When I try to specify this in the code below for the vue router it says that is can't read it because it's null. I assume this is just an async issue.
state in store.js
state: {
token: null,
isPaid: null,
isTrial: null,
error: {
registerErrorMessage: '',
loginErrorMessage: '',
resetError: ''
},
user: null
}
main.js which contains my vue router
} else if(to.path != '/billing' && !(store.state.isPaid && store.state.isTrial)) {
next({
path: '/billings',
query: {
paid: false,
}
})
Can anyone spot a potential issue or maybe a solution to my issue? This code should be enough to reproduce the issue though if missing I can provide more, there is no public repo to show the rest of the code in.
EDIT**
So something weird happened.. I am now seeing more correct data than before (isPaid and isTrial are valid) however I'm still not able to go to other routes now.
Adding output of store.state at the beginning of my beforeEach
{
token: 'random string',
isPaid: true,
isTrial: false,
error: {},
user: {
name: "Joe",
isPaid: true,
isTrial: false
}
}
EDIT 2**
storeUser({commit, state}) {
if(!state.token) return
axios.get('/user/userInfo')
.then(res => {
if(res.data.success) {
commit('storeUser', {
name: res.data.user.name,
isPaid: res.data.user.company.stripe.isPaid,
isTrial: res.data.user.company.stripe.isTrial
})
}
})
.catch(err => {
console.log(err)
})
},
EDIT 3**
Here is my whole vue route from main.js
router.beforeEach((to, from, next) => {
store.dispatch('tryAutoLogin')
console.log(store.state) // this is test only not for prod
if(!store.state.token && (to.path == '/login'
|| to.path == '/signup'
|| to.path == '/forgot'
|| to.path == '/resend'
|| to.path.includes('/confirmation/')))
{
return next()
} else if (to.path == '/signup') {
return next({ query: {plan: from.query.plan }})
} else if(to.path != '/billing' && !(store.state.isPaid && store.state.isTrial)) {
next({
path: '/billing',
query: {
paid: false,
}
})
} else if(store.state.token) {
return next()
} else {
return next('/login')
}
})
You can see I do the auto login which just checks if a token exists or not that's it. It's not related to the issue.
EDIT 4**
I have an idea but not sure how to implement it.. Use promises to make sure the data is right. My confusion on the promise part is getting them to work together. So I'm think authUser mutation then somehow make the storeUser action a promise that I can resolve in my beforeEach
Actions
tryAutoLogin({commit, dispatch}) {
const token = localStorage.getItem('token')
if(!token) {return}
commit('authUser',{
token
})
dispatch('storeUser')
},
storeUser({commit, state}) {
if(!state.token) return
axios.get('/user/userInfo')
.then(res => {
if(res.data.success) {
commit('storeUser', {
name: res.data.user.name,
companyName: res.data.user.company.companyName,
role: res.data.user.role,
isPaid: res.data.user.company.stripe.isPaid,
isTrial: res.data.user.company.stripe.isTrial
})
}
})
.catch(err => {
console.log(err)
})
},
Mutations
authUser(state, userData) {
state.token = userData.token
},
storeUser(state, user) {
state.user = user
state.isPaid = user.isPaid
state.isTrial = user.isTrial
},
Looks like you have a lot of duplicate and confusing code.
Why is there 2 of isPaid and isTrial in the state?
You also did not use the name property you provided to commit function.
The commit
commit('storeUser', {
name: res.data.user.name,
isPaid: res.data.user.company.stripe.isPaid,
isTrial: res.data.user.company.stripe.isTrial
});
const store = new Vuex.Store({
state: {
userName: '',
isPaid: false,
isTrial: false,
},
mutations: {
storeUser(state, data) {
state.userName = data.name;
state.isPaid = data.isPaid;
state.isTrial = data.isTrial;
},
},
});
Now you access the state store.state.isPaid and store.state.isTrial.
You can see a working example at this jsfiddle. If you open up the console you can see how the current state is logged.
I am learning how to create react Apps and got myself in very deep hole that I cannot leave by myself, so I've decided to reach out for help of the most spectacular people out there that are you! :)
When I run my App for the first time everything goes well until I logout and I try to log back in again and my session storage is not updated with the function.
It happens that after I run yarn add axios it works just fine and if I logout I have to run the command 'yarn add axios' and restart my server to get my login working for once more.
It is a pain in the neck.
I hope someone out there with fresh eyes can help me out on this.
Thank you so much!
import React, { Component } from "react";
import { Router } from "#reach/router";
import NavBar from "./components/navbar/NavBar";
import { SERVER_URL } from "./config.js";
import axios from "axios";
import "./index.css"
export default class App extends Component {
constructor(props) {
super(props);
let auth = JSON.parse(sessionStorage.getItem("auth"));
this.state = {
isLoggedIn: !!auth ? true : false,
currentUser: null
};
}
componentDidMount() {
this.getUser();
}
getUser() {
let auth = JSON.parse(sessionStorage.getItem("auth"));
if (!auth) return;
axios
.get(`${SERVER_URL}/api/users/${auth.userId}`, {
headers: { Authorization: `Bearer ${auth.token}` }
})
.then(response => {
this.setState({
currentUser: response.data,
isLoggedIn: true
});
});
}
handleLogin(email, password) {
axios
.post(`${SERVER_URL}/api/auth/get_token`, {
email: email,
password: password
})
.then(response => {
sessionStorage.setItem('auth', JSON.stringify(response.data));
this.getUser();
})
.catch(err => {
alert(err)
});
}
handleLogout() {
sessionStorage.setItem("auth", null);
this.setState({ currentUser: null, isLoggedIn: false });
}
render() {
const userProps = {
isLoggedIn: this.state.isLoggedIn,
currentUser: this.state.currentUser,
logout: () => this.handleLogout(),
login: (email, pass) => this.handleLogin(email, pass)
};
return (
<>
<NavBar user={userProps}></NavBar>
</>
);
}
}
This is my Rails backend that seems to be working well:
def get_by_id
user = User.find(params[:user_id])
if user
render json: { id: user.id, name: user.name, email: user.email},
status: :ok
else
render json: { errors: 'User not found' }, status: :not_found
end
end
def get_token
user = User.find_by_email(params[:email])
if !user
render json: { error: 'unauthorized' }, status: :unauthorized
return
end
if !user.authenticate( params[:password] )
render json: { error: 'unauthorized' }, status: :unauthorized
return
end
token = jwt_encode({user_id: user.id}, 24.hours.from_now)
render json: {token: token, exp: 24, username: user.email, userId: user.id},
status: :ok
return
end
The Json file that is returned by the backend looks good to me:
{
"token": "eyJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoxLCJleHAiOjE1NzY3MzcxOTZ9.aK7oLuHZ1r-aI8t-QVT0kV-i5mTYb3B9NiacWJJD9aU",
"exp": 24,
"username": "a#a.com",
"userId": 1
}
But even after getting an apparently good response from the backend I still cannot update my sessionStorage more than once =/
Are you getting any error? Or are you getting the response from get_token api when you retried login?
You don't need to yarn add axios everytime. If you think axios is the problem then you can use fetch api for http requests.
I would like to update my dashboard if there are any changes from backend as notification in Facebook.
I have two pages:
A page for the user sending a request message
A page for the user profile where the user can see all the request messages
If there is a new request message, the user needs to refresh the user profile in order to see the new message. I want the new message to be displayed without refreshing the page. Here is my code:
In a message page
state = {
team: {
message: 'Hi! I would like to join in your team! Please accept my request',
invitation_message: 'Hi! I would like to invite you to join in my team.',
email: '',
},
}
// Invite user to a team
handleInvite = event => {
event.preventDefault();
const userObject = JSON.parse(localStorage.getItem('user'));
const jwt = userObject.jwt;
const config = {
headers: { 'Authorization': `bearer ${jwt}` },
};
api
.post('/teammembers', {
team: this.state.teaminfo,
profile: responseData.data[0],
status: "invited",
message: this.state.team.invitation_message,
}, config)
.then(response => {
this.setState({
success_message: true,
})
console.log('Success', response);
})
.catch(err => {
console.log('An error occurred:', err);
});
}
In a user profile page
export class UserProfile extends React.Component {
import socketIOClient from "socket.io-client";
state = {
invited_teams:[],
endpoint: "myurl"
}
componentDidMount() {
const { endpoint } = this.state;
//Very simply connect to the socket
const socket = socketIOClient(endpoint);
socket.on('request', (data) => {
this.setState({ state: data.requests });
});
if (localStorage.getItem('userData')) {
const userObject = JSON.parse(localStorage.getItem('user'));
api
.get(`/profiles/?user=${userObject.user.id}`)
.then(responseData => {
this.setState({
invited_teams: responseData.data
})
}
}
}
Could anyone help me to solve this problem?
Use socket.IO library. You can set a listener on new request and then update the state.
socket.on('request' , (data) => {
this.setState({state: data.requests});
});
I have start my first unit test in react with jest this afternoon. The 5 firsts tests that i have to do are about testing the return functions. No so difficult.
But i have difficulty to understand how to unit test my function login that return something i dont understand yet. Is someone see what i have to put in my action.test.js, show me and explain me ?
How can i unit testing login and what represent the dispatch that return the login function ?
**In action.js**
<pre>
import { userConstants } from '../shared/constants';
import { userService } from '../shared/services';
import { history } from '../shared/helpers';
function request(user) {
return { type: userConstants.LOGIN_REQUEST, user };
}
function success(user) {
return { type: userConstants.LOGIN_SUCCESS, user };
}
function failure(error) {
return { type: userConstants.LOGIN_FAILURE, error };
}
function login(username, password) {
return (dispatch) => {
dispatch(request({ username }));
userService.login(username, password).then(
(user) => {
dispatch(success(user));
history.push('/');
},
(error) => {
dispatch(failure(error));
console.error(error); // eslint-disable-line no-console
},
);
};
}
function logout() {
userService.logout();
return { type: userConstants.LOGOUT };
}
function oldLogin() {
return { type: userConstants.OLD_LOGIN };
}
export const userActions = {
login,
logout,
oldLogin,
};
</pre>
**In service.js**
<pre>
function logout() {
// remove user from local storage to log user out
if (localStorage.getItem('user')) {
localStorage.removeItem('user');
}
}
function handleResponse(response) {
return response.text().then((text) => {
const data = text && JSON.parse(text);
if (!response.ok) {
if (response.status === 401) {
// auto logout if 401 response returned from api
logout();
window.location.reload(true);
}
const error = (data && data.message) || response.statusText;
return Promise.reject(error);
}
return data;
});
}
function login(username, password) {
return fetch(
'https://mon-api',
{
method: 'POST',
headers: {
Accept: 'application/json',
'Content-Type': 'application/json',
},
body: JSON.stringify({
username,
password,
context: {
deviceToken: '1cb1b51d19665cb45dc1caf254b02af',
},
}),
},
)
.then(handleResponse)
.then((user) => {
// login successful if there's a jwt token in the response
if (user.sessionToken) {
// store user details and jwt token in local storage to
// keep user logged in between page refreshes
localStorage.setItem('user', JSON.stringify(user));
}
return user;
});
}
export const userService = {
login,
logout,
};
</pre>
dispatch is a redux action. To be able to test you need to mock it. There are utilities like redux-mock-store that facilitate this task, refer to the following article for more details.