Merged in feature/move-login (pull request #37)
Feature/move login Approved-by: Ramon Wenger <ramon.wenger@iterativ.ch>
This commit is contained in:
commit
38777cf914
|
|
@ -6,13 +6,17 @@ describe('Change Password Page', () => {
|
||||||
const validationErrorMsg = 'Das Passwort muss Grossbuchstaben, Zahlen und Sonderzeichen beinhalten';
|
const validationErrorMsg = 'Das Passwort muss Grossbuchstaben, Zahlen und Sonderzeichen beinhalten';
|
||||||
const validationOldWrongMsg = 'Die Eingabe ist falsch';
|
const validationOldWrongMsg = 'Die Eingabe ist falsch';
|
||||||
|
|
||||||
|
beforeEach(function () {
|
||||||
|
cy.clearCookies();
|
||||||
|
cy.visit('/me/profile');
|
||||||
|
cy.login('rahel.cueni', 'test');
|
||||||
|
});
|
||||||
|
|
||||||
after(function () {
|
after(function () {
|
||||||
cy.exec("python ../server/manage.py reset_testuser_password rahel.cueni");
|
cy.exec("python ../server/manage.py reset_testuser_password rahel.cueni");
|
||||||
});
|
});
|
||||||
|
|
||||||
it('shows an empty form', () => {
|
it('shows an empty form', () => {
|
||||||
cy.login('rahel.cueni', 'test');
|
|
||||||
cy.visit('/me/profile');
|
|
||||||
|
|
||||||
cy.get('[data-cy=password-change-success]').should('not.exist');
|
cy.get('[data-cy=password-change-success]').should('not.exist');
|
||||||
cy.get('[data-cy=old-password]').should('have.value', '');
|
cy.get('[data-cy=old-password]').should('have.value', '');
|
||||||
|
|
@ -20,73 +24,46 @@ describe('Change Password Page', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it('shows errors if old password is not entered', () => {
|
it('shows errors if old password is not entered', () => {
|
||||||
cy.login('rahel.cueni', 'test');
|
|
||||||
cy.visit('/me/profile');
|
|
||||||
|
|
||||||
cy.changePassword('', validNewPassword);
|
cy.changePassword('', validNewPassword);
|
||||||
cy.get('[data-cy=old-password-local-errors]').should('contain', 'Dein aktuelles Passwort fehlt')
|
cy.get('[data-cy=old-password-local-errors]').should('contain', 'Dein aktuelles Passwort fehlt')
|
||||||
});
|
});
|
||||||
|
|
||||||
it('shows errors if new password is not entered', () => {
|
it('shows errors if new password is not entered', () => {
|
||||||
cy.login('rahel.cueni', 'test');
|
|
||||||
cy.visit('/me/profile');
|
|
||||||
|
|
||||||
cy.changePassword(validOldPassword, '');
|
cy.changePassword(validOldPassword, '');
|
||||||
cy.get('[data-cy=new-password-local-errors]').should('contain', 'Dein neues Passwort fehlt')
|
cy.get('[data-cy=new-password-local-errors]').should('contain', 'Dein neues Passwort fehlt')
|
||||||
});
|
});
|
||||||
|
|
||||||
it('shows errors if new password is too short', () => {
|
it('shows errors if new password is too short', () => {
|
||||||
cy.login('rahel.cueni', 'test');
|
|
||||||
cy.visit('/me/profile');
|
|
||||||
|
|
||||||
cy.changePassword(validOldPassword, 'Abc1!');
|
cy.changePassword(validOldPassword, 'Abc1!');
|
||||||
cy.get('[data-cy=new-password-local-errors]').should('contain', validationTooShort)
|
cy.get('[data-cy=new-password-local-errors]').should('contain', validationTooShort)
|
||||||
});
|
});
|
||||||
|
|
||||||
it('shows errors if new password has no uppercase letter', () => {
|
it('shows errors if new password has no uppercase letter', () => {
|
||||||
cy.login('rahel.cueni', 'test');
|
|
||||||
cy.visit('/me/profile');
|
|
||||||
|
|
||||||
cy.changePassword(validOldPassword, 'aabdddedddbc1!');
|
cy.changePassword(validOldPassword, 'aabdddedddbc1!');
|
||||||
cy.get('[data-cy=new-password-local-errors]').should('contain', validationErrorMsg)
|
cy.get('[data-cy=new-password-local-errors]').should('contain', validationErrorMsg)
|
||||||
});
|
});
|
||||||
|
|
||||||
it('shows errors if new password has no lowercase letter', () => {
|
it('shows errors if new password has no lowercase letter', () => {
|
||||||
cy.login('rahel.cueni', 'test');
|
|
||||||
cy.visit('/me/profile');
|
|
||||||
|
|
||||||
cy.changePassword(validOldPassword, 'ABCDDD334551!');
|
cy.changePassword(validOldPassword, 'ABCDDD334551!');
|
||||||
cy.get('[data-cy=new-password-local-errors]').should('contain', validationErrorMsg)
|
cy.get('[data-cy=new-password-local-errors]').should('contain', validationErrorMsg)
|
||||||
});
|
});
|
||||||
|
|
||||||
it('shows errors if new password has no digit', () => {
|
it('shows errors if new password has no digit', () => {
|
||||||
cy.login('rahel.cueni', 'test');
|
|
||||||
cy.visit('/me/profile');
|
|
||||||
|
|
||||||
cy.changePassword(validOldPassword, 'AbcdEEDE!');
|
cy.changePassword(validOldPassword, 'AbcdEEDE!');
|
||||||
cy.get('[data-cy=new-password-local-errors]').should('contain', validationErrorMsg)
|
cy.get('[data-cy=new-password-local-errors]').should('contain', validationErrorMsg)
|
||||||
});
|
});
|
||||||
|
|
||||||
it('shows errors if new password has no special character', () => {
|
it('shows errors if new password has no special character', () => {
|
||||||
cy.login('rahel.cueni', 'test');
|
|
||||||
cy.visit('/me/profile');
|
|
||||||
|
|
||||||
cy.changePassword(validOldPassword, 'AbcdEEDE09877');
|
cy.changePassword(validOldPassword, 'AbcdEEDE09877');
|
||||||
cy.get('[data-cy=new-password-local-errors]').should('contain', validationErrorMsg)
|
cy.get('[data-cy=new-password-local-errors]').should('contain', validationErrorMsg)
|
||||||
});
|
});
|
||||||
|
|
||||||
it('shows errors if old password does not match', () => {
|
it('shows errors if old password does not match', () => {
|
||||||
cy.login('rahel.cueni', 'test');
|
|
||||||
cy.visit('/me/profile');
|
|
||||||
|
|
||||||
cy.changePassword('test12345', validNewPassword);
|
cy.changePassword('test12345', validNewPassword);
|
||||||
cy.get('[data-cy=old-password-remote-errors]').should('contain', validationOldWrongMsg)
|
cy.get('[data-cy=old-password-remote-errors]').should('contain', validationOldWrongMsg)
|
||||||
});
|
});
|
||||||
|
|
||||||
it('shows success if change was successful', () => {
|
it('shows success if change was successful', () => {
|
||||||
cy.login('rahel.cueni', 'test');
|
|
||||||
cy.visit('/me/profile');
|
|
||||||
|
|
||||||
cy.changePassword(validOldPassword, validNewPassword);
|
cy.changePassword(validOldPassword, validNewPassword);
|
||||||
cy.get('[data-cy=password-change-success]').should('contain', 'Dein Password wurde erfolgreich geändert.');
|
cy.get('[data-cy=password-change-success]').should('contain', 'Dein Password wurde erfolgreich geändert.');
|
||||||
cy.get('[data-cy=old-password]').should('have.value', '');
|
cy.get('[data-cy=old-password]').should('have.value', '');
|
||||||
|
|
|
||||||
|
|
@ -4,8 +4,8 @@ describe('Current Module', () => {
|
||||||
cy.startGraphQLCapture();
|
cy.startGraphQLCapture();
|
||||||
cy.viewport('macbook-15');
|
cy.viewport('macbook-15');
|
||||||
|
|
||||||
cy.login('nico.zickgraf', 'test');
|
|
||||||
cy.visit('/module/lohn-und-budget');
|
cy.visit('/module/lohn-und-budget');
|
||||||
|
cy.login('nico.zickgraf', 'test');
|
||||||
|
|
||||||
cy.get('[data-cy=module-title]').should('contain', 'Lohn und Budget')
|
cy.get('[data-cy=module-title]').should('contain', 'Lohn und Budget')
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
describe('The Logged In Home Page', () => {
|
describe('The Logged In Home Page', () => {
|
||||||
it('successfully loads', () => {
|
it('successfully loads', () => {
|
||||||
cy.login('test', 'test');
|
|
||||||
cy.visit('/');
|
cy.visit('/');
|
||||||
|
cy.login('test', 'test');
|
||||||
|
|
||||||
cy.get('.block-title__title').should('contain', 'Inhalte')
|
cy.get('.block-title__title').should('contain', 'Inhalte')
|
||||||
})
|
})
|
||||||
|
|
|
||||||
|
|
@ -1,13 +0,0 @@
|
||||||
describe('The Login CSRF Token', () => {
|
|
||||||
|
|
||||||
it('403 status without token', () => {
|
|
||||||
cy.loginByCsrf('some-token')
|
|
||||||
.its('status')
|
|
||||||
.should('eq', 403)
|
|
||||||
});
|
|
||||||
|
|
||||||
it('gets token from response body', () => {
|
|
||||||
cy.login('test', 'test')
|
|
||||||
})
|
|
||||||
|
|
||||||
});
|
|
||||||
|
|
@ -1,15 +1,47 @@
|
||||||
describe('The Login Page', () => {
|
describe('The Login Page', () => {
|
||||||
it('sets auth cookie when logging in via form submission', () => {
|
it('login and redirect to main page', () => {
|
||||||
const username = 'test';
|
const username = 'test';
|
||||||
const password = 'test';
|
const password = 'test';
|
||||||
|
|
||||||
cy.visit('/');
|
cy.visit('/');
|
||||||
|
cy.login(username, password, true);
|
||||||
|
cy.get('body').contains('Neues Wissen erwerben');
|
||||||
|
});
|
||||||
|
|
||||||
cy.get('#id_username').type(username);
|
it('user sees error message if username is omitted', () => {
|
||||||
cy.get('#id_password').type(`${password}{enter}`);
|
const username = '';
|
||||||
|
const password = 'test';
|
||||||
|
|
||||||
cy.getCookie('sessionid').should('exist');
|
cy.visit('/');
|
||||||
cy.get('.start-page__header').should('exist')
|
cy.login(username, password);
|
||||||
|
cy.get('[data-cy=email-local-errors]').contains('ist ein Pflichtfeld');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('user sees error message if password is omitted', () => {
|
||||||
|
const username = 'test';
|
||||||
|
const password = '';
|
||||||
|
|
||||||
|
cy.visit('/');
|
||||||
|
cy.login(username, password);
|
||||||
|
cy.get('[data-cy=password-local-errors]').contains('ist ein Pflichtfeld');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('user sees error message if credentials are invalid', () => {
|
||||||
|
const username = 'test';
|
||||||
|
const password = '12345';
|
||||||
|
|
||||||
|
cy.visit('/');
|
||||||
|
cy.login(username, password);
|
||||||
|
cy.get('[data-cy=login-error]').contains('Die E-Mail oder das Passwort ist falsch. Bitte versuchen Sie nochmals.');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('redirect after login', () => {
|
||||||
|
const username = 'test';
|
||||||
|
const password = 'test';
|
||||||
|
|
||||||
|
cy.visit('/book/topic/berufliche-grundbildung');
|
||||||
|
cy.login(username, password);
|
||||||
|
cy.get('body').contains('Berufliche Grundbildung');
|
||||||
});
|
});
|
||||||
// it('logs in programmatically without using the UI', () => {
|
// it('logs in programmatically without using the UI', () => {
|
||||||
// cy.visit('/accounts/login/'); // have to get a csrf token by getting the base page first
|
// cy.visit('/accounts/login/'); // have to get a csrf token by getting the base page first
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,9 @@
|
||||||
describe('New project', () => {
|
describe('New project', () => {
|
||||||
it('creates a new project and displays it', () => {
|
it('creates a new project and displays it', () => {
|
||||||
cy.viewport('macbook-15');
|
cy.viewport('macbook-15');
|
||||||
|
cy.visit('/portfolio');
|
||||||
cy.login('rahel.cueni', 'test');
|
cy.login('rahel.cueni', 'test');
|
||||||
|
|
||||||
cy.visit('/portfolio');
|
|
||||||
cy.get('[data-cy=add-project-button]').click();
|
cy.get('[data-cy=add-project-button]').click();
|
||||||
cy.get('[data-cy=page-form-input-titel]').type('Some random title');
|
cy.get('[data-cy=page-form-input-titel]').type('Some random title');
|
||||||
cy.get('[data-cy=page-form-input-beschreibung]').type('This description rocks');
|
cy.get('[data-cy=page-form-input-beschreibung]').type('This description rocks');
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,8 @@ describe('Project Entry', () => {
|
||||||
|
|
||||||
cy.viewport('macbook-15');
|
cy.viewport('macbook-15');
|
||||||
cy.startGraphQLCapture();
|
cy.startGraphQLCapture();
|
||||||
cy.login('rahel.cueni', 'test');
|
cy.login('rahel.cueni', 'test', true);
|
||||||
|
cy.get('body').contains('Neues Wissen erwerben');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should create a new project entry', () => {
|
it('should create a new project entry', () => {
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,9 @@
|
||||||
describe('The Room Page', () => {
|
describe('The Room Page', () => {
|
||||||
it('displays new room entry with author name', () => {
|
it('displays new room entry with author name', () => {
|
||||||
cy.viewport('macbook-15');
|
cy.viewport('macbook-15');
|
||||||
|
cy.visit('/room/ein-historisches-festival');
|
||||||
cy.login('rahel.cueni', 'test');
|
cy.login('rahel.cueni', 'test');
|
||||||
|
|
||||||
cy.visit('/room/ein-historisches-festival');
|
|
||||||
cy.get('[data-cy=add-room-entry-button]').click();
|
cy.get('[data-cy=add-room-entry-button]').click();
|
||||||
cy.get('.add-content-element:first-of-type').click();
|
cy.get('.add-content-element:first-of-type').click();
|
||||||
cy.get('[data-cy=choose-text-widget]').click();
|
cy.get('[data-cy=choose-text-widget]').click();
|
||||||
|
|
|
||||||
|
|
@ -1,15 +1,16 @@
|
||||||
describe('The Rooms Page', () => {
|
describe('The Rooms Page', () => {
|
||||||
it('goes to the rooms page', () => {
|
it('goes to the rooms page', () => {
|
||||||
|
cy.visit('/rooms');
|
||||||
cy.login('nico.zickgraf', 'test');
|
cy.login('nico.zickgraf', 'test');
|
||||||
|
|
||||||
cy.visit('/rooms');
|
|
||||||
|
|
||||||
cy.get('[data-cy=add-room]').should('exist');
|
cy.get('[data-cy=add-room]').should('exist');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('add room should not exist for student', () => {
|
it('add room should not exist for student', () => {
|
||||||
|
cy.visit('/rooms');
|
||||||
cy.login('rahel.cueni', 'test');
|
cy.login('rahel.cueni', 'test');
|
||||||
|
|
||||||
cy.visit('/rooms');
|
|
||||||
|
|
||||||
cy.get('[data-cy=add-room]').should('not.exist');
|
cy.get('[data-cy=add-room]').should('not.exist');
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -21,15 +21,15 @@ describe('Solutions', () => {
|
||||||
// cy.logout();
|
// cy.logout();
|
||||||
cy.viewport('macbook-15');
|
cy.viewport('macbook-15');
|
||||||
|
|
||||||
cy.login('rahel.cueni', 'test');
|
|
||||||
cy.visit('/module/lohn-und-budget');
|
cy.visit('/module/lohn-und-budget');
|
||||||
|
cy.login('rahel.cueni', 'test');
|
||||||
cy.get('[data-cy=toggle-enable-solutions]')
|
cy.get('[data-cy=toggle-enable-solutions]')
|
||||||
.should('not.exist');
|
.should('not.exist');
|
||||||
cy.get('[data-cy=solution]').should('not.exist');
|
cy.get('[data-cy=solution]').should('not.exist');
|
||||||
cy.logout();
|
cy.logout();
|
||||||
|
|
||||||
cy.login('nico.zickgraf', 'test');
|
|
||||||
cy.visit('/module/lohn-und-budget');
|
cy.visit('/module/lohn-und-budget');
|
||||||
|
cy.login('nico.zickgraf', 'test');
|
||||||
cy.get('[data-cy=toggle-enable-solutions]').click();
|
cy.get('[data-cy=toggle-enable-solutions]').click();
|
||||||
cy.waitFor('UpdateSolutionVisibility');
|
cy.waitFor('UpdateSolutionVisibility');
|
||||||
// cy.get('[data-cy=toggle-enable-solutions]').within(() => {
|
// cy.get('[data-cy=toggle-enable-solutions]').within(() => {
|
||||||
|
|
@ -40,8 +40,8 @@ describe('Solutions', () => {
|
||||||
|
|
||||||
cy.logout();
|
cy.logout();
|
||||||
|
|
||||||
cy.login('rahel.cueni', 'test');
|
|
||||||
cy.visit('/module/lohn-und-budget');
|
cy.visit('/module/lohn-und-budget');
|
||||||
|
cy.login('rahel.cueni', 'test');
|
||||||
// cy.get('[data-cy=solution]').should('exist');
|
// cy.get('[data-cy=solution]').should('exist');
|
||||||
cy.get('[data-cy=solution]').first()
|
cy.get('[data-cy=solution]').first()
|
||||||
.should('contain', 'anzeigen')
|
.should('contain', 'anzeigen')
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,8 @@ describe('Survey', () => {
|
||||||
|
|
||||||
cy.viewport('macbook-15');
|
cy.viewport('macbook-15');
|
||||||
cy.startGraphQLCapture();
|
cy.startGraphQLCapture();
|
||||||
cy.login('rahel.cueni', 'test');
|
cy.login('rahel.cueni', 'test', true);
|
||||||
|
cy.get('body').contains('Neues Wissen erwerben');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should display and fill out the survey', () => {
|
it('should display and fill out the survey', () => {
|
||||||
|
|
|
||||||
|
|
@ -24,26 +24,24 @@
|
||||||
// -- This is will overwrite an existing command --
|
// -- This is will overwrite an existing command --
|
||||||
// Cypress.Commands.overwrite("visit", (originalFn, url, options) => { ... })
|
// Cypress.Commands.overwrite("visit", (originalFn, url, options) => { ... })
|
||||||
|
|
||||||
Cypress.Commands.add("login", (username, password) => {
|
Cypress.Commands.add("login", (username, password, visitLogin=false) => {
|
||||||
cy.request('/')
|
if (visitLogin) {
|
||||||
.its('body')
|
cy.visit('/login');
|
||||||
.then(body => {
|
}
|
||||||
console.log(body);
|
|
||||||
const $html = Cypress.$(body);
|
|
||||||
|
|
||||||
const csrf = $html.find('input[name=csrfmiddlewaretoken]').val();
|
if (username != '') {
|
||||||
console.log(csrf);
|
cy.get('[data-cy=email-input]').type(username);
|
||||||
cy.loginByCsrf(username, password, csrf)
|
}
|
||||||
.then(resp => {
|
|
||||||
expect(resp.status).to.eq(200);
|
|
||||||
expect(resp.body).to.include('skillbox');
|
|
||||||
});
|
|
||||||
})
|
|
||||||
|
|
||||||
|
if (password != '') {
|
||||||
|
cy.get('[data-cy=password-input]').type(password);
|
||||||
|
}
|
||||||
|
cy.get('[data-cy=login-button]').click();
|
||||||
});
|
});
|
||||||
|
|
||||||
Cypress.Commands.add("logout", () => {
|
Cypress.Commands.add("logout", () => {
|
||||||
cy.clearCookies();
|
cy.get('[data-cy=user-icon]').click();
|
||||||
|
cy.get('[data-cy=logout]').click();
|
||||||
});
|
});
|
||||||
|
|
||||||
Cypress.Commands.add('loginByCsrf', (username, password, csrftoken) => {
|
Cypress.Commands.add('loginByCsrf', (username, password, csrftoken) => {
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,7 @@
|
||||||
import SimpleLayout from '@/layouts/SimpleLayout';
|
import SimpleLayout from '@/layouts/SimpleLayout';
|
||||||
import BlankLayout from '@/layouts/BlankLayout';
|
import BlankLayout from '@/layouts/BlankLayout';
|
||||||
import FullScreenLayout from '@/layouts/FullScreenLayout';
|
import FullScreenLayout from '@/layouts/FullScreenLayout';
|
||||||
|
import PublicLayout from '@/layouts/PublicLayout';
|
||||||
import Modal from '@/components/Modal';
|
import Modal from '@/components/Modal';
|
||||||
import MobileNavigation from '@/components/MobileNavigation';
|
import MobileNavigation from '@/components/MobileNavigation';
|
||||||
import NewContentBlockWizard from '@/components/content-block-form/NewContentBlockWizard';
|
import NewContentBlockWizard from '@/components/content-block-form/NewContentBlockWizard';
|
||||||
|
|
@ -36,6 +37,7 @@
|
||||||
SimpleLayout,
|
SimpleLayout,
|
||||||
BlankLayout,
|
BlankLayout,
|
||||||
FullScreenLayout,
|
FullScreenLayout,
|
||||||
|
PublicLayout,
|
||||||
Modal,
|
Modal,
|
||||||
MobileNavigation,
|
MobileNavigation,
|
||||||
NewContentBlockWizard,
|
NewContentBlockWizard,
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
<template>
|
<template>
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100">
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100" data-cy="user-icon">
|
||||||
<path
|
<path
|
||||||
d="M50,0A50.12,50.12,0,0,0,.07,46.65S0,48.23,0,50s.07,3.25.07,3.36A50,50,0,1,0,50,0ZM24.14,86V79.84A15.18,15.18,0,0,1,39.31,64.67H60.69A15.18,15.18,0,0,1,75.86,79.84V86a44.27,44.27,0,0,1-51.72,0Zm57.47-5V79.84A20.94,20.94,0,0,0,60.69,58.93H39.31A20.94,20.94,0,0,0,18.39,79.84v1.31a44.4,44.4,0,1,1,63.22-.06Z"/>
|
d="M50,0A50.12,50.12,0,0,0,.07,46.65S0,48.23,0,50s.07,3.25.07,3.36A50,50,0,1,0,50,0ZM24.14,86V79.84A15.18,15.18,0,0,1,39.31,64.67H60.69A15.18,15.18,0,0,1,75.86,79.84V86a44.27,44.27,0,0,1-51.72,0Zm57.47-5V79.84A20.94,20.94,0,0,0,60.69,58.93H39.31A20.94,20.94,0,0,0,18.39,79.84v1.31a44.4,44.4,0,1,1,63.22-.06Z"/>
|
||||||
<path
|
<path
|
||||||
|
|
|
||||||
|
|
@ -1,34 +1,34 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="pw-change">
|
<div class="pw-change">
|
||||||
<form class="pw-change__form change-form" novalidate @submit.prevent="validateBeforeSubmit">
|
<form class="pw-change__form change-form" novalidate @submit.prevent="validateBeforeSubmit">
|
||||||
<div class="change-form__field sbform-input">
|
<div class="change-form__field skillboxform-input">
|
||||||
<label for="old-pw" class="sbform-input__label">Aktuelles Passwort</label>
|
<label for="old-pw" class="skillboxform-input__label">Aktuelles Passwort</label>
|
||||||
<input id="old-pw"
|
<input id="old-pw"
|
||||||
name="oldPassword"
|
name="oldPassword"
|
||||||
type="text"
|
type="text"
|
||||||
v-model="oldPassword"
|
v-model="oldPassword"
|
||||||
v-validate="'required'"
|
v-validate="'required'"
|
||||||
:class="{ 'sbform-input__input--error': errors.has('oldPassword') }"
|
:class="{ 'skillboxform-input__input--error': errors.has('oldPassword') }"
|
||||||
class="change-form__old skillbox-input sbform-input__input"
|
class="change-form__old skillbox-input skillboxform-input__input"
|
||||||
autocomplete="off"
|
autocomplete="off"
|
||||||
data-cy="old-password">
|
data-cy="old-password">
|
||||||
<small v-if="errors.has('oldPassword') && submitted" class="sbform-input__error" data-cy="old-password-local-errors">{{ errors.first('oldPassword') }}</small>
|
<small v-if="errors.has('oldPassword') && submitted" class="skillboxform-input__error" data-cy="old-password-local-errors">{{ errors.first('oldPassword') }}</small>
|
||||||
<small v-for="error in oldPasswordErrors" :key="error" class=" sbform-input__error" data-cy="old-password-remote-errors">{{ error }}</small>
|
<small v-for="error in oldPasswordErrors" :key="error" class=" skillboxform-input__error" data-cy="old-password-remote-errors">{{ error }}</small>
|
||||||
</div>
|
</div>
|
||||||
<div class="change-form__field sbform-input">
|
<div class="change-form__field skillboxform-input">
|
||||||
<label for="new-pw" class="sbform-input__label">Neues Passwort</label>
|
<label for="new-pw" class="skillboxform-input__label">Neues Passwort</label>
|
||||||
<input id="new-pw"
|
<input id="new-pw"
|
||||||
name="newPassword"
|
name="newPassword"
|
||||||
type="text"
|
type="text"
|
||||||
v-model="newPassword"
|
v-model="newPassword"
|
||||||
v-validate="'required|min:8|strongPassword'"
|
v-validate="'required|min:8|strongPassword'"
|
||||||
:class="{ 'sbform-input__input--error': errors.has('newPassword') }"
|
:class="{ 'skillboxform-input__input--error': errors.has('newPassword') }"
|
||||||
class="change-form__new skillbox-input sbform-input__input"
|
class="change-form__new skillbox-input skillboxform-input__input"
|
||||||
autocomplete="off"
|
autocomplete="off"
|
||||||
data-cy="new-password">
|
data-cy="new-password">
|
||||||
<small v-if="errors.has('newPassword') && submitted" class=" sbform-input__error" data-cy="new-password-local-errors">{{ errors.first('newPassword') }}</small>
|
<small v-if="errors.has('newPassword') && submitted" class=" skillboxform-input__error" data-cy="new-password-local-errors">{{ errors.first('newPassword') }}</small>
|
||||||
<small v-for="error in newPasswordErrors" :key="error" class=" sbform-input__error" data-cy="new-password-remote-errors">{{ error }}</small>
|
<small v-for="error in newPasswordErrors" :key="error" class=" skillboxform-input__error" data-cy="new-password-remote-errors">{{ error }}</small>
|
||||||
<p class="sbform-input__hint">Das Passwort muss mindestens 8 Zeichen lang sein und Grossbuchstaben, Zahlen und Sonderzeichen beinhalten.</p>
|
<p class="skillboxform-input__hint">Das Passwort muss mindestens 8 Zeichen lang sein und Grossbuchstaben, Zahlen und Sonderzeichen beinhalten.</p>
|
||||||
</div>
|
</div>
|
||||||
<button class="button button--primary change-form__submit" data-cy="change-password-button">Speichern</button>
|
<button class="button button--primary change-form__submit" data-cy="change-password-button">Speichern</button>
|
||||||
</form>
|
</form>
|
||||||
|
|
@ -82,35 +82,4 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.sbform-input {
|
|
||||||
|
|
||||||
margin-bottom: 20px;
|
|
||||||
font-family: $sans-serif-font-family;
|
|
||||||
|
|
||||||
&__label {
|
|
||||||
margin-bottom: 10px;
|
|
||||||
display: inline-block;
|
|
||||||
}
|
|
||||||
|
|
||||||
&__input {
|
|
||||||
width: 100%;
|
|
||||||
|
|
||||||
&--error {
|
|
||||||
border-color: $color-error;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&__error {
|
|
||||||
margin-top: 10px;
|
|
||||||
color: $color-error;
|
|
||||||
display: inline-block;
|
|
||||||
}
|
|
||||||
|
|
||||||
&__hint {
|
|
||||||
margin-top: $small-spacing;
|
|
||||||
font-family: $sans-serif-font-family;
|
|
||||||
color: $color-silver-dark;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
</style>
|
</style>
|
||||||
|
|
|
||||||
|
|
@ -4,17 +4,18 @@ import {ApolloClient} from 'apollo-client/index'
|
||||||
import {ApolloLink} from 'apollo-link'
|
import {ApolloLink} from 'apollo-link'
|
||||||
import fetch from 'unfetch'
|
import fetch from 'unfetch'
|
||||||
|
|
||||||
const httpLink = new HttpLink({
|
export default function (uri) {
|
||||||
|
const httpLink = new HttpLink({
|
||||||
// uri: process.env.NODE_ENV !== 'production' ? 'http://localhost:8000/api/graphql/' : '/api/graphql/',
|
// uri: process.env.NODE_ENV !== 'production' ? 'http://localhost:8000/api/graphql/' : '/api/graphql/',
|
||||||
uri: '/api/graphql/',
|
uri,
|
||||||
credentials: 'include',
|
credentials: 'include',
|
||||||
fetch: fetch,
|
fetch: fetch,
|
||||||
headers: {
|
headers: {
|
||||||
'X-CSRFToken': document.cookie.replace(/(?:(?:^|.*;\s*)csrftoken\s*=\s*([^;]*).*$)|^.*$/, '$1')
|
'X-CSRFToken': document.cookie.replace(/(?:(?:^|.*;\s*)csrftoken\s*=\s*([^;]*).*$)|^.*$/, '$1')
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
const consoleLink = new ApolloLink((operation, forward) => {
|
const consoleLink = new ApolloLink((operation, forward) => {
|
||||||
// console.log(`starting request for ${operation.operationName}`);
|
// console.log(`starting request for ${operation.operationName}`);
|
||||||
|
|
||||||
return forward(operation).map((data) => {
|
return forward(operation).map((data) => {
|
||||||
|
|
@ -22,24 +23,24 @@ const consoleLink = new ApolloLink((operation, forward) => {
|
||||||
|
|
||||||
return data
|
return data
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
|
|
||||||
// from https://github.com/apollographql/apollo-client/issues/1564#issuecomment-357492659
|
// from https://github.com/apollographql/apollo-client/issues/1564#issuecomment-357492659
|
||||||
const omitTypename = (key, value) => {
|
const omitTypename = (key, value) => {
|
||||||
return key === '__typename' ? undefined : value
|
return key === '__typename' ? undefined : value
|
||||||
};
|
};
|
||||||
|
|
||||||
const createOmitTypenameLink = new ApolloLink((operation, forward) => {
|
const createOmitTypenameLink = new ApolloLink((operation, forward) => {
|
||||||
if (operation.variables) {
|
if (operation.variables) {
|
||||||
operation.variables = JSON.parse(JSON.stringify(operation.variables), omitTypename)
|
operation.variables = JSON.parse(JSON.stringify(operation.variables), omitTypename)
|
||||||
}
|
}
|
||||||
|
|
||||||
return forward(operation)
|
return forward(operation)
|
||||||
});
|
});
|
||||||
|
|
||||||
const composedLink = ApolloLink.from([createOmitTypenameLink, consoleLink, httpLink]);
|
const composedLink = ApolloLink.from([createOmitTypenameLink, consoleLink, httpLink]);
|
||||||
|
|
||||||
const cache = new InMemoryCache({
|
const cache = new InMemoryCache({
|
||||||
cacheRedirects: {
|
cacheRedirects: {
|
||||||
Query: {
|
Query: {
|
||||||
contentBlock: (_, args, {getCacheKey}) => getCacheKey({__typename: 'ContentBlockNode', id: args.id}),
|
contentBlock: (_, args, {getCacheKey}) => getCacheKey({__typename: 'ContentBlockNode', id: args.id}),
|
||||||
|
|
@ -51,24 +52,25 @@ const cache = new InMemoryCache({
|
||||||
projectEntry: (_, args, {getCacheKey}) => getCacheKey({__typename: 'ProjectEntryNode', id: args.id}),
|
projectEntry: (_, args, {getCacheKey}) => getCacheKey({__typename: 'ProjectEntryNode', id: args.id}),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// TODO: Monkey-patching in a fix for an open issue suggesting that
|
// TODO: Monkey-patching in a fix for an open issue suggesting that
|
||||||
// `readQuery` should return null or undefined if the query is not yet in the
|
// `readQuery` should return null or undefined if the query is not yet in the
|
||||||
// cache: https://github.com/apollographql/apollo-feature-requests/issues/1
|
// cache: https://github.com/apollographql/apollo-feature-requests/issues/1
|
||||||
cache.originalReadQuery = cache.readQuery;
|
cache.originalReadQuery = cache.readQuery;
|
||||||
cache.readQuery = (...args) => {
|
cache.readQuery = (...args) => {
|
||||||
try {
|
try {
|
||||||
return cache.originalReadQuery(...args);
|
return cache.originalReadQuery(...args);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// Create the apollo client
|
// Create the apollo client
|
||||||
export default new ApolloClient({
|
return new ApolloClient({
|
||||||
link: composedLink,
|
link: composedLink,
|
||||||
// link: httpLink,
|
// link: httpLink,
|
||||||
cache: cache,
|
cache: cache,
|
||||||
connectToDevTools: true
|
connectToDevTools: true
|
||||||
})
|
})
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,8 @@
|
||||||
|
mutation Login($input: LoginInput!) {
|
||||||
|
login(input: $input) {
|
||||||
|
success
|
||||||
|
errors {
|
||||||
|
field
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,33 @@
|
||||||
|
<template>
|
||||||
|
<div class="skillbox public">
|
||||||
|
<logo class="public__logo"></logo>
|
||||||
|
<router-view class="skillbox__content"></router-view>
|
||||||
|
<footer class="skillbox__footer">Footer</footer>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
|
||||||
|
import Logo from '@/components/icons/Logo';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
components: {Logo},
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
@import "@/styles/_variables.scss";
|
||||||
|
@import "@/styles/_mixins.scss";
|
||||||
|
@import "@/styles/_default-layout.scss";
|
||||||
|
|
||||||
|
.public {
|
||||||
|
max-width: 800px;
|
||||||
|
min-width: 320px;
|
||||||
|
|
||||||
|
&__logo {
|
||||||
|
position: relative;
|
||||||
|
left: -10px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
</style>
|
||||||
|
|
@ -3,7 +3,7 @@ import Vue from 'vue'
|
||||||
import axios from 'axios'
|
import axios from 'axios'
|
||||||
import VueAxios from 'vue-axios'
|
import VueAxios from 'vue-axios'
|
||||||
import VueVimeoPlayer from 'vue-vimeo-player'
|
import VueVimeoPlayer from 'vue-vimeo-player'
|
||||||
import apolloClient from './graphql/client'
|
import apolloClientFactory from './graphql/client'
|
||||||
import VueApollo from 'vue-apollo'
|
import VueApollo from 'vue-apollo'
|
||||||
import App from './App'
|
import App from './App'
|
||||||
import router from './router'
|
import router from './router'
|
||||||
|
|
@ -63,8 +63,14 @@ if (process.env.GOOGLE_ANALYTICS_ID) {
|
||||||
Vue.directive('click-outside', clickOutside);
|
Vue.directive('click-outside', clickOutside);
|
||||||
Vue.directive('auto-grow', autoGrow);
|
Vue.directive('auto-grow', autoGrow);
|
||||||
|
|
||||||
|
const publicApolloClient = apolloClientFactory('/api/graphql-public/');
|
||||||
|
const privateApolloClient = apolloClientFactory('/api/graphql/');
|
||||||
|
|
||||||
const apolloProvider = new VueApollo({
|
const apolloProvider = new VueApollo({
|
||||||
defaultClient: apolloClient
|
clients: {
|
||||||
|
publicClient: publicApolloClient
|
||||||
|
},
|
||||||
|
defaultClient: privateApolloClient
|
||||||
});
|
});
|
||||||
|
|
||||||
Validator.extend('required', required);
|
Validator.extend('required', required);
|
||||||
|
|
@ -98,6 +104,28 @@ Vue.use(VeeValidate, {
|
||||||
|
|
||||||
Vue.filter('date', dateFilter);
|
Vue.filter('date', dateFilter);
|
||||||
|
|
||||||
|
/* logged in guard */
|
||||||
|
|
||||||
|
function getCookieValue(cookieName) {
|
||||||
|
// https://stackoverflow.com/questions/5639346/what-is-the-shortest-function-for-reading-a-cookie-by-name-in-javascript
|
||||||
|
let cookieValue = document.cookie.match('(^|[^;]+)\\s*' + cookieName + '\\s*=\\s*([^;]+)');
|
||||||
|
return cookieValue ? cookieValue.pop() : '';
|
||||||
|
}
|
||||||
|
|
||||||
|
function redirectIfLoginRequird(to) {
|
||||||
|
// public pages have the meta.public property set to true
|
||||||
|
return (!to.hasOwnProperty('meta') || !to.meta.hasOwnProperty('public') || !to.meta.public) && getCookieValue('loginStatus') !== 'true';
|
||||||
|
}
|
||||||
|
|
||||||
|
router.beforeEach((to, from, next) => {
|
||||||
|
if (redirectIfLoginRequird(to)) {
|
||||||
|
const redirectUrl = `/login?redirect=${to.path}`;
|
||||||
|
next(redirectUrl);
|
||||||
|
} else {
|
||||||
|
next();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
/* eslint-disable no-new */
|
/* eslint-disable no-new */
|
||||||
new Vue({
|
new Vue({
|
||||||
el: '#app',
|
el: '#app',
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,169 @@
|
||||||
|
<template>
|
||||||
|
<div class="login">
|
||||||
|
<h1 class="login__title">Melden Sie sich jetzt an</h1>
|
||||||
|
<form class="login__form login-form" novalidate @submit.prevent="validateBeforeSubmit">
|
||||||
|
<div class="login-form__field skillboxform-input">
|
||||||
|
<label for="email" class="skillboxform-input__label">E-Mail</label>
|
||||||
|
<input
|
||||||
|
id="email"
|
||||||
|
name="email"
|
||||||
|
type="text"
|
||||||
|
v-model="email"
|
||||||
|
v-validate="'required'"
|
||||||
|
:class="{ 'skillboxform-input__input--error': errors.has('email') }"
|
||||||
|
class="change-form__email skillbox-input skillboxform-input__input"
|
||||||
|
autocomplete="off"
|
||||||
|
data-cy="email-input"
|
||||||
|
/>
|
||||||
|
<small
|
||||||
|
v-if="errors.has('email') && submitted"
|
||||||
|
class="skillboxform-input__error"
|
||||||
|
data-cy="email-local-errors"
|
||||||
|
>{{ errors.first('email') }}</small>
|
||||||
|
<small
|
||||||
|
v-for="error in emailErrors"
|
||||||
|
:key="error"
|
||||||
|
class="skillboxform-input__error"
|
||||||
|
data-cy="email-remote-errors"
|
||||||
|
>{{ error }}</small>
|
||||||
|
</div>
|
||||||
|
<div class="change-form__field skillboxform-input">
|
||||||
|
<label for="pw" class="skillboxform-input__label">Passwort</label>
|
||||||
|
<input
|
||||||
|
id="pw"
|
||||||
|
name="password"
|
||||||
|
type="password"
|
||||||
|
v-model="password"
|
||||||
|
v-validate="'required'"
|
||||||
|
:class="{ 'skillboxform-input__input--error': errors.has('password') }"
|
||||||
|
class="change-form__new skillbox-input skillboxform-input__input"
|
||||||
|
autocomplete="off"
|
||||||
|
data-cy="password-input"
|
||||||
|
/>
|
||||||
|
<small
|
||||||
|
v-if="errors.has('password') && submitted"
|
||||||
|
class="skillboxform-input__error"
|
||||||
|
data-cy="password-local-errors"
|
||||||
|
>{{ errors.first('password') }}</small>
|
||||||
|
<small
|
||||||
|
v-for="error in passwordErrors"
|
||||||
|
:key="error"
|
||||||
|
class="skillboxform-input__error"
|
||||||
|
data-cy="password-remote-errors"
|
||||||
|
>{{ error }}</small>
|
||||||
|
</div>
|
||||||
|
<div class="skillboxform-input">
|
||||||
|
<small class="skillboxform-input__error" data-cy="login-error" v-if="loginError">{{loginError}}</small>
|
||||||
|
</div>
|
||||||
|
<div class="actions">
|
||||||
|
<button class="button button--primary button--big actions__submit" data-cy="login-button">Anmelden</button>
|
||||||
|
<a class="actions__reset text-link" href="/accounts/password_reset/">Passwort vergessen?</a>
|
||||||
|
</div>
|
||||||
|
<!--div class="registration">
|
||||||
|
<p class="registration__text">Haben Sie noch kein Konto?</p>
|
||||||
|
<a class="registration__link text-link" href="/accounts/password_reset/">Jetzt registrieren</a>
|
||||||
|
</div-->
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import LOGIN_MUTATION from '@/graphql/gql/mutations/login.gql';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
components: {},
|
||||||
|
|
||||||
|
methods: {
|
||||||
|
validateBeforeSubmit() {
|
||||||
|
this.$validator.validate().then(result => {
|
||||||
|
this.submitted = true;
|
||||||
|
let that = this;
|
||||||
|
if (result) {
|
||||||
|
this.$apollo.mutate({
|
||||||
|
client: 'publicClient',
|
||||||
|
mutation: LOGIN_MUTATION,
|
||||||
|
variables: {
|
||||||
|
input: {
|
||||||
|
usernameInput: this.email,
|
||||||
|
passwordInput: this.password
|
||||||
|
}
|
||||||
|
},
|
||||||
|
update(
|
||||||
|
store,
|
||||||
|
{
|
||||||
|
data: {
|
||||||
|
login: { success }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
) {
|
||||||
|
try {
|
||||||
|
if (success) {
|
||||||
|
const redirectUrl = that.$route.query.redirect ? that.$route.query.redirect : '/'
|
||||||
|
that.$router.push(redirectUrl);
|
||||||
|
} else {
|
||||||
|
that.loginError = 'Die E-Mail oder das Passwort ist falsch. Bitte versuchen Sie nochmals.';
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.warn(e);
|
||||||
|
that.loginError = 'Es ist ein Fehler aufgetreten. Bitte versuchen Sie nochmals.';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
resetForm() {
|
||||||
|
this.email = '';
|
||||||
|
this.password = '';
|
||||||
|
this.submitted = false;
|
||||||
|
this.$validator.reset();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
email: '',
|
||||||
|
password: '',
|
||||||
|
emailErrors: [],
|
||||||
|
passwordErrors: [],
|
||||||
|
loginError: '',
|
||||||
|
submitted: false
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
@import "@/styles/_variables.scss";
|
||||||
|
@import "@/styles/_mixins.scss";
|
||||||
|
|
||||||
|
.login {
|
||||||
|
&__title {
|
||||||
|
margin-top: 48px;
|
||||||
|
font-size: 2.75rem; // 44px
|
||||||
|
margin-bottom: 24px;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.text-link {
|
||||||
|
font-family: $sans-serif-font-family;
|
||||||
|
color: $color-brand;
|
||||||
|
}
|
||||||
|
|
||||||
|
.actions {
|
||||||
|
&__reset {
|
||||||
|
display: inline-block;
|
||||||
|
margin-left: $large-spacing;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.registration {
|
||||||
|
margin-top: $large-spacing;
|
||||||
|
&__text {
|
||||||
|
font-family: $sans-serif-font-family;
|
||||||
|
margin-bottom: $small-spacing;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
</style>
|
||||||
|
|
@ -27,11 +27,26 @@ import newProject from '@/pages/newProject'
|
||||||
import surveyPage from '@/pages/survey'
|
import surveyPage from '@/pages/survey'
|
||||||
import styleGuidePage from '@/pages/styleguide'
|
import styleGuidePage from '@/pages/styleguide'
|
||||||
import moduleRoom from '@/pages/moduleRoom'
|
import moduleRoom from '@/pages/moduleRoom'
|
||||||
|
import login from '@/pages/login'
|
||||||
|
|
||||||
import store from '@/store/index';
|
import store from '@/store/index';
|
||||||
|
|
||||||
const routes = [
|
const routes = [
|
||||||
{path: '/', component: start, meta: {layout: 'blank'}},
|
{
|
||||||
|
path: '/',
|
||||||
|
name: 'home',
|
||||||
|
component: start,
|
||||||
|
meta: {layout: 'blank'}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '/login',
|
||||||
|
name: 'login',
|
||||||
|
component: login,
|
||||||
|
meta: {
|
||||||
|
layout: 'public',
|
||||||
|
public: true
|
||||||
|
}
|
||||||
|
},
|
||||||
{
|
{
|
||||||
path: '/module/:slug',
|
path: '/module/:slug',
|
||||||
component: moduleBase,
|
component: moduleBase,
|
||||||
|
|
@ -118,6 +133,7 @@ const router = new Router({
|
||||||
return {x: 0, y: 0}
|
return {x: 0, y: 0}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
router.afterEach((to, from) => {
|
router.afterEach((to, from) => {
|
||||||
store.dispatch('showMobileNavigation', false);
|
store.dispatch('showMobileNavigation', false);
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -23,6 +23,9 @@
|
||||||
border: 2px solid $color-silver-light;
|
border: 2px solid $color-silver-light;
|
||||||
background-color: $color-silver-light;
|
background-color: $color-silver-light;
|
||||||
}
|
}
|
||||||
|
&--big {
|
||||||
|
padding: 15px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.icon-button {
|
.icon-button {
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,30 @@
|
||||||
|
.skillboxform-input {
|
||||||
|
|
||||||
|
margin-bottom: 20px;
|
||||||
|
font-family: $sans-serif-font-family;
|
||||||
|
|
||||||
|
&__label {
|
||||||
|
margin-bottom: 10px;
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__input {
|
||||||
|
width: 100%;
|
||||||
|
|
||||||
|
&--error {
|
||||||
|
border-color: $color-error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&__error {
|
||||||
|
margin-top: 10px;
|
||||||
|
color: $color-error;
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__hint {
|
||||||
|
margin-top: $small-spacing;
|
||||||
|
font-family: $sans-serif-font-family;
|
||||||
|
color: $color-silver-dark;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -18,3 +18,4 @@
|
||||||
@import "survey";
|
@import "survey";
|
||||||
@import "visibility";
|
@import "visibility";
|
||||||
@import "solutions";
|
@import "solutions";
|
||||||
|
@import "password_forms";
|
||||||
|
|
|
||||||
|
|
@ -19,12 +19,13 @@ from surveys.schema import SurveysQuery
|
||||||
from surveys.mutations import SurveysMutations
|
from surveys.mutations import SurveysMutations
|
||||||
from rooms.mutations import RoomMutations
|
from rooms.mutations import RoomMutations
|
||||||
from rooms.schema import RoomsQuery, ModuleRoomsQuery
|
from rooms.schema import RoomsQuery, ModuleRoomsQuery
|
||||||
from users.schema import UsersQuery
|
from users.schema import AllUsersQuery, UsersQuery
|
||||||
from users.mutations import ProfileMutations
|
from users.mutations import ProfileMutations
|
||||||
|
|
||||||
|
|
||||||
class Query(UsersQuery, ModuleRoomsQuery, RoomsQuery, ObjectivesQuery, BookQuery, AssignmentsQuery, StudentSubmissionQuery,
|
class Query(UsersQuery, AllUsersQuery, ModuleRoomsQuery, RoomsQuery, ObjectivesQuery, BookQuery, AssignmentsQuery,
|
||||||
BasicKnowledgeQuery, PortfolioQuery, MyActivityQuery, SurveysQuery, graphene.ObjectType):
|
StudentSubmissionQuery, BasicKnowledgeQuery, PortfolioQuery, MyActivityQuery, SurveysQuery,
|
||||||
|
graphene.ObjectType):
|
||||||
node = relay.Node.Field()
|
node = relay.Node.Field()
|
||||||
|
|
||||||
if settings.DEBUG:
|
if settings.DEBUG:
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,14 @@
|
||||||
|
import graphene
|
||||||
|
from django.conf import settings
|
||||||
|
from graphene_django.debug import DjangoDebug
|
||||||
|
|
||||||
|
from users.mutations_public import UserMutations
|
||||||
|
|
||||||
|
|
||||||
|
class Mutation(UserMutations, graphene.ObjectType):
|
||||||
|
|
||||||
|
if settings.DEBUG:
|
||||||
|
debug = graphene.Field(DjangoDebug, name='__debug')
|
||||||
|
|
||||||
|
|
||||||
|
schema = graphene.Schema(mutation=Mutation)
|
||||||
|
|
@ -1,13 +1,21 @@
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.conf.urls import url
|
from django.conf.urls import url
|
||||||
from django.views.decorators.csrf import csrf_exempt
|
from django.views.decorators.csrf import csrf_exempt
|
||||||
|
from graphene_django.views import GraphQLView
|
||||||
|
|
||||||
|
from api.schema_public import schema
|
||||||
|
|
||||||
from core.views import PrivateGraphQLView
|
from core.views import PrivateGraphQLView
|
||||||
|
|
||||||
app_name = 'api'
|
app_name = 'api'
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
|
url(r'^graphql-public', csrf_exempt(GraphQLView.as_view(schema=schema))),
|
||||||
url(r'^graphql', csrf_exempt(PrivateGraphQLView.as_view())),
|
url(r'^graphql', csrf_exempt(PrivateGraphQLView.as_view())),
|
||||||
]
|
]
|
||||||
|
|
||||||
if settings.DEBUG:
|
if settings.DEBUG:
|
||||||
|
urlpatterns += [url(r'^graphiql-public', csrf_exempt(GraphQLView.as_view(schema=schema, graphiql=True,
|
||||||
|
pretty=True)))]
|
||||||
urlpatterns += [url(r'^graphiql', csrf_exempt(PrivateGraphQLView.as_view(graphiql=True, pretty=True)))]
|
urlpatterns += [url(r'^graphiql', csrf_exempt(PrivateGraphQLView.as_view(graphiql=True, pretty=True)))]
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -75,3 +75,23 @@ class CommonRedirectMiddleware(MiddlewareMixin):
|
||||||
# or dummy image: return 'http://via.placeholder.com/{}'.format(m.group('dimensions'))
|
# or dummy image: return 'http://via.placeholder.com/{}'.format(m.group('dimensions'))
|
||||||
if '.png' in path or '.jpg' in path or '.svg' in path or 'not-found' in path:
|
if '.png' in path or '.jpg' in path or '.svg' in path or 'not-found' in path:
|
||||||
return 'https://picsum.photos/400/400'
|
return 'https://picsum.photos/400/400'
|
||||||
|
|
||||||
|
|
||||||
|
# https://stackoverflow.com/questions/4898408/how-to-set-a-login-cookie-in-django
|
||||||
|
class UserLoggedInCookieMiddleWare(MiddlewareMixin):
|
||||||
|
"""
|
||||||
|
Middleware to set user cookie
|
||||||
|
If user is authenticated and there is no cookie, set the cookie,
|
||||||
|
If the user is not authenticated and the cookie remains, delete it
|
||||||
|
"""
|
||||||
|
|
||||||
|
cookie_name = 'loginStatus'
|
||||||
|
|
||||||
|
def process_response(self, request, response):
|
||||||
|
#if user and no cookie, set cookie
|
||||||
|
if request.user.is_authenticated and not request.COOKIES.get(self.cookie_name):
|
||||||
|
response.set_cookie(self.cookie_name, 'true')
|
||||||
|
elif not request.user.is_authenticated and request.COOKIES.get(self.cookie_name):
|
||||||
|
#else if if no user and cookie remove user cookie, logout
|
||||||
|
response.delete_cookie(self.cookie_name)
|
||||||
|
return response
|
||||||
|
|
|
||||||
|
|
@ -116,6 +116,7 @@ MIDDLEWARE += [
|
||||||
|
|
||||||
'core.middleware.ThreadLocalMiddleware',
|
'core.middleware.ThreadLocalMiddleware',
|
||||||
'core.middleware.CommonRedirectMiddleware',
|
'core.middleware.CommonRedirectMiddleware',
|
||||||
|
'core.middleware.UserLoggedInCookieMiddleWare',
|
||||||
]
|
]
|
||||||
|
|
||||||
ROOT_URLCONF = 'core.urls'
|
ROOT_URLCONF = 'core.urls'
|
||||||
|
|
@ -172,8 +173,10 @@ else:
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
LOGOUT_REDIRECT_URL = '/'
|
LOGOUT_REDIRECT_URL = '/login'
|
||||||
LOGIN_REDIRECT_URL = '/'
|
LOGIN_REDIRECT_URL = '/login'
|
||||||
|
|
||||||
|
LOGIN_URL = LOGIN_REDIRECT_URL
|
||||||
|
|
||||||
# Internationalization
|
# Internationalization
|
||||||
# https://docs.djangoproject.com/en/1.11/topics/i18n/
|
# https://docs.djangoproject.com/en/1.11/topics/i18n/
|
||||||
|
|
|
||||||
|
|
@ -14,3 +14,4 @@ MIGRATION_MODULES = DisableMigrations()
|
||||||
# Email Settings
|
# Email Settings
|
||||||
SENDGRID_API_KEY = ""
|
SENDGRID_API_KEY = ""
|
||||||
EMAIL_BACKEND = 'django.core.mail.backends.dummy.EmailBackend'
|
EMAIL_BACKEND = 'django.core.mail.backends.dummy.EmailBackend'
|
||||||
|
LOGIN_REDIRECT_URL = '/accounts/login/'
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,3 @@
|
||||||
@import "materialize/materialize";
|
|
||||||
|
|
||||||
$white: #fff;
|
$white: #fff;
|
||||||
|
|
||||||
$grey-1: #F7F7F7;
|
$grey-1: #F7F7F7;
|
||||||
|
|
@ -99,12 +97,69 @@ input[type=text], input[type=password], input[type=email], select {
|
||||||
}
|
}
|
||||||
|
|
||||||
.logo {
|
.logo {
|
||||||
color: $brand;
|
width: 250px;
|
||||||
font-size: 36px;
|
height: 48px;
|
||||||
font-weight: 800;
|
position: relative;
|
||||||
font-family: Montserrat, Arial, sans-serif;
|
left: -10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.reset-heading {
|
/* reset forms */
|
||||||
font-size: 2.4rem;
|
|
||||||
|
.reset__heading {
|
||||||
|
line-height: 4.5rem;
|
||||||
|
font-size: 44px;
|
||||||
|
margin-bottom: 24px;
|
||||||
|
margin-top: 52px;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.reset__text {
|
||||||
|
margin-bottom: 52px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.reset__form label {
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.reset__form input {
|
||||||
|
display: flex;
|
||||||
|
padding: 16px;
|
||||||
|
box-shadow: inset 0 1px 4px 0 rgba(0, 0, 0, 0.15);
|
||||||
|
border-radius: 4px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
border: 1px solid #f0f0f0;
|
||||||
|
max-width: 100%;
|
||||||
|
background-color: #ffffff;
|
||||||
|
font-size: 16px;
|
||||||
|
font-family: "Montserrat", Arial, sans-serif;
|
||||||
|
font-weight: 400;
|
||||||
|
}
|
||||||
|
|
||||||
|
.reset__form button {
|
||||||
|
background: transparent;
|
||||||
|
border: 2px solid #17A887;
|
||||||
|
padding: 5px 15px;
|
||||||
|
border-radius: 3px;
|
||||||
|
font-family: "Montserrat", Arial, sans-serif;
|
||||||
|
font-weight: 400;
|
||||||
|
display: inline-flex;
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.reset__form label {
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.reset__form div {
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.container {
|
||||||
|
max-width: 800px;
|
||||||
|
min-width: 320px;
|
||||||
|
margin: 0 auto;
|
||||||
|
@media (max-width: 1023px) {
|
||||||
|
margin: 0 30px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -28,6 +28,34 @@
|
||||||
|
|
||||||
<body class="{% block body_class %}{% endblock %}">
|
<body class="{% block body_class %}{% endblock %}">
|
||||||
<div class="container">
|
<div class="container">
|
||||||
|
<svg class="logo" xmlns="http://www.w3.org/2000/svg"
|
||||||
|
viewBox="0 0 1350 250">
|
||||||
|
<path
|
||||||
|
d="M304.4,242.15a60,60,0,0,1-19.59-3.1,64.2,64.2,0,0,1-17.6-9.63l-2.94-2.22,21.17-34,3.58,3.21a21.91,21.91,0,0,0,6,4,15.21,15.21,0,0,0,5.81,1.09c4,0,6.51-1.44,8.08-4.68l1.15-2.19L263.73,85.72H313.8L334.27,143l17.38-57.3h48.8L353,208.39c-4.53,11.34-10.91,19.87-19,25.41h0C326,239.34,316,242.15,304.4,242.15Zm-29.33-17a53.63,53.63,0,0,0,12.38,6.3,51.94,51.94,0,0,0,17,2.66c10,0,18.42-2.33,25.12-6.94h0c6.71-4.62,12.1-11.92,16-21.71L388.67,93.79h-31l-22.74,75-26.79-75H275.94L319,195l-2.88,5.47c-2.87,5.92-8.18,9.11-15.29,9.11a23.28,23.28,0,0,1-8.88-1.69,24.83,24.83,0,0,1-4.58-2.53Z"
|
||||||
|
style="fill:#36c0a1"/>
|
||||||
|
<path
|
||||||
|
d="M458.66,113a12.63,12.63,0,0,0-6.43,1.39,4.55,4.55,0,0,0-2.36,4.18q0,3.22,4.4,5.25a93.59,93.59,0,0,0,14,4.61,178.08,178.08,0,0,1,21.33,7.29,40.28,40.28,0,0,1,14.79,11q6.32,7.39,6.33,19,0,17.8-14,28.19t-37.19,10.4A102.76,102.76,0,0,1,430,200.15a84.64,84.64,0,0,1-25.4-12.33l13.29-27.22a97.33,97.33,0,0,0,21.76,10.72A64.21,64.21,0,0,0,460.16,175a14.94,14.94,0,0,0,7.07-1.39,4.33,4.33,0,0,0,2.57-4q0-3.22-4.18-5.25a84.51,84.51,0,0,0-13.83-4.61A157.5,157.5,0,0,1,431,152.67a40,40,0,0,1-14.58-10.93q-6.22-7.29-6.22-18.86,0-18,13.72-28.51t36-10.5q26.79,0,51.23,14.15l-14.36,27.22Q473,113,458.66,113Z"
|
||||||
|
style="fill:#36c0a1"/>
|
||||||
|
<path d="M604.69,202.4l-21.22-40.51-8.79,9.22v31.3h-43.3V43.35h43.3v77.38l32.15-34.94h48.87l-42.66,45,42.87,71.6Z"
|
||||||
|
style="fill:#36c0a1"/>
|
||||||
|
<path
|
||||||
|
d="M712.25,36.49q6.22,6.22,6.22,16.08t-6.22,16.08q-6.22,6.22-16.08,6.22T680,68.64q-6.33-6.21-6.32-16.08T680,36.49q6.32-6.21,16.18-6.22T712.25,36.49Zm-37.51,49.3H718V202.4h-43.3Z"
|
||||||
|
style="fill:#36c0a1"/>
|
||||||
|
<path d="M748.47,43.35h43.3V202.4h-43.3Z" style="fill:#36c0a1"/>
|
||||||
|
<path d="M823.5,43.35h43.3V202.4H823.5Z" style="fill:#36c0a1"/>
|
||||||
|
<path
|
||||||
|
d="M1002.06,91.79A50.33,50.33,0,0,1,1021,113q6.75,13.72,6.75,31.73,0,17.8-6.54,31.19a48.35,48.35,0,0,1-18.54,20.69q-12,7.29-27.87,7.29A44,44,0,0,1,956.19,200a40.21,40.21,0,0,1-14.36-11.15v13.5h-43.3V43.35h43.3V99.29a38.85,38.85,0,0,1,13.93-11.15,41.53,41.53,0,0,1,18-3.86Q989.85,84.29,1002.06,91.79Zm-23.8,70.63q5.79-7.18,5.79-18.76t-5.79-18.76a18.82,18.82,0,0,0-15.43-7.18,18.59,18.59,0,0,0-15.22,7.18q-5.79,7.19-5.79,18.76t5.79,18.76a18.58,18.58,0,0,0,15.22,7.18A18.8,18.8,0,0,0,978.27,162.42Z"
|
||||||
|
style="fill:#36c0a1"/>
|
||||||
|
<path
|
||||||
|
d="M1142.8,91.69a54.24,54.24,0,0,1,22.62,20.9q8,13.5,8,31.51,0,17.8-8,31.4a54,54,0,0,1-22.62,21q-14.58,7.4-34.08,7.4t-34.19-7.4a53.86,53.86,0,0,1-22.73-21q-8-13.61-8-31.4,0-18,8-31.51a54.08,54.08,0,0,1,22.73-20.9q14.68-7.4,34.19-7.4T1142.8,91.69Zm-49.52,34.08q-5.79,7.18-5.79,18.76,0,11.79,5.79,18.86a18.92,18.92,0,0,0,15.43,7.07,18.7,18.7,0,0,0,15.22-7.07q5.79-7.07,5.79-18.86,0-11.58-5.79-18.76a18.6,18.6,0,0,0-15.22-7.18A18.81,18.81,0,0,0,1093.28,125.77Z"
|
||||||
|
style="fill:#36c0a1"/>
|
||||||
|
<path
|
||||||
|
d="M1176.45,85.79h49.73L1242.26,116l18-30.23h47.16L1271,142.6l39,59.81h-49.73l-18-33-20.58,33h-47.59l39-59.59Z"
|
||||||
|
style="fill:#36c0a1"/>
|
||||||
|
<path
|
||||||
|
d="M245,105.8A38.35,38.35,0,0,0,229.9,89.74h0a46.56,46.56,0,0,0-46.21,1.09A41.77,41.77,0,0,0,171.45,103a38.76,38.76,0,0,0-11.67-12,42.9,42.9,0,0,0-24.06-6.82,44.09,44.09,0,0,0-21.4,5.16,41.05,41.05,0,0,0-8.13,5.83v-9.4H58V201.83h48.19V144.37c0-5.32,1.23-9.42,3.77-12.55a11.7,11.7,0,0,1,9.27-4.46,9.48,9.48,0,0,1,7.75,3.35c2.09,2.45,3.11,5.75,3.11,10.09v61h48.19V144.37c0-5.26,1.24-9.49,3.69-12.59a11.44,11.44,0,0,1,9.15-4.43,9.48,9.48,0,0,1,7.75,3.35c2.09,2.45,3.11,5.75,3.11,10.09v61h48.19V129.28A51.17,51.17,0,0,0,245,105.8Zm-2.87,88h-32v-53c0-6.25-1.7-11.41-5-15.33a17.51,17.51,0,0,0-14-6.18h0a19.38,19.38,0,0,0-15.37,7.49c-3.61,4.55-5.44,10.48-5.44,17.6v49.39h-32v-53c0-6.25-1.7-11.41-5-15.33a17.53,17.53,0,0,0-14-6.18h0a19.66,19.66,0,0,0-15.44,7.45c-3.69,4.56-5.57,10.49-5.57,17.63v49.39h-32v-100h32v18.16h5.19l2.25-3.54a34.63,34.63,0,0,1,12.63-12,36.13,36.13,0,0,1,17.53-4.17,35,35,0,0,1,19.62,5.49,31.51,31.51,0,0,1,12.17,15.38l.46,1.18h6.52l.45-1a36.75,36.75,0,0,1,50.91-16.55,30,30,0,0,1,11.93,12.74,43.2,43.2,0,0,1,4.33,19.81Z"
|
||||||
|
style="fill:#36c0a1"/>
|
||||||
|
</svg>
|
||||||
{% block body %}
|
{% block body %}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -4,10 +4,9 @@
|
||||||
{% block title %}{% trans 'Passwort zurücksetzen abgeschlossen' %}{% endblock %}
|
{% block title %}{% trans 'Passwort zurücksetzen abgeschlossen' %}{% endblock %}
|
||||||
|
|
||||||
{% block body %}
|
{% block body %}
|
||||||
<h1 class="logo">myskillbox</h1>
|
<div class="reset">
|
||||||
<h2 class="reset-heading">{% trans 'Passwort zurücksetzen abgeschlossen' %}</h2>
|
<h2 class="reset__heading">{% trans 'Passwort zurücksetzen abgeschlossen' %}</h2>
|
||||||
<p>{% trans 'Ihr Passwort wurde zurückgesetzt. Sie können sich nun auf der Loginseite anmelden.' %}</p>
|
<p class="reset__text">{% trans 'Ihr Passwort wurde zurückgesetzt. Sie können sich nun auf der Loginseite anmelden.' %}</p>
|
||||||
<p><a href="{% url "login" %}">{% trans 'Einloggen' %}</a></p>
|
<p class="reset__text"><a href="/login">{% trans 'Einloggen' %}</a></p>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
|
||||||
|
|
@ -5,12 +5,13 @@
|
||||||
{% block title %}{% trans 'Setzen Sie Ihr Passwort' %}{% endblock %}
|
{% block title %}{% trans 'Setzen Sie Ihr Passwort' %}{% endblock %}
|
||||||
|
|
||||||
{% block body %}
|
{% block body %}
|
||||||
<h1 class="logo">myskillbox</h1>
|
<div class="reset">
|
||||||
<h2 class="reset-heading">{% trans 'Setzen Sie Ihr neues Passwort' %}</h2>
|
<h2 class="reset__heading">{% trans 'Setzen Sie Ihr neues Passwort' %}</h2>
|
||||||
|
<p class="reset__text">{% trans 'Kein Problem! Geben Sie Ihre E-Mail-Adresse ein und erhalten Sie weitere Anweisungen.' %}</p>
|
||||||
<form method="post" class="mt-1">
|
<form method="post" class="mt-1 reset__form">
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
{{ form.as_p }}
|
{{ form.as_p }}
|
||||||
<button class="btn mt-1" type="submit" name="action">{% trans 'Passwort zurücksetzen' %}</button>
|
<button class="btn mt-1" type="submit" name="action">{% trans 'Passwort zurücksetzen' %}</button>
|
||||||
</form>
|
</form>
|
||||||
|
</div>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
|
||||||
|
|
@ -5,8 +5,8 @@
|
||||||
{% block title %}{% trans 'Anweisungen versandt' %}{% endblock %}
|
{% block title %}{% trans 'Anweisungen versandt' %}{% endblock %}
|
||||||
|
|
||||||
{% block body %}
|
{% block body %}
|
||||||
<h1 class="logo">myskillbox</h1>
|
<div class="reset">
|
||||||
<h2 class="reset-heading">{% trans 'Schauen Sie in Ihr Postfach' %}</h2>
|
<h2 class="reset__heading">{% trans 'Schauen Sie in Ihr Postfach' %}</h2>
|
||||||
<p>{% trans 'Wir haben die Anweisungen, um Ihr Passwort zurückzusetzen, an Sie verschickt. Die E-Mail sollte in Kürze bei Ihnen ankommen.' %}</p>
|
<p class="reset__text">{% trans 'Wir haben die Anweisungen, um Ihr Passwort zurückzusetzen, an Sie verschickt. Die E-Mail sollte in Kürze bei Ihnen ankommen.' %}</p>
|
||||||
</div>
|
</div>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
|
||||||
|
|
@ -5,20 +5,17 @@
|
||||||
{% block title %}{% trans 'Passwort vergessen?' %}{% endblock %}
|
{% block title %}{% trans 'Passwort vergessen?' %}{% endblock %}
|
||||||
|
|
||||||
{% block body %}
|
{% block body %}
|
||||||
<h1 class="logo">myskillbox</h1>
|
<div class="reset">
|
||||||
<h2 class="reset-heading">{% trans 'Passwort vergessen?' %}</h2>
|
<h2 class="reset__heading">{% trans 'Passwort vergessen?' %}</h2>
|
||||||
<p>{% trans 'Kein Problem! Geben Sie Ihre E-Mail-Adresse ein und erhalten Sie weitere Anweisungen.' %}</p>
|
<p class="reset__text">{% trans 'Kein Problem! Geben Sie Ihre E-Mail-Adresse ein und erhalten Sie weitere Anweisungen.' %}</p>
|
||||||
|
<form method="post" class="mt-1 reset__form">
|
||||||
<form method="post" class="mt-1">
|
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
{{ form.email.label_tag }}
|
{{ form.email.label_tag }}
|
||||||
{{ form.email }}
|
{{ form.email }}
|
||||||
</div>
|
</div>
|
||||||
<button class="btn mt-1" type="submit" name="action">{% trans 'Passwort zurücksetzen' %}</button>
|
<button type="submit" name="action">{% trans 'Passwort zurücksetzen' %}</button>
|
||||||
<input type="hidden" name="next" value="{{ next }}"/>
|
<input type="hidden" name="next" value="{{ next }}"/>
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
|
||||||
|
|
@ -4,10 +4,9 @@
|
||||||
{% block title %}{% trans 'Sie haben es geschafft' %}{% endblock %}
|
{% block title %}{% trans 'Sie haben es geschafft' %}{% endblock %}
|
||||||
|
|
||||||
{% block body %}
|
{% block body %}
|
||||||
<h1 class="logo">myskillbox</h1>
|
<div class="reset">
|
||||||
<h2 class="reset-heading">{% trans 'Sie haben es geschafft' %}</h2>
|
<h2 class="reset__heading">{% trans 'Sie haben es geschafft' %}</h2>
|
||||||
<p>{% trans 'Ihr Passwort wurde erfolgreich gespeichert. Sie können sich nun anmelden.' %}</p>
|
<p class="reset__text">% trans 'Ihr Passwort wurde erfolgreich gespeichert. Sie können sich nun anmelden.' %}</p>
|
||||||
<p><a href="{% url "login" %}">{% trans 'Jetzt anmelden' %}</a></p>
|
<p class="reset__text"><a href="/login">{% trans 'Jetzt anmelden' %}</a></p>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
|
||||||
|
|
@ -5,13 +5,13 @@
|
||||||
{% block title %}{% trans 'Setzen Sie Ihr Passwort' %}{% endblock %}
|
{% block title %}{% trans 'Setzen Sie Ihr Passwort' %}{% endblock %}
|
||||||
|
|
||||||
{% block body %}
|
{% block body %}
|
||||||
<h1 class="logo">myskillbox</h1>
|
<div class="reset">
|
||||||
<h2 class="reset-heading">{% trans 'Geben Sie ein persönliches Passwort ein:' %}</h2>
|
<h2 class="reset__heading">{% trans 'Geben Sie ein persönliches Passwort ein:' %}</h2>
|
||||||
|
<p class="reset__text">{% trans 'Kein Problem! Geben Sie Ihre E-Mail-Adresse ein und erhalten Sie weitere Anweisungen.' %}</p>
|
||||||
<form method="post" class="mt-1">
|
<form method="post" class="mt-1 reset__form">
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
{{ form.as_p }}
|
{{ form.as_p }}
|
||||||
|
<button type="submit" name="action">{% trans 'Passwort speichern' %}</button>
|
||||||
<button class="btn mt-1" type="submit" name="action">{% trans 'Passwort speichern' %}</button>
|
|
||||||
</form>
|
</form>
|
||||||
|
</div>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
|
||||||
|
|
@ -5,9 +5,9 @@
|
||||||
{% block title %}{% trans 'Schauen Sie in Ihr Postfach' %}{% endblock %}
|
{% block title %}{% trans 'Schauen Sie in Ihr Postfach' %}{% endblock %}
|
||||||
|
|
||||||
{% block body %}
|
{% block body %}
|
||||||
<h1 class="logo">myskillbox</h1>
|
<div class="reset">
|
||||||
<h2 class="reset-heading">{% trans 'Schauen Sie in Ihr Postfach' %}</h2>
|
<h2 class="reset__heading">{% trans 'Schauen Sie in Ihr Postfach' %}</h2>
|
||||||
<p>{% trans 'Wir haben ein E-Mail mit allen weiteren Anweisungen an Sie verschickt. Die E-Mail sollte in Kürze bei Ihnen ankommen.' %}</p>
|
<p class="reset__text">{% trans 'Wir haben ein E-Mail mit allen weiteren Anweisungen an Sie verschickt. Die E-Mail sollte in Kürze bei Ihnen ankommen.' %}</p>
|
||||||
<p>{% trans 'Hinweis: Ihre persönlichen Angaben für Ihr Benutzerkonto wurden zuvor in mySkillbox importiert. Sie können ausschliesslich die importierte E-Mail-Adresse verwenden. Wenn Sie nicht wissen, welche E-Mail-Adresse für Sie importiert wurde, können Sie Ihre Lehrperson fragen.' %}</p>
|
<p class="reset__text">{% trans 'Hinweis: Ihre persönlichen Angaben für Ihr Benutzerkonto wurden zuvor in mySkillbox importiert. Sie können ausschliesslich die importierte E-Mail-Adresse verwenden. Wenn Sie nicht wissen, welche E-Mail-Adresse für Sie importiert wurde, können Sie Ihre Lehrperson fragen.' %}</p>
|
||||||
</div>
|
</div>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
|
||||||
|
|
@ -5,13 +5,11 @@
|
||||||
{% block title %}{% trans 'Willkommen bei mySkillbox' %}{% endblock %}
|
{% block title %}{% trans 'Willkommen bei mySkillbox' %}{% endblock %}
|
||||||
|
|
||||||
{% block body %}
|
{% block body %}
|
||||||
<h1 class="logo">myskillbox</h1>
|
<div class="reset">
|
||||||
<h2 class="reset-heading">{% trans 'Willkommen bei Myskillbox' %}</h2>
|
<h2 class="reset__heading">{% trans 'Willkommen bei Myskillbox' %}</h2>
|
||||||
<p>{% trans 'Bevor Sie mySkillbox verwenden können, müssen Sie Ihre E-Mail-Adresse bestätigen und ein persönliches Passwort festlegen.' %}</p>
|
<p class="reset__text">{% trans 'Bevor Sie mySkillbox verwenden können, müssen Sie Ihre E-Mail-Adresse bestätigen und ein persönliches Passwort festlegen.' %}</p>
|
||||||
|
<form method="post" class="mt-1 reset__form">
|
||||||
<form method="post" class="mt-1">
|
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<label for="id_email">{% trans 'Geben Sie als erstes hier Ihre E-Mail-Adresse ein:' %}</label>
|
<label for="id_email">{% trans 'Geben Sie als erstes hier Ihre E-Mail-Adresse ein:' %}</label>
|
||||||
{{ form.email }}
|
{{ form.email }}
|
||||||
|
|
@ -19,6 +17,5 @@
|
||||||
<button class="btn mt-1" type="submit" name="action">{% trans 'E-Mail bestätigen' %}</button>
|
<button class="btn mt-1" type="submit" name="action">{% trans 'E-Mail bestätigen' %}</button>
|
||||||
<input type="hidden" name="next" value="{{ next }}"/>
|
<input type="hidden" name="next" value="{{ next }}"/>
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,9 @@
|
||||||
import json
|
import json
|
||||||
|
|
||||||
from django.test import TestCase, Client
|
from django.test import TestCase, Client
|
||||||
|
from django.test.utils import override_settings
|
||||||
|
|
||||||
|
from core import settings
|
||||||
from core.factories import UserFactory
|
from core.factories import UserFactory
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -17,7 +19,8 @@ class ApiAccessTestCase(TestCase):
|
||||||
def test_graphqlEndpoint_shouldNotBeAccessibleWithoutLogin(self):
|
def test_graphqlEndpoint_shouldNotBeAccessibleWithoutLogin(self):
|
||||||
c = Client()
|
c = Client()
|
||||||
response = c.post('/api/graphql/', data=self.query, content_type='application/json')
|
response = c.post('/api/graphql/', data=self.query, content_type='application/json')
|
||||||
self.assertRedirects(response, '/accounts/login/?next=/api/graphql/')
|
self.assertEqual(response.status_code, 302)
|
||||||
|
self.assertEqual(response.url, '/login?next=/api/graphql/')
|
||||||
|
|
||||||
def test_graphqlEndpoint_shouldBeAccessibleWithLogin(self):
|
def test_graphqlEndpoint_shouldBeAccessibleWithLogin(self):
|
||||||
user = UserFactory(username='admin')
|
user = UserFactory(username='admin')
|
||||||
|
|
@ -27,3 +30,28 @@ class ApiAccessTestCase(TestCase):
|
||||||
response = c.post('/api/graphql/', data=self.query, content_type='application/json')
|
response = c.post('/api/graphql/', data=self.query, content_type='application/json')
|
||||||
|
|
||||||
self.assertEqual(200, response.status_code)
|
self.assertEqual(200, response.status_code)
|
||||||
|
|
||||||
|
def test_publicGraphqlEndpoint_shouldBeAccessibleWithoutLogin(self):
|
||||||
|
|
||||||
|
query= json.dumps({
|
||||||
|
'operationName': 'Login',
|
||||||
|
'query': '''
|
||||||
|
mutation Login($input: LoginInput!){
|
||||||
|
login(input: $input) {
|
||||||
|
success
|
||||||
|
errors {
|
||||||
|
field
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
''',
|
||||||
|
'variables': {
|
||||||
|
'input': {
|
||||||
|
'usernameInput': 'test',
|
||||||
|
'passwordInput': 'test'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
})
|
||||||
|
c = Client()
|
||||||
|
response = c.post('/api/graphql-public/', data=query, content_type='application/json')
|
||||||
|
self.assertEqual(response.status_code, 200)
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,5 @@
|
||||||
import requests
|
import requests
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.contrib.auth.decorators import login_required
|
|
||||||
from django.contrib.auth.mixins import LoginRequiredMixin
|
from django.contrib.auth.mixins import LoginRequiredMixin
|
||||||
from django.contrib.auth.views import PasswordResetView, PasswordResetDoneView, PasswordResetConfirmView, \
|
from django.contrib.auth.views import PasswordResetView, PasswordResetDoneView, PasswordResetConfirmView, \
|
||||||
PasswordResetCompleteView
|
PasswordResetCompleteView
|
||||||
|
|
@ -16,7 +15,6 @@ class PrivateGraphQLView(LoginRequiredMixin, GraphQLView):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
@login_required
|
|
||||||
@ensure_csrf_cookie
|
@ensure_csrf_cookie
|
||||||
def home(request):
|
def home(request):
|
||||||
if settings.DEBUG:
|
if settings.DEBUG:
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,49 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
#
|
||||||
|
# ITerativ GmbH
|
||||||
|
# http://www.iterativ.ch/
|
||||||
|
#
|
||||||
|
# Copyright (c) 2019 ITerativ GmbH. All rights reserved.
|
||||||
|
#
|
||||||
|
# Created on 2019-10-01
|
||||||
|
# @author: chrigu <christian.cueni@iterativ.ch>
|
||||||
|
import re
|
||||||
|
|
||||||
|
import graphene
|
||||||
|
from django.contrib.auth import authenticate, login
|
||||||
|
from graphene import relay
|
||||||
|
|
||||||
|
|
||||||
|
class FieldError(graphene.ObjectType):
|
||||||
|
code = graphene.String()
|
||||||
|
|
||||||
|
|
||||||
|
class MutationError(graphene.ObjectType):
|
||||||
|
field = graphene.String()
|
||||||
|
errors = graphene.List(FieldError)
|
||||||
|
|
||||||
|
|
||||||
|
class Login(relay.ClientIDMutation):
|
||||||
|
class Input:
|
||||||
|
username_input = graphene.String()
|
||||||
|
password_input = graphene.String()
|
||||||
|
|
||||||
|
success = graphene.Boolean()
|
||||||
|
errors = graphene.List(MutationError) # todo: change for consistency
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def mutate_and_get_payload(cls, root, info, **kwargs):
|
||||||
|
|
||||||
|
user = authenticate(username=kwargs.get('username_input'), password=kwargs.get('password_input'))
|
||||||
|
if user is not None:
|
||||||
|
login(info.context, user)
|
||||||
|
return cls(success=True, errors=[])
|
||||||
|
else:
|
||||||
|
return cls(success=False, errors=['invalid_credentials'])
|
||||||
|
|
||||||
|
|
||||||
|
class UserMutations:
|
||||||
|
login = Login.Field()
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -3,7 +3,7 @@ from graphene import relay
|
||||||
from graphene_django import DjangoObjectType
|
from graphene_django import DjangoObjectType
|
||||||
from graphene_django.filter import DjangoFilterConnectionField
|
from graphene_django.filter import DjangoFilterConnectionField
|
||||||
|
|
||||||
from users.models import SchoolClass, User
|
from users.models import User, SchoolClass
|
||||||
|
|
||||||
|
|
||||||
class SchoolClassNode(DjangoObjectType):
|
class SchoolClassNode(DjangoObjectType):
|
||||||
|
|
@ -52,3 +52,16 @@ class UsersQuery(object):
|
||||||
return User.objects.none()
|
return User.objects.none()
|
||||||
else:
|
else:
|
||||||
return User.objects.all()
|
return User.objects.all()
|
||||||
|
|
||||||
|
|
||||||
|
class AllUsersQuery(object):
|
||||||
|
me = graphene.Field(UserNode)
|
||||||
|
all_users = DjangoFilterConnectionField(UserNode)
|
||||||
|
|
||||||
|
def resolve_all_users(self, info, **kwargs):
|
||||||
|
if not info.context.user.is_superuser:
|
||||||
|
return User.objects.none()
|
||||||
|
else:
|
||||||
|
return User.objects.all()
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,64 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
#
|
||||||
|
# ITerativ GmbH
|
||||||
|
# http://www.iterativ.ch/
|
||||||
|
#
|
||||||
|
# Copyright (c) 2019 ITerativ GmbH. All rights reserved.
|
||||||
|
#
|
||||||
|
# Created on 2019-10-02
|
||||||
|
# @author: chrigu <christian.cueni@iterativ.ch>
|
||||||
|
from django.contrib.sessions.middleware import SessionMiddleware
|
||||||
|
from django.test import TestCase, RequestFactory
|
||||||
|
from graphene.test import Client
|
||||||
|
|
||||||
|
from api.schema_public import schema
|
||||||
|
from core.factories import UserFactory
|
||||||
|
|
||||||
|
|
||||||
|
class PasswordResetTests(TestCase):
|
||||||
|
def setUp(self):
|
||||||
|
self.user = UserFactory(username='aschi@iterativ.ch', email='aschi@iterativ.ch')
|
||||||
|
|
||||||
|
request = RequestFactory().post('/')
|
||||||
|
|
||||||
|
# adding session
|
||||||
|
middleware = SessionMiddleware()
|
||||||
|
middleware.process_request(request)
|
||||||
|
request.session.save()
|
||||||
|
self.client = Client(schema=schema, context_value=request)
|
||||||
|
|
||||||
|
def make_login_mutation(self, username, password):
|
||||||
|
mutation = '''
|
||||||
|
mutation Login($input: LoginInput!){
|
||||||
|
login(input: $input) {
|
||||||
|
success
|
||||||
|
errors {
|
||||||
|
field
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
'''
|
||||||
|
|
||||||
|
return self.client.execute(mutation, variables={
|
||||||
|
'input': {
|
||||||
|
'usernameInput': username,
|
||||||
|
'passwordInput': password
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
def test_user_can_login(self):
|
||||||
|
password = 'test123'
|
||||||
|
self.user.set_password(password)
|
||||||
|
self.user.save()
|
||||||
|
|
||||||
|
result = self.make_login_mutation(self.user.email, password)
|
||||||
|
self.assertTrue(result.get('data').get('login').get('success'))
|
||||||
|
self.assertTrue(self.user.is_authenticated)
|
||||||
|
|
||||||
|
def test_user_cannot_login_with_invalid_password(self):
|
||||||
|
password = 'test123'
|
||||||
|
self.user.set_password(password)
|
||||||
|
self.user.save()
|
||||||
|
|
||||||
|
result = self.make_login_mutation(self.user.email, 'test1234')
|
||||||
|
self.assertFalse(result.get('data').get('login').get('success'))
|
||||||
Loading…
Reference in New Issue