So I'm trying to set up a login page for a Vue application. The login component references the imported loginService.login() function, however, when I test the code I get an error "Cannot read property 'login' of undefined." So I logged the entire loginService to the console and it came back as undefined. Why is it that when I access the imported loginService in webstorm it can access the service just fine, but when I try to use it at runtime it's undefined? Here is the (condensed) login component:
<div class="text-center py-4 mt-3 mb-1">
<button class="btn btn-primary " type="submit" style="width:300px" #click="login">Login</button>
</div>
<script>
import {router} from '../../../main'
import { LoginService } from '../login/loginService';
import { StateStorageService } from '../auth/stateStorageService';
import toastr from 'toastr'
export default {
name:'login',
loginService: new LoginService(),
stateStorageService: StateStorageService,
data: function() {
return {
authenticationError: false,
password: '',
rememberMe: false,
username: '',
credentials: {}
}
},
methods:{
login() {
this.loginService.login({
username: this.username,
password: this.password,
rememberMe: this.rememberMe
}).then(() => {
this.authenticationError = false;
if (router.url === '/register' || (/^\/activate\//.test(router.url)) ||
(/^\/reset\//.test(this.router.url))) {
router.navigate(['']);
}
const redirect = StateStorageService.getUrl();
if (redirect) {
this.stateStorageService.storeUrl(null);
this.router.push('/search');
}
}).catch(() => {
this.authenticationError = true;
});
},
And here is the loginService.js
import { Principal } from '../auth/principalService';
import { AuthServerProvider } from '../auth/authJwtService';
export class LoginService {
constructor() {
this.principal = new Principal();
this.authServerProvider = new AuthServerProvider();
}
login(credentials, callback) {
const cb = callback || function() {};
return new Promise((resolve, reject) => {
this.authServerProvider.login(credentials).subscribe((data) => {
this.principal.identity(true).then((account) => {
resolve(data);
});
return cb();
}, (err) => {
this.logout();
reject(err);
return cb(err);
});
});
}
The this in the context of your login method is the current Vue instance, which isn't the same as the base object being exported in this file. That base object contains all of the info for the constructor for the Vue instance, but the properties being passed don't get directly mapped to the generated Vue instance.
In order to make a property available on this like you want, you should set it on the object returned in the data function, not the base object being exported.
So for your loginService and stateStorageService properties:
data: function() {
return {
loginService: new LoginService(),
stateStorageService: StateStorageService,
authenticationError: false,
password: '',
rememberMe: false,
username: '',
credentials: {}
}
},
But, you also don't need to set the loginService property on the Vue instance to have access to it in your login method. You can simply instantiate a new LoginSevice outside of the scope of your exported object and then reference that in your Vue method:
import { LoginService } from '../login/loginService';
const loginService = new LoginService();
export default {
...
methods: {
login() {
loginService.login({
...
})
},
},
}
Related
How do I mock authenticate with Nuxt and Cypress?
I have a FastAPI backend that issues a JWT to a frontend NuxtJS application. I want to test the frontend using Cypress. I am battling to mock authenticate.
Here is a simple Cypress test:
// cypress/e2e/user_authentication_test.cy.js
describe("A User logging in", () => {
it.only("can login by supplying the correct credentials", () => {
cy.mockLogin().then(() => {
cy.visit(`${Cypress.env("BASE_URL")}/dashboard`)
.window()
.its("$nuxt.$auth")
.its("loggedIn")
.should("equal", true);
});
});
});
The test above fails at the should assertion, and the user is not redirected.
The mockLogin command is defined as:
// cypress/support/commands.js
Cypress.Commands.add(
'mockLogin',
(username = 'someone', password = 'my_secret_password_123') => {
cy.intercept('POST', 'http://localhost:5000/api/v1/auth/token', {
fixture: 'auth/valid_auth_token.json',
}).as('token_mock')
cy.visit(`${Cypress.env('BASE_URL')}/login`)
cy.get('#login-username').type(username)
cy.get('#login-password').type(`${password}{enter}`)
cy.wait('#token_mock')
}
)
Where valid_auth_token.json contains a JWT.
The actual login is done as follows:
<!-- components/auth/LoginForm.vue -->
<template>
<!-- Login form goes here -->
</template>
<script>
import jwt_decode from 'jwt-decode' // eslint-disable-line camelcase
export default {
name: 'LoginForm',
data() {
return {
username: '',
password: '',
}
},
methods: {
async login() {
const formData = new FormData()
formData.append('username', this.username)
formData.append('password', this.password)
try {
await this.$auth
.loginWith('cookie', {
data: formData,
})
.then((res) => {
const decode = jwt_decode(res.data.access_token) // eslint-disable-line camelcase
this.$auth.setUser(decode)
this.$router.push('/')
})
} catch (error) {
// error handling
}
},
},
}
</script>
I am working with Vue and Laravel, the issue is that I made an error class and as the form works with 3 different components.
For example I have the base form of this form and further down I have a child component of the form both are sent in the same form but I can't figure out how to pass the error message to that component that is in another vue file.
I tried to pass the error message as a prop but it doesn't take it and inside that child component I used watch to capture the error but it doesn't capture it at all.
Error Class
class Errors{
constructor(){
this.errors = {};
}
get(field){
if (this.errors[field]) {
return this.errors[field][0];
}
}
record(errors){
this.errors = errors.errors;
}
}
export default Errors;
**Parent component: CreateClient **
<template>
<contract v-for="contract in contractsFields" :key="contract.id"
:id="contract.id"
:resultForm="onResultsContracts"
:hasErrors="errors"
#remove="removeContract"
></contract>
</template>
<script>
import Errors from '../../class/Errors.js'
import Contract from '../contratos/ContractComponent.vue'
export default {
name: 'CreateClient',
props: ['results'],
components:{
Contract
},
data() {
return {
errors: new Errors(),
}
},
sendForm(){
const params = {
client: this.formClient,
company: this.formCompany,
contract: this.formContract
}
axios.post(route('clients.store'),params)
.then((res) => {
this.errors = []
console.log(res)
})
.catch(error => {
if (error.response.status === 422) {
this.errors = error.response.data.errors || {};
}
})
}
}
</script>
Child Component
<script>
import Errors from '../../class/Errors.js'
import AreaClient from '../clients/AreasClientComponent.vue'
export default {
name: 'Contract',
components: {
AreaClient
},
props:['id','resultForm','hasErrors'],
data() {
return {
error: new Errors(),
}
},
watch: {
hasErrors: function(){
console.log(this.hasErrors) // No working
}
},
}
</script>
Have you tried a deep watcher for your object?
hasErrors: {
deep: true,
handler () {
// do whatever you would like to do
}
}
I am trying to pass the name of the user after authentication into a Vue component, but I get a name: undefined value after load.
Here is my AuthService.js:
//config details taken from OAUTH JS doc: https://github.com/andreassolberg/jso
import { JSO, Fetcher } from 'jso';
const client = new JSO({
providerID: '<my-provider>',
default_lifetime: 1800,
client_id: '<my-client-id>',
redirect_uri: 'http://localhost:8080/',
authorization:'<my-auth-server>/oauth/authorize'
//scopes: { request: ['https://www.googleapis.com/auth/userinfo.profile'] }
});
export default {
getProfile() {
// JSO plugin provides a simple wrapper around the fetch API to handle headers
let f = new Fetcher(client);
let url = 'https://www.googleapis.com/auth/userinfo.profile';
f.fetch(url, {})
.then(data => {
return data.json();
})
.then(data => {
return data.user_name;
})
.catch(err => {
console.error('Error from fetcher', err);
});
}
};
Then, in my single file component named MainNav, I have:
import AuthService from "#/AuthService";
export default {
name: "MainNav",
data() {
return {
name: ""
};
},
created() {
this.name = AuthService.getProfile();
}
};
</script>
Anyone have any tips on how I can get the user_name value from the AuthService to my component? I will then need to then display the name in my nav template. Doing a console.log test works fine, just can't return it to my SFC. Also, the JSO library is here: https://github.com/andreassolberg/jso#fetching-data-from-a-oauth-protected-endpoint
Because getProfile returns nothing (undefined). I see you use es6 then you can use async functions
//config details taken from OAUTH JS doc: https://github.com/andreassolberg/jso
import { JSO, Fetcher } from 'jso';
const client = new JSO({
providerID: '<my-provider>',
default_lifetime: 1800,
client_id: '<my-client-id>',
redirect_uri: 'http://localhost:8080/',
authorization:'<my-auth-server>/oauth/authorize'
//scopes: { request: ['https://www.googleapis.com/auth/userinfo.profile'] }
});
export default {
getProfile() {
// JSO plugin provides a simple wrapper around the fetch API to handle headers
let f = new Fetcher(client);
let url = 'https://www.googleapis.com/auth/userinfo.profile';
return f.fetch(url, {}) // return promise here
.then(data => {
return data.json();
})
.then(data => {
return data.user_name;
})
.catch(err => {
console.error('Error from fetcher', err);
});
}
};
And
import AuthService from "#/AuthService";
export default {
name: "MainNav",
data() {
return {
name: ""
};
},
async created() {
try {
this.name = await AuthService.getProfile();
} catch(error) {
// handle
}
}
};
</script>
Or without async (add one more then)
import AuthService from "#/AuthService";
export default {
name: "MainNav",
data() {
return {
name: ""
};
},
created() {
AuthService.getProfile().then((userName) => this.name = userName))
.catch((error) => { /* handle */ })
}
};
</script>
I am working on Angular5 app user authentication, api return token on successful login. For some reasons LoginPageComponent has no idea what is the token, even if I store it in localstorage I will still get null.
What to do?
App Structure:
LoginPageComponent
import { Component, ViewChild } from '#angular/core';
import { NgForm } from '#angular/forms';
import { Router, ActivatedRoute } from "#angular/router";
import { AuthService } from '../../../shared/auth/auth.service';
#Component({
selector: 'app-login-page',
templateUrl: './login-page.component.html',
styleUrls: ['./login-page.component.scss']
})
export class LoginPageComponent {
#ViewChild('f') loginForm: NgForm;
private user: any = {
email: '',
password: ''
};
constructor(
private auth: AuthService,
private router: Router,
private route: ActivatedRoute) { }
// On submit button click
onSubmit(f: NgForm) {
this.auth.signinUser(f.value.email, f.value.password);
// Returns even signinUser has token
console.log(this.auth.getToken());
}
}
AuthService
signinUser(email: string, password: string) {
this.userService.login(email, password).then(res => {
console.log(res);
this.token = res.token;
// here app has token and this.token contains value
});
}
getToken() {
return this.token;
}
UserService:
login(email, password) {
this.endpoint = this.endpointLocal + '/api/auth/login';
return new Promise(resolve => {
this.http.post(this.endpoint, {"email": email, "password": password})
.subscribe(res => resolve(res.json()));
});
}
Your call to signinUser invokes asynchronous code, so the then portion of the Promise in signinUser won't have been executed before you make your console.log call in onSubmit.
If you modify your signinUser method in the AuthService to return the Promise, then you can just chain a .then call in your LoginPageComponent.onSubmit method:
signinUser(email: string, password: string): Promise {
return this.userService.login(email, password).then(res => {
console.log(res);
this.token = res.token;
// here app has token and this.token contains value
});
}
// On submit button click
onSubmit(f: NgForm) {
this.auth.signinUser(f.value.email, f.value.password).then(() => {
console.log(this.auth.getToken());
});
}
I'm trying to have access to the data from the component, i can have access to it using the template but i actually want to be able to have access to the dispatch users details from the created method.
How can i have access to it?
<template>
<div>
<input type='text' class='e_pearl ep_pearl form-control' placeholder='Perle..' autoComplete='false' autoFocus spellCheck='false' :value='user.pearl_color' >
</div>
</template>
<script>
var colors = '#006064';
export default {
mixins: [userMixin],
components: {
Sidebar,
'swatches-picker': Swatches
},
methods: {
created(){
let {
session: { email },
$store: { dispatch }
} = this
dispatch('userDetails', email)
console.log(this.$data.colors); // access to data from created
},
data () {
return {
colors
}
},
mounted(){
$('.ep_password').focus()
}
}
</script>
I tried:
console.log(this.$store.state.user);
{ob: Observer}
session
:
(...)
userDetails
:
(...)
module
import actions from '../actions/user-a'
import $ from 'jquery'
let d = $('.data')
export default {
state: {
session: {
id: d.data('session'),
email: d.data('email')
},
userDetails: {}
},
mutations: {
USER_DETAILS: (state, payload) => state.userDetails = payload,
},
actions,
}
action
import { post } from 'axios'
export default {
userDetails: async (context, payload) => {
console.log(payload);
let { data } = await post('/api/get-details', { email: payload })
context.commit('USER_DETAILS', data)
},
}
If i try console.log(this.$store.state.userDetails);
Data are not available however i can see them from this.$store.state.user
But no luck!