I am newbie with Ember and trying to implement a basic auth (username + password in the 'authentication' header) with a custom server using ember simple auth and ember-cli. The problem is that the credentials object is undefined when it is received within the 'authenticate' method defined in the CustomAuthenticator.
What's wrong with this code?
app/initializers/login.js
import Ember from 'ember';
import BaseAuthenticator from 'simple-auth/authenticators/base';
import BaseAuthorizer from 'simple-auth/authorizers/base';
window.ENV = window.ENV || {};
window.ENV['simple-auth'] = {
authorizer: 'authorizer:custom',
session: 'session:withCurrentUser'
};
export default {
name: 'authentication',
before: 'simple-auth',
initialize: function(container/*, application*/) {
container.register('authorizer:custom', CustomAuthorizer);
container.register('authenticator:custom', CustomAuthenticator);
}
};
var CustomAuthorizer = BaseAuthorizer.extend({
authorize: function(jqXHR/*, requestOptions*/) {
if (this.get('session.isAuthenticated') && !Ember.isEmpty(this.get('session.token'))) {
jqXHR.setRequestHeader('Authorization', 'Token: ' + this.get('session.token'));
}
}
});
var CustomAuthenticator = BaseAuthenticator.extend({
tokenEndpoint: '/v1/login',
restore: function(data) {
return new Ember.RSVP.Promise(function(resolve, reject) {
if (!Ember.isEmpty(data.token)) {
resolve(data);
} else {
reject();
}
});
},
authenticate: function(credentials) {
//*** HERE THE CREDENTIALS OBJECT IS NULL ***
var _this = this;
if(!Ember.isEmpty(credentials.identification)) {
return this._super(credentials);
} else {
return new Ember.RSVP.Promise(function(resolve, reject) {
Ember.$.ajax({
url: _this.tokenEndpoint,
type: 'POST',
data: JSON.stringify({ session: { identification: credentials.identification, password: credentials.password } }),
contentType: 'application/json'
}).then(function(response) {
Ember.run(function() {
resolve({ token: response.session.token });
});
}, function(xhr/*, status, error*/) {
var response = JSON.parse(xhr.responseText);
Ember.run(function() {
reject(response.error);
});
});
});
}
},
invalidate: function() {
var _this = this;
return new Ember.RSVP.Promise(function(resolve) {
Ember.$.ajax({ url: _this.tokenEndpoint, type: 'DELETE' }).always(function() {
resolve();
});
});
},
});
app/pods/login/controller.js
import AuthenticationControllerMixin from 'simple-auth/mixins/authentication-controller-mixin';
import Ember from 'ember';
export default Ember.Controller.extend(AuthenticationControllerMixin, {
authenticator: 'authenticator:custom'
});
app/pods/login/template.hbs
<div class='container'>
<form {{action 'authenticate' on='submit'}}>
<label for="identification">Login</label>
{{input value=identification placeholder='Enter Login'}}
<label for="password">Password</label>
{{input value=password placeholder='Enter Password' type='password'}}
<button type="submit">Login</button>
</form>
{{#if errorMessage}}
<div class="alert alert-danger">
<p>
<strong>Login failed:</strong> <code>{{errorMessage}}</code>
</p>
</div>
{{/if}}
</div>
import LoginControllerMixin from 'simple-auth/mixins/login-controller-mixin';
instead of
import AuthenticationControllerMixin from 'simple-auth/mixins/authentication-controller-mixin';
Related
I included a Vue form component in one of my Django templates. Now, I would like to send a CSRF token along with the data, since Django views require a CSRF token. Is there any way I can include it in my Vue form?
Here is my component:
<template>
<form #submit.prevent="formSubmit()">
<input type="text" class="form-control" v-model="amount">
<br>
<input type="text" class="form-control" v-model="price">
<br>
<button class="btn btn-primary" style="width: 100%">BUY</button>
</form>
</template>
<script>
import axios from 'axios'
export default {
mounted() {
console.log('Component mounted.')
},
data() {
return {
name: '',
description: '',
output: ''
};
},
methods: {
formSubmit() {
let currentObj = this;
axios.post('MY_URL', {
price: this.price,
amount: this.amount,
})
.then(function (response) {
currentObj.output = response.data;
}.bind(this))
.catch(function (error) {
currentObj.output = error;
});
},
}
}
</script>
First, acquire the token from the csrftoken cookie:
function getCookie(name) {
let cookieValue = null;
if (document.cookie && document.cookie !== '') {
const cookies = document.cookie.split(';');
for (let i = 0; i < cookies.length; i++) {
const cookie = cookies[i].trim();
// Does this cookie string begin with the name we want?
if (cookie.substring(0, name.length + 1) === (name + '=')) {
cookieValue = decodeURIComponent(cookie.substring(name.length + 1));
break;
}
}
}
return cookieValue;
}
const csrftoken = getCookie('csrftoken');
...or from querying the document:
const csrftoken = document.querySelector('[name=csrfmiddlewaretoken]').value
Then, add the token value to your POST header:
axios.post('MY_URL', {
price: this.price,
amount: this.amount,
}, {
headers: {
'X-CSRFToken': csrftoken
}
})
I have a component that I want to test that uses Vuex.
components/Main/Header.vue
<template>
<v-container fluid grid-list-xl>
<v-card flat class="header" color="transparent">
<v-layout align-center justify-start row fill-height class="content">
<v-flex xs5>
<v-img :src="avatar" class="avatar" aspect-ratio="1" contain></v-img>
</v-flex>
<v-flex xs7>
<div class="main-text font-weight-black">
WELCOME
</div>
<div class="sub-text">
{{currentLocation.description}}
</div>
<div #click="showStory" class="show-more font-weight-bold">
Explore More
</div>
</v-flex>
</v-layout>
</v-card>
</v-container>
</template>
<script>
import avatar from '../../assets/images/home/avatar.png';
export default {
name: 'Header',
computed: {
currentLocation() {
return this.$store.getters.getCurrentLocation;
},
avatar() {
return avatar;
}
},
methods: {
showStory() {
this.$router.push( { name: 'Stories', params: { name: 'Our Story' } });
}
}
}
</script>
and from /test/unit/components/Main/Header.spec.js
import Vuex from 'vuex'
import { shallowMount, createLocalVue } from "#vue/test-utils"
import Header from "#/components/Main/Header.vue"
const localVue = createLocalVue()
localVue.use(Vuex)
const store = new Vuex.Store({
state: {
currentLocation: {
name: 'this is a name',
description: "lorem ipsum",
latitude: '1.123123',
longitude: '103.123123',
radius: '5000'
}
},
getters: {
getCurrentLocation: (state) => state.currentLocation
}
})
describe('Header', () => {
const wrapper = shallowMount(Header, {
store,
localVue
})
it('should render a computed property currentLocation', () => {
expect(Header.computed.currentLocation()).toBe(store.getters.getCurrentLocation)
});
});
The error I'm getting is from the computed property currentLocation
TypeError: Cannot read property 'getters' of undefined
I'm create user login page in vue js and consuming data from django with axios. I have utilized jwt to create token session in client-side
The problem is the session is not saved when the page is refreshed. I have frustated because it. This is my source code :
In '../src/store/modules/auth.js'
import Vue from 'vue'
import Axios from 'axios'
import 'es6-promise/auto'
// In order that Axios work nice with Django CSRF
Axios.defaults.xsrfCookieName = 'csrftoken'
Axios.defaults.xsrfHeaderName = 'X-CSRFToken'
const state = {
authUser: {},
users: [],
isAuthenticated: false,
jwt: localStorage.getItem('token'),
endpoints: {
obtainJWT: 'http://127.0.0.1:8000/api/auth/obtain_token/',
refreshJWT: 'http://127.0.0.1:8000/api/auth/refresh_token/',
baseUrl: 'http://127.0.0.1:8000/api/auth/',
register: 'http://127.0.0.1:8000/signup/'
}
}
const 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;
},
}
const actions = {
refreshToken(){
const payload = {
token: this.state.jwt
}
Axios.post(state.endpoints.refreshJWT, payload)
.then((response)=>{
this.commit('updateToken', response.data.token)
})
.catch((error)=>{
console.log(error)
})
}
}
export default {
state,
mutations,
actions,
}
In '../src/store/index.js'
import Vue from 'vue'
import Vuex from 'vuex'
import axios from 'axios'
import auth from './modules/auth'
Vue.use(Vuex)
// Make Axios play nice with Django CSRF
axios.defaults.xsrfCookieName = 'csrftoken'
axios.defaults.xsrfHeaderName = 'X-CSRFToken'
export default new Vuex.Store({
modules: {
auth
},
})
In '../src/components/login.vue'
<template>
<div class="login">
<form>
<label for="username">Username</label>
<input
type="text"
name="username"
v-model="username"
/><br>
<label for="password">Password</label>
<input
type="password"
name="password"
v-model="password"
/><br>
<input
type="button"
#click="login()"
value="Login"
/>
</form>
</template>
<script>
import axios from 'axios'
/* eslint-disable */
export default {
name: 'Login',
data(){
return {
username: '',
password: ''
}
},
methods: {
login(){
const payload = {
username: this.username,
password: this.password
}
axios.post(this.$store.state.auth.endpoints.obtainJWT, payload)
.then((response) => {
this.$store.commit('updateToken', response.data.token)
this.$store.commit('setAuthUser',
{
authUser: response.data,
isAuthenticated: true
}
)
this.$router.push({path: 'dashboard-user/id/list-vendor'})
})
.catch((error) => {
//NOTE: erase this when production
console.log(error);
console.debug(error);
console.dir(error);
alert("The username or password is incorrect");
})
}
}
}
</script>
In 'main.js'
import Vue from 'vue'
import VueRouter from 'vue-router'
import App from './App.vue'
import 'tachyons'
import routes from './routes'
import './styles.css'
import store from '#/store'
Vue.config.productionTip = false
Vue.use(VueRouter)
import '#/assets/fonts/all.css';
const router = new VueRouter({
mode: 'history',
routes
})
router.beforeEach((to, from, next) => {
// to and from are both route objects. must call `next`.
if(to.fullPath === '/dashboard-user/id/list-vendor') {
if(!store.state.jwt) {
next('/login')
}
}
if(to.fullPath === '/login') {
if(store.state.jwt) {
next('/dashboard-user/id/list-vendor')
}
}
next();
})
new Vue({
router,
store,
render: h => h(App)
}).$mount('#app')
Since use register auth as a module, you should use store.state.auth.jwt instead of store.state.jwt
router.beforeEach((to, from, next) => {
// to and from are both route objects. must call `next`.
if(to.fullPath === '/dashboard-user/id/list-vendor') {
if(!store.state.auth.jwt) {
next('/login')
}
}
if(to.fullPath === '/login') {
if(store.state.auth.jwt) {
next('/dashboard-user/id/list-vendor')
}
}
next();
})
i am trying to use ember-simple-auth with custom authentication and authorization: authenticator works but authorizer doesn't. Token successfully assigned but there is no injection in ajax calls.
app/authenticator/custom.js
import Ember from 'ember';
import Base from 'simple-auth/authenticators/base';
export default Base.extend({
tokenEndpoint: 'http://...',
restore: function(data) {
return new Ember.RSVP.Promise(function(resolve, reject) {
if (!Ember.isEmpty(data.token)) {
resolve(data);
} else {
reject();
}
});
},
authenticate: function(credentials) {
var _this = this;
return new Ember.RSVP.Promise(function(resolve, reject) {
Ember.$.ajax({
url: _this.tokenEndpoint,
type: 'POST',
data: JSON.stringify({ username: credentials.identification, password: credentials.password }),
contentType: 'application/json'
}).then(function(response) {
Ember.run(function() {
resolve({ token: response.token });
});
}, function(xhr, status, error) {
var response = JSON.parse(xhr.responseText);
Ember.run(function() {
reject(response.error);
});
});
});
},
invalidate: function() {
var _this = this;
return new Ember.RSVP.Promise(function(resolve) {
Ember.$.ajax({ url: _this.tokenEndpoint, type: 'DELETE' }).always(function() {
resolve();
});
});
},
});
app/authorizers/custom.js
import Ember from 'ember';
import Base from 'simple-auth/authorizers/base';
export default Base.extend({
authorize: function(jqXHR, requestOptions) {
if (this.get('session.isAuthenticated') && !Ember.isEmpty(this.get('session.secure.token'))) {
jqXHR.setRequestHeader('X-CSRFToken', this.get('session.secure.token'));
}
}
});
app/initializers/authentication.js
import CustomAuthenticator from 'app/authenticators/custom';
import CustomAuthorizer from 'app/authorizers/custom';
export default {
name: 'authentication',
before: 'simple-auth',
initialize: function(container) {
container.register('authenticator:custom', CustomAuthenticator);
container.register('authorizer:custom', CustomAuthorizer);
}
};
config/environment.js
ENV['simple-auth'] = {
authorizer: 'authorizer:custom',
crossOriginWhitelist: ['http://...']
};
app/controllers/login.js
import Ember from 'ember';
export default Ember.Controller.extend({
actions: {
authenticate: function(){
var credentials = this.getProperties('identification', 'password');
this.get('session').authenticate('authenticator:custom', credentials);
}
}
});
I not sure why the return is not being passed to the component template. It's listening and the value is found but its not working.
return result.resources; is not returning the data when result.resoures has an array of objects.
here is the autocomplete template auto-complete.hbs
<ul>
<li class='row input-group-lg'>
{{input type="text" value=searchText placeholder="Enter Street Address" class='col-md-6 col-md-offset-3 col-xs-12 street-address'}}
</li>
</ul>
<ul>
{{#each searchResults}}
<li>{{this.name}}</li>
{{/each}}
</ul>
here is auto-complete.js
import Ember from 'ember';
export default Ember.Component.extend({
searchText: null,
searchResults: function() {
var searchText = this.get('searchText');
if(!searchText) {
return;
}
Ember.$.ajax({
url: "http://dev.virtualearth.net/REST/v1/Locations",
dataType: "jsonp",
data: {
key: "",
q: searchText
},
jsonp: "jsonp"
}).then(function(data) {
var result = data.resourceSets[0];
if (result) {
if (result.estimatedTotal > 0) {
return result.resources;
}
}
});
}.property('searchText')
});
The code block
.then(function(data) {
var result = data.resourceSets[0];
if (result) {
if (result.estimatedTotal > 0) {
return result.resources;
}
}
will return from the promise and will not return the value for the computed property, which obviously means that you are not returning anything for the CP.
A possible work around can be
searchResults: function() {
var searchText = this.get('searchText');
var searchResults = Ember.ArrayProxy.create();
if(!searchText) {
return;
}
Ember.$.ajax({
url: "http://dev.virtualearth.net/REST/v1/Locations",
dataType: "jsonp",
data: {
key: "",
q: searchText
},
jsonp: "jsonp"
}).then(function(data) {
var result = data.resourceSets[0];
if (result) {
if (result.estimatedTotal > 0) {
searchResults.set('content',result.resources);
}
}
});
return searchResults;
}.property('searchText')
You can create an arrayproxy and return the arrayproxy for the CP. Upon completion of the promise, set the result as content to the arrayproxy, which will update the template.