Skip to content

Commit 469a251

Browse files
authored
Merge pull request #91 from Rebolon/fix/loader-angular-and-login-js-jwt
jwt isLoggedin now check jwt validity (it previsouly returned true ev…
2 parents 1222949 + 385a573 commit 469a251

35 files changed

+8225
-7204
lines changed

.scrutinizer.yml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ build:
66
version: '7.1'
77
node:
88
version: '8.10.0'
9+
hosts:
10+
localhost: 127.0.0.1
911
nodes:
1012
# angular:
1113
# project_setup:
@@ -27,6 +29,8 @@ build:
2729
root_path: './'
2830
tests:
2931
override:
32+
- npm run sf-dev &
33+
- npm run test-cafe
3034
- npm run test-karma
3135
- php-scrutinizer-run
3236
-
@@ -39,6 +43,8 @@ build:
3943
coverage:
4044
file: 'var/report/clover.xml'
4145
format: 'clover'
46+
#it fails look at this build where localhost doesn't seem to respond https://scrutinizer-ci.com/g/Rebolon/php-sf-flex-webpack-encore-vuejs/inspections/b75b10a6-6e41-4936-a453-da3632eda436
47+
#- npm run test-cafe
4248

4349
filter:
4450
excluded_paths:

.travis.yml

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ before_install:
2929
- sudo add-apt-repository -y ppa:ondrej/nginx-mainline
3030
- sudo apt-get update
3131
- sudo apt-get install nginx
32-
# - sudo apt-get install php7.1-fpm
32+
- sudo apt-get install php7.1-fpm
3333
- sudo cp ~/.phpenv/versions/$(phpenv version-name)/etc/php-fpm.conf.default ~/.phpenv/versions/$(phpenv version-name)/etc/php-fpm.conf
3434
- sudo cp ~/.phpenv/versions/$(phpenv version-name)/etc/php-fpm.d/www.conf.default ~/.phpenv/versions/$(phpenv version-name)/etc/php-fpm.d/www.conf
3535
- echo "cgi.fix_pathinfo = 1" >> ~/.phpenv/versions/$(phpenv version-name)/etc/php.ini
@@ -43,12 +43,14 @@ before_install:
4343

4444
install:
4545
- npm run init-project
46+
- php bin/console cache:warmup
4647
- npm run jwt-generation-test
4748

4849
before_script:
4950
# - curl -H "Accept: application/json" https://security.sensiolabs.org/check_lock -F lock=@composer.lock
5051
# - sudo service php7.1-fpm restart
51-
- sudo service nginx restart
52+
# - sudo service nginx restart
53+
- npm run sf-dev &
5254
# see http://devexpress.github.io/testcafe/documentation/recipes/integrating-testcafe-with-ci-systems/travis.html
5355
- "export DISPLAY=:99.0"
5456
- "sh -e /etc/init.d/xvfb start"
@@ -58,6 +60,7 @@ before_script:
5860
script:
5961
# until this issue is open i disable npm test and use test-php and test-karma in place (issue:https://github.com/DevExpress/testcafe/issues/2195, original post: https://testcafe-discuss.devexpress.com/t/role-sometime-it-doesnt-seem-to-be-played/875/9)
6062
# - npm test
63+
- npm run test-cafe
6164
- npm run test-php
6265
- npm run test-karma
6366

README.md

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ Then some php controllers has been created on following routes :
4646
* /demo/http-plug : HttpPlugController to show how to call external API from your controller
4747
* /demo/login/standard/secured : LoginController for standard login by Symfony
4848
* /demo/login/json/authenticate : LoginJsonController for json login with JS applications but in stateful context
49+
* /demo/login/jwt/frontend: LoginJwtController for jwt login with JS applications in a stateless context
4950
* /demo/vuejs : VuejsController with route config in annotations and VueJS app with specific js/css import
5051
* /demo/quasar : QuasarController like VuejsController but with the Quasar framework for UX components
5152
* /demo/form/quasar-vuejs : [Work in progress] authentification with javascript, and a full web application with vuejs and api-platform(rest/graphql)
@@ -232,6 +233,12 @@ On JS i use snyk services.
232233
@TODO explain the usage of tools like OWASP ZED, sqlmap, php avenger...
233234
@TODO help to setup security system: stateful app = take care at csrf ; stateless app = should i use jwt, api key, OAuth, anything else ?
234235

236+
Don't forget to use HTTPS, even in local to help you find errors that will happen in production. One certificate has been generated for localhost (with http://www.selfsignedcertificate.com/) and is available in /var/certificates/*.cert|*.key
237+
There is a simple nginx conf (used for travis CI) that use those certificates so you can use nginx to work (just don't forget to change the port that is fixed to 80 like setup in the package.json).
238+
239+
TestCafé for functional testing generate an error when you don't use ssl: Uncaught (in promise) DOMException: Only secure origins are allowed (see: https://goo.gl/Y0ZkNV).
240+
But for instance i didn't found any solution to run it finely without --skip-js-errors parameters.
241+
235242
### Symfony security
236243
In Symfony i configured different firewalls:
237244
* *security_js* and *security_php* share the same context so when you are logged on one, you are also logged on the other. I did this because a firewall cannot use both form_login and json_login (or i didn't found the way), and i wnated you to understand the concept of context.
@@ -479,11 +486,12 @@ It takes the following JSON string as Body:
479486
- [ ] api: graphQL: multiple mutations in one call ?
480487
- [ ] api: graphQL: how to mutate nested objects in a minimal call ?
481488
- [X] api: check best security system to setup with ApiPlatform (JWT / ApiKey / cookie & csrf system but in that case we are stateful which is not cool for deployment and replication ?). Finally we use JWT which is the best thing to do and compliant with statefull or stateless.
482-
- [ ] api: JWT setup the pattern for the refresh-token or anything else more info here https://auth0.com/blog/refresh-tokens-what-are-they-and-when-to-use-them/
489+
- [ ] api: JWT setup the pattern for the refresh-token or anything else more info here https://auth0.com/blog/refresh-tokens-what-are-they-and-when-to-use-them/ : when getting a 401 from api it should tells more information: does the token is valid or not ?
483490
- [x] front: setup VueJS
484491
- [x] front: use Quasar with VueJS
485492
- [x] front: move on Quasar 0.15.x
486493
- [x] front: setup CSRF protection with VueJS app
494+
- [ ] front: migrate app 'form-devextrem-angular' to angular6 when there will be compatible (this thread may helps: https://stackoverflow.com/questions/48970553/want-to-upgrade-project-from-angular-v5-to-angular-v6)
487495
- [x] quality: setup unit tests for JS (karma/jasmine)
488496
- [x] quality: setup e2e tests for JS (testcafé)
489497
- [x] quality: setup phpunit tests for PHP (unit test and webtestcase)
@@ -506,6 +514,8 @@ It takes the following JSON string as Body:
506514
- [x] security: check if i need the JMSSerializerBundle or if the serializer component is enough (if autowiring runs well, why not): **I prefer to use Symfony serializer, it's enough**
507515
- [ ] db: have a lookAt the HauteLookAliceBundle to help in the creation of real fixtures during tests (instead of generating a new test.db which could be long)
508516
- [ ] api: try https://github.com/overblog/GraphQLBundle instead of ApiPlatform to try nested query/mutations (resolver are not auto-generated)
517+
- [ ] quality: use a server logger for both JS and PHP (and also maybe HTTP, DB, MessageQueuing, ...), it will helps to improve quality of the app by identifing users system/browser and most current errors (Sentry or other service must be tested https://www.slant.co/options/964/alternatives/~sentry-alternatives)
518+
- [ ] front: move on babel 7 with babel-preset-env (remove all related babel from readme and read babeljs.io for more info on update)
509519

510520
* improve this tutorial with ~~an API Route built with Api platform (without DB)~~ and install the vue-generator from api-platform for a crud sample
511521
* manage Entity orphanRemoval / CASCADE onDelete
@@ -564,7 +574,10 @@ I wrote some articles on medium to explain some practices setup in this project:
564574
* https://github.com/symfony/symfony/issues/25806
565575
* https://github.com/symfony/symfony/issues/8467
566576
* https://github.com/symfony/webpack-encore/issues/256
577+
* https://github.com/symfony/webpack-encore/issues/326 (compat with vue-loader >15)
567578
* https://testcafe-discuss.devexpress.com/t/role-sometime-it-doesnt-seem-to-be-played/875
579+
* https://testcafe-discuss.devexpress.com/t/it-doesnt-run-all-tests-files/1230/2
580+
* https://testcafe-discuss.devexpress.com/t/object-domexceptio-error-when-running-tests/1231
568581

569582

570583
## License
Lines changed: 16 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,20 @@
1-
<div class="row">
2-
<my-wizard-sumup></my-wizard-sumup>
3-
</div>
1+
<dx-load-indicator class='button-indicator'
2+
[visible]="isLoading">
3+
</dx-load-indicator>
44

5-
<div class="form-group">
6-
<div class="form-row">
7-
<label for="titleChange">Change the title:</label>
8-
<input #newTitle [value]=book?.title class="input-sm form-control" type="text" id="titleChange">
5+
<div *ngIf="!isLoading" id="form-edit-title">
6+
<div class="row">
7+
<my-wizard-sumup></my-wizard-sumup>
98
</div>
10-
<div class="form-row">
11-
<button (click)="sendBookToParent(newTitle.value)" class="btn btn-primary btn-lg btn-block">send Book with new title</button>
12-
<button (click)="sendMessage()" class="btn btn-info btn-lg btn-block">send event to other window</button>
9+
10+
<div *ngIf="!isLoading" class="form-group form-edit-title">
11+
<div class="form-row">
12+
<label for="titleChange">Change the title:</label>
13+
<input #newTitle [value]=book?.title class="input-sm form-control" type="text" id="titleChange">
14+
</div>
15+
<div class="form-row">
16+
<button (click)="sendBookToParent(newTitle.value)" class="btn btn-primary btn-lg btn-block">send Book with new title</button>
17+
<button *ngIf="isSecondWindow()" (click)="sendMessage()" class="btn btn-info btn-lg btn-block">send event to other window</button>
18+
</div>
1319
</div>
1420
</div>

assets/js/form-devxpress-angular/src/app/book-container/book/book.component.ts

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import {Component, OnDestroy, OnInit} from '@angular/core'
1+
import {Component, OnDestroy, OnInit, ViewChild} from '@angular/core'
22
import { Observable } from 'rxjs/Observable'
33
import {Subject} from "rxjs/Subject";
44
import 'rxjs/add/operator/toPromise';
@@ -16,6 +16,7 @@ import {Book} from "../../../entities/library/book";
1616
export class BookComponent implements OnInit, OnDestroy {
1717
protected ngUnsubscribe: Subject<void> = new Subject()
1818
protected book: Book
19+
protected isLoading = true
1920

2021
constructor(private route: ActivatedRoute, private bookService: WizardBook, private broadcastChannel: BroadcastChannelApi) {}
2122

@@ -39,7 +40,9 @@ export class BookComponent implements OnInit, OnDestroy {
3940
this.bookService
4041
.get(parseInt(bookId, 10))
4142
.takeUntil(this.ngUnsubscribe)
42-
.subscribe()
43+
.subscribe((res: Book) => {
44+
this.isLoading = false
45+
})
4346
})
4447
}
4548

@@ -57,4 +60,8 @@ export class BookComponent implements OnInit, OnDestroy {
5760

5861
this.broadcastChannel.sendBook(this.book)
5962
}
63+
64+
isSecondWindow() {
65+
return window['name'] === 'second-screen'
66+
}
6067
}

assets/js/form-devxpress-angular/src/app/datagrid/datagrid.component.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -47,14 +47,14 @@ export class DatagridComponent implements OnInit {
4747

4848
// Take care, until you click on the parent window, data won't be refreshed in datagrid. don't know if it's because of browser behavior or DevXpress.datagrid
4949
notify(`new Book received with id ${book.id}, focus the window to see the changes in datagrid.
50-
Data is not saved until you edit it`, "info", 5000)
50+
Data is not saved until you edit it`, "info", 5000)
5151
this.dataGrid.instance.clearSelection()
5252

5353
break
5454
case 'hello':
5555
case 'ping':
5656
default:
57-
notify("data received from second screen", "info", 5000)
57+
notify(`data received from second screen (cmd=${message.cmd})`, "info", 5000)
5858
break
5959
}
6060
})
@@ -151,7 +151,7 @@ export class DatagridComponent implements OnInit {
151151

152152
getSelectedRow () {
153153
const rows = this.dataGrid.instance.getSelectedRowKeys()
154-
const url = `/demo/devxpress-angular/book/${rows[0].id}`
154+
const url = `/demo/form/devxpress-angular/book/${rows[0].id}`
155155
const options = {
156156
menubar: 'false',
157157
toolbar: 'false',

assets/js/form-devxpress-angular/src/services/api.ts

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { Injectable } from '@angular/core'
22
import { HttpClient } from '@angular/common/http'
33
import { Observable } from 'rxjs/Observable'
44
import { environment } from '../environments/environment'
5-
import {apiPlatformPrefix} from '../../../lib/config'
5+
import {apiPlatformPrefix, tokenJwtBearer} from '../../../lib/config'
66
import 'rxjs/add/operator/do'
77
import 'rxjs/add/operator/map'
88

@@ -107,7 +107,12 @@ export class ApiService {
107107
}*/
108108

109109
if (!Object.keys(this.options.headers).find(prop => prop.toLowerCase() === 'authorization')) {
110-
this.options.headers['Authorization'] = `Bearer ${user.token}`
110+
let token = user.token
111+
if (!token.match(tokenJwtBearer + ' ')) {
112+
token = `${tokenJwtBearer} ${token}`
113+
}
114+
115+
this.options.headers['Authorization'] = token
111116
}
112117

113118
return this

assets/js/lib/axiosMiddlewares.js

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import axios from 'axios'
22
import { logout } from './login'
3-
import { getTokenFromMeta } from './csrfToken'
4-
import { csrfParameter } from './config'
3+
import getToken, { getTokenFromMeta } from './csrfToken'
4+
import { csrfParameter, tokenJwtBearer } from './config'
55
import { Notify } from 'quasar-framework/dist/quasar.mat.esm'
66

77
// @todo add an interceptors that will always retrieve the csrf token and add it inside the request
@@ -53,8 +53,13 @@ const JwtTokenHeader = function (config) {
5353
const rememberMe = localStorage.getItem('rememberMe')
5454
if (rememberMe) {
5555
const jsonInfos = JSON.parse(rememberMe)
56+
let token = jsonInfos.token
57+
if (!token.match(tokenJwtBearer + ' ')) {
58+
token = `${tokenJwtBearer} ${token}`
59+
}
60+
5661
const headers = {
57-
'Authorization': `Bearer ${jsonInfos.token}`,
62+
'Authorization': token,
5863
}
5964

6065
config.headers = Object.assign(config.headers ? config.headers : {}, headers)
@@ -82,6 +87,12 @@ const CsrfTokenRetreiveOnInvalidResponse = function (error) {
8287
console.info('axios intercep response error', 'csrf')
8388
// @todo Is it possible to do a retry of the request with a new token ?
8489

90+
if (!error.response) {
91+
console.info('axios intercep response error, unknown error', 'csrf', error)
92+
93+
return Promise.reject(error)
94+
}
95+
8596
let status = error.response.status
8697
if (error.response.data && error.response.data.code) {
8798
status = error.response.data.code

assets/js/login/components/Login.vue

Lines changed: 17 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -175,12 +175,17 @@ export default {
175175
let errMsg = err.response.statusText
176176
let code = err.response.status
177177
if (err.response.data) {
178-
if (err.response.data.message) {
179-
errMsg = err.response.data.message
180-
}
181-
182-
if (err.response.data.code) {
183-
code = err.response.data.code
178+
if (err.response.data.error.exception) {
179+
err.response.data.error.exception["0"].message.startsWith('Exception: UsernameNotFoundException')
180+
errMsg = 'User not found'
181+
} else {
182+
if (err.response.data.message) {
183+
errMsg = err.response.data.message
184+
}
185+
186+
if (err.response.data.code) {
187+
code = err.response.data.code
188+
}
184189
}
185190
}
186191
@@ -196,6 +201,12 @@ export default {
196201
type: 'warning'
197202
})
198203
break;
204+
case 404:
205+
Notify.create({
206+
message: `Wrong credentials, please try again (${errMsg})`,
207+
type: 'warning'
208+
})
209+
break;
199210
case 420:
200211
Notify.create({
201212
message: `Invalid user name or password (${errMsg})`,

assets/tests/login-js.js

Lines changed: 14 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,21 @@
11
import { Selector, ClientFunction } from 'testcafe'
2-
import { StandardVueJSAccUser } from './tools/authentification'
2+
import {StandardVueJSAccUser, usernameStd, passwordStd, scheme} from './tools/authentification'
33
import { jsLoginFormPath, jsLoginSuccessPath } from './tools/uris'
44
import { host, csrfParameter } from '../js/lib/config'
55

66
const booksList = Selector('div.books')
77
const head = Selector('head')
88
const form = Selector('form.login')
9-
const toast = Selector('div.q-toast-container')
9+
const toast = Selector('div.q-notifications')
1010
const getLocation = ClientFunction(() => document.location.href)
1111

12-
fixture.only `Test vuejs login`
13-
.page `http://${host}${jsLoginFormPath}`
12+
// to debug in testcafe, use .debug() on t variable, or add --debug-mode in the running script
13+
// to focus on this test use 'only'
14+
//fixture.only `Test vuejs login`
15+
fixture `Test vuejs login`
16+
.page `${scheme}://${host}${jsLoginFormPath}`
1417

15-
test('Login page: fake user', async t => {
18+
test('Login page: vuejs login', async t => {
1619
await t
1720
.navigateTo(jsLoginSuccessPath)
1821
.expect(getLocation()).notContains('#/books')
@@ -23,21 +26,11 @@ test('Login page: fake user', async t => {
2326
.typeText('input[name="password"]', 'fakeUser11111')
2427
.click('button')
2528
.expect(getLocation()).contains(jsLoginFormPath)
26-
.expect(toast.find('div.q-toast.bg-warning').count).eql(1, "Missing warning toast message")
27-
})
28-
29-
// don't understand why, but sometimes it works (and the login form is well filled) and sometimes nothing happen !
30-
// ugly behavior
31-
test('Login page: normal user', async t => {
32-
await t
33-
//.useRole(StandardVueJSAccUser)
34-
// @todo until role doesn't work each time, i prefer to use those 3 lines of code
35-
.typeText('input[name="username"]', 'test')
36-
.typeText('input[name="password"]', 'test')
37-
.click('button')
38-
// works with it but useless
39-
//.navigateTo(jsLoginSuccessPath)
40-
.expect(getLocation()).contains(jsLoginSuccessPath)
41-
.expect(booksList.exists).ok()
29+
.expect(toast.find('div.q-alert.bg-warning').count).eql(1, "Missing warning toast message")
4230

31+
.useRole(StandardVueJSAccUser)
32+
//.debug() //this is where firefox seems to crash, but not sure
33+
.wait(3000)
34+
.expect(getLocation()).contains(jsLoginSuccessPath)
35+
.expect(booksList.exists).ok()
4336
})

0 commit comments

Comments
 (0)