I have this code:
signin(context, payload, resolve) {
console.log("SIGNIN action")
const aemail = payload.locmail
const apassw = payload.locpass
backend.get("api/auth/signin", {
headers: {
'planck': 'FRESH'
},
crossDomain: true,
params: {
password: apassw,
email: aemail
}
}).then(function(ret) {
console.log("SENT")
if (ret.data.auth === 'TRUE') {
context.commit('authenticate', ret.data.planck)
state.isAuthenticated = true
} else {
state.isAuthenticated = false
}
console.log(ret)
return Promise.resolve(ret)
});
}
and when I call it from component:
this.$store.dispatch('signin', {
locmail,
locpass
}).then(ret => {
console.log(ret);
});
then console log prints undefined. What am I doing wrong here? In documentation I read that I should use resolve() but then I get error that it's not a function.
Return the backend promise in the signin action.
signin(context, payload) {
// ...
// The action needs to return a Promise
return backend.get("api/auth/signin", {
/* ...*/
}).then(function(ret) {
/* ... */
});
}
Also, it looks like you're changing a state object in the action and this should be limited to mutations.
If you're using the strict mode in development, you'll see a warning about this.
You are not returning the promise. Change your signin() function to this as #Emile Bergeron suggested:
signin(context, payload) {
console.log("SIGNIN action")
const aemail = payload.locmail
const apassw = payload.locpass
return backend.get("api/auth/signin", {
headers: {
'planck': 'FRESH'
},
crossDomain: true,
params: {
password: apassw,
email: aemail
}
}).then(function(ret) {
console.log("SENT")
if (ret.data.auth === 'TRUE') {
context.commit('authenticate', ret.data.planck)
state.isAuthenticated = true
} else {
state.isAuthenticated = false
}
console.log(ret)
return Promise.resolve(ret)
});
}
Related
I have a change password function that hits this api for verification and I want to display an error if the current password is incorrect.
Any direction on how to go about this or if what I'm doing makes no sense please point me in the right direction if would be so kind, it would be greatly appreciated!
case "PUT":
try {
const validContact = await Contact.findOne({ _id: req.body.id });
const valid = bcrypt.compareSync(
req.body.currentPassword,
validContact.password
);
if (valid) {
const hashedPassword = bcrypt.hashSync(
req.body.newPassword,
bcrypt.genSaltSync()
);
const contact = await Contact.findOneAndUpdate(
{ _id: req.body.id },
{ password: hashedPassword },
{ new: true }
);
res.status(200).json({ success: true, data: contact });
}
res.status(400).json({ success: false });
} catch (error) {
res.status(400).json({ success: false });
}
break;
This is the function that calls the API upon form submission
const submitNewPassword = (submitNewPasswordForm, resetForm) => {
submitNewPasswordForm(); // <-- I want to put this in a conditional
resetForm();
setOpenPasswordPopup(false);
setNotify({
isOpen: true,
message: "Password updated successfully",
type: "success",
});
};
edit: submitNewPassword function
const submitNewPasswordForm = async () => {
try {
const res = await fetch(`${process.env.APP_DOMAIN}/api/${apiRoute}`, {
method: "PUT",
headers: {
Accept: "application/json",
"Content-Type": "application/json",
},
body: JSON.stringify(values),
});
router.push(`${process.env.APP_DOMAIN}/`);
} catch (error) {
console.log(error);
}
};
Your submitNewPasswordForm doesn't return anything right now (well, it does, but it's just an empty Promise). To be able to check if it was a good request or not, you need to return something from it. Example:
const submitNewPasswordForm = async () => {
try {
const res = await fetch(`${process.env.APP_DOMAIN}/api/${apiRoute}`, {
method: "PUT",
headers: {
Accept: "application/json",
"Content-Type": "application/json",
},
body: JSON.stringify(values),
});
// this check is also necessary; 400 isn't an exception that would get caught
if (!res.ok) {
throw new Error(res.statusText)
}
router.push(`${process.env.APP_DOMAIN}/`);
return true;
} catch (error) {
console.log(error);
// you could also re-throw the error, or return something else
return false;
}
};
Because it's an async function, it returns a Promise, so you need to get the return value out with a .then:
const submitNewPassword = (submitNewPasswordForm, resetForm) => {
submitNewPasswordForm()
.then((ok) => {
if (!ok) {
// show error
} else {
resetForm();
setOpenPasswordPopup(false);
setNotify({
isOpen: true,
message: "Password updated successfully",
type: "success",
});
}
})
};
If you re-throw the error in the first function, you could .catch it rather than checking for an ok value. You could also make the second function an async function if you wanted to. Example:
const submitNewPassword = async (submitNewPasswordForm, resetForm) => {
try {
submitNewPasswordForm()
// rest of the code
} catch (err) {
// show error
}
}
Whichever way you go, you'll have to return something from the function in order to know if it was a success or not.
You will need to declare the submitNewPassword with async as you will require to await.
Then I added the await before the submitNewPasswordForm() and the happy and unhappy path.
You can check the result here.
const submitNewPassword = async (submitNewPasswordForm, resetForm) => {
const response = await submitNewPasswordForm(); // <-- I want to put this in a conditional
if(response.status === 200){
const body = await response.json()
//happy path
}else
{
const bodyError = await response.json()
//unhappy path
}
resetForm();
setOpenPasswordPopup(false);
setNotify({
isOpen: true,
message: "Password updated successfully",
type: "success",
});
};
I have spent the night looking for solutions to this issue, it seems like a lot of people have it and the best advice is often "just switch to SPA mode", which is not an option for me.
I have JWT for authentication, using the JWTSessions gem for Rails.
On the frontend, I have Nuxt with nuxt-auth, using a custom scheme, and the following authorization middleware:
export default function ({ $auth, route, redirect }) {
const role = $auth.user && $auth.user.role
if (route.meta[0].requiredRole !== role) {
redirect('/login')
}
}
The symptom I have is as follows: if I log in and navigate around restricted pages, everything works as expected. I even have fetchOnServer: false for restricted pages, as I only need SSR for my public ones.
However, once I refresh the page or just navigate directly to a restricted URL, I get immediately redirected to the login page by the middleware. Clearly, the user that's authenticated on the client side is not being authenticated on the server side too.
I have the following relevant files.
nuxt.config.js
...
plugins: [
// ...
{ src: '~/plugins/axios' },
// ...
],
// ...
modules: [
'cookie-universal-nuxt',
'#nuxtjs/axios',
'#nuxtjs/auth'
],
// ...
axios: {
baseURL: process.env.NODE_ENV === 'production' ? 'https://api.example.com/v1' : 'http://localhost:3000/v1',
credentials: true
},
auth: {
strategies: {
jwtSessions: {
_scheme: '~/plugins/auth-jwt-scheme.js',
endpoints: {
login: { url: '/signin', method: 'post', propertyName: 'csrf' },
logout: { url: '/signin', method: 'delete' },
user: { url: '/users/active', method: 'get', propertyName: false }
},
tokenRequired: true,
tokenType: false
}
},
cookie: {
options: {
maxAge: 64800,
secure: process.env.NODE_ENV === 'production'
}
}
},
auth-jwt-scheme.js
const tokenOptions = {
tokenRequired: true,
tokenType: false,
globalToken: true,
tokenName: 'X-CSRF-TOKEN'
}
export default class LocalScheme {
constructor (auth, options) {
this.$auth = auth
this.name = options._name
this.options = Object.assign({}, tokenOptions, options)
}
_setToken (token) {
if (this.options.globalToken) {
this.$auth.ctx.app.$axios.setHeader(this.options.tokenName, token)
}
}
_clearToken () {
if (this.options.globalToken) {
this.$auth.ctx.app.$axios.setHeader(this.options.tokenName, false)
this.$auth.ctx.app.$axios.setHeader('Authorization', false)
}
}
mounted () {
if (this.options.tokenRequired) {
const token = this.$auth.syncToken(this.name)
this._setToken(token)
}
return this.$auth.fetchUserOnce()
}
async login (endpoint) {
if (!this.options.endpoints.login) {
return
}
await this._logoutLocally()
const result = await this.$auth.request(
endpoint,
this.options.endpoints.login
)
if (this.options.tokenRequired) {
const token = this.options.tokenType
? this.options.tokenType + ' ' + result
: result
this.$auth.setToken(this.name, token)
this._setToken(token)
}
return this.fetchUser()
}
async setUserToken (tokenValue) {
await this._logoutLocally()
if (this.options.tokenRequired) {
const token = this.options.tokenType
? this.options.tokenType + ' ' + tokenValue
: tokenValue
this.$auth.setToken(this.name, token)
this._setToken(token)
}
return this.fetchUser()
}
async fetchUser (endpoint) {
if (this.options.tokenRequired && !this.$auth.getToken(this.name)) {
return
}
if (!this.options.endpoints.user) {
this.$auth.setUser({})
return
}
const user = await this.$auth.requestWith(
this.name,
endpoint,
this.options.endpoints.user
)
this.$auth.setUser(user)
}
async logout (endpoint) {
if (this.options.endpoints.logout) {
await this.$auth
.requestWith(this.name, endpoint, this.options.endpoints.logout)
.catch(() => {})
}
return this._logoutLocally()
}
async _logoutLocally () {
if (this.options.tokenRequired) {
this._clearToken()
}
return await this.$auth.reset()
}
}
axios.js
export default function (context) {
const { app, $axios, redirect } = context
$axios.onResponseError(async (error) => {
const response = error.response
const originalRequest = response.config
const access = app.$cookies.get('jwt_access')
const csrf = originalRequest.headers['X-CSRF-TOKEN']
const credentialed = (process.client && csrf) || (process.server && access)
if (credentialed && response.status === 401 && !originalRequest.headers.REFRESH) {
if (process.server) {
$axios.setHeader('X-CSRF-TOKEN', csrf)
$axios.setHeader('Authorization', access)
}
const newToken = await $axios.post('/refresh', {}, { headers: { REFRESH: true } })
if (newToken.data.csrf) {
$axios.setHeader('X-CSRF-TOKEN', newToken.data.csrf)
$axios.setHeader('Authorization', newToken.data.access)
if (app.$auth) {
app.$auth.setToken('jwt_access', newToken.data.csrf)
app.$auth.syncToken('jwt_access')
}
originalRequest.headers['X-CSRF-TOKEN'] = newToken.data.csrf
originalRequest.headers.Authorization = newToken.data.access
if (process.server) {
app.$cookies.set('jwt_access', newToken.data.access, { path: '/', httpOnly: true, maxAge: 64800, secure: false, overwrite: true })
}
return $axios(originalRequest)
} else {
if (app.$auth) {
app.$auth.logout()
}
redirect(301, '/login')
}
} else {
return Promise.reject(error)
}
})
}
This solution is already heavily inspired by material available under other threads and at this point I am pretty much clueless regarding how to authenticate my users universally across Nuxt. Any help and guidance much appreciated.
In order for You not to lose Your authentication session in the system, You first need to save your JWT token to some storage on the client: localStorage or sessionStorage or as well as token data can be saved in cookies.
For to work of the application will be optimally, You also need to save the token in the store of Nuxt. (Vuex)
If You save Your token only in srore of Nuxt and use only state, then every time You refresh the page, Your token will be reset to zero, since the state will not have time to initialize. Therefore, you are redirected to the page /login.
To prevent this from happening, after you save Your token to some storage, You need to read it and reinitialize it in the special method nuxtServerInit(), in the universal mode his will be work on the server side the very first. (Nuxt2)
Then, accordingly, You use Your token when sending requests to the api server, adding to each request that requires authorization, a header of the Authorization type.
Since Your question is specific to the Nuxt2 version, for this version a working code example using cookies to store the token would be:
/store/auth.js
import jwtDecode from 'jwt-decode'
export const state = () => ({
token: null
})
export const getters = {
isAuthenticated: state => Boolean(state.token),
token: state => state.token
}
export const mutations = {
SET_TOKEN (state, token) {
state.token = token
}
}
export const actions = {
autoLogin ({ dispatch }) {
const token = this.$cookies.get('jwt-token')
if (isJWTValid(token)) {
dispatch('setToken', token)
} else {
dispatch('logout')
}
},
async login ({ commit, dispatch }, formData) {
const { token } = await this.$axios.$post('/api/auth/login', formData, { progress: false })
dispatch('setToken', token)
},
logout ({ commit }) {
this.$axios.setToken(false)
commit('SET_TOKEN', null)
this.$cookies.remove('jwt-token')
},
setToken ({ commit }, token) {
this.$axios.setToken(token, 'Bearer')
commit('SET_TOKEN', token)
this.$cookies.set('jwt-token', token, { path: '/', expires: new Date('2024') })
// <-- above use, for example, moment or add function that will computed date
}
}
/**
* Check valid JWT token.
*
* #param token
* #returns {boolean}
*/
function isJWTValid (token) {
if (!token) {
return false
}
const jwtData = jwtDecode(token) || {}
const expires = jwtData.exp || 0
return new Date().getTime() / 1000 < expires
}
/store/index.js
export const state = () => ({
// ... Your state here
})
export const getters = {
// ... Your getters here
}
export const mutations = {
// ... Your mutations here
}
export const actions = {
nuxtServerInit ({ dispatch }) { // <-- init auth
dispatch('auth/autoLogin')
}
}
/middleware/isGuest.js
export default function ({ store, redirect }) {
if (store.getters['auth/isAuthenticated']) {
redirect('/admin')
}
}
/middleware/auth.js
export default function ({ store, redirect }) {
if (!store.getters['auth/isAuthenticated']) {
redirect('/login')
}
}
/pages/login.vue
<template>
<div>
<!-- Your template here-->
</div>
</template>
<script>
export default {
name: 'Login',
layout: 'empty',
middleware: ['isGuest'], // <-- if the user is authorized, then he should not have access to the page !!!
data () {
return {
controls: {
login: '',
password: ''
},
rules: {
login: [
{ required: true, message: 'login is required', trigger: 'blur' }
],
password: [
{ required: true, message: 'password is required', trigger: 'blur' },
{ min: 6, message: 'minimum 6 length', trigger: 'blur' }
]
}
}
},
head: {
title: 'Login'
},
methods: {
onSubmit () {
this.$refs.form.validate(async (valid) => { // <-- Your validate
if (valid) {
// here for example: on loader
try {
await this.$store.dispatch('auth/login', {
login: this.controls.login,
password: this.controls.password
})
await this.$router.push('/admin')
} catch (e) {
// eslint-disable-next-line no-console
console.error(e)
} finally {
// here for example: off loader
}
}
})
}
}
}
</script>
! - You must have the following packages installed:
cookie-universal-nuxt
jsonwebtoken
jwt-decode
I think you will find my answer helpful. If something is not clear, ask!
I have an error in react native that says '.then is not a function' in this code where I use .this in .then(()=>{this.manageAccess()})
What can I do?
Or tell me if there is a replacement for .this
export function signIn(data) {
const request = axios({
method:"POST",
url:SIGNIN,
data:{
email: data.email,
password: data.password,
returnSecureToken:true
},
headers:{
"Content-Type":"application/json"
}
}).then( response => {
return response.data
}).catch(e =>{
return false
});
return {
type: SIGN_USER,
payload: request
}
}
class LoginForm extends Component {
manageAccess = () => {
if(!this.props.User.userData.uid){
this.setState({hasErrors:true})
} else {
setTokens(this.props.User.userData,()=>{
this.setState({hasErrors:false});
this.props.navigation.navigate('Dashboard')
})
}
};
submitUserHandler = ()=>{
let isFromValid = true;
let formToSubmit= {};
if(isFromValid){
if(this.state.type === "Login"){
this.props.signIn(formToSubmit).then(()=>{
this.manageAccess()
})
}
}
};
}
Your signIn() function returns an object where the request object is in the payload property
Try changing to
this.props.signIn(formToSubmit).payload.then(...
I have a component which has a form where at the moment to do clic on submit button, I call a function handleSubmit (it is on my component), this function call an action through of dispatch and this action, I make a call to a service (HTTP Request).
handleSubmit
handleSubmit = (e) => {
e.preventDefault()
const { validateFields } = this.props.form;
validateFields((err, params) => {
if (!err) {
const { user, dispatch } = this.props;
let response = dispatch(actions.addDevice(params))
console.log(response); //Response is undefined
}
});
}
actions.addDevice
function addDevice(params){
return (dispatch, getState) => {
let { authentication } = getState();
dispatch(request({}));
service.addDevice(params, authentication.user.access_token)
.then(
response => {
if(response.status === 201) {
dispatch(success(response.data));
}
return response;
},
error => {
dispatch(failure(error.toString()));
dispatch(alertActions.error(error.toString()));
}
)
}
function request(response) { return { type: constants.ADD_DEVICE_REQUEST, response } }
function success(response) { return { type: constants.ADD_DEVICE_SUCCESS, response } }
function failure(error) { return { type: constants.ADD_DEVICE_FAILURE, error } }
}
service.addDevice
function addDevice(params, token){
return axios({
url: 'http://localhost:5000/user/add-device',
method: 'POST',
headers: { 'Authorization': 'Bearer ' + token},
data: {
data1: params.data1,
data2: params.data2,
data3: params.data3
}
})
.then(function(response) {
return response;
})
.catch(function(error) {
return error.response;
});
}
I would like to get the response in my component to be able to make validations but as the request is async, I never can get the response and only prints an undefined variable. How can I get the response sync? Or what do I need do to be able to make validations?
You are not returning the promise service.addDevice.
So you can do return service.addDevice... and in the handleSubmit you do dispatch(...).then((data) => ...do something with the data...)
let response = dispatch(actions.addDevice(params))
this is asynchronous. So it is not surprising to return undefined from console.log(). console.log() execute even before dispatch process is completed. Use promise or async await syntax. I would recommend using the async-await syntax.
handleSubmit = (e) => {
e.preventDefault()
const { validateFields } = this.props.form;
validateFields(async (err, params) => {
if (!err) {
const { user, dispatch } = this.props;
let response =await dispatch(actions.addDevice(params))
console.log(response); //Response is undefined
}
});
}
Please replace your code with this code
handleSubmit
handleSubmit = (e) => {
e.preventDefault()
const { validateFields } = this.props.form;
validateFields((err, params) => {
if (!err) {
const { user, dispatch } = this.props;
dispatch(actions.addDevice(params)).then((response)=>{
console.log(response);
})
}
});
}
actions.addDevice
function addDevice(params){
return (dispatch, getState) => {
let { authentication } = getState();
dispatch(request({}));
return service.addDevice(params, authentication.user.access_token)
.then(
response => {
if(response.status === 201) {
dispatch(success(response.data));
}
return response;
},
error => {
dispatch(failure(error.toString()));
dispatch(alertActions.error(error.toString()));
}
)
}
function request(response) { return { type: constants.ADD_DEVICE_REQUEST, response } }
function success(response) { return { type: constants.ADD_DEVICE_SUCCESS, response } }
function failure(error) { return { type: constants.ADD_DEVICE_FAILURE, error } }
}
Thanks for reading my question in advance. I'm using the dva and Ant Design Mobile of React handling phone register function.
Before sending the verify code, I will judge if the phone has been registered. If yes, it will Toast " This phone has been registered".
Now, the return value is correct:
const mapStateToProps = (state) => {
console.log(state.register.message)
}
// {code: 221, message: "This phone has been registered"}
So I write it as:
const mapStateToProps = (state) => ({
returnData: state.register.message
})
And then when I click the button, it will dispatch an action (send a request):
getVerifyCode() {
const { form, returnData } = this.props;
const { getFieldsValue } = form;
const values = getFieldsValue();
this.props.dispatcher.register.send({
phone: values.phone,
purpose: 'register',
})
// if(returnData.code === 221){
// Toast.fail("This phone has been registered", 1);
// } else {
// Toast.success("Send verify code successfully", 1);
// }
}
But when I tried to add the if...else condiction according to the return value
if(returnData.code === 221){
Toast.fail("This phone has been registered", 1);
} else {
Toast.success("Send verify code successfully", 1);
}
only to get the error:
Uncaught (in promise) TypeError: Cannot read property 'code' of
undefined
I supposed it's the problem about aynchromous and tried to use async await:
async getVerifyCode() {
...
await this.props.dispatcher.register.send({
phone: values.phone,
purpose: 'register',
})
}
But get the same error
Cannot read property 'code' of undefined
I wonder why and how to fix this problem ?
added: this is the models
import * as regiserService from '../services/register';
export default {
namespace: 'register',
state: {},
subscriptions: {
},
reducers: {
save(state, { payload: { data: message, code } }) {
return { ...state, message, code };
},
},
effects: {
*send({ payload }, { call, put }) {
const { data } = yield call(regiserService.sendAuthCode, { ...payload });
const message = data.message;
yield put({ type: 'save', payload: { data },});
},
},
};
handle conditions in the models solved the problem:
*send({ payload }, { call, put }) {
const { data } = yield call(regiserService.sendAuthCode, { ...payload });
if(data.code === 221){
Toast.fail("This phone has been registered", 1);
} else {
Toast.success("Send verify code successfully", 1);
}
yield put({ type: 'save', payload: { data }});
}