Not able to access token using axios and djangorestframework-jwt - vue.js

I am trying to make an application which servers api through django rest framework and the frontend is done in vue.js2.
I am referring this blog to help me authenticating jwt via axios. I also tried this one when I couldn't make the first one run correctly
Here is my settings.py file
INSTALLED_APPS = [
...
'rest_framework',
'rest_framework.authtoken',
'admindash',
'corsheaders',
]
MIDDLEWARE = [
'corsheaders.middleware.CorsMiddleware',
...
'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
]
# Password validation
# https://docs.djangoproject.com/en/2.1/ref/settings/#auth-password-validators
AUTH_PASSWORD_VALIDATORS = [
{
'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
},
{
'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
},
{
'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
},
{
'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
},
]
REST_FRAMEWORK = {
'DEFAULT_PERMISSION_CLASSES': (
'rest_framework.permissions.IsAdminUser',
),
'TEST_REQUEST_RENDERER_CLASSES': (
'rest_framework.renderers.MultiPartRenderer',
'rest_framework.renderers.JSONRenderer',
'rest_framework.renderers.TemplateHTMLRenderer'
),
'DEFAULT_AUTHENTICATION_CLASSES': (
'rest_framework_jwt.authentication.JSONWebTokenAuthentication',
'rest_framework.authentication.SessionAuthentication',
),
'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.LimitOffsetPagination',
'PAGE_SIZE': 20,
}
JWT_AUTH = {
'JWT_ALLOW_REFRESH': True,
'JWT_EXPIRATION_DELTA': timedelta(hours=1),
'JWT_REFRESH_EXPIRATION_DELTA': timedelta(days=7),
}
#Cors origin
CORS_ORIGIN_WHITELIST = (
'localhost:8080'
)
CORS_ORIGIN_ALLOW_ALL = True
CORS_ALLOW_CREDENTIALS = True
Here is my vue store
axios.defaults.xsrfCookieName = 'csrftoken'
axios.defaults.xsrfHeaderName = 'X-CSRFToken'
export default new Vuex.Store({
state: {
authUser: {},
isAuthenticated: false,
jwt: localStorage.getItem('token'),
endpoints: {
obtainJWT: 'http://127.0.0.1:8000/api/v1/auth/obtain_token/',
refreshJWT: 'http://127.0.0.1:8000/api/v1/auth/refresh_token/',
baseUrl: 'http://127.0.0.1:8000/api/v1'
}
},
mutations: {
setAuthUser(state, {
authUser,
isAuthenticated
}) {
Vue.set(state, 'authUser', authUser)
Vue.set(state, 'isAuthenticated', isAuthenticated)
},
updateToken(state, newToken) {
localStorage.setItem('token', newToken);
state.jwt = newToken;
},
removeToken(state) {
localStorage.removeItem('token');
state.jwt = null;
}
},
actions: {
obtainToken(context, {username, password}) {
const payload = {
username: username,
password: password
}
const headers= {
'Content-Type': 'application/json'
}
axios.post(this.state.endpoints.obtainJWT, headers, payload)
.then((response) => {
this.commit('updateToken', response.data.token);
console.log(this.state.jwt);
})
.catch((error) => {
console.log(error);
})
},
refreshToken () {
const payload = {
token: this.state.jwt
}
axios.post(this.state.endpoints.refreshJWT, payload)
.then((response) => {
this.commit('updateToken', response.data.token);
console.log(this.state.jwt)
})
.catch((error) => {
console.log(error)
})
}
}
})
and here is login.vue
<script>
import axios from 'axios'
import FormInput from './FormInput'
export default {
name: 'Login',
components: {
FormInput
},
data () {
return {
username: '',
password: ''
}
},
computed: {
/* ...mapState([
'jwt',
'endpoints'
]) */
},
methods: {
/* ...mapActions([
'obtainToken'
]), */
authenticateBeforeSubmit () {
this.$store.dispatch('obtainToken', {
username: this.username,
password: this.password
}).then(() => {
this.$router.push('/')
}).catch((error) => {
console.log(error)
})
/* const payload = {
username: this.username,
password: this.password
}
axios.post(this.$store.state.endpoints.obtainJWT, payload)
.then((response) => {
this.$store.commit('updateToken', response.data.token)
console.log(this.$store.state.jwt);
const base = {
baseUrl: this.$store.state.endpoints.baseUrl,
headers: {
Authorization: `JWT ${this.$store.state.jwt}`,
'Content-Type': 'application/json'
},
xhrFields: {
withCredentials: true
}
}
const axiosInstance = axios.create(base)
axiosInstance({
url: "/user/",
method: "get",
params: {}
})
.then((response) => {
this.$store.commit("setAuthUser",
{authUser: response.data, isAuthenticated: true}
)
this.$router.push({name: 'Home'})
})
})
.catch((error) => {
console.log(error);
console.debug(error);
console.dir(error);
}) */
}
}
}
</script>
Now the problem is I am getting two errors
Just as I load login view in browser, i get this error
Uncaught (in promise) TypeError: Cannot read property 'protocol' of undefined
at isURLSameOrigin (VM34519 isURLSameOrigin.js:57)
at dispatchXhrRequest (VM34513 xhr.js:109)
at new Promise ()
at xhrAdapter (VM34513 xhr.js:12)
at dispatchRequest (VM34521 dispatchRequest.js:59)
isURLSameOrigin # VM34519 isURLSameOrigin.js:57
dispatchXhrRequest # VM34513 xhr.js:109
xhrAdapter # VM34513 xhr.js:12
dispatchRequest # VM34521 dispatchRequest.js:59
18:29:09.976
I don't have slightest idea what this error is about, I searched it and i didn't find anything that works
This I get when I click submit which fires authenticateBeforeSubmit method
Uncaught TypeError: Cannot read property 'dispatch' of undefined
at VueComponent.authenticateBeforeSubmit (VM34576 Login.vue:68)
at invoker (VM34494 vue.esm.js:2026)
at HTMLButtonElement.fn._withTask.fn._withTask (VM34494 vue.esm.js:1825)
authenticateBeforeSubmit # VM34576 Login.vue:68
invoker # VM34494 vue.esm.js:2026
fn._withTask.fn._withTask # VM34494 vue.esm.js:1825
18:29:30.912
What i understand it is saying that i am calling dispatch on action incorrectly but i don't get how to make it work
As I click submit it "redirects" to
http://127.0.0.1:8080/login?username=f1uk3r&password=thisissparta
but it doesn't recieves any token
I have tested token using
curl -X POST -H "Content-Type: application/json" -d '{"username":"f1uk3r","password":"thisissparta"}' http://127.0.0.1:8000/api/v1/auth/obtain_token/
and it gives me a token so there shouldn't be any problem in django part I think. What am I doing wrong, how can I rectify it.

I figured it out, so answering for future reference and if anybody else finds this useful
TypeError: Cannot read property 'protocol' of undefined at isURLSameOrigin
this can be solved by importing axios and VueAxios correctly
I initialized it like this
import Vue from 'vue'
import VueAxios from 'vue-axios'
import Vuex from 'vuex'
import axios from 'axios'
Vue.use(Vuex);
Vue.use(axios, VueAxios);
While it should have been initialized like this
import Vue from 'vue'
import VueAxios from 'vue-axios'
import Vuex from 'vuex'
import axios from 'axios'
Vue.use(Vuex);
Vue.use(VueAxios, axios);
Cannot read property 'dispatch' of undefined at VueComponent
Again I wasn't initializing store correctly so i made a directory in "src" directory named it "store" and in this directory I made a file named "store.js"
Then in main.js import store and initialize in the instance
import store from './store/store.js';
Vue.use(VeeValidate);
/* eslint-disable no-new */
new Vue({
el: '#app',
router,
store,
components: { App },
template: '<App/>'
})

Related

Why Vitest mock Axios doesn't work on vuex store testing?

I have the same issue than this post: Why does vitest mock not catch my axios get-requests?
I would like to test my vuex store on vuejs and it works for getters etc but not for actions part with axios get request.
I don't know if it's a good practice to test vuex store than the component in Vue ?
But I guess I need to test both, right ?
a project https://stackblitz.com/edit/vitest-dev-vitest-nyks4u?file=test%2Ftag.spec.js
my js file to test tag.js
import axios from "axios";
const actions = {
async fetchTags({ commit }) {
try {
const response = await axios.get(
CONST_CONFIG.VUE_APP_URLAPI + "tag?OrderBy=id&Skip=0&Take=100"
);
commit("setTags", response.data);
} catch (error) {
console.log(error);
return;
}
},
};
export default {
state,
getters,
actions,
mutations,
};
then my test (tag.spec.js)
import { expect } from "chai";
import { vi } from "vitest";
import axios from "axios";
vi.mock("axios", () => {
return {
default: {
get: vi.fn(),
},
};
});
describe("tag", () => {
test("actions - fetchTags", async () => {
const users = [
{ id: 1, name: "John" },
{ id: 2, name: "Andrew" },
];
axios.get.mockImplementation(() => Promise.resolve({ data: users }));
axios.get.mockResolvedValueOnce(users);
const commit = vi.fn();
await tag.actions.fetchTags({ commit });
expect(axios.get).toHaveBeenCalledTimes(1);
expect(commit).toHaveBeenCalledTimes(1);
});
});
It looks like some other peolpe have the same issues https://github.com/vitest-dev/vitest/issues/1274 but it's still not working.
I try with .ts too but I have exactly the same mistake:
FAIL tests/unit/store/apiObject/tag.spec.js > tag > actions - fetchTags
AssertionError: expected "spy" to be called 1 times
❯ tests/unit/store/apiObject/tag.spec.js:64:24
62| await tag.actions.fetchTags({ commit });
63|
64| expect(axios.get).toHaveBeenCalledTimes(1);
| ^
65| expect(commit).toHaveBeenCalledTimes(1);
66| });
Expected "1"
Received "0"
Thanks a lot for your help.
I finally found the mistake, it was on my vitest.config.ts file, I have to add my global config varaible for my api: import { config } from "#vue/test-utils";
import { defineConfig } from "vitest/config";
import { resolve } from "path";
var configApi = require("./public/config.js");
const { createVuePlugin } = require("vite-plugin-vue2");
const r = (p: string) => resolve(__dirname, p);
export default defineConfig({
test: {
globals: true,
environment: "jsdom",
},
define: {
CONST_CONFIG: configApi,
},
plugins: [createVuePlugin()],
resolve: {
alias: {
"#": r("."),
"~": r("."),
},
// alias: {
// "#": fileURLToPath(new URL("./src", import.meta.url)),
// },
},
});

Catch(error) on dispatched method in store not working in Vue 3

I am working on login of a vue 3 app, both the login and registration work fine, but i still need to throw send back a meaningful response to user if login in credentials are rejected by the back-end, i have tried every possible means to log the rejection response from server to console but to no avail, the login is fine when credential is correct, but the console just stay mute when incorrect credential is entered
this is my login.vue
import store from "../store"
import { useRouter } from "vue-router";
import { ref } from "vue";
const router = useRouter()
const user = { email: '', password: '', remember : false }
let errorMsg = ref('');
async function login(ev) {
ev.preventDefault();
await store.dispatch('login', user)
.then(()=> {
router.push({
name: 'Dashboard'
})
})
.catch((err) => {
errorMsg = err.response.data.error
console.log(err)
})
}
and this is my vuex store
import {createStore} from 'vuex'
import axiosClient from "../axios";
const store = createStore({
state: {
user: {
data: {},
token: sessionStorage.getItem('TOKEN')
}
},
getters: {},
setters: {},
actions: {
register({commit}, user) {
return axiosClient.post('/register', user)
.then(({data}) => {
commit('setUser', data);
return data;
})
},
login({commit}, user) {
return axiosClient.post('/login', user)
.then(({data}) => {
commit('setUser', data);
return data;
})
},
},
mutations: {
logout: state => {
state.user.data = {};
state.user.token = null;
},
setUser: (state, userData)=> {
state.user.token = userData.token;
state.user.data = userData.user;
sessionStorage.setItem('TOKEN', userData.token)
}
},
modules: {}
})
export default store;
And here is my axios js file
import axios from "axios";
import store from "./store";
const axiosClient = axios.create({
baseURL: 'http://localhost:8000/api'
})
axiosClient.interceptors.request.use(config=> {
config.headers.Authorization = `Bearer ${store.state.user.token}`
return config;
})
export default axiosClient;
Response from backend as seen from Network Tab
{"error":"The provided credentials are incorrect","0":422}
After checking through my controller in my Laravel project, I discovered that I did not set the status code for the response properly.
Incorrect code:
if (!Auth::attempt($credentials, $remember)) {
return response([
'error'=> 'The provided credentials are incorrect',
422
]);
}
Corrected code:
if (!Auth::attempt($credentials, $remember)) {
return response([
'error'=> 'The provided credentials are incorrect',
], 422);
}
Axios does not treat the response received as a rejection; which needs to get its catch triggered.
Therefore my console.log that I had in my try/catch does not run at all.
I'm very happy we got this solved, big thanks to every one.

vuex "url" of undefined using axios

I've recently learnt a bit about vuex and store.
I was about to use it for calling my api but it keeps saying my
url is undefined.
here is my vuex codes:
import { createStore } from 'vuex'
import axios from "axios";
const url = 'https://myurl'
export default createStore({
state: {
catList: [],
transactList: [],
user: [],
requestList: [],
catInList: [],
productList: [],
errorMs: '',
calling: false,
mobile: ''
},
getters: {
allUsers: (state) => state.user,
transactList: (state) => state.transactList,
categoryList: (state) => state.catList,
requestList: (state) => state.requestList,
productList: (state) => state.productList,
},
mutations: {
SET_Users (state, user) {
state.user = user
}
},
actions: {
checkAuth() {
const token = localStorage.getItem('token') ? localStorage.getItem('token') : ''
axios.defaults.baseURL = url
axios.defaults.headers.common['Authorization'] = token ? `Bearer ${token}` : ''
axios.defaults.headers.post['Content-Type'] = 'application/x-www-form-urlencoded'
},
async axiosPost ({dispatch} ,{url}) {
dispatch('checkAuth')
await axios.post(url+'/login', {
mobile: this.mobile
}).then(response => {
this.calling = false
localStorage.setItem('token', response.data.token)
})
},
async axiosGet ({dispatch} , {url, formData}) {
dispatch('checkAuth')
await axios.get(url, formData).catch(err => {
console.log(err)
})
}
},
created() {
}
})
actually I wanted to define my api globally, so that I can use it for different components only by adding url + '/login' but I'm not sure why it keeps saying my url is not defined.
can anyone help me with the errors?

Jest + Vue test utlis TypeError: Cannot set property 'content' of null [duplicate]

This question already has an answer here:
Test suite failed to run TypeError: Cannot set property 'content' of null Running in Jest
(1 answer)
Closed 1 year ago.
I am writing a test on a Vue project, and I am new to Framwork Jest and Vue testing utilities, have not found solutions to similar issues, have tried with multiple components but the error is still similar, I thought about Mock axios, it didn't work, if someone can help me I'm stuck,
if someone can offer me a solution or have an idea how it goes, it will be perfect
Api.js
import axios from 'axios'
import qs from 'qs';
import router from '#/router';
import defaultExport from '#/store';
let apiBaseUrl = document.querySelector('meta[name="apiBaseUrl"]');
if(!apiBaseUrl) {
apiBaseUrl.content = '/api/';
}
let newAxios = axios.create({
headers: {
// A fix for IE11 - we need to define Pragma header
Pragma: 'no-cache',
// 'X-Requested-With': 'XMLHttpRequest'
},
withCredentials: true,
//baseURL: apiBaseUrl.content,
paramsSerializer: function (params) {
return qs.stringify(params)
}
});
Login.vue
<script>
****
data() {
return {
currentMode: "login",
passwordForgotMode: false,
registerMode: false,
email: "",
password: "",
rememberMe: false,
emailRules: [
v => !!v || 'E-Mail wird benötigt',
],
passwordRules: [
v => !!v || 'Passwort wird benötigt',
],
valid: false,
}
},
computed: {
...mapGetters({
isAdministrator: 'account/isAdministrator',
})
},
methods: {
...mapActions({
handleLogin: 'account/handleLogin',
addSnackbarFromError: 'app/addSnackbarFromError',
}),
send() {
if (this.$refs.form.validate()) {
this.handleLogin({
rememberMe: this.rememberMe,
email: this.email,
password: this.password,
})
.then(() => {
window.localStorage.setItem('logged_in', true);
if (this.$route.query.redirect) {
this.$router.push(decodeURIComponent(this.$route.query.redirect));
} else {
if (this.$store.getters["account/isAdministrator"]) {
this.$router.push({name: 'userNotificationsOverview'});
} else {
this.$router.push({name: 'startingSite'});
}
}
})
.catch((error) => {
this.password = '';
this.addSnackbarFromError(error)
})
}
}
}
}
</script>
Test.test.js
import { shallowMount, createLocalVue } from '#vue/test-utils';
import Vuex from 'vuex';
import Login from "#pages/Login";
let wrapper;
let store;
let actions;
let mutations;
let state;
const localVue = createLocalVue();
localVue.use(Vuex);
beforeEach(() => {
actions = {
someAction: jest.fn()
};
mutations = {
someMutation: jest.fn()
};
state = {
key: {}
};
store = new Vuex.Store({
actions,
mutations,
state,
});
wrapper = shallowMount(Login, {
propsData: {},
attachTO: '#root',
mocks: {},
stubs: {},
methods: {},
store,
localVue,
});
});
afterEach(() => {
wrapper.destroy();
});
describe('Component', () => {
test('is a Vue instance', () => {
expect(wrapper.contains('h2')).toBe(true)
});
});
● Test suite failed to run
TypeError: Cannot set property 'content' of null
let apiBaseUrl = document.querySelector('meta[name="apiBaseUrl"]');
7 | if(!apiBaseUrl) {
> 8 | apiBaseUrl.content = '/api/';
| ^
9 | }
10 |
11 | let newAxios = axios.create({
The bug is here:
if(!apiBaseUrl) {
^^^^^^^^^^^
apiBaseUrl.content = '/api/';
}
The !apiBaseUrl condition checks that apiBaseUrl is falsy (null or undefined), and then tries to use it, leading to the error you observed.
Simply remove the ! operator from the condition to fix that error:
if(apiBaseUrl) {
apiBaseUrl.content = '/api/';
}

Issue importing default actions for vuex

Im creating a simple spa todo app with vue + vuex.
My problem is that each module will have the same 5 default method for manipulating the state. If i decide to change the default state management behavior then i have to go to every module and update them. The five actions that all modules should have when written out in the module work, but as soon as i import the exact same object and assign it to the actions property on the module the action cant be found. and i get this error [vuex] unknown action type: namespacedTodos/getCollection
// This is in a component
mounted: function () {
this.$store.dispatch('namespacedTodos/getCollection')
},
// import baseActions from '../base-actions'
import baseGetters from '../base-getters'
import baseMutations from '../base-mutations'
import axios from 'axios/index'
import mainStore from '../index'
// import _ from 'lodash'
const namespacedTodos = {
namespaced: true,
state: {
collection: [],
defaultInstance: {},
collectionLoaded: false,
url: 'api/todos',
namespace: 'namespacedTodos'
},
mutations: baseMutations,
getters: baseGetters,
actions: {
getCollection: function ({state, commit}) {
if (state.collectionLoaded) {
return Promise.resolve({data: state.collection})
}
return axios.get(`${mainStore.state.baseUrl}/${state.url}`)
.then((response) => {
commit(`setCollection`, response.data.data)
return response
})
.catch((response) => {
console.log('Error Response: ', response)
throw response
})
}
},
strict: process.env.NODE_ENV !== 'production'
}
export default namespacedTodos
The above Code Works But the following Dose Not
import baseActions from '../base-actions'
import baseGetters from '../base-getters'
import baseMutations from '../base-mutations'
const namespacedTodos = {
namespaced: true,
state: {
collection: [],
defaultInstance: {},
collectionLoaded: false,
url: 'api/todos',
namespace: 'namespacedTodos'
},
mutations: baseMutations,
getters: baseGetters,
actions: baseActions,
strict: process.env.NODE_ENV !== 'production'
}
export default namespacedTodos
import axios from 'axios'
import _ from 'lodash'
import mainStore from './index'
export default {
getCollection: function ({state, commit}) {
if (state.collectionLoaded) {
return Promise.resolve({data: state.collection})
}
console.log('this: ', this)
console.log('Namespace: ', state.namespace)
return axios.get(`${mainStore.state.baseUrl}/${state.url}`)
.then((response) => {
commit(`setCollection`, response.data.data)
return response
})
.catch((response) => {
console.log('Error Response: ', response)
throw response
})
},
}
import baseActions from '../base-actions'
import baseGetters from '../base-getters'
import baseMutations from '../base-mutations'
const todos = {
namespaced: true,
state: {
collection: [],
defaultInstance: {},
collectionLoaded: false,
url: 'api/todos'
},
// The mutations get namespaced!!
mutations: Object.assign(baseMutations, {}),
// The getters get namespaced!!
getters: Object.assign(baseGetters, {}),
// The actions get namespaced!!
actions: Object.assign(baseActions, {
// any methods defined here will also be available
// You can over write existing methods when nessicary
}),
strict: process.env.NODE_ENV !== 'production'
}
export default todos