mirror of
https://github.com/IT4Change/gradido.git
synced 2025-12-13 07:45:54 +00:00
merge master
This commit is contained in:
commit
e91314f695
6
.github/workflows/test.yml
vendored
6
.github/workflows/test.yml
vendored
@ -399,7 +399,7 @@ jobs:
|
|||||||
report_name: Coverage Frontend
|
report_name: Coverage Frontend
|
||||||
type: lcov
|
type: lcov
|
||||||
result_path: ./coverage/lcov.info
|
result_path: ./coverage/lcov.info
|
||||||
min_coverage: 87
|
min_coverage: 90
|
||||||
token: ${{ github.token }}
|
token: ${{ github.token }}
|
||||||
|
|
||||||
##############################################################################
|
##############################################################################
|
||||||
@ -441,7 +441,7 @@ jobs:
|
|||||||
report_name: Coverage Admin Interface
|
report_name: Coverage Admin Interface
|
||||||
type: lcov
|
type: lcov
|
||||||
result_path: ./coverage/lcov.info
|
result_path: ./coverage/lcov.info
|
||||||
min_coverage: 60
|
min_coverage: 69
|
||||||
token: ${{ github.token }}
|
token: ${{ github.token }}
|
||||||
|
|
||||||
##############################################################################
|
##############################################################################
|
||||||
@ -491,7 +491,7 @@ jobs:
|
|||||||
report_name: Coverage Backend
|
report_name: Coverage Backend
|
||||||
type: lcov
|
type: lcov
|
||||||
result_path: ./backend/coverage/lcov.info
|
result_path: ./backend/coverage/lcov.info
|
||||||
min_coverage: 39
|
min_coverage: 37
|
||||||
token: ${{ github.token }}
|
token: ${{ github.token }}
|
||||||
|
|
||||||
##############################################################################
|
##############################################################################
|
||||||
|
|||||||
@ -11,11 +11,8 @@ import addNavigationGuards from './router/guards'
|
|||||||
|
|
||||||
import i18n from './i18n'
|
import i18n from './i18n'
|
||||||
|
|
||||||
import { ApolloClient, ApolloLink, InMemoryCache, HttpLink } from 'apollo-boost'
|
|
||||||
import VueApollo from 'vue-apollo'
|
import VueApollo from 'vue-apollo'
|
||||||
|
|
||||||
import CONFIG from './config'
|
|
||||||
|
|
||||||
import { BootstrapVue, IconsPlugin } from 'bootstrap-vue'
|
import { BootstrapVue, IconsPlugin } from 'bootstrap-vue'
|
||||||
import 'bootstrap/dist/css/bootstrap.css'
|
import 'bootstrap/dist/css/bootstrap.css'
|
||||||
import 'bootstrap-vue/dist/bootstrap-vue.css'
|
import 'bootstrap-vue/dist/bootstrap-vue.css'
|
||||||
@ -23,37 +20,7 @@ import 'bootstrap-vue/dist/bootstrap-vue.css'
|
|||||||
import moment from 'vue-moment'
|
import moment from 'vue-moment'
|
||||||
import Toasted from 'vue-toasted'
|
import Toasted from 'vue-toasted'
|
||||||
|
|
||||||
const httpLink = new HttpLink({ uri: CONFIG.GRAPHQL_URI })
|
import { apolloProvider } from './plugins/apolloProvider'
|
||||||
|
|
||||||
const authLink = new ApolloLink((operation, forward) => {
|
|
||||||
const token = store.state.token
|
|
||||||
|
|
||||||
operation.setContext({
|
|
||||||
headers: {
|
|
||||||
Authorization: token && token.length > 0 ? `Bearer ${token}` : '',
|
|
||||||
},
|
|
||||||
})
|
|
||||||
return forward(operation).map((response) => {
|
|
||||||
if (response.errors && response.errors[0].message === '403.13 - Client certificate revoked') {
|
|
||||||
response.errors[0].message = i18n.t('error.session-expired')
|
|
||||||
store.dispatch('logout', null)
|
|
||||||
if (router.currentRoute.path !== '/logout') router.push('/logout')
|
|
||||||
return response
|
|
||||||
}
|
|
||||||
const newToken = operation.getContext().response.headers.get('token')
|
|
||||||
if (newToken) store.commit('token', newToken)
|
|
||||||
return response
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
const apolloClient = new ApolloClient({
|
|
||||||
link: authLink.concat(httpLink),
|
|
||||||
cache: new InMemoryCache(),
|
|
||||||
})
|
|
||||||
|
|
||||||
const apolloProvider = new VueApollo({
|
|
||||||
defaultClient: apolloClient,
|
|
||||||
})
|
|
||||||
|
|
||||||
Vue.use(BootstrapVue)
|
Vue.use(BootstrapVue)
|
||||||
|
|
||||||
|
|||||||
@ -101,77 +101,4 @@ describe('main', () => {
|
|||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('ApolloLink', () => {
|
|
||||||
// mock store
|
|
||||||
const storeDispatchMock = jest.fn()
|
|
||||||
store.state = {
|
|
||||||
token: 'some-token',
|
|
||||||
}
|
|
||||||
store.dispatch = storeDispatchMock
|
|
||||||
|
|
||||||
// mock i18n.t
|
|
||||||
i18n.t = jest.fn((t) => t)
|
|
||||||
|
|
||||||
// mock apllo response
|
|
||||||
const responseMock = {
|
|
||||||
errors: [{ message: '403.13 - Client certificate revoked' }],
|
|
||||||
}
|
|
||||||
|
|
||||||
// mock router
|
|
||||||
const routerPushMock = jest.fn()
|
|
||||||
router.push = routerPushMock
|
|
||||||
router.currentRoute = {
|
|
||||||
path: '/overview',
|
|
||||||
}
|
|
||||||
|
|
||||||
// mock context
|
|
||||||
const setContextMock = jest.fn()
|
|
||||||
const getContextMock = jest.fn(() => {
|
|
||||||
return {
|
|
||||||
response: {
|
|
||||||
headers: {
|
|
||||||
get: jest.fn(),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
// mock apollo link function params
|
|
||||||
const operationMock = {
|
|
||||||
setContext: setContextMock,
|
|
||||||
getContext: getContextMock,
|
|
||||||
}
|
|
||||||
|
|
||||||
const forwardMock = jest.fn(() => {
|
|
||||||
return [responseMock]
|
|
||||||
})
|
|
||||||
|
|
||||||
// get apollo link callback
|
|
||||||
const middleware = ApolloLink.mock.calls[0][0]
|
|
||||||
|
|
||||||
beforeEach(() => {
|
|
||||||
jest.clearAllMocks()
|
|
||||||
// run the callback with mocked params
|
|
||||||
middleware(operationMock, forwardMock)
|
|
||||||
})
|
|
||||||
|
|
||||||
it('sets authorization header', () => {
|
|
||||||
expect(setContextMock).toBeCalledWith({
|
|
||||||
headers: {
|
|
||||||
Authorization: 'Bearer some-token',
|
|
||||||
},
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
describe('apollo response is 403.13', () => {
|
|
||||||
it.skip('dispatches logout', () => {
|
|
||||||
expect(storeDispatchMock).toBeCalledWith('logout', null)
|
|
||||||
})
|
|
||||||
|
|
||||||
it.skip('redirects to logout', () => {
|
|
||||||
expect(routerPushMock).toBeCalledWith('/logout')
|
|
||||||
})
|
|
||||||
})
|
|
||||||
})
|
|
||||||
})
|
})
|
||||||
|
|||||||
@ -76,6 +76,7 @@ export default {
|
|||||||
this.$apollo
|
this.$apollo
|
||||||
.query({
|
.query({
|
||||||
query: getPendingCreations,
|
query: getPendingCreations,
|
||||||
|
fetchPolicy: 'network-only',
|
||||||
})
|
})
|
||||||
.then((result) => {
|
.then((result) => {
|
||||||
this.$store.commit('resetOpenCreations')
|
this.$store.commit('resetOpenCreations')
|
||||||
|
|||||||
@ -62,6 +62,7 @@ export default {
|
|||||||
this.$apollo
|
this.$apollo
|
||||||
.query({
|
.query({
|
||||||
query: getPendingCreations,
|
query: getPendingCreations,
|
||||||
|
fetchPolicy: 'network-only',
|
||||||
})
|
})
|
||||||
.then((result) => {
|
.then((result) => {
|
||||||
this.$store.commit('setOpenCreations', result.data.getPendingCreations.length)
|
this.$store.commit('setOpenCreations', result.data.getPendingCreations.length)
|
||||||
|
|||||||
37
admin/src/plugins/apolloProvider.js
Normal file
37
admin/src/plugins/apolloProvider.js
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
import { ApolloClient, ApolloLink, InMemoryCache, HttpLink } from 'apollo-boost'
|
||||||
|
import VueApollo from 'vue-apollo'
|
||||||
|
import CONFIG from '../config'
|
||||||
|
import store from '../store/store'
|
||||||
|
import router from '../router/router'
|
||||||
|
import i18n from '../i18n'
|
||||||
|
|
||||||
|
const httpLink = new HttpLink({ uri: CONFIG.GRAPHQL_URI })
|
||||||
|
|
||||||
|
const authLink = new ApolloLink((operation, forward) => {
|
||||||
|
const token = store.state.token
|
||||||
|
operation.setContext({
|
||||||
|
headers: {
|
||||||
|
Authorization: token && token.length > 0 ? `Bearer ${token}` : '',
|
||||||
|
},
|
||||||
|
})
|
||||||
|
return forward(operation).map((response) => {
|
||||||
|
if (response.errors && response.errors[0].message === '403.13 - Client certificate revoked') {
|
||||||
|
response.errors[0].message = i18n.t('error.session-expired')
|
||||||
|
store.dispatch('logout', null)
|
||||||
|
if (router.currentRoute.path !== '/logout') router.push('/logout')
|
||||||
|
return response
|
||||||
|
}
|
||||||
|
const newToken = operation.getContext().response.headers.get('token')
|
||||||
|
if (newToken) store.commit('token', newToken)
|
||||||
|
return response
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
const apolloClient = new ApolloClient({
|
||||||
|
link: authLink.concat(httpLink),
|
||||||
|
cache: new InMemoryCache(),
|
||||||
|
})
|
||||||
|
|
||||||
|
export const apolloProvider = new VueApollo({
|
||||||
|
defaultClient: apolloClient,
|
||||||
|
})
|
||||||
178
admin/src/plugins/apolloProvider.test.js
Normal file
178
admin/src/plugins/apolloProvider.test.js
Normal file
@ -0,0 +1,178 @@
|
|||||||
|
import { ApolloClient, ApolloLink, HttpLink } from 'apollo-boost'
|
||||||
|
import './apolloProvider'
|
||||||
|
import CONFIG from '../config'
|
||||||
|
|
||||||
|
import VueApollo from 'vue-apollo'
|
||||||
|
import store from '../store/store'
|
||||||
|
import router from '../router/router'
|
||||||
|
import i18n from '../i18n'
|
||||||
|
|
||||||
|
jest.mock('vue-apollo')
|
||||||
|
jest.mock('../store/store')
|
||||||
|
jest.mock('../router/router')
|
||||||
|
jest.mock('../i18n')
|
||||||
|
|
||||||
|
jest.mock('apollo-boost', () => {
|
||||||
|
return {
|
||||||
|
__esModule: true,
|
||||||
|
ApolloClient: jest.fn(),
|
||||||
|
ApolloLink: jest.fn(() => {
|
||||||
|
return { concat: jest.fn() }
|
||||||
|
}),
|
||||||
|
InMemoryCache: jest.fn(),
|
||||||
|
HttpLink: jest.fn(),
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('apolloProvider', () => {
|
||||||
|
it('calls the HttpLink', () => {
|
||||||
|
expect(HttpLink).toBeCalledWith({ uri: CONFIG.GRAPHQL_URI })
|
||||||
|
})
|
||||||
|
|
||||||
|
it('calls the ApolloLink', () => {
|
||||||
|
expect(ApolloLink).toBeCalled()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('calls the ApolloClient', () => {
|
||||||
|
expect(ApolloClient).toBeCalled()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('calls the VueApollo', () => {
|
||||||
|
expect(VueApollo).toBeCalled()
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('ApolloLink', () => {
|
||||||
|
// mock store
|
||||||
|
const storeDispatchMock = jest.fn()
|
||||||
|
const storeCommitMock = jest.fn()
|
||||||
|
store.state = {
|
||||||
|
token: 'some-token',
|
||||||
|
}
|
||||||
|
store.dispatch = storeDispatchMock
|
||||||
|
store.commit = storeCommitMock
|
||||||
|
|
||||||
|
// mock i18n.t
|
||||||
|
i18n.t = jest.fn((t) => t)
|
||||||
|
|
||||||
|
// mock apllo response
|
||||||
|
const responseMock = {
|
||||||
|
errors: [{ message: '403.13 - Client certificate revoked' }],
|
||||||
|
}
|
||||||
|
|
||||||
|
// mock router
|
||||||
|
const routerPushMock = jest.fn()
|
||||||
|
router.push = routerPushMock
|
||||||
|
router.currentRoute = {
|
||||||
|
path: '/overview',
|
||||||
|
}
|
||||||
|
|
||||||
|
// mock context
|
||||||
|
const setContextMock = jest.fn()
|
||||||
|
const getContextMock = jest.fn(() => {
|
||||||
|
return {
|
||||||
|
response: {
|
||||||
|
headers: {
|
||||||
|
get: jest.fn(() => 'another-token'),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// mock apollo link function params
|
||||||
|
const operationMock = {
|
||||||
|
setContext: setContextMock,
|
||||||
|
getContext: getContextMock,
|
||||||
|
}
|
||||||
|
|
||||||
|
const forwardMock = jest.fn(() => {
|
||||||
|
return [responseMock]
|
||||||
|
})
|
||||||
|
|
||||||
|
// get apollo link callback
|
||||||
|
const middleware = ApolloLink.mock.calls[0][0]
|
||||||
|
|
||||||
|
describe('with token in store', () => {
|
||||||
|
it('sets authorization header with token', () => {
|
||||||
|
// run the apollo link callback with mocked params
|
||||||
|
middleware(operationMock, forwardMock)
|
||||||
|
expect(setContextMock).toBeCalledWith({
|
||||||
|
headers: {
|
||||||
|
Authorization: 'Bearer some-token',
|
||||||
|
},
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('without token in store', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
store.state.token = null
|
||||||
|
})
|
||||||
|
|
||||||
|
it('sets authorization header empty', () => {
|
||||||
|
middleware(operationMock, forwardMock)
|
||||||
|
expect(setContextMock).toBeCalledWith({
|
||||||
|
headers: {
|
||||||
|
Authorization: '',
|
||||||
|
},
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('apollo response is 403.13', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
// run the apollo link callback with mocked params
|
||||||
|
middleware(operationMock, forwardMock)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('dispatches logout', () => {
|
||||||
|
expect(storeDispatchMock).toBeCalledWith('logout', null)
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('current route is not logout', () => {
|
||||||
|
it('redirects to logout', () => {
|
||||||
|
expect(routerPushMock).toBeCalledWith('/logout')
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('current route is logout', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
jest.clearAllMocks()
|
||||||
|
router.currentRoute.path = '/logout'
|
||||||
|
})
|
||||||
|
|
||||||
|
it('does not redirect to logout', () => {
|
||||||
|
expect(routerPushMock).not.toBeCalled()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('apollo response is with new token', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
delete responseMock.errors
|
||||||
|
middleware(operationMock, forwardMock)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('commits new token to store', () => {
|
||||||
|
expect(storeCommitMock).toBeCalledWith('token', 'another-token')
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('apollo response is without new token', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
jest.clearAllMocks()
|
||||||
|
getContextMock.mockReturnValue({
|
||||||
|
response: {
|
||||||
|
headers: {
|
||||||
|
get: jest.fn(() => null),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
middleware(operationMock, forwardMock)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('does not commit token to store', () => {
|
||||||
|
expect(storeCommitMock).not.toBeCalled()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
@ -2,8 +2,6 @@ PORT=4000
|
|||||||
JWT_SECRET=secret123
|
JWT_SECRET=secret123
|
||||||
JWT_EXPIRES_IN=10m
|
JWT_EXPIRES_IN=10m
|
||||||
GRAPHIQL=false
|
GRAPHIQL=false
|
||||||
LOGIN_API_URL=http://login-server:1201/
|
|
||||||
COMMUNITY_API_URL=http://nginx/api/
|
|
||||||
GDT_API_URL=https://gdt.gradido.net
|
GDT_API_URL=https://gdt.gradido.net
|
||||||
DB_HOST=localhost
|
DB_HOST=localhost
|
||||||
DB_PORT=3306
|
DB_PORT=3306
|
||||||
|
|||||||
@ -4,10 +4,8 @@ export const INALIENABLE_RIGHTS = [
|
|||||||
RIGHTS.LOGIN,
|
RIGHTS.LOGIN,
|
||||||
RIGHTS.GET_COMMUNITY_INFO,
|
RIGHTS.GET_COMMUNITY_INFO,
|
||||||
RIGHTS.COMMUNITIES,
|
RIGHTS.COMMUNITIES,
|
||||||
RIGHTS.LOGIN_VIA_EMAIL_VERIFICATION_CODE,
|
|
||||||
RIGHTS.CREATE_USER,
|
RIGHTS.CREATE_USER,
|
||||||
RIGHTS.SEND_RESET_PASSWORD_EMAIL,
|
RIGHTS.SEND_RESET_PASSWORD_EMAIL,
|
||||||
RIGHTS.RESET_PASSWORD,
|
RIGHTS.SET_PASSWORD,
|
||||||
RIGHTS.CHECK_USERNAME,
|
RIGHTS.CHECK_USERNAME,
|
||||||
RIGHTS.CHECK_EMAIL,
|
|
||||||
]
|
]
|
||||||
|
|||||||
@ -12,14 +12,12 @@ export enum RIGHTS {
|
|||||||
SUBSCRIBE_NEWSLETTER = 'SUBSCRIBE_NEWSLETTER',
|
SUBSCRIBE_NEWSLETTER = 'SUBSCRIBE_NEWSLETTER',
|
||||||
TRANSACTION_LIST = 'TRANSACTION_LIST',
|
TRANSACTION_LIST = 'TRANSACTION_LIST',
|
||||||
SEND_COINS = 'SEND_COINS',
|
SEND_COINS = 'SEND_COINS',
|
||||||
LOGIN_VIA_EMAIL_VERIFICATION_CODE = 'LOGIN_VIA_EMAIL_VERIFICATION_CODE',
|
|
||||||
LOGOUT = 'LOGOUT',
|
LOGOUT = 'LOGOUT',
|
||||||
CREATE_USER = 'CREATE_USER',
|
CREATE_USER = 'CREATE_USER',
|
||||||
SEND_RESET_PASSWORD_EMAIL = 'SEND_RESET_PASSWORD_EMAIL',
|
SEND_RESET_PASSWORD_EMAIL = 'SEND_RESET_PASSWORD_EMAIL',
|
||||||
RESET_PASSWORD = 'RESET_PASSWORD',
|
SET_PASSWORD = 'SET_PASSWORD',
|
||||||
UPDATE_USER_INFOS = 'UPDATE_USER_INFOS',
|
UPDATE_USER_INFOS = 'UPDATE_USER_INFOS',
|
||||||
CHECK_USERNAME = 'CHECK_USERNAME',
|
CHECK_USERNAME = 'CHECK_USERNAME',
|
||||||
CHECK_EMAIL = 'CHECK_EMAIL',
|
|
||||||
HAS_ELOPAGE = 'HAS_ELOPAGE',
|
HAS_ELOPAGE = 'HAS_ELOPAGE',
|
||||||
// Admin
|
// Admin
|
||||||
SEARCH_USERS = 'SEARCH_USERS',
|
SEARCH_USERS = 'SEARCH_USERS',
|
||||||
|
|||||||
@ -8,8 +8,6 @@ const server = {
|
|||||||
JWT_SECRET: process.env.JWT_SECRET || 'secret123',
|
JWT_SECRET: process.env.JWT_SECRET || 'secret123',
|
||||||
JWT_EXPIRES_IN: process.env.JWT_EXPIRES_IN || '10m',
|
JWT_EXPIRES_IN: process.env.JWT_EXPIRES_IN || '10m',
|
||||||
GRAPHIQL: process.env.GRAPHIQL === 'true' || false,
|
GRAPHIQL: process.env.GRAPHIQL === 'true' || false,
|
||||||
LOGIN_API_URL: process.env.LOGIN_API_URL || 'http://login-server:1201/',
|
|
||||||
COMMUNITY_API_URL: process.env.COMMUNITY_API_URL || 'http://nginx/api/',
|
|
||||||
GDT_API_URL: process.env.GDT_API_URL || 'https://gdt.gradido.net',
|
GDT_API_URL: process.env.GDT_API_URL || 'https://gdt.gradido.net',
|
||||||
PRODUCTION: process.env.NODE_ENV === 'production' || false,
|
PRODUCTION: process.env.NODE_ENV === 'production' || false,
|
||||||
}
|
}
|
||||||
@ -53,6 +51,7 @@ const email = {
|
|||||||
EMAIL_SMTP_PORT: process.env.EMAIL_SMTP_PORT || '587',
|
EMAIL_SMTP_PORT: process.env.EMAIL_SMTP_PORT || '587',
|
||||||
EMAIL_LINK_VERIFICATION:
|
EMAIL_LINK_VERIFICATION:
|
||||||
process.env.EMAIL_LINK_VERIFICATION || 'http://localhost/vue/checkEmail/$1',
|
process.env.EMAIL_LINK_VERIFICATION || 'http://localhost/vue/checkEmail/$1',
|
||||||
|
EMAIL_LINK_SETPASSWORD: process.env.EMAIL_LINK_SETPASSWORD || 'http://localhost/vue/reset/$1',
|
||||||
}
|
}
|
||||||
|
|
||||||
const webhook = {
|
const webhook = {
|
||||||
|
|||||||
@ -1,13 +0,0 @@
|
|||||||
import { ArgsType, Field } from 'type-graphql'
|
|
||||||
|
|
||||||
@ArgsType()
|
|
||||||
export default class ChangePasswordArgs {
|
|
||||||
@Field(() => Number)
|
|
||||||
sessionId: number
|
|
||||||
|
|
||||||
@Field(() => String)
|
|
||||||
email: string
|
|
||||||
|
|
||||||
@Field(() => String)
|
|
||||||
password: string
|
|
||||||
}
|
|
||||||
@ -1,29 +0,0 @@
|
|||||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
||||||
/* eslint-disable @typescript-eslint/explicit-module-boundary-types */
|
|
||||||
import { ObjectType, Field } from 'type-graphql'
|
|
||||||
|
|
||||||
@ObjectType()
|
|
||||||
export class CheckEmailResponse {
|
|
||||||
constructor(json: any) {
|
|
||||||
this.sessionId = json.session_id
|
|
||||||
this.email = json.user.email
|
|
||||||
this.language = json.user.language
|
|
||||||
this.firstName = json.user.first_name
|
|
||||||
this.lastName = json.user.last_name
|
|
||||||
}
|
|
||||||
|
|
||||||
@Field(() => Number)
|
|
||||||
sessionId: number
|
|
||||||
|
|
||||||
@Field(() => String)
|
|
||||||
email: string
|
|
||||||
|
|
||||||
@Field(() => String)
|
|
||||||
firstName: string
|
|
||||||
|
|
||||||
@Field(() => String)
|
|
||||||
lastName: string
|
|
||||||
|
|
||||||
@Field(() => String)
|
|
||||||
language: string
|
|
||||||
}
|
|
||||||
@ -1,17 +0,0 @@
|
|||||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
||||||
/* eslint-disable @typescript-eslint/explicit-module-boundary-types */
|
|
||||||
import { ObjectType, Field } from 'type-graphql'
|
|
||||||
|
|
||||||
@ObjectType()
|
|
||||||
export class LoginViaVerificationCode {
|
|
||||||
constructor(json: any) {
|
|
||||||
this.sessionId = json.session_id
|
|
||||||
this.email = json.user.email
|
|
||||||
}
|
|
||||||
|
|
||||||
@Field(() => Number)
|
|
||||||
sessionId: number
|
|
||||||
|
|
||||||
@Field(() => String)
|
|
||||||
email: string
|
|
||||||
}
|
|
||||||
@ -1,17 +0,0 @@
|
|||||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
||||||
/* eslint-disable @typescript-eslint/explicit-module-boundary-types */
|
|
||||||
import { ObjectType, Field } from 'type-graphql'
|
|
||||||
|
|
||||||
@ObjectType()
|
|
||||||
export class SendPasswordResetEmailResponse {
|
|
||||||
constructor(json: any) {
|
|
||||||
this.state = json.state
|
|
||||||
this.msg = json.msg
|
|
||||||
}
|
|
||||||
|
|
||||||
@Field(() => String)
|
|
||||||
state: string
|
|
||||||
|
|
||||||
@Field(() => String)
|
|
||||||
msg?: string
|
|
||||||
}
|
|
||||||
@ -83,7 +83,7 @@ export class AdminResolver {
|
|||||||
|
|
||||||
await pendingCreationRepository.save(updatedCreation)
|
await pendingCreationRepository.save(updatedCreation)
|
||||||
const result = new UpdatePendingCreation()
|
const result = new UpdatePendingCreation()
|
||||||
result.amount = parseInt(updatedCreation.amount.toString())
|
result.amount = parseInt(amount.toString())
|
||||||
result.memo = updatedCreation.memo
|
result.memo = updatedCreation.memo
|
||||||
result.date = updatedCreation.date
|
result.date = updatedCreation.date
|
||||||
result.moderator = updatedCreation.moderator
|
result.moderator = updatedCreation.moderator
|
||||||
@ -176,7 +176,7 @@ export class AdminResolver {
|
|||||||
} else {
|
} else {
|
||||||
newBalance = lastUserTransaction.balance
|
newBalance = lastUserTransaction.balance
|
||||||
}
|
}
|
||||||
newBalance = Number(newBalance) + Number(parseInt(pendingCreation.amount.toString()) / 10000)
|
newBalance = Number(newBalance) + Number(parseInt(pendingCreation.amount.toString()))
|
||||||
|
|
||||||
const newUserTransaction = new UserTransaction()
|
const newUserTransaction = new UserTransaction()
|
||||||
newUserTransaction.userId = pendingCreation.userId
|
newUserTransaction.userId = pendingCreation.userId
|
||||||
@ -194,7 +194,7 @@ export class AdminResolver {
|
|||||||
|
|
||||||
if (!userBalance) userBalance = balanceRepository.create()
|
if (!userBalance) userBalance = balanceRepository.create()
|
||||||
userBalance.userId = pendingCreation.userId
|
userBalance.userId = pendingCreation.userId
|
||||||
userBalance.amount = Number(newBalance * 10000)
|
userBalance.amount = Number(newBalance)
|
||||||
userBalance.modified = new Date()
|
userBalance.modified = new Date()
|
||||||
userBalance.recordDate = userBalance.recordDate ? userBalance.recordDate : new Date()
|
userBalance.recordDate = userBalance.recordDate ? userBalance.recordDate : new Date()
|
||||||
await balanceRepository.save(userBalance)
|
await balanceRepository.save(userBalance)
|
||||||
|
|||||||
@ -428,7 +428,7 @@ async function addUserTransaction(
|
|||||||
if (lastUserTransaction) {
|
if (lastUserTransaction) {
|
||||||
newBalance += Number(
|
newBalance += Number(
|
||||||
await calculateDecay(
|
await calculateDecay(
|
||||||
Number(lastUserTransaction.balance * 10000),
|
Number(lastUserTransaction.balance),
|
||||||
lastUserTransaction.balanceDate,
|
lastUserTransaction.balanceDate,
|
||||||
transaction.received,
|
transaction.received,
|
||||||
).catch(() => {
|
).catch(() => {
|
||||||
|
|||||||
@ -3,24 +3,16 @@
|
|||||||
|
|
||||||
import fs from 'fs'
|
import fs from 'fs'
|
||||||
import { Resolver, Query, Args, Arg, Authorized, Ctx, UseMiddleware, Mutation } from 'type-graphql'
|
import { Resolver, Query, Args, Arg, Authorized, Ctx, UseMiddleware, Mutation } from 'type-graphql'
|
||||||
import { getConnection, getCustomRepository } from 'typeorm'
|
import { getConnection, getCustomRepository, getRepository } from 'typeorm'
|
||||||
import CONFIG from '../../config'
|
import CONFIG from '../../config'
|
||||||
import { LoginViaVerificationCode } from '../model/LoginViaVerificationCode'
|
|
||||||
import { SendPasswordResetEmailResponse } from '../model/SendPasswordResetEmailResponse'
|
|
||||||
import { User } from '../model/User'
|
import { User } from '../model/User'
|
||||||
import { User as DbUser } from '@entity/User'
|
import { User as DbUser } from '@entity/User'
|
||||||
import { encode } from '../../auth/JWT'
|
import { encode } from '../../auth/JWT'
|
||||||
import ChangePasswordArgs from '../arg/ChangePasswordArgs'
|
|
||||||
import CheckUsernameArgs from '../arg/CheckUsernameArgs'
|
import CheckUsernameArgs from '../arg/CheckUsernameArgs'
|
||||||
import CreateUserArgs from '../arg/CreateUserArgs'
|
import CreateUserArgs from '../arg/CreateUserArgs'
|
||||||
import UnsecureLoginArgs from '../arg/UnsecureLoginArgs'
|
import UnsecureLoginArgs from '../arg/UnsecureLoginArgs'
|
||||||
import UpdateUserInfosArgs from '../arg/UpdateUserInfosArgs'
|
import UpdateUserInfosArgs from '../arg/UpdateUserInfosArgs'
|
||||||
import { apiPost, apiGet } from '../../apis/HttpRequest'
|
import { klicktippNewsletterStateMiddleware } from '../../middleware/klicktippMiddleware'
|
||||||
import {
|
|
||||||
klicktippRegistrationMiddleware,
|
|
||||||
klicktippNewsletterStateMiddleware,
|
|
||||||
} from '../../middleware/klicktippMiddleware'
|
|
||||||
import { CheckEmailResponse } from '../model/CheckEmailResponse'
|
|
||||||
import { UserSettingRepository } from '../../typeorm/repository/UserSettingRepository'
|
import { UserSettingRepository } from '../../typeorm/repository/UserSettingRepository'
|
||||||
import { LoginUserRepository } from '../../typeorm/repository/LoginUser'
|
import { LoginUserRepository } from '../../typeorm/repository/LoginUser'
|
||||||
import { Setting } from '../enum/Setting'
|
import { Setting } from '../enum/Setting'
|
||||||
@ -30,10 +22,14 @@ import { LoginUserBackup } from '@entity/LoginUserBackup'
|
|||||||
import { LoginEmailOptIn } from '@entity/LoginEmailOptIn'
|
import { LoginEmailOptIn } from '@entity/LoginEmailOptIn'
|
||||||
import { sendEMail } from '../../util/sendEMail'
|
import { sendEMail } from '../../util/sendEMail'
|
||||||
import { LoginElopageBuysRepository } from '../../typeorm/repository/LoginElopageBuys'
|
import { LoginElopageBuysRepository } from '../../typeorm/repository/LoginElopageBuys'
|
||||||
|
import { signIn } from '../../apis/KlicktippController'
|
||||||
import { RIGHTS } from '../../auth/RIGHTS'
|
import { RIGHTS } from '../../auth/RIGHTS'
|
||||||
import { ServerUserRepository } from '../../typeorm/repository/ServerUser'
|
import { ServerUserRepository } from '../../typeorm/repository/ServerUser'
|
||||||
import { ROLE_ADMIN } from '../../auth/ROLES'
|
import { ROLE_ADMIN } from '../../auth/ROLES'
|
||||||
|
|
||||||
|
const EMAIL_OPT_IN_RESET_PASSWORD = 2
|
||||||
|
const EMAIL_OPT_IN_REGISTER = 1
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||||
const sodium = require('sodium-native')
|
const sodium = require('sodium-native')
|
||||||
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||||
@ -58,50 +54,8 @@ const PassphraseGenerate = (): string[] => {
|
|||||||
result.push(WORDS[sodium.randombytes_random() % 2048])
|
result.push(WORDS[sodium.randombytes_random() % 2048])
|
||||||
}
|
}
|
||||||
return result
|
return result
|
||||||
/*
|
|
||||||
return [
|
|
||||||
'behind',
|
|
||||||
'salmon',
|
|
||||||
'fluid',
|
|
||||||
'orphan',
|
|
||||||
'frost',
|
|
||||||
'elder',
|
|
||||||
'amateur',
|
|
||||||
'always',
|
|
||||||
'panel',
|
|
||||||
'palm',
|
|
||||||
'leopard',
|
|
||||||
'essay',
|
|
||||||
'punch',
|
|
||||||
'title',
|
|
||||||
'fun',
|
|
||||||
'annual',
|
|
||||||
'page',
|
|
||||||
'hundred',
|
|
||||||
'journey',
|
|
||||||
'select',
|
|
||||||
'figure',
|
|
||||||
'tunnel',
|
|
||||||
'casual',
|
|
||||||
'bar',
|
|
||||||
]
|
|
||||||
*/
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
|
||||||
Test results:
|
|
||||||
INSERT INTO `login_users` (`id`, `email`, `first_name`, `last_name`, `username`, `description`, `password`, `pubkey`, `privkey`, `email_hash`, `created`, `email_checked`, `passphrase_shown`, `language`, `disabled`, `group_id`, `publisher_id`) VALUES
|
|
||||||
// old
|
|
||||||
(1, 'peter@lustig.de', 'peter', 'lustig', '', '', 4747956395458240931, 0x8c75edd507f470e5378f927489374694d68f3d155523f1c4402c36affd35a7ed, 0xb0e310655726b088631ccfd31ad6470ee50115c161dde8559572fa90657270ff13dc1200b2d3ea90dfbe92f3a4475ee4d9cee4989e39736a0870c33284bc73a8ae690e6da89f241a121eb3b500c22885, 0x9f700e6f6ec351a140b674c0edd4479509697b023bd8bee8826915ef6c2af036, '2021-11-03 20:05:04', 0, 0, 'de', 0, 1, 0);
|
|
||||||
// new
|
|
||||||
(2, 'peter@lustig.de', 'peter', 'lustig', '', '', 4747956395458240931, 0x8c75edd507f470e5378f927489374694d68f3d155523f1c4402c36affd35a7ed, 0xb0e310655726b088631ccfd31ad6470ee50115c161dde8559572fa90657270ff13dc1200b2d3ea90dfbe92f3a4475ee4d9cee4989e39736a0870c33284bc73a8ae690e6da89f241a121eb3b500c22885, 0x9f700e6f6ec351a140b674c0edd4479509697b023bd8bee8826915ef6c2af036, '2021-11-03 20:22:15', 0, 0, 'de', 0, 1, 0);
|
|
||||||
INSERT INTO `login_user_backups` (`id`, `user_id`, `passphrase`, `mnemonic_type`) VALUES
|
|
||||||
// old
|
|
||||||
(1, 1, 'behind salmon fluid orphan frost elder amateur always panel palm leopard essay punch title fun annual page hundred journey select figure tunnel casual bar ', 2);
|
|
||||||
// new
|
|
||||||
(2, 2, 'behind salmon fluid orphan frost elder amateur always panel palm leopard essay punch title fun annual page hundred journey select figure tunnel casual bar ', 2);
|
|
||||||
*/
|
|
||||||
|
|
||||||
const KeyPairEd25519Create = (passphrase: string[]): Buffer[] => {
|
const KeyPairEd25519Create = (passphrase: string[]): Buffer[] => {
|
||||||
if (!passphrase.length || passphrase.length < PHRASE_WORD_COUNT) {
|
if (!passphrase.length || passphrase.length < PHRASE_WORD_COUNT) {
|
||||||
throw new Error('passphrase empty or to short')
|
throw new Error('passphrase empty or to short')
|
||||||
@ -240,13 +194,21 @@ export class UserResolver {
|
|||||||
@Ctx() context: any,
|
@Ctx() context: any,
|
||||||
): Promise<User> {
|
): Promise<User> {
|
||||||
email = email.trim().toLowerCase()
|
email = email.trim().toLowerCase()
|
||||||
// const result = await apiPost(CONFIG.LOGIN_API_URL + 'unsecureLogin', { email, password })
|
|
||||||
// UnsecureLogin
|
|
||||||
const loginUserRepository = getCustomRepository(LoginUserRepository)
|
const loginUserRepository = getCustomRepository(LoginUserRepository)
|
||||||
const loginUser = await loginUserRepository.findByEmail(email).catch(() => {
|
const loginUser = await loginUserRepository.findByEmail(email).catch(() => {
|
||||||
throw new Error('No user with this credentials')
|
throw new Error('No user with this credentials')
|
||||||
})
|
})
|
||||||
if (!loginUser.emailChecked) throw new Error('user email not validated')
|
if (!loginUser.emailChecked) {
|
||||||
|
throw new Error('User email not validated')
|
||||||
|
}
|
||||||
|
if (loginUser.password === BigInt(0)) {
|
||||||
|
// TODO we want to catch this on the frontend and ask the user to check his emails or resend code
|
||||||
|
throw new Error('User has no password set yet')
|
||||||
|
}
|
||||||
|
if (!loginUser.pubKey || !loginUser.privKey) {
|
||||||
|
// TODO we want to catch this on the frontend and ask the user to check his emails or resend code
|
||||||
|
throw new Error('User has no private or publicKey')
|
||||||
|
}
|
||||||
const passwordHash = SecretKeyCryptographyCreateKey(email, password) // return short and long hash
|
const passwordHash = SecretKeyCryptographyCreateKey(email, password) // return short and long hash
|
||||||
const loginUserPassword = BigInt(loginUser.password.toString())
|
const loginUserPassword = BigInt(loginUser.password.toString())
|
||||||
if (loginUserPassword !== passwordHash[0].readBigUInt64LE()) {
|
if (loginUserPassword !== passwordHash[0].readBigUInt64LE()) {
|
||||||
@ -320,22 +282,6 @@ export class UserResolver {
|
|||||||
return user
|
return user
|
||||||
}
|
}
|
||||||
|
|
||||||
@Authorized([RIGHTS.LOGIN_VIA_EMAIL_VERIFICATION_CODE])
|
|
||||||
@Query(() => LoginViaVerificationCode)
|
|
||||||
async loginViaEmailVerificationCode(
|
|
||||||
@Arg('optin') optin: string,
|
|
||||||
): Promise<LoginViaVerificationCode> {
|
|
||||||
// I cannot use number as type here.
|
|
||||||
// The value received is not the same as sent by the query
|
|
||||||
const result = await apiGet(
|
|
||||||
CONFIG.LOGIN_API_URL + 'loginViaEmailVerificationCode?emailVerificationCode=' + optin,
|
|
||||||
)
|
|
||||||
if (!result.success) {
|
|
||||||
throw new Error(result.data)
|
|
||||||
}
|
|
||||||
return new LoginViaVerificationCode(result.data)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Authorized([RIGHTS.LOGOUT])
|
@Authorized([RIGHTS.LOGOUT])
|
||||||
@Query(() => String)
|
@Query(() => String)
|
||||||
async logout(): Promise<boolean> {
|
async logout(): Promise<boolean> {
|
||||||
@ -350,7 +296,7 @@ export class UserResolver {
|
|||||||
@Authorized([RIGHTS.CREATE_USER])
|
@Authorized([RIGHTS.CREATE_USER])
|
||||||
@Mutation(() => String)
|
@Mutation(() => String)
|
||||||
async createUser(
|
async createUser(
|
||||||
@Args() { email, firstName, lastName, password, language, publisherId }: CreateUserArgs,
|
@Args() { email, firstName, lastName, language, publisherId }: CreateUserArgs,
|
||||||
): Promise<string> {
|
): Promise<string> {
|
||||||
// TODO: wrong default value (should be null), how does graphql work here? Is it an required field?
|
// TODO: wrong default value (should be null), how does graphql work here? Is it an required field?
|
||||||
// default int publisher_id = 0;
|
// default int publisher_id = 0;
|
||||||
@ -360,13 +306,6 @@ export class UserResolver {
|
|||||||
language = DEFAULT_LANGUAGE
|
language = DEFAULT_LANGUAGE
|
||||||
}
|
}
|
||||||
|
|
||||||
// Validate Password
|
|
||||||
if (!isPassword(password)) {
|
|
||||||
throw new Error(
|
|
||||||
'Please enter a valid password with at least 8 characters, upper and lower case letters, at least one number and one special character!',
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Validate username
|
// Validate username
|
||||||
// TODO: never true
|
// TODO: never true
|
||||||
const username = ''
|
const username = ''
|
||||||
@ -384,10 +323,10 @@ export class UserResolver {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const passphrase = PassphraseGenerate()
|
const passphrase = PassphraseGenerate()
|
||||||
const keyPair = KeyPairEd25519Create(passphrase) // return pub, priv Key
|
// const keyPair = KeyPairEd25519Create(passphrase) // return pub, priv Key
|
||||||
const passwordHash = SecretKeyCryptographyCreateKey(email, password) // return short and long hash
|
// const passwordHash = SecretKeyCryptographyCreateKey(email, password) // return short and long hash
|
||||||
|
// const encryptedPrivkey = SecretKeyCryptographyEncrypt(keyPair[1], passwordHash[1])
|
||||||
const emailHash = getEmailHash(email)
|
const emailHash = getEmailHash(email)
|
||||||
const encryptedPrivkey = SecretKeyCryptographyEncrypt(keyPair[1], passwordHash[1])
|
|
||||||
|
|
||||||
// Table: login_users
|
// Table: login_users
|
||||||
const loginUser = new LoginUser()
|
const loginUser = new LoginUser()
|
||||||
@ -396,13 +335,13 @@ export class UserResolver {
|
|||||||
loginUser.lastName = lastName
|
loginUser.lastName = lastName
|
||||||
loginUser.username = username
|
loginUser.username = username
|
||||||
loginUser.description = ''
|
loginUser.description = ''
|
||||||
loginUser.password = passwordHash[0].readBigUInt64LE() // using the shorthash
|
// loginUser.password = passwordHash[0].readBigUInt64LE() // using the shorthash
|
||||||
loginUser.emailHash = emailHash
|
loginUser.emailHash = emailHash
|
||||||
loginUser.language = language
|
loginUser.language = language
|
||||||
loginUser.groupId = 1
|
loginUser.groupId = 1
|
||||||
loginUser.publisherId = publisherId
|
loginUser.publisherId = publisherId
|
||||||
loginUser.pubKey = keyPair[0]
|
// loginUser.pubKey = keyPair[0]
|
||||||
loginUser.privKey = encryptedPrivkey
|
// loginUser.privKey = encryptedPrivkey
|
||||||
|
|
||||||
const queryRunner = getConnection().createQueryRunner()
|
const queryRunner = getConnection().createQueryRunner()
|
||||||
await queryRunner.connect()
|
await queryRunner.connect()
|
||||||
@ -428,11 +367,13 @@ export class UserResolver {
|
|||||||
|
|
||||||
// Table: state_users
|
// Table: state_users
|
||||||
const dbUser = new DbUser()
|
const dbUser = new DbUser()
|
||||||
dbUser.pubkey = keyPair[0]
|
|
||||||
dbUser.email = email
|
dbUser.email = email
|
||||||
dbUser.firstName = firstName
|
dbUser.firstName = firstName
|
||||||
dbUser.lastName = lastName
|
dbUser.lastName = lastName
|
||||||
dbUser.username = username
|
dbUser.username = username
|
||||||
|
// TODO this field has no null allowed unlike the loginServer table
|
||||||
|
dbUser.pubkey = Buffer.alloc(32, 0) // default to 0000...
|
||||||
|
// dbUser.pubkey = keyPair[0]
|
||||||
|
|
||||||
await queryRunner.manager.save(dbUser).catch((er) => {
|
await queryRunner.manager.save(dbUser).catch((er) => {
|
||||||
// eslint-disable-next-line no-console
|
// eslint-disable-next-line no-console
|
||||||
@ -441,10 +382,11 @@ export class UserResolver {
|
|||||||
})
|
})
|
||||||
|
|
||||||
// Store EmailOptIn in DB
|
// Store EmailOptIn in DB
|
||||||
|
// TODO: this has duplicate code with sendResetPasswordEmail
|
||||||
const emailOptIn = new LoginEmailOptIn()
|
const emailOptIn = new LoginEmailOptIn()
|
||||||
emailOptIn.userId = loginUserId
|
emailOptIn.userId = loginUserId
|
||||||
emailOptIn.verificationCode = random(64)
|
emailOptIn.verificationCode = random(64)
|
||||||
emailOptIn.emailOptInTypeId = 2
|
emailOptIn.emailOptInTypeId = EMAIL_OPT_IN_REGISTER
|
||||||
|
|
||||||
await queryRunner.manager.save(emailOptIn).catch((error) => {
|
await queryRunner.manager.save(emailOptIn).catch((error) => {
|
||||||
// eslint-disable-next-line no-console
|
// eslint-disable-next-line no-console
|
||||||
@ -489,38 +431,172 @@ export class UserResolver {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Authorized([RIGHTS.SEND_RESET_PASSWORD_EMAIL])
|
@Authorized([RIGHTS.SEND_RESET_PASSWORD_EMAIL])
|
||||||
@Query(() => SendPasswordResetEmailResponse)
|
@Query(() => Boolean)
|
||||||
async sendResetPasswordEmail(
|
async sendResetPasswordEmail(@Arg('email') email: string): Promise<boolean> {
|
||||||
@Arg('email') email: string,
|
// TODO: this has duplicate code with createUser
|
||||||
): Promise<SendPasswordResetEmailResponse> {
|
// TODO: Moriz: I think we do not need this variable.
|
||||||
const payload = {
|
let emailAlreadySend = false
|
||||||
email,
|
|
||||||
email_text: 7,
|
const loginUserRepository = await getCustomRepository(LoginUserRepository)
|
||||||
email_verification_code_type: 'resetPassword',
|
const loginUser = await loginUserRepository.findOneOrFail({ email })
|
||||||
}
|
|
||||||
const response = await apiPost(CONFIG.LOGIN_API_URL + 'sendEmail', payload)
|
const loginEmailOptInRepository = await getRepository(LoginEmailOptIn)
|
||||||
if (!response.success) {
|
let optInCode = await loginEmailOptInRepository.findOne({
|
||||||
throw new Error(response.data)
|
userId: loginUser.id,
|
||||||
}
|
emailOptInTypeId: EMAIL_OPT_IN_RESET_PASSWORD,
|
||||||
return new SendPasswordResetEmailResponse(response.data)
|
})
|
||||||
|
if (optInCode) {
|
||||||
|
emailAlreadySend = true
|
||||||
|
} else {
|
||||||
|
optInCode = new LoginEmailOptIn()
|
||||||
|
optInCode.verificationCode = random(64)
|
||||||
|
optInCode.userId = loginUser.id
|
||||||
|
optInCode.emailOptInTypeId = EMAIL_OPT_IN_RESET_PASSWORD
|
||||||
|
await loginEmailOptInRepository.save(optInCode)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Authorized([RIGHTS.RESET_PASSWORD])
|
const link = CONFIG.EMAIL_LINK_SETPASSWORD.replace(
|
||||||
@Mutation(() => String)
|
/\$1/g,
|
||||||
async resetPassword(
|
optInCode.verificationCode.toString(),
|
||||||
@Args()
|
)
|
||||||
{ sessionId, email, password }: ChangePasswordArgs,
|
|
||||||
): Promise<string> {
|
if (emailAlreadySend) {
|
||||||
const payload = {
|
const timeElapsed = Date.now() - new Date(optInCode.updatedAt).getTime()
|
||||||
session_id: sessionId,
|
if (timeElapsed <= 10 * 60 * 1000) {
|
||||||
email,
|
throw new Error('email already sent less than 10 minutes before')
|
||||||
password,
|
|
||||||
}
|
}
|
||||||
const result = await apiPost(CONFIG.LOGIN_API_URL + 'resetPassword', payload)
|
|
||||||
if (!result.success) {
|
|
||||||
throw new Error(result.data)
|
|
||||||
}
|
}
|
||||||
return 'success'
|
|
||||||
|
const emailSent = await sendEMail({
|
||||||
|
from: `Gradido (nicht antworten) <${CONFIG.EMAIL_SENDER}>`,
|
||||||
|
to: `${loginUser.firstName} ${loginUser.lastName} <${email}>`,
|
||||||
|
subject: 'Gradido: Reset Password',
|
||||||
|
text: `Hallo ${loginUser.firstName} ${loginUser.lastName},
|
||||||
|
|
||||||
|
Du oder jemand anderes hat für dieses Konto ein Zurücksetzen des Passworts angefordert.
|
||||||
|
Wenn du es warst, klicke bitte auf den Link: ${link}
|
||||||
|
oder kopiere den obigen Link in Dein Browserfenster.
|
||||||
|
|
||||||
|
Mit freundlichen Grüßen,
|
||||||
|
dein Gradido-Team`,
|
||||||
|
})
|
||||||
|
|
||||||
|
// In case EMails are disabled log the activation link for the user
|
||||||
|
if (!emailSent) {
|
||||||
|
// eslint-disable-next-line no-console
|
||||||
|
console.log(`Reset password link: ${link}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
@Authorized([RIGHTS.SET_PASSWORD])
|
||||||
|
@Mutation(() => Boolean)
|
||||||
|
async setPassword(
|
||||||
|
@Arg('code') code: string,
|
||||||
|
@Arg('password') password: string,
|
||||||
|
): Promise<boolean> {
|
||||||
|
// Validate Password
|
||||||
|
if (!isPassword(password)) {
|
||||||
|
throw new Error(
|
||||||
|
'Please enter a valid password with at least 8 characters, upper and lower case letters, at least one number and one special character!',
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load code
|
||||||
|
const loginEmailOptInRepository = await getRepository(LoginEmailOptIn)
|
||||||
|
const optInCode = await loginEmailOptInRepository
|
||||||
|
.findOneOrFail({ verificationCode: code })
|
||||||
|
.catch(() => {
|
||||||
|
throw new Error('Could not login with emailVerificationCode')
|
||||||
|
})
|
||||||
|
|
||||||
|
// Code is only valid for 10minutes
|
||||||
|
const timeElapsed = Date.now() - new Date(optInCode.updatedAt).getTime()
|
||||||
|
if (timeElapsed > 10 * 60 * 1000) {
|
||||||
|
throw new Error('Code is older than 10 minutes')
|
||||||
|
}
|
||||||
|
|
||||||
|
// load loginUser
|
||||||
|
const loginUserRepository = await getCustomRepository(LoginUserRepository)
|
||||||
|
const loginUser = await loginUserRepository
|
||||||
|
.findOneOrFail({ id: optInCode.userId })
|
||||||
|
.catch(() => {
|
||||||
|
throw new Error('Could not find corresponding Login User')
|
||||||
|
})
|
||||||
|
|
||||||
|
// load user
|
||||||
|
const dbUserRepository = await getCustomRepository(UserRepository)
|
||||||
|
const dbUser = await dbUserRepository.findOneOrFail({ email: loginUser.email }).catch(() => {
|
||||||
|
throw new Error('Could not find corresponding User')
|
||||||
|
})
|
||||||
|
|
||||||
|
const loginUserBackupRepository = await getRepository(LoginUserBackup)
|
||||||
|
const loginUserBackup = await loginUserBackupRepository
|
||||||
|
.findOneOrFail({ userId: loginUser.id })
|
||||||
|
.catch(() => {
|
||||||
|
throw new Error('Could not find corresponding BackupUser')
|
||||||
|
})
|
||||||
|
|
||||||
|
const passphrase = loginUserBackup.passphrase.slice(0, -1).split(' ')
|
||||||
|
if (passphrase.length < PHRASE_WORD_COUNT) {
|
||||||
|
// TODO if this can happen we cannot recover from that
|
||||||
|
throw new Error('Could not load a correct passphrase')
|
||||||
|
}
|
||||||
|
|
||||||
|
// Activate EMail
|
||||||
|
loginUser.emailChecked = true
|
||||||
|
|
||||||
|
// Update Password
|
||||||
|
const passwordHash = SecretKeyCryptographyCreateKey(loginUser.email, password) // return short and long hash
|
||||||
|
const keyPair = KeyPairEd25519Create(passphrase) // return pub, priv Key
|
||||||
|
const encryptedPrivkey = SecretKeyCryptographyEncrypt(keyPair[1], passwordHash[1])
|
||||||
|
loginUser.password = passwordHash[0].readBigUInt64LE() // using the shorthash
|
||||||
|
loginUser.pubKey = keyPair[0]
|
||||||
|
loginUser.privKey = encryptedPrivkey
|
||||||
|
dbUser.pubkey = keyPair[0]
|
||||||
|
|
||||||
|
const queryRunner = getConnection().createQueryRunner()
|
||||||
|
await queryRunner.connect()
|
||||||
|
await queryRunner.startTransaction('READ UNCOMMITTED')
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Save loginUser
|
||||||
|
await queryRunner.manager.save(loginUser).catch((error) => {
|
||||||
|
throw new Error('error saving loginUser: ' + error)
|
||||||
|
})
|
||||||
|
|
||||||
|
// Save user
|
||||||
|
await queryRunner.manager.save(dbUser).catch((error) => {
|
||||||
|
throw new Error('error saving user: ' + error)
|
||||||
|
})
|
||||||
|
|
||||||
|
// Delete Code
|
||||||
|
await queryRunner.manager.remove(optInCode).catch((error) => {
|
||||||
|
throw new Error('error deleting code: ' + error)
|
||||||
|
})
|
||||||
|
|
||||||
|
await queryRunner.commitTransaction()
|
||||||
|
} catch (e) {
|
||||||
|
await queryRunner.rollbackTransaction()
|
||||||
|
throw e
|
||||||
|
} finally {
|
||||||
|
await queryRunner.release()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sign into Klicktipp
|
||||||
|
// TODO do we always signUp the user? How to handle things with old users?
|
||||||
|
if (optInCode.emailOptInTypeId === EMAIL_OPT_IN_REGISTER) {
|
||||||
|
try {
|
||||||
|
await signIn(loginUser.email, loginUser.language, loginUser.firstName, loginUser.lastName)
|
||||||
|
} catch {
|
||||||
|
// TODO is this a problem?
|
||||||
|
// eslint-disable-next-line no-console
|
||||||
|
console.log('Could not subscribe to klicktipp')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
@Authorized([RIGHTS.UPDATE_USER_INFOS])
|
@Authorized([RIGHTS.UPDATE_USER_INFOS])
|
||||||
@ -656,19 +732,6 @@ export class UserResolver {
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
@Authorized([RIGHTS.CHECK_EMAIL])
|
|
||||||
@Query(() => CheckEmailResponse)
|
|
||||||
@UseMiddleware(klicktippRegistrationMiddleware)
|
|
||||||
async checkEmail(@Arg('optin') optin: string): Promise<CheckEmailResponse> {
|
|
||||||
const result = await apiGet(
|
|
||||||
CONFIG.LOGIN_API_URL + 'loginViaEmailVerificationCode?emailVerificationCode=' + optin,
|
|
||||||
)
|
|
||||||
if (!result.success) {
|
|
||||||
throw new Error(result.data)
|
|
||||||
}
|
|
||||||
return new CheckEmailResponse(result.data)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Authorized([RIGHTS.HAS_ELOPAGE])
|
@Authorized([RIGHTS.HAS_ELOPAGE])
|
||||||
@Query(() => Boolean)
|
@Query(() => Boolean)
|
||||||
async hasElopage(@Ctx() context: any): Promise<boolean> {
|
async hasElopage(@Ctx() context: any): Promise<boolean> {
|
||||||
|
|||||||
@ -1,20 +1,20 @@
|
|||||||
import { MiddlewareFn } from 'type-graphql'
|
import { MiddlewareFn } from 'type-graphql'
|
||||||
import { signIn, getKlickTippUser } from '../apis/KlicktippController'
|
import { /* signIn, */ getKlickTippUser } from '../apis/KlicktippController'
|
||||||
import { KlickTipp } from '../graphql/model/KlickTipp'
|
import { KlickTipp } from '../graphql/model/KlickTipp'
|
||||||
import CONFIG from '../config/index'
|
import CONFIG from '../config/index'
|
||||||
|
|
||||||
export const klicktippRegistrationMiddleware: MiddlewareFn = async (
|
// export const klicktippRegistrationMiddleware: MiddlewareFn = async (
|
||||||
// Only for demo
|
// // Only for demo
|
||||||
/* eslint-disable-next-line @typescript-eslint/no-unused-vars */
|
// /* eslint-disable-next-line @typescript-eslint/no-unused-vars */
|
||||||
{ root, args, context, info },
|
// { root, args, context, info },
|
||||||
next,
|
// next,
|
||||||
) => {
|
// ) => {
|
||||||
// Do Something here before resolver is called
|
// // Do Something here before resolver is called
|
||||||
const result = await next()
|
// const result = await next()
|
||||||
// Do Something here after resolver is completed
|
// // Do Something here after resolver is completed
|
||||||
await signIn(result.email, result.language, result.firstName, result.lastName)
|
// await signIn(result.email, result.language, result.firstName, result.lastName)
|
||||||
return result
|
// return result
|
||||||
}
|
// }
|
||||||
|
|
||||||
export const klicktippNewsletterStateMiddleware: MiddlewareFn = async (
|
export const klicktippNewsletterStateMiddleware: MiddlewareFn = async (
|
||||||
/* eslint-disable-next-line @typescript-eslint/no-unused-vars */
|
/* eslint-disable-next-line @typescript-eslint/no-unused-vars */
|
||||||
|
|||||||
@ -39,6 +39,7 @@ yarn seed
|
|||||||
## Seeded Users
|
## Seeded Users
|
||||||
|
|
||||||
| email | password | admin |
|
| email | password | admin |
|
||||||
|
|------------------------|------------|---------|
|
||||||
| peter@lustig.de | `Aa12345_` | `true` |
|
| peter@lustig.de | `Aa12345_` | `true` |
|
||||||
| bibi@bloxberg.de | `Aa12345_` | `false` |
|
| bibi@bloxberg.de | `Aa12345_` | `false` |
|
||||||
| raeuber@hotzenplotz.de | `Aa12345_` | `false` |
|
| raeuber@hotzenplotz.de | `Aa12345_` | `false` |
|
||||||
|
|||||||
@ -1,4 +1,5 @@
|
|||||||
import { BaseEntity, Entity, PrimaryGeneratedColumn, Column } from 'typeorm'
|
import { BaseEntity, Entity, PrimaryGeneratedColumn, Column, JoinColumn, OneToOne } from 'typeorm'
|
||||||
|
import { User } from '../User'
|
||||||
|
|
||||||
@Entity('state_balances')
|
@Entity('state_balances')
|
||||||
export class Balance extends BaseEntity {
|
export class Balance extends BaseEntity {
|
||||||
@ -16,4 +17,8 @@ export class Balance extends BaseEntity {
|
|||||||
|
|
||||||
@Column({ type: 'bigint' })
|
@Column({ type: 'bigint' })
|
||||||
amount: number
|
amount: number
|
||||||
|
|
||||||
|
@OneToOne(() => User, { nullable: false })
|
||||||
|
@JoinColumn({ name: 'state_user_id' })
|
||||||
|
user: User
|
||||||
}
|
}
|
||||||
|
|||||||
21
database/entity/0001-init_db/TransactionSignature.ts
Normal file
21
database/entity/0001-init_db/TransactionSignature.ts
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
import { BaseEntity, Entity, PrimaryGeneratedColumn, Column, ManyToOne, JoinColumn } from 'typeorm'
|
||||||
|
import { Transaction } from './Transaction'
|
||||||
|
|
||||||
|
@Entity('transaction_signatures')
|
||||||
|
export class TransactionSignature extends BaseEntity {
|
||||||
|
@PrimaryGeneratedColumn()
|
||||||
|
id: number
|
||||||
|
|
||||||
|
@Column({ name: 'transaction_id' })
|
||||||
|
transactionId: number
|
||||||
|
|
||||||
|
@Column({ type: 'binary', length: 64 })
|
||||||
|
signature: Buffer
|
||||||
|
|
||||||
|
@Column({ type: 'binary', length: 32 })
|
||||||
|
pubkey: Buffer
|
||||||
|
|
||||||
|
@ManyToOne(() => Transaction)
|
||||||
|
@JoinColumn({ name: 'transaction_id' })
|
||||||
|
transaction: Transaction
|
||||||
|
}
|
||||||
@ -1,4 +1,5 @@
|
|||||||
import { BaseEntity, Entity, PrimaryGeneratedColumn, Column } from 'typeorm'
|
import { BaseEntity, Entity, PrimaryGeneratedColumn, Column, OneToOne } from 'typeorm'
|
||||||
|
import { Balance } from '../Balance'
|
||||||
|
|
||||||
// Moriz: I do not like the idea of having two user tables
|
// Moriz: I do not like the idea of having two user tables
|
||||||
@Entity('state_users')
|
@Entity('state_users')
|
||||||
@ -29,4 +30,7 @@ export class User extends BaseEntity {
|
|||||||
|
|
||||||
@Column()
|
@Column()
|
||||||
disabled: boolean
|
disabled: boolean
|
||||||
|
|
||||||
|
@OneToOne(() => Balance, (balance) => balance.user)
|
||||||
|
balance: Balance
|
||||||
}
|
}
|
||||||
|
|||||||
1
database/entity/TransactionSignature.ts
Normal file
1
database/entity/TransactionSignature.ts
Normal file
@ -0,0 +1 @@
|
|||||||
|
export { TransactionSignature } from './0001-init_db/TransactionSignature'
|
||||||
@ -8,6 +8,7 @@ import { Migration } from './Migration'
|
|||||||
import { ServerUser } from './ServerUser'
|
import { ServerUser } from './ServerUser'
|
||||||
import { Transaction } from './Transaction'
|
import { Transaction } from './Transaction'
|
||||||
import { TransactionCreation } from './TransactionCreation'
|
import { TransactionCreation } from './TransactionCreation'
|
||||||
|
import { TransactionSignature } from './TransactionSignature'
|
||||||
import { TransactionSendCoin } from './TransactionSendCoin'
|
import { TransactionSendCoin } from './TransactionSendCoin'
|
||||||
import { User } from './User'
|
import { User } from './User'
|
||||||
import { UserSetting } from './UserSetting'
|
import { UserSetting } from './UserSetting'
|
||||||
@ -25,6 +26,7 @@ export const entities = [
|
|||||||
ServerUser,
|
ServerUser,
|
||||||
Transaction,
|
Transaction,
|
||||||
TransactionCreation,
|
TransactionCreation,
|
||||||
|
TransactionSignature,
|
||||||
TransactionSendCoin,
|
TransactionSendCoin,
|
||||||
User,
|
User,
|
||||||
UserSetting,
|
UserSetting,
|
||||||
|
|||||||
@ -25,34 +25,34 @@ export async function upgrade(queryFn: (query: string, values?: any[]) => Promis
|
|||||||
}
|
}
|
||||||
|
|
||||||
await queryFn(`
|
await queryFn(`
|
||||||
INSERT INTO \`login_app_access_tokens\` SELECT * FROM ${LOGIN_SERVER_DB}.\`app_access_tokens\`;
|
INSERT IGNORE INTO \`login_app_access_tokens\` SELECT * FROM ${LOGIN_SERVER_DB}.\`app_access_tokens\`;
|
||||||
`)
|
`)
|
||||||
await queryFn(`
|
await queryFn(`
|
||||||
INSERT INTO \`login_elopage_buys\` SELECT * FROM ${LOGIN_SERVER_DB}.\`elopage_buys\`;
|
INSERT IGNORE INTO \`login_elopage_buys\` SELECT * FROM ${LOGIN_SERVER_DB}.\`elopage_buys\`;
|
||||||
`)
|
`)
|
||||||
await queryFn(`
|
await queryFn(`
|
||||||
INSERT INTO \`login_email_opt_in_types\` SELECT * FROM ${LOGIN_SERVER_DB}.\`email_opt_in_types\`;
|
INSERT IGNORE INTO \`login_email_opt_in_types\` SELECT * FROM ${LOGIN_SERVER_DB}.\`email_opt_in_types\`;
|
||||||
`)
|
`)
|
||||||
await queryFn(`
|
await queryFn(`
|
||||||
INSERT INTO \`login_email_opt_in\` SELECT * FROM ${LOGIN_SERVER_DB}.\`email_opt_in\`;
|
INSERT IGNORE INTO \`login_email_opt_in\` SELECT * FROM ${LOGIN_SERVER_DB}.\`email_opt_in\`;
|
||||||
`)
|
`)
|
||||||
await queryFn(`
|
await queryFn(`
|
||||||
INSERT INTO \`login_groups\` SELECT * FROM ${LOGIN_SERVER_DB}.\`groups\`;
|
INSERT IGNORE INTO \`login_groups\` SELECT * FROM ${LOGIN_SERVER_DB}.\`groups\`;
|
||||||
`)
|
`)
|
||||||
await queryFn(`
|
await queryFn(`
|
||||||
INSERT INTO \`login_pending_tasks\` SELECT * FROM ${LOGIN_SERVER_DB}.\`pending_tasks\`;
|
INSERT IGNORE INTO \`login_pending_tasks\` SELECT * FROM ${LOGIN_SERVER_DB}.\`pending_tasks\`;
|
||||||
`)
|
`)
|
||||||
await queryFn(`
|
await queryFn(`
|
||||||
INSERT INTO \`login_roles\` SELECT * FROM ${LOGIN_SERVER_DB}.\`roles\`;
|
INSERT IGNORE INTO \`login_roles\` SELECT * FROM ${LOGIN_SERVER_DB}.\`roles\`;
|
||||||
`)
|
`)
|
||||||
await queryFn(`
|
await queryFn(`
|
||||||
INSERT INTO \`login_user_backups\` SELECT * FROM ${LOGIN_SERVER_DB}.\`user_backups\`;
|
INSERT IGNORE INTO \`login_user_backups\` SELECT * FROM ${LOGIN_SERVER_DB}.\`user_backups\`;
|
||||||
`)
|
`)
|
||||||
await queryFn(`
|
await queryFn(`
|
||||||
INSERT INTO \`login_user_roles\` SELECT * FROM ${LOGIN_SERVER_DB}.\`user_roles\`;
|
INSERT IGNORE INTO \`login_user_roles\` SELECT * FROM ${LOGIN_SERVER_DB}.\`user_roles\`;
|
||||||
`)
|
`)
|
||||||
await queryFn(`
|
await queryFn(`
|
||||||
INSERT INTO \`login_users\` SELECT * FROM ${LOGIN_SERVER_DB}.\`users\`;
|
INSERT IGNORE INTO \`login_users\` SELECT * FROM ${LOGIN_SERVER_DB}.\`users\`;
|
||||||
`)
|
`)
|
||||||
|
|
||||||
// TODO clarify if we need this on non docker environment?
|
// TODO clarify if we need this on non docker environment?
|
||||||
|
|||||||
18
database/src/factories/balance.factory.ts
Normal file
18
database/src/factories/balance.factory.ts
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
import Faker from 'faker'
|
||||||
|
import { define } from 'typeorm-seeding'
|
||||||
|
import { Balance } from '../../entity/Balance'
|
||||||
|
import { BalanceContext } from '../interface/TransactionContext'
|
||||||
|
|
||||||
|
define(Balance, (faker: typeof Faker, context?: BalanceContext) => {
|
||||||
|
if (!context || !context.user) {
|
||||||
|
throw new Error('Balance: No user present!')
|
||||||
|
}
|
||||||
|
|
||||||
|
const balance = new Balance()
|
||||||
|
balance.modified = context.modified ? context.modified : faker.date.recent()
|
||||||
|
balance.recordDate = context.recordDate ? context.recordDate : faker.date.recent()
|
||||||
|
balance.amount = context.amount ? context.amount : 10000000
|
||||||
|
balance.user = context.user
|
||||||
|
|
||||||
|
return balance
|
||||||
|
})
|
||||||
18
database/src/factories/transaction-creation.factory.ts
Normal file
18
database/src/factories/transaction-creation.factory.ts
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
import Faker from 'faker'
|
||||||
|
import { define } from 'typeorm-seeding'
|
||||||
|
import { TransactionCreation } from '../../entity/TransactionCreation'
|
||||||
|
import { TransactionCreationContext } from '../interface/TransactionContext'
|
||||||
|
|
||||||
|
define(TransactionCreation, (faker: typeof Faker, context?: TransactionCreationContext) => {
|
||||||
|
if (!context || !context.userId || !context.transaction) {
|
||||||
|
throw new Error('TransactionCreation: No userId and/or transaction present!')
|
||||||
|
}
|
||||||
|
|
||||||
|
const transactionCreation = new TransactionCreation()
|
||||||
|
transactionCreation.userId = context.userId
|
||||||
|
transactionCreation.amount = context.amount ? context.amount : 100000
|
||||||
|
transactionCreation.targetDate = context.targetDate ? context.targetDate : new Date()
|
||||||
|
transactionCreation.transaction = context.transaction
|
||||||
|
|
||||||
|
return transactionCreation
|
||||||
|
})
|
||||||
18
database/src/factories/transaction-signature.factory.ts
Normal file
18
database/src/factories/transaction-signature.factory.ts
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
import Faker from 'faker'
|
||||||
|
import { define } from 'typeorm-seeding'
|
||||||
|
import { TransactionSignature } from '../../entity/TransactionSignature'
|
||||||
|
import { TransactionSignatureContext } from '../interface/TransactionContext'
|
||||||
|
import { randomBytes } from 'crypto'
|
||||||
|
|
||||||
|
define(TransactionSignature, (faker: typeof Faker, context?: TransactionSignatureContext) => {
|
||||||
|
if (!context || !context.transaction) {
|
||||||
|
throw new Error('TransactionSignature: No transaction present!')
|
||||||
|
}
|
||||||
|
|
||||||
|
const transactionSignature = new TransactionSignature()
|
||||||
|
transactionSignature.signature = context.signature ? context.signature : randomBytes(64)
|
||||||
|
transactionSignature.pubkey = context.pubkey ? context.pubkey : randomBytes(32)
|
||||||
|
transactionSignature.transaction = context.transaction
|
||||||
|
|
||||||
|
return transactionSignature
|
||||||
|
})
|
||||||
20
database/src/factories/transaction.factory.ts
Normal file
20
database/src/factories/transaction.factory.ts
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
import Faker from 'faker'
|
||||||
|
import { define } from 'typeorm-seeding'
|
||||||
|
import { Transaction } from '../../entity/Transaction'
|
||||||
|
import { TransactionContext } from '../interface/TransactionContext'
|
||||||
|
import { randomBytes } from 'crypto'
|
||||||
|
|
||||||
|
define(Transaction, (faker: typeof Faker, context?: TransactionContext) => {
|
||||||
|
if (!context) context = {}
|
||||||
|
|
||||||
|
const transaction = new Transaction()
|
||||||
|
transaction.transactionTypeId = context.transactionTypeId ? context.transactionTypeId : 2
|
||||||
|
transaction.txHash = context.txHash ? context.txHash : randomBytes(48)
|
||||||
|
transaction.memo = context.memo || context.memo === '' ? context.memo : faker.lorem.sentence()
|
||||||
|
transaction.received = context.received ? context.received : new Date()
|
||||||
|
transaction.blockchainTypeId = context.blockchainTypeId ? context.blockchainTypeId : 1
|
||||||
|
if (context.transactionSendCoin) transaction.transactionSendCoin = context.transactionSendCoin
|
||||||
|
if (context.transactionCreation) transaction.transactionCreation = context.transactionCreation
|
||||||
|
|
||||||
|
return transaction
|
||||||
|
})
|
||||||
19
database/src/factories/user-transaction.factory.ts
Normal file
19
database/src/factories/user-transaction.factory.ts
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
import Faker from 'faker'
|
||||||
|
import { define } from 'typeorm-seeding'
|
||||||
|
import { UserTransaction } from '../../entity/UserTransaction'
|
||||||
|
import { UserTransactionContext } from '../interface/TransactionContext'
|
||||||
|
|
||||||
|
define(UserTransaction, (faker: typeof Faker, context?: UserTransactionContext) => {
|
||||||
|
if (!context || !context.userId || !context.transactionId) {
|
||||||
|
throw new Error('UserTransaction: No userId and/or transactionId present!')
|
||||||
|
}
|
||||||
|
|
||||||
|
const userTransaction = new UserTransaction()
|
||||||
|
userTransaction.userId = context.userId
|
||||||
|
userTransaction.transactionId = context.transactionId
|
||||||
|
userTransaction.transactionTypeId = context.transactionTypeId ? context.transactionTypeId : 1
|
||||||
|
userTransaction.balance = context.balance ? context.balance : 100000
|
||||||
|
userTransaction.balanceDate = context.balanceDate ? context.balanceDate : new Date()
|
||||||
|
|
||||||
|
return userTransaction
|
||||||
|
})
|
||||||
@ -9,6 +9,7 @@ import { CreatePeterLustigSeed } from './seeds/users/peter-lustig.admin.seed'
|
|||||||
import { CreateBibiBloxbergSeed } from './seeds/users/bibi-bloxberg.seed'
|
import { CreateBibiBloxbergSeed } from './seeds/users/bibi-bloxberg.seed'
|
||||||
import { CreateRaeuberHotzenplotzSeed } from './seeds/users/raeuber-hotzenplotz.seed'
|
import { CreateRaeuberHotzenplotzSeed } from './seeds/users/raeuber-hotzenplotz.seed'
|
||||||
import { CreateBobBaumeisterSeed } from './seeds/users/bob-baumeister.seed'
|
import { CreateBobBaumeisterSeed } from './seeds/users/bob-baumeister.seed'
|
||||||
|
import { DecayStartBlockSeed } from './seeds/decay-start-block.seed'
|
||||||
|
|
||||||
const run = async (command: string) => {
|
const run = async (command: string) => {
|
||||||
// Database actions not supported by our migration library
|
// Database actions not supported by our migration library
|
||||||
@ -59,6 +60,7 @@ const run = async (command: string) => {
|
|||||||
root: process.cwd(),
|
root: process.cwd(),
|
||||||
configName: 'ormconfig.js',
|
configName: 'ormconfig.js',
|
||||||
})
|
})
|
||||||
|
await runSeeder(DecayStartBlockSeed)
|
||||||
await runSeeder(CreatePeterLustigSeed)
|
await runSeeder(CreatePeterLustigSeed)
|
||||||
await runSeeder(CreateBibiBloxbergSeed)
|
await runSeeder(CreateBibiBloxbergSeed)
|
||||||
await runSeeder(CreateRaeuberHotzenplotzSeed)
|
await runSeeder(CreateRaeuberHotzenplotzSeed)
|
||||||
|
|||||||
52
database/src/interface/TransactionContext.ts
Normal file
52
database/src/interface/TransactionContext.ts
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
import { Transaction } from '../../entity/Transaction'
|
||||||
|
import { TransactionSendCoin } from '../../entity/TransactionSendCoin'
|
||||||
|
import { TransactionCreation } from '../../entity/TransactionCreation'
|
||||||
|
import { User } from '../../entity/User'
|
||||||
|
|
||||||
|
export interface TransactionContext {
|
||||||
|
transactionTypeId?: number
|
||||||
|
txHash?: Buffer
|
||||||
|
memo?: string
|
||||||
|
received?: Date
|
||||||
|
blockchainTypeId?: number
|
||||||
|
transactionSendCoin?: TransactionSendCoin
|
||||||
|
transactionCreation?: TransactionCreation
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface BalanceContext {
|
||||||
|
modified?: Date
|
||||||
|
recordDate?: Date
|
||||||
|
amount?: number
|
||||||
|
user?: User
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface TransactionSendCoinContext {
|
||||||
|
senderPublic?: Buffer
|
||||||
|
userId?: number
|
||||||
|
recipiantPublic?: Buffer
|
||||||
|
recipiantUserId?: number
|
||||||
|
amount?: number
|
||||||
|
senderFinalBalance?: number
|
||||||
|
transaction?: Transaction
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface TransactionCreationContext {
|
||||||
|
userId?: number
|
||||||
|
amount?: number
|
||||||
|
targetDate?: Date
|
||||||
|
transaction?: Transaction
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface UserTransactionContext {
|
||||||
|
userId?: number
|
||||||
|
transactionId?: number
|
||||||
|
transactionTypeId?: number
|
||||||
|
balance?: number
|
||||||
|
balanceDate?: Date
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface TransactionSignatureContext {
|
||||||
|
signature?: Buffer
|
||||||
|
pubkey?: Buffer
|
||||||
|
transaction?: Transaction
|
||||||
|
}
|
||||||
@ -27,4 +27,14 @@ export interface UserInterface {
|
|||||||
modified?: Date
|
modified?: Date
|
||||||
// flag for admin
|
// flag for admin
|
||||||
isAdmin?: boolean
|
isAdmin?: boolean
|
||||||
|
// flag for balance (creation of 1000 GDD)
|
||||||
|
addBalance?: boolean
|
||||||
|
// balance
|
||||||
|
balanceModified?: Date
|
||||||
|
recordDate?: Date
|
||||||
|
targetDate?: Date
|
||||||
|
amount?: number
|
||||||
|
creationTxHash?: Buffer
|
||||||
|
signature?: Buffer
|
||||||
|
signaturePubkey?: Buffer
|
||||||
}
|
}
|
||||||
|
|||||||
17
database/src/seeds/decay-start-block.seed.ts
Normal file
17
database/src/seeds/decay-start-block.seed.ts
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
import { Factory, Seeder } from 'typeorm-seeding'
|
||||||
|
import { Transaction } from '../../entity/Transaction'
|
||||||
|
|
||||||
|
export class DecayStartBlockSeed implements Seeder {
|
||||||
|
public async run(factory: Factory): Promise<void> {
|
||||||
|
await factory(Transaction)({
|
||||||
|
transactionTypeId: 9,
|
||||||
|
txHash: Buffer.from(
|
||||||
|
'9c9c4152b8a4cfbac287eee18d2d262e9de756fae726fc0ca36b788564973fff00000000000000000000000000000000',
|
||||||
|
'hex',
|
||||||
|
),
|
||||||
|
memo: '',
|
||||||
|
received: new Date('2021-11-30T09:13:26'),
|
||||||
|
blockchainTypeId: 1,
|
||||||
|
}).create()
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -5,16 +5,28 @@ import {
|
|||||||
ServerUserContext,
|
ServerUserContext,
|
||||||
LoginUserRolesContext,
|
LoginUserRolesContext,
|
||||||
} from '../../interface/UserContext'
|
} from '../../interface/UserContext'
|
||||||
|
import {
|
||||||
|
BalanceContext,
|
||||||
|
TransactionContext,
|
||||||
|
TransactionCreationContext,
|
||||||
|
UserTransactionContext,
|
||||||
|
TransactionSignatureContext,
|
||||||
|
} from '../../interface/TransactionContext'
|
||||||
import { UserInterface } from '../../interface/UserInterface'
|
import { UserInterface } from '../../interface/UserInterface'
|
||||||
import { User } from '../../../entity/User'
|
import { User } from '../../../entity/User'
|
||||||
import { LoginUser } from '../../../entity/LoginUser'
|
import { LoginUser } from '../../../entity/LoginUser'
|
||||||
import { LoginUserBackup } from '../../../entity/LoginUserBackup'
|
import { LoginUserBackup } from '../../../entity/LoginUserBackup'
|
||||||
import { ServerUser } from '../../../entity/ServerUser'
|
import { ServerUser } from '../../../entity/ServerUser'
|
||||||
import { LoginUserRoles } from '../../../entity/LoginUserRoles'
|
import { LoginUserRoles } from '../../../entity/LoginUserRoles'
|
||||||
|
import { Balance } from '../../../entity/Balance'
|
||||||
|
import { Transaction } from '../../../entity/Transaction'
|
||||||
|
import { TransactionSignature } from '../../../entity/TransactionSignature'
|
||||||
|
import { UserTransaction } from '../../../entity/UserTransaction'
|
||||||
|
import { TransactionCreation } from '../../../entity/TransactionCreation'
|
||||||
import { Factory } from 'typeorm-seeding'
|
import { Factory } from 'typeorm-seeding'
|
||||||
|
|
||||||
export const userSeeder = async (factory: Factory, userData: UserInterface): Promise<void> => {
|
export const userSeeder = async (factory: Factory, userData: UserInterface): Promise<void> => {
|
||||||
await factory(User)(createUserContext(userData)).create()
|
const user = await factory(User)(createUserContext(userData)).create()
|
||||||
const loginUser = await factory(LoginUser)(createLoginUserContext(userData)).create()
|
const loginUser = await factory(LoginUser)(createLoginUserContext(userData)).create()
|
||||||
await factory(LoginUserBackup)(createLoginUserBackupContext(userData, loginUser)).create()
|
await factory(LoginUserBackup)(createLoginUserBackupContext(userData, loginUser)).create()
|
||||||
|
|
||||||
@ -25,9 +37,26 @@ export const userSeeder = async (factory: Factory, userData: UserInterface): Pro
|
|||||||
// It works with LoginRoles empty!!
|
// It works with LoginRoles empty!!
|
||||||
await factory(LoginUserRoles)(createLoginUserRolesContext(loginUser)).create()
|
await factory(LoginUserRoles)(createLoginUserRolesContext(loginUser)).create()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (userData.addBalance) {
|
||||||
|
// create some GDD for the user
|
||||||
|
await factory(Balance)(createBalanceContext(userData, user)).create()
|
||||||
|
const transaction = await factory(Transaction)(
|
||||||
|
createTransactionContext(userData, 1, 'Herzlich Willkommen bei Gradido!'),
|
||||||
|
).create()
|
||||||
|
await factory(TransactionCreation)(
|
||||||
|
createTransactionCreationContext(userData, user, transaction),
|
||||||
|
).create()
|
||||||
|
await factory(UserTransaction)(
|
||||||
|
createUserTransactionContext(userData, user, transaction),
|
||||||
|
).create()
|
||||||
|
await factory(TransactionSignature)(
|
||||||
|
createTransactionSignatureContext(userData, transaction),
|
||||||
|
).create()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const createUserContext = (context: UserInterface): UserContext => {
|
const createUserContext = (context: UserInterface): UserContext => {
|
||||||
return {
|
return {
|
||||||
pubkey: context.pubKey,
|
pubkey: context.pubKey,
|
||||||
email: context.email,
|
email: context.email,
|
||||||
@ -38,7 +67,7 @@ export const createUserContext = (context: UserInterface): UserContext => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const createLoginUserContext = (context: UserInterface): LoginUserContext => {
|
const createLoginUserContext = (context: UserInterface): LoginUserContext => {
|
||||||
return {
|
return {
|
||||||
email: context.email,
|
email: context.email,
|
||||||
firstName: context.firstName,
|
firstName: context.firstName,
|
||||||
@ -59,7 +88,7 @@ export const createLoginUserContext = (context: UserInterface): LoginUserContext
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const createLoginUserBackupContext = (
|
const createLoginUserBackupContext = (
|
||||||
context: UserInterface,
|
context: UserInterface,
|
||||||
loginUser: LoginUser,
|
loginUser: LoginUser,
|
||||||
): LoginUserBackupContext => {
|
): LoginUserBackupContext => {
|
||||||
@ -70,7 +99,7 @@ export const createLoginUserBackupContext = (
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const createServerUserContext = (context: UserInterface): ServerUserContext => {
|
const createServerUserContext = (context: UserInterface): ServerUserContext => {
|
||||||
return {
|
return {
|
||||||
role: context.role,
|
role: context.role,
|
||||||
username: context.username,
|
username: context.username,
|
||||||
@ -83,9 +112,69 @@ export const createServerUserContext = (context: UserInterface): ServerUserConte
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const createLoginUserRolesContext = (loginUser: LoginUser): LoginUserRolesContext => {
|
const createLoginUserRolesContext = (loginUser: LoginUser): LoginUserRolesContext => {
|
||||||
return {
|
return {
|
||||||
userId: loginUser.id,
|
userId: loginUser.id,
|
||||||
roleId: 1,
|
roleId: 1,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const createBalanceContext = (context: UserInterface, user: User): BalanceContext => {
|
||||||
|
return {
|
||||||
|
modified: context.balanceModified,
|
||||||
|
recordDate: context.recordDate,
|
||||||
|
amount: context.amount,
|
||||||
|
user,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const createTransactionContext = (
|
||||||
|
context: UserInterface,
|
||||||
|
type: number,
|
||||||
|
memo: string,
|
||||||
|
): TransactionContext => {
|
||||||
|
return {
|
||||||
|
transactionTypeId: type,
|
||||||
|
txHash: context.creationTxHash,
|
||||||
|
memo,
|
||||||
|
received: context.recordDate,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const createTransactionCreationContext = (
|
||||||
|
context: UserInterface,
|
||||||
|
user: User,
|
||||||
|
transaction: Transaction,
|
||||||
|
): TransactionCreationContext => {
|
||||||
|
return {
|
||||||
|
userId: user.id,
|
||||||
|
amount: context.amount,
|
||||||
|
targetDate: context.targetDate,
|
||||||
|
transaction,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const createUserTransactionContext = (
|
||||||
|
context: UserInterface,
|
||||||
|
user: User,
|
||||||
|
transaction: Transaction,
|
||||||
|
): UserTransactionContext => {
|
||||||
|
return {
|
||||||
|
userId: user.id,
|
||||||
|
transactionId: transaction.id,
|
||||||
|
transactionTypeId: transaction.transactionTypeId,
|
||||||
|
balance: context.amount,
|
||||||
|
balanceDate: context.recordDate,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const createTransactionSignatureContext = (
|
||||||
|
context: UserInterface,
|
||||||
|
transaction: Transaction,
|
||||||
|
): TransactionSignatureContext => {
|
||||||
|
return {
|
||||||
|
signature: context.signature,
|
||||||
|
pubkey: context.signaturePubkey,
|
||||||
|
transaction,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@ -22,4 +22,21 @@ export const bibiBloxberg = {
|
|||||||
'knife normal level all hurdle crucial color avoid warrior stadium road bachelor affair topple hawk pottery right afford immune two ceiling budget glance hour ',
|
'knife normal level all hurdle crucial color avoid warrior stadium road bachelor affair topple hawk pottery right afford immune two ceiling budget glance hour ',
|
||||||
mnemonicType: 2,
|
mnemonicType: 2,
|
||||||
isAdmin: false,
|
isAdmin: false,
|
||||||
|
addBalance: true,
|
||||||
|
balanceModified: new Date('2021-11-30T10:37:11'),
|
||||||
|
recordDate: new Date('2021-11-30T10:37:11'),
|
||||||
|
targetDate: new Date('2021-08-01 00:00:00'),
|
||||||
|
amount: 10000000,
|
||||||
|
creationTxHash: Buffer.from(
|
||||||
|
'51103dc0fc2ca5d5d75a9557a1e899304e5406cfdb1328d8df6414d527b0118100000000000000000000000000000000',
|
||||||
|
'hex',
|
||||||
|
),
|
||||||
|
signature: Buffer.from(
|
||||||
|
'2a2c71f3e41adc060bbc3086577e2d57d24eeeb0a7727339c3f85aad813808f601d7e1df56a26e0929d2e67fc054fca429ccfa283ed2782185c7f009fe008f0c',
|
||||||
|
'hex',
|
||||||
|
),
|
||||||
|
signaturePubkey: Buffer.from(
|
||||||
|
'7281e0ee3258b08801f3ec73e431b4519677f65c03b0382c63a913b5784ee770',
|
||||||
|
'hex',
|
||||||
|
),
|
||||||
}
|
}
|
||||||
|
|||||||
@ -22,4 +22,21 @@ export const bobBaumeister = {
|
|||||||
'detail master source effort unable waste tilt flush domain orchard art truck hint barrel response gate impose peanut secret merry three uncle wink resource ',
|
'detail master source effort unable waste tilt flush domain orchard art truck hint barrel response gate impose peanut secret merry three uncle wink resource ',
|
||||||
mnemonicType: 2,
|
mnemonicType: 2,
|
||||||
isAdmin: false,
|
isAdmin: false,
|
||||||
|
addBalance: true,
|
||||||
|
balanceModified: new Date('2021-11-30T10:37:14'),
|
||||||
|
recordDate: new Date('2021-11-30T10:37:14'),
|
||||||
|
targetDate: new Date('2021-08-01 00:00:00'),
|
||||||
|
amount: 10000000,
|
||||||
|
creationTxHash: Buffer.from(
|
||||||
|
'be095dc87acb94987e71168fee8ecbf50ecb43a180b1006e75d573b35725c69c00000000000000000000000000000000',
|
||||||
|
'hex',
|
||||||
|
),
|
||||||
|
signature: Buffer.from(
|
||||||
|
'1fbd6b9a3d359923b2501557f3bc79fa7e428127c8090fb16bc490b4d87870ab142b3817ddd902d22f0b26472a483233784a0e460c0622661752a13978903905',
|
||||||
|
'hex',
|
||||||
|
),
|
||||||
|
signaturePubkey: Buffer.from(
|
||||||
|
'7281e0ee3258b08801f3ec73e431b4519677f65c03b0382c63a913b5784ee770',
|
||||||
|
'hex',
|
||||||
|
),
|
||||||
}
|
}
|
||||||
|
|||||||
@ -22,4 +22,21 @@ export const raeuberHotzenplotz = {
|
|||||||
'gospel trip tenant mouse spider skill auto curious man video chief response same little over expire drum display fancy clinic keen throw urge basket ',
|
'gospel trip tenant mouse spider skill auto curious man video chief response same little over expire drum display fancy clinic keen throw urge basket ',
|
||||||
mnemonicType: 2,
|
mnemonicType: 2,
|
||||||
isAdmin: false,
|
isAdmin: false,
|
||||||
|
addBalance: true,
|
||||||
|
balanceModified: new Date('2021-11-30T10:37:13'),
|
||||||
|
recordDate: new Date('2021-11-30T10:37:13'),
|
||||||
|
targetDate: new Date('2021-08-01 00:00:00'),
|
||||||
|
amount: 10000000,
|
||||||
|
creationTxHash: Buffer.from(
|
||||||
|
'23ba44fd84deb59b9f32969ad0cb18bfa4588be1bdb99c396888506474c16c1900000000000000000000000000000000',
|
||||||
|
'hex',
|
||||||
|
),
|
||||||
|
signature: Buffer.from(
|
||||||
|
'756d3da061687c575d1dbc5073908f646aa5f498b0927b217c83b48af471450e571dfe8421fb8e1f1ebd1104526b7e7c6fa78684e2da59c8f7f5a8dc3d9e5b0b',
|
||||||
|
'hex',
|
||||||
|
),
|
||||||
|
signaturePubkey: Buffer.from(
|
||||||
|
'7281e0ee3258b08801f3ec73e431b4519677f65c03b0382c63a913b5784ee770',
|
||||||
|
'hex',
|
||||||
|
),
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
module.exports = {
|
module.exports = {
|
||||||
presets: ['@babel/preset-env'],
|
presets: ['@babel/preset-env'],
|
||||||
plugins: [
|
plugins: [
|
||||||
|
'transform-require-context',
|
||||||
[
|
[
|
||||||
'component',
|
'component',
|
||||||
{
|
{
|
||||||
|
|||||||
@ -22,4 +22,5 @@ module.exports = {
|
|||||||
testMatch: ['**/?(*.)+(spec|test).js?(x)'],
|
testMatch: ['**/?(*.)+(spec|test).js?(x)'],
|
||||||
// snapshotSerializers: ['jest-serializer-vue'],
|
// snapshotSerializers: ['jest-serializer-vue'],
|
||||||
transformIgnorePatterns: ['<rootDir>/node_modules/(?!vee-validate/dist/rules)'],
|
transformIgnorePatterns: ['<rootDir>/node_modules/(?!vee-validate/dist/rules)'],
|
||||||
|
testEnvironment: 'jest-environment-jsdom-sixteen',
|
||||||
}
|
}
|
||||||
|
|||||||
@ -22,8 +22,9 @@
|
|||||||
"apollo-boost": "^0.4.9",
|
"apollo-boost": "^0.4.9",
|
||||||
"axios": "^0.21.1",
|
"axios": "^0.21.1",
|
||||||
"babel-core": "^7.0.0-bridge.0",
|
"babel-core": "^7.0.0-bridge.0",
|
||||||
"babel-jest": "^26.6.3",
|
"babel-jest": "^27.3.1",
|
||||||
"babel-plugin-require-context-hook": "^1.0.0",
|
"babel-plugin-require-context-hook": "^1.0.0",
|
||||||
|
"babel-plugin-transform-require-context": "^0.1.1",
|
||||||
"babel-preset-vue": "^2.0.2",
|
"babel-preset-vue": "^2.0.2",
|
||||||
"bootstrap": "4.3.1",
|
"bootstrap": "4.3.1",
|
||||||
"bootstrap-vue": "^2.5.0",
|
"bootstrap-vue": "^2.5.0",
|
||||||
@ -51,6 +52,7 @@
|
|||||||
"identity-obj-proxy": "^3.0.0",
|
"identity-obj-proxy": "^3.0.0",
|
||||||
"jest": "^26.6.3",
|
"jest": "^26.6.3",
|
||||||
"jest-canvas-mock": "^2.3.1",
|
"jest-canvas-mock": "^2.3.1",
|
||||||
|
"jest-environment-jsdom-sixteen": "^2.0.0",
|
||||||
"nouislider": "^12.1.0",
|
"nouislider": "^12.1.0",
|
||||||
"particles-bg-vue": "1.2.3",
|
"particles-bg-vue": "1.2.3",
|
||||||
"perfect-scrollbar": "^1.3.0",
|
"perfect-scrollbar": "^1.3.0",
|
||||||
|
|||||||
@ -23,6 +23,7 @@
|
|||||||
variant="outline-light"
|
variant="outline-light"
|
||||||
@click="toggleShowPassword"
|
@click="toggleShowPassword"
|
||||||
class="border-left-0 rounded-right"
|
class="border-left-0 rounded-right"
|
||||||
|
tabindex="-1"
|
||||||
>
|
>
|
||||||
<b-icon :icon="showPassword ? 'eye' : 'eye-slash'" />
|
<b-icon :icon="showPassword ? 'eye' : 'eye-slash'" />
|
||||||
</b-button>
|
</b-button>
|
||||||
|
|||||||
200
frontend/src/components/SidebarPlugin/SideBar.spec.js
Normal file
200
frontend/src/components/SidebarPlugin/SideBar.spec.js
Normal file
@ -0,0 +1,200 @@
|
|||||||
|
import { mount, RouterLinkStub } from '@vue/test-utils'
|
||||||
|
import SideBar from './SideBar'
|
||||||
|
|
||||||
|
const localVue = global.localVue
|
||||||
|
|
||||||
|
const storeDispatchMock = jest.fn()
|
||||||
|
|
||||||
|
describe('SideBar', () => {
|
||||||
|
let wrapper
|
||||||
|
|
||||||
|
const stubs = {
|
||||||
|
RouterLink: RouterLinkStub,
|
||||||
|
}
|
||||||
|
|
||||||
|
const propsData = {
|
||||||
|
balance: 1234.56,
|
||||||
|
}
|
||||||
|
|
||||||
|
const mocks = {
|
||||||
|
$store: {
|
||||||
|
state: {
|
||||||
|
email: 'test@example.org',
|
||||||
|
publisherId: 123,
|
||||||
|
firstName: 'test',
|
||||||
|
lastName: 'example',
|
||||||
|
hasElopage: false,
|
||||||
|
},
|
||||||
|
dispatch: storeDispatchMock,
|
||||||
|
},
|
||||||
|
$i18n: {
|
||||||
|
locale: 'en',
|
||||||
|
},
|
||||||
|
$t: jest.fn((t) => t),
|
||||||
|
$n: jest.fn((n) => n),
|
||||||
|
}
|
||||||
|
|
||||||
|
const Wrapper = () => {
|
||||||
|
return mount(SideBar, { localVue, mocks, stubs, propsData })
|
||||||
|
}
|
||||||
|
|
||||||
|
describe('mount', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
wrapper = Wrapper()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('renders the component', () => {
|
||||||
|
expect(wrapper.find('#sidenav-main').exists()).toBeTruthy()
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('navbar button', () => {
|
||||||
|
it('has a navbar button', () => {
|
||||||
|
expect(wrapper.find('button.navbar-toggler').exists()).toBeTruthy()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('calls showSidebar when clicked', async () => {
|
||||||
|
const spy = jest.spyOn(wrapper.vm.$sidebar, 'displaySidebar')
|
||||||
|
wrapper.find('button.navbar-toggler').trigger('click')
|
||||||
|
await wrapper.vm.$nextTick()
|
||||||
|
expect(spy).toHaveBeenCalledWith(true)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('balance', () => {
|
||||||
|
it('shows em-dash as balance while loading', () => {
|
||||||
|
expect(wrapper.find('div.row.text-center').text()).toBe('— GDD')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('shows the when loaded', async () => {
|
||||||
|
wrapper.setProps({
|
||||||
|
pending: false,
|
||||||
|
})
|
||||||
|
await wrapper.vm.$nextTick()
|
||||||
|
expect(wrapper.find('div.row.text-center').text()).toBe('1234.56 GDD')
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('close siedbar', () => {
|
||||||
|
it('calls closeSidebar when clicked', async () => {
|
||||||
|
const spy = jest.spyOn(wrapper.vm.$sidebar, 'displaySidebar')
|
||||||
|
wrapper.find('#sidenav-collapse-main').find('button.navbar-toggler').trigger('click')
|
||||||
|
await wrapper.vm.$nextTick()
|
||||||
|
expect(spy).toHaveBeenCalledWith(false)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('static menu items', () => {
|
||||||
|
describe("member's area without publisher ID", () => {
|
||||||
|
it('has a link to the elopage', () => {
|
||||||
|
expect(wrapper.findAll('li').at(0).text()).toContain('members_area')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('has a badge', () => {
|
||||||
|
expect(wrapper.findAll('li').at(0).text()).toContain('!')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('links to the elopage registration', () => {
|
||||||
|
expect(wrapper.findAll('li').at(0).find('a').attributes('href')).toBe(
|
||||||
|
'https://elopage.com/s/gradido/basic-de/payment?locale=en&prid=111&pid=123&firstName=test&lastName=example&email=test@example.org',
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('with locale="de"', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
mocks.$i18n.locale = 'de'
|
||||||
|
})
|
||||||
|
|
||||||
|
it('links to the German elopage registration when locale is set to de', () => {
|
||||||
|
expect(wrapper.findAll('li').at(0).find('a').attributes('href')).toBe(
|
||||||
|
'https://elopage.com/s/gradido/basic-de/payment?locale=de&prid=111&pid=123&firstName=test&lastName=example&email=test@example.org',
|
||||||
|
)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe("member's area with publisher ID", () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
mocks.$store.state.hasElopage = true
|
||||||
|
})
|
||||||
|
|
||||||
|
it('links to the elopage member area', () => {
|
||||||
|
expect(wrapper.findAll('li').at(0).find('a').attributes('href')).toBe(
|
||||||
|
'https://elopage.com/s/gradido/sign_in?locale=de',
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('has no badge', () => {
|
||||||
|
expect(wrapper.findAll('li').at(0).text()).not.toContain('!')
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe("member's area with default publisher ID and no elopage", () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
mocks.$store.state.publisherId = null
|
||||||
|
mocks.$store.state.hasElopage = false
|
||||||
|
})
|
||||||
|
|
||||||
|
it('links to the elopage member area with default publisher ID', () => {
|
||||||
|
expect(wrapper.findAll('li').at(0).find('a').attributes('href')).toBe(
|
||||||
|
'https://elopage.com/s/gradido/basic-de/payment?locale=de&prid=111&pid=2896&firstName=test&lastName=example&email=test@example.org',
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('has a badge', () => {
|
||||||
|
expect(wrapper.findAll('li').at(0).text()).toContain('!')
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('logout', () => {
|
||||||
|
it('has a logout button', () => {
|
||||||
|
expect(wrapper.findAll('li').at(1).text()).toBe('logout')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('emits logout when logout is clicked', async () => {
|
||||||
|
wrapper.findAll('li').at(1).find('a').trigger('click')
|
||||||
|
await wrapper.vm.$nextTick()
|
||||||
|
expect(wrapper.emitted('logout')).toEqual([[]])
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('admin-area', () => {
|
||||||
|
it('is not visible when not an admin', () => {
|
||||||
|
expect(wrapper.findAll('li').at(1).text()).not.toBe('admin_area')
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('logged in as admin', () => {
|
||||||
|
const assignLocationSpy = jest.fn()
|
||||||
|
beforeEach(async () => {
|
||||||
|
mocks.$store.state.isAdmin = true
|
||||||
|
mocks.$store.state.token = 'valid-token'
|
||||||
|
delete window.location
|
||||||
|
window.location = {
|
||||||
|
assign: assignLocationSpy,
|
||||||
|
}
|
||||||
|
wrapper = Wrapper()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('is visible', () => {
|
||||||
|
expect(wrapper.findAll('li').at(1).text()).toBe('admin_area')
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('click on admin area', () => {
|
||||||
|
beforeEach(async () => {
|
||||||
|
await wrapper.findAll('li').at(1).find('a').trigger('click')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('opens a new window when clicked', () => {
|
||||||
|
expect(assignLocationSpy).toHaveBeenCalledWith(
|
||||||
|
'http://localhost/admin/authenticate?token=valid-token',
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('dispatches logout to store', () => {
|
||||||
|
expect(storeDispatchMock).toHaveBeenCalledWith('logout')
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
@ -12,9 +12,9 @@ export const unsubscribeNewsletter = gql`
|
|||||||
}
|
}
|
||||||
`
|
`
|
||||||
|
|
||||||
export const resetPassword = gql`
|
export const setPassword = gql`
|
||||||
mutation($sessionId: Float!, $email: String!, $password: String!) {
|
mutation($code: String!, $password: String!) {
|
||||||
resetPassword(sessionId: $sessionId, email: $email, password: $password)
|
setPassword(code: $code, password: $password)
|
||||||
}
|
}
|
||||||
`
|
`
|
||||||
|
|
||||||
@ -42,12 +42,11 @@ export const updateUserInfos = gql`
|
|||||||
}
|
}
|
||||||
`
|
`
|
||||||
|
|
||||||
export const registerUser = gql`
|
export const createUser = gql`
|
||||||
mutation(
|
mutation(
|
||||||
$firstName: String!
|
$firstName: String!
|
||||||
$lastName: String!
|
$lastName: String!
|
||||||
$email: String!
|
$email: String!
|
||||||
$password: String!
|
|
||||||
$language: String!
|
$language: String!
|
||||||
$publisherId: Int
|
$publisherId: Int
|
||||||
) {
|
) {
|
||||||
@ -55,7 +54,6 @@ export const registerUser = gql`
|
|||||||
email: $email
|
email: $email
|
||||||
firstName: $firstName
|
firstName: $firstName
|
||||||
lastName: $lastName
|
lastName: $lastName
|
||||||
password: $password
|
|
||||||
language: $language
|
language: $language
|
||||||
publisherId: $publisherId
|
publisherId: $publisherId
|
||||||
)
|
)
|
||||||
|
|||||||
@ -46,15 +46,6 @@ export const logout = gql`
|
|||||||
}
|
}
|
||||||
`
|
`
|
||||||
|
|
||||||
export const loginViaEmailVerificationCode = gql`
|
|
||||||
query($optin: String!) {
|
|
||||||
loginViaEmailVerificationCode(optin: $optin) {
|
|
||||||
sessionId
|
|
||||||
email
|
|
||||||
}
|
|
||||||
}
|
|
||||||
`
|
|
||||||
|
|
||||||
export const transactionsQuery = gql`
|
export const transactionsQuery = gql`
|
||||||
query($currentPage: Int = 1, $pageSize: Int = 25, $order: Order = DESC) {
|
query($currentPage: Int = 1, $pageSize: Int = 25, $order: Order = DESC) {
|
||||||
transactionList(currentPage: $currentPage, pageSize: $pageSize, order: $order) {
|
transactionList(currentPage: $currentPage, pageSize: $pageSize, order: $order) {
|
||||||
@ -88,9 +79,7 @@ export const transactionsQuery = gql`
|
|||||||
|
|
||||||
export const sendResetPasswordEmail = gql`
|
export const sendResetPasswordEmail = gql`
|
||||||
query($email: String!) {
|
query($email: String!) {
|
||||||
sendResetPasswordEmail(email: $email) {
|
sendResetPasswordEmail(email: $email)
|
||||||
state
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
`
|
`
|
||||||
|
|
||||||
@ -118,15 +107,6 @@ export const listGDTEntriesQuery = gql`
|
|||||||
}
|
}
|
||||||
`
|
`
|
||||||
|
|
||||||
export const checkEmailQuery = gql`
|
|
||||||
query($optin: String!) {
|
|
||||||
checkEmail(optin: $optin) {
|
|
||||||
email
|
|
||||||
sessionId
|
|
||||||
}
|
|
||||||
}
|
|
||||||
`
|
|
||||||
|
|
||||||
export const communityInfo = gql`
|
export const communityInfo = gql`
|
||||||
query {
|
query {
|
||||||
getCommunityInfo {
|
getCommunityInfo {
|
||||||
|
|||||||
@ -48,7 +48,7 @@
|
|||||||
"error": "Fehler",
|
"error": "Fehler",
|
||||||
"no-account": "Leider konnten wir keinen Account finden mit diesen Daten!",
|
"no-account": "Leider konnten wir keinen Account finden mit diesen Daten!",
|
||||||
"no-email-verify": "Die Email wurde noch nicht bestätigt, bitte überprüfe deine Emails und klicke auf den Aktivierungslink!",
|
"no-email-verify": "Die Email wurde noch nicht bestätigt, bitte überprüfe deine Emails und klicke auf den Aktivierungslink!",
|
||||||
"session-expired": "Sitzung abgelaufen!"
|
"session-expired": "Die Sitzung wurde aus Sicherheitsgründen beendet."
|
||||||
},
|
},
|
||||||
"form": {
|
"form": {
|
||||||
"amount": "Betrag",
|
"amount": "Betrag",
|
||||||
@ -145,12 +145,17 @@
|
|||||||
"password": {
|
"password": {
|
||||||
"change-password": "Passwort ändern",
|
"change-password": "Passwort ändern",
|
||||||
"forgot_pwd": "Passwort vergessen?",
|
"forgot_pwd": "Passwort vergessen?",
|
||||||
|
"not-authenticated": "Leider konnten wir dich nicht authentifizieren. Bitte wende dich an den Support.",
|
||||||
|
"resend_subtitle": "Dein Aktivierungslink ist abgelaufen, Du kannst hier ein neuen anfordern.",
|
||||||
"reset": "Passwort zurücksetzen",
|
"reset": "Passwort zurücksetzen",
|
||||||
"reset-password": {
|
"reset-password": {
|
||||||
"not-authenticated": "Leider konnten wir dich nicht authentifizieren. Bitte wende dich an den Support.",
|
|
||||||
"text": "Jetzt kannst du ein neues Passwort speichern, mit dem du dich zukünftig in der Gradido-App anmelden kannst."
|
"text": "Jetzt kannst du ein neues Passwort speichern, mit dem du dich zukünftig in der Gradido-App anmelden kannst."
|
||||||
},
|
},
|
||||||
"send_now": "Jetzt senden",
|
"send_now": "Jetzt senden",
|
||||||
|
"set": "Passwort festlegen",
|
||||||
|
"set-password": {
|
||||||
|
"text": "Jetzt kannst du ein neues Passwort speichern, mit dem du dich zukünftig in der Gradido-App anmelden kannst."
|
||||||
|
},
|
||||||
"subtitle": "Wenn du dein Passwort vergessen hast, kannst du es hier zurücksetzen."
|
"subtitle": "Wenn du dein Passwort vergessen hast, kannst du es hier zurücksetzen."
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
@ -48,7 +48,7 @@
|
|||||||
"error": "Error",
|
"error": "Error",
|
||||||
"no-account": "Unfortunately we could not find an account to the given data!",
|
"no-account": "Unfortunately we could not find an account to the given data!",
|
||||||
"no-email-verify": "Your email is not activated yet, please check your emails and click the activation link!",
|
"no-email-verify": "Your email is not activated yet, please check your emails and click the activation link!",
|
||||||
"session-expired": "The session expired"
|
"session-expired": "The session was closed for security reasons."
|
||||||
},
|
},
|
||||||
"form": {
|
"form": {
|
||||||
"amount": "Amount",
|
"amount": "Amount",
|
||||||
@ -145,12 +145,17 @@
|
|||||||
"password": {
|
"password": {
|
||||||
"change-password": "Change password",
|
"change-password": "Change password",
|
||||||
"forgot_pwd": "Forgot password?",
|
"forgot_pwd": "Forgot password?",
|
||||||
|
"not-authenticated": "Unfortunately we could not authenticate you. Please contact the support.",
|
||||||
|
"resend_subtitle": "Your activation link is expired, here you can order a new one.",
|
||||||
"reset": "Reset password",
|
"reset": "Reset password",
|
||||||
"reset-password": {
|
"reset-password": {
|
||||||
"not-authenticated": "Unfortunately we could not authenticate you. Please contact the support.",
|
|
||||||
"text": "Now you can save a new password to login to the Gradido-App in the future."
|
"text": "Now you can save a new password to login to the Gradido-App in the future."
|
||||||
},
|
},
|
||||||
"send_now": "Send now",
|
"send_now": "Send now",
|
||||||
|
"set": "Set password",
|
||||||
|
"set-password": {
|
||||||
|
"text": "Now you can save a new password to login to the Gradido-App in the future."
|
||||||
|
},
|
||||||
"subtitle": "If you have forgotten your password, you can reset it here."
|
"subtitle": "If you have forgotten your password, you can reset it here."
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
@ -3,9 +3,6 @@ import DashboardPlugin from './plugins/dashboard-plugin'
|
|||||||
import App from './App.vue'
|
import App from './App.vue'
|
||||||
import i18n from './i18n.js'
|
import i18n from './i18n.js'
|
||||||
import { loadAllRules } from './validation-rules'
|
import { loadAllRules } from './validation-rules'
|
||||||
import { ApolloClient, ApolloLink, InMemoryCache, HttpLink } from 'apollo-boost'
|
|
||||||
import VueApollo from 'vue-apollo'
|
|
||||||
import CONFIG from './config'
|
|
||||||
|
|
||||||
import addNavigationGuards from './routes/guards'
|
import addNavigationGuards from './routes/guards'
|
||||||
|
|
||||||
@ -13,42 +10,22 @@ import { store } from './store/store'
|
|||||||
|
|
||||||
import router from './routes/router'
|
import router from './routes/router'
|
||||||
|
|
||||||
const httpLink = new HttpLink({ uri: CONFIG.GRAPHQL_URI })
|
import { apolloProvider } from './plugins/apolloProvider'
|
||||||
|
|
||||||
const authLink = new ApolloLink((operation, forward) => {
|
|
||||||
const token = store.state.token
|
|
||||||
operation.setContext({
|
|
||||||
headers: {
|
|
||||||
Authorization: token && token.length > 0 ? `Bearer ${token}` : '',
|
|
||||||
},
|
|
||||||
})
|
|
||||||
return forward(operation).map((response) => {
|
|
||||||
if (response.errors && response.errors[0].message === '403.13 - Client certificate revoked') {
|
|
||||||
response.errors[0].message = i18n.t('error.session-expired')
|
|
||||||
store.dispatch('logout', null)
|
|
||||||
if (router.currentRoute.path !== '/login') router.push('/login')
|
|
||||||
return response
|
|
||||||
}
|
|
||||||
const newToken = operation.getContext().response.headers.get('token')
|
|
||||||
if (newToken) store.commit('token', newToken)
|
|
||||||
return response
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
const apolloClient = new ApolloClient({
|
|
||||||
link: authLink.concat(httpLink),
|
|
||||||
cache: new InMemoryCache(),
|
|
||||||
uri: CONFIG.GRAPHQL_URI,
|
|
||||||
})
|
|
||||||
|
|
||||||
const apolloProvider = new VueApollo({
|
|
||||||
defaultClient: apolloClient,
|
|
||||||
})
|
|
||||||
|
|
||||||
// plugin setup
|
// plugin setup
|
||||||
Vue.use(DashboardPlugin)
|
Vue.use(DashboardPlugin)
|
||||||
Vue.config.productionTip = false
|
Vue.config.productionTip = false
|
||||||
|
|
||||||
|
Vue.toasted.register(
|
||||||
|
'error',
|
||||||
|
(payload) => {
|
||||||
|
return payload.replace(/^GraphQL error: /, '')
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'error',
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
loadAllRules(i18n)
|
loadAllRules(i18n)
|
||||||
|
|
||||||
addNavigationGuards(router, store, apolloProvider.defaultClient)
|
addNavigationGuards(router, store, apolloProvider.defaultClient)
|
||||||
|
|||||||
@ -13,7 +13,7 @@ export const getCommunityInfoMixin = {
|
|||||||
return result.data.getCommunityInfo
|
return result.data.getCommunityInfo
|
||||||
})
|
})
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
this.$toasted.error(error.message)
|
this.$toasted.global.error(error.message)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
37
frontend/src/plugins/apolloProvider.js
Normal file
37
frontend/src/plugins/apolloProvider.js
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
import { ApolloClient, ApolloLink, InMemoryCache, HttpLink } from 'apollo-boost'
|
||||||
|
import VueApollo from 'vue-apollo'
|
||||||
|
import CONFIG from '../config'
|
||||||
|
import { store } from '../store/store'
|
||||||
|
import router from '../routes/router'
|
||||||
|
import i18n from '../i18n'
|
||||||
|
|
||||||
|
const httpLink = new HttpLink({ uri: CONFIG.GRAPHQL_URI })
|
||||||
|
|
||||||
|
const authLink = new ApolloLink((operation, forward) => {
|
||||||
|
const token = store.state.token
|
||||||
|
operation.setContext({
|
||||||
|
headers: {
|
||||||
|
Authorization: token && token.length > 0 ? `Bearer ${token}` : '',
|
||||||
|
},
|
||||||
|
})
|
||||||
|
return forward(operation).map((response) => {
|
||||||
|
if (response.errors && response.errors[0].message === '403.13 - Client certificate revoked') {
|
||||||
|
response.errors[0].message = i18n.t('error.session-expired')
|
||||||
|
store.dispatch('logout', null)
|
||||||
|
if (router.currentRoute.path !== '/login') router.push('/login')
|
||||||
|
return response
|
||||||
|
}
|
||||||
|
const newToken = operation.getContext().response.headers.get('token')
|
||||||
|
if (newToken) store.commit('token', newToken)
|
||||||
|
return response
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
const apolloClient = new ApolloClient({
|
||||||
|
link: authLink.concat(httpLink),
|
||||||
|
cache: new InMemoryCache(),
|
||||||
|
})
|
||||||
|
|
||||||
|
export const apolloProvider = new VueApollo({
|
||||||
|
defaultClient: apolloClient,
|
||||||
|
})
|
||||||
178
frontend/src/plugins/apolloProvider.test.js
Normal file
178
frontend/src/plugins/apolloProvider.test.js
Normal file
@ -0,0 +1,178 @@
|
|||||||
|
import { ApolloClient, ApolloLink, HttpLink } from 'apollo-boost'
|
||||||
|
import './apolloProvider'
|
||||||
|
import CONFIG from '../config'
|
||||||
|
|
||||||
|
import VueApollo from 'vue-apollo'
|
||||||
|
import { store } from '../store/store.js'
|
||||||
|
import router from '../routes/router'
|
||||||
|
import i18n from '../i18n'
|
||||||
|
|
||||||
|
jest.mock('vue-apollo')
|
||||||
|
jest.mock('../store/store')
|
||||||
|
jest.mock('../routes/router')
|
||||||
|
jest.mock('../i18n')
|
||||||
|
|
||||||
|
jest.mock('apollo-boost', () => {
|
||||||
|
return {
|
||||||
|
__esModule: true,
|
||||||
|
ApolloClient: jest.fn(),
|
||||||
|
ApolloLink: jest.fn(() => {
|
||||||
|
return { concat: jest.fn() }
|
||||||
|
}),
|
||||||
|
InMemoryCache: jest.fn(),
|
||||||
|
HttpLink: jest.fn(),
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('apolloProvider', () => {
|
||||||
|
it('calls the HttpLink', () => {
|
||||||
|
expect(HttpLink).toBeCalledWith({ uri: CONFIG.GRAPHQL_URI })
|
||||||
|
})
|
||||||
|
|
||||||
|
it('calls the ApolloLink', () => {
|
||||||
|
expect(ApolloLink).toBeCalled()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('calls the ApolloClient', () => {
|
||||||
|
expect(ApolloClient).toBeCalled()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('calls the VueApollo', () => {
|
||||||
|
expect(VueApollo).toBeCalled()
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('ApolloLink', () => {
|
||||||
|
// mock store
|
||||||
|
const storeDispatchMock = jest.fn()
|
||||||
|
const storeCommitMock = jest.fn()
|
||||||
|
store.state = {
|
||||||
|
token: 'some-token',
|
||||||
|
}
|
||||||
|
store.dispatch = storeDispatchMock
|
||||||
|
store.commit = storeCommitMock
|
||||||
|
|
||||||
|
// mock i18n.t
|
||||||
|
i18n.t = jest.fn((t) => t)
|
||||||
|
|
||||||
|
// mock apllo response
|
||||||
|
const responseMock = {
|
||||||
|
errors: [{ message: '403.13 - Client certificate revoked' }],
|
||||||
|
}
|
||||||
|
|
||||||
|
// mock router
|
||||||
|
const routerPushMock = jest.fn()
|
||||||
|
router.push = routerPushMock
|
||||||
|
router.currentRoute = {
|
||||||
|
path: '/overview',
|
||||||
|
}
|
||||||
|
|
||||||
|
// mock context
|
||||||
|
const setContextMock = jest.fn()
|
||||||
|
const getContextMock = jest.fn(() => {
|
||||||
|
return {
|
||||||
|
response: {
|
||||||
|
headers: {
|
||||||
|
get: jest.fn(() => 'another-token'),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// mock apollo link function params
|
||||||
|
const operationMock = {
|
||||||
|
setContext: setContextMock,
|
||||||
|
getContext: getContextMock,
|
||||||
|
}
|
||||||
|
|
||||||
|
const forwardMock = jest.fn(() => {
|
||||||
|
return [responseMock]
|
||||||
|
})
|
||||||
|
|
||||||
|
// get apollo link callback
|
||||||
|
const middleware = ApolloLink.mock.calls[0][0]
|
||||||
|
|
||||||
|
describe('with token in store', () => {
|
||||||
|
it('sets authorization header with token', () => {
|
||||||
|
// run the apollo link callback with mocked params
|
||||||
|
middleware(operationMock, forwardMock)
|
||||||
|
expect(setContextMock).toBeCalledWith({
|
||||||
|
headers: {
|
||||||
|
Authorization: 'Bearer some-token',
|
||||||
|
},
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('without token in store', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
store.state.token = null
|
||||||
|
})
|
||||||
|
|
||||||
|
it('sets authorization header empty', () => {
|
||||||
|
middleware(operationMock, forwardMock)
|
||||||
|
expect(setContextMock).toBeCalledWith({
|
||||||
|
headers: {
|
||||||
|
Authorization: '',
|
||||||
|
},
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('apollo response is 403.13', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
// run the apollo link callback with mocked params
|
||||||
|
middleware(operationMock, forwardMock)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('dispatches logout', () => {
|
||||||
|
expect(storeDispatchMock).toBeCalledWith('logout', null)
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('current route is not login', () => {
|
||||||
|
it('redirects to logout', () => {
|
||||||
|
expect(routerPushMock).toBeCalledWith('/login')
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('current route is login', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
jest.clearAllMocks()
|
||||||
|
router.currentRoute.path = '/login'
|
||||||
|
})
|
||||||
|
|
||||||
|
it('does not redirect to login', () => {
|
||||||
|
expect(routerPushMock).not.toBeCalled()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('apollo response is with new token', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
delete responseMock.errors
|
||||||
|
middleware(operationMock, forwardMock)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('commits new token to store', () => {
|
||||||
|
expect(storeCommitMock).toBeCalledWith('token', 'another-token')
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('apollo response is without new token', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
jest.clearAllMocks()
|
||||||
|
getContextMock.mockReturnValue({
|
||||||
|
response: {
|
||||||
|
headers: {
|
||||||
|
get: jest.fn(() => null),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
middleware(operationMock, forwardMock)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('does not commit token to store', () => {
|
||||||
|
expect(storeCommitMock).not.toBeCalled()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
@ -4,8 +4,11 @@ import Vue from 'vue'
|
|||||||
import GlobalComponents from './globalComponents'
|
import GlobalComponents from './globalComponents'
|
||||||
import GlobalDirectives from './globalDirectives'
|
import GlobalDirectives from './globalDirectives'
|
||||||
|
|
||||||
|
import Toasted from 'vue-toasted'
|
||||||
|
|
||||||
jest.mock('./globalComponents')
|
jest.mock('./globalComponents')
|
||||||
jest.mock('./globalDirectives')
|
jest.mock('./globalDirectives')
|
||||||
|
jest.mock('vue-toasted')
|
||||||
|
|
||||||
jest.mock('vue')
|
jest.mock('vue')
|
||||||
|
|
||||||
@ -22,4 +25,21 @@ describe('dashboard plugin', () => {
|
|||||||
it('installs the global directives', () => {
|
it('installs the global directives', () => {
|
||||||
expect(vueUseMock).toBeCalledWith(GlobalDirectives)
|
expect(vueUseMock).toBeCalledWith(GlobalDirectives)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
describe('vue toasted', () => {
|
||||||
|
const toastedAction = vueUseMock.mock.calls[11][1].action.onClick
|
||||||
|
const goAwayMock = jest.fn()
|
||||||
|
const toastObject = {
|
||||||
|
goAway: goAwayMock,
|
||||||
|
}
|
||||||
|
|
||||||
|
it('installs vue toasted', () => {
|
||||||
|
expect(vueUseMock).toBeCalledWith(Toasted, expect.anything())
|
||||||
|
})
|
||||||
|
|
||||||
|
it('onClick calls goAway(0)', () => {
|
||||||
|
toastedAction({}, toastObject)
|
||||||
|
expect(goAwayMock).toBeCalledWith(0)
|
||||||
|
})
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
@ -49,8 +49,8 @@ describe('router', () => {
|
|||||||
expect(routes.find((r) => r.path === '/').redirect()).toEqual({ path: '/login' })
|
expect(routes.find((r) => r.path === '/').redirect()).toEqual({ path: '/login' })
|
||||||
})
|
})
|
||||||
|
|
||||||
it('has fifteen routes defined', () => {
|
it('has sixteen routes defined', () => {
|
||||||
expect(routes).toHaveLength(15)
|
expect(routes).toHaveLength(16)
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('overview', () => {
|
describe('overview', () => {
|
||||||
@ -143,6 +143,13 @@ describe('router', () => {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
describe('password with param comingFrom', () => {
|
||||||
|
it('loads the "Password" component', async () => {
|
||||||
|
const component = await routes.find((r) => r.path === '/password/:comingFrom').component()
|
||||||
|
expect(component.default.name).toBe('password')
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
describe('register-community', () => {
|
describe('register-community', () => {
|
||||||
it('loads the "registerCommunity" component', async () => {
|
it('loads the "registerCommunity" component', async () => {
|
||||||
const component = await routes.find((r) => r.path === '/register-community').component()
|
const component = await routes.find((r) => r.path === '/register-community').component()
|
||||||
@ -167,7 +174,7 @@ describe('router', () => {
|
|||||||
describe('checkEmail', () => {
|
describe('checkEmail', () => {
|
||||||
it('loads the "CheckEmail" component', async () => {
|
it('loads the "CheckEmail" component', async () => {
|
||||||
const component = await routes.find((r) => r.path === '/checkEmail/:optin').component()
|
const component = await routes.find((r) => r.path === '/checkEmail/:optin').component()
|
||||||
expect(component.default.name).toBe('CheckEmail')
|
expect(component.default.name).toBe('ResetPassword')
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@ -50,7 +50,7 @@ const routes = [
|
|||||||
path: '/thx/:comingFrom',
|
path: '/thx/:comingFrom',
|
||||||
component: () => import('../views/Pages/thx.vue'),
|
component: () => import('../views/Pages/thx.vue'),
|
||||||
beforeEnter: (to, from, next) => {
|
beforeEnter: (to, from, next) => {
|
||||||
const validFrom = ['password', 'reset', 'register', 'login']
|
const validFrom = ['password', 'reset', 'register', 'login', 'Login']
|
||||||
if (!validFrom.includes(from.path.split('/')[1])) {
|
if (!validFrom.includes(from.path.split('/')[1])) {
|
||||||
next({ path: '/login' })
|
next({ path: '/login' })
|
||||||
} else {
|
} else {
|
||||||
@ -62,6 +62,10 @@ const routes = [
|
|||||||
path: '/password',
|
path: '/password',
|
||||||
component: () => import('../views/Pages/ForgotPassword.vue'),
|
component: () => import('../views/Pages/ForgotPassword.vue'),
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: '/password/:comingFrom',
|
||||||
|
component: () => import('../views/Pages/ForgotPassword.vue'),
|
||||||
|
},
|
||||||
{
|
{
|
||||||
path: '/register-community',
|
path: '/register-community',
|
||||||
component: () => import('../views/Pages/RegisterCommunity.vue'),
|
component: () => import('../views/Pages/RegisterCommunity.vue'),
|
||||||
@ -76,7 +80,7 @@ const routes = [
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: '/checkEmail/:optin',
|
path: '/checkEmail/:optin',
|
||||||
component: () => import('../views/Pages/CheckEmail.vue'),
|
component: () => import('../views/Pages/ResetPassword.vue'),
|
||||||
},
|
},
|
||||||
{ path: '*', component: NotFound },
|
{ path: '*', component: NotFound },
|
||||||
]
|
]
|
||||||
|
|||||||
@ -39,8 +39,10 @@ describe('DashboardLayoutGdd', () => {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
$toasted: {
|
$toasted: {
|
||||||
|
global: {
|
||||||
error: toasterMock,
|
error: toasterMock,
|
||||||
},
|
},
|
||||||
|
},
|
||||||
$apollo: {
|
$apollo: {
|
||||||
query: apolloMock,
|
query: apolloMock,
|
||||||
},
|
},
|
||||||
@ -216,7 +218,7 @@ describe('DashboardLayoutGdd', () => {
|
|||||||
expect(wrapper.vm.pending).toBeTruthy()
|
expect(wrapper.vm.pending).toBeTruthy()
|
||||||
})
|
})
|
||||||
|
|
||||||
it('calls $toasted.error method', () => {
|
it('calls $toasted.global.error method', () => {
|
||||||
expect(toasterMock).toBeCalledWith('Ouch!')
|
expect(toasterMock).toBeCalledWith('Ouch!')
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
@ -101,7 +101,7 @@ export default {
|
|||||||
})
|
})
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
this.pending = true
|
this.pending = true
|
||||||
this.$toasted.error(error.message)
|
this.$toasted.global.error(error.message)
|
||||||
// what to do when loading balance fails?
|
// what to do when loading balance fails?
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
|
|||||||
@ -37,8 +37,10 @@ describe('GdtTransactionList ', () => {
|
|||||||
$n: jest.fn((n) => n),
|
$n: jest.fn((n) => n),
|
||||||
$d: jest.fn((d) => d),
|
$d: jest.fn((d) => d),
|
||||||
$toasted: {
|
$toasted: {
|
||||||
|
global: {
|
||||||
error: toastErrorMock,
|
error: toastErrorMock,
|
||||||
},
|
},
|
||||||
|
},
|
||||||
$apollo: {
|
$apollo: {
|
||||||
query: apolloMock,
|
query: apolloMock,
|
||||||
},
|
},
|
||||||
|
|||||||
@ -71,7 +71,7 @@ export default {
|
|||||||
window.scrollTo(0, 0)
|
window.scrollTo(0, 0)
|
||||||
})
|
})
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
this.$toasted.error(error.message)
|
this.$toasted.global.error(error.message)
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
@ -1,105 +0,0 @@
|
|||||||
import { mount, RouterLinkStub } from '@vue/test-utils'
|
|
||||||
import CheckEmail from './CheckEmail'
|
|
||||||
|
|
||||||
const localVue = global.localVue
|
|
||||||
|
|
||||||
const apolloQueryMock = jest.fn().mockRejectedValue({ message: 'error' })
|
|
||||||
|
|
||||||
const toasterMock = jest.fn()
|
|
||||||
const routerPushMock = jest.fn()
|
|
||||||
|
|
||||||
describe('CheckEmail', () => {
|
|
||||||
let wrapper
|
|
||||||
|
|
||||||
const mocks = {
|
|
||||||
$i18n: {
|
|
||||||
locale: 'en',
|
|
||||||
},
|
|
||||||
$t: jest.fn((t) => t),
|
|
||||||
$route: {
|
|
||||||
params: {
|
|
||||||
optin: '123',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
$toasted: {
|
|
||||||
error: toasterMock,
|
|
||||||
},
|
|
||||||
$router: {
|
|
||||||
push: routerPushMock,
|
|
||||||
},
|
|
||||||
$loading: {
|
|
||||||
show: jest.fn(() => {
|
|
||||||
return { hide: jest.fn() }
|
|
||||||
}),
|
|
||||||
},
|
|
||||||
$apollo: {
|
|
||||||
query: apolloQueryMock,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
const stubs = {
|
|
||||||
RouterLink: RouterLinkStub,
|
|
||||||
}
|
|
||||||
|
|
||||||
const Wrapper = () => {
|
|
||||||
return mount(CheckEmail, { localVue, mocks, stubs })
|
|
||||||
}
|
|
||||||
|
|
||||||
describe('mount', () => {
|
|
||||||
beforeEach(() => {
|
|
||||||
wrapper = Wrapper()
|
|
||||||
})
|
|
||||||
|
|
||||||
it('calls the checkEmail when created', async () => {
|
|
||||||
expect(apolloQueryMock).toBeCalledWith(
|
|
||||||
expect.objectContaining({ variables: { optin: '123' } }),
|
|
||||||
)
|
|
||||||
})
|
|
||||||
|
|
||||||
describe('No valid optin', () => {
|
|
||||||
it('toasts an error when no valid optin is given', () => {
|
|
||||||
expect(toasterMock).toHaveBeenCalledWith('error')
|
|
||||||
})
|
|
||||||
|
|
||||||
it('has a message suggesting to contact the support', () => {
|
|
||||||
expect(wrapper.find('div.header').text()).toContain('checkEmail.title')
|
|
||||||
expect(wrapper.find('div.header').text()).toContain('checkEmail.errorText')
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
describe('is authenticated', () => {
|
|
||||||
beforeEach(() => {
|
|
||||||
apolloQueryMock.mockResolvedValue({
|
|
||||||
data: {
|
|
||||||
checkEmail: {
|
|
||||||
sessionId: 1,
|
|
||||||
email: 'user@example.org',
|
|
||||||
language: 'de',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
it.skip('Has sessionId from API call', async () => {
|
|
||||||
await wrapper.vm.$nextTick()
|
|
||||||
expect(wrapper.vm.sessionId).toBe(1)
|
|
||||||
})
|
|
||||||
|
|
||||||
describe('Register header', () => {
|
|
||||||
it('has a welcome message', async () => {
|
|
||||||
expect(wrapper.find('div.header').text()).toContain('checkEmail.title')
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
describe('links', () => {
|
|
||||||
it('has a link "Back"', async () => {
|
|
||||||
expect(wrapper.findAllComponents(RouterLinkStub).at(0).text()).toEqual('back')
|
|
||||||
})
|
|
||||||
|
|
||||||
it('links to /login when clicking "Back"', async () => {
|
|
||||||
expect(wrapper.findAllComponents(RouterLinkStub).at(0).props().to).toBe('/Login')
|
|
||||||
})
|
|
||||||
})
|
|
||||||
})
|
|
||||||
})
|
|
||||||
})
|
|
||||||
@ -1,72 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div class="checkemail-form">
|
|
||||||
<b-container>
|
|
||||||
<div class="header p-4" ref="header">
|
|
||||||
<div class="header-body text-center mb-7">
|
|
||||||
<b-row class="justify-content-center">
|
|
||||||
<b-col xl="5" lg="6" md="8" class="px-2">
|
|
||||||
<h1>{{ $t('site.checkEmail.title') }}</h1>
|
|
||||||
<div class="pb-4" v-if="!pending">
|
|
||||||
<span v-if="!authenticated">
|
|
||||||
{{ $t('site.checkEmail.errorText') }}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</b-col>
|
|
||||||
</b-row>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</b-container>
|
|
||||||
<b-container class="mt--8 p-1">
|
|
||||||
<b-row>
|
|
||||||
<b-col class="text-center py-lg-4">
|
|
||||||
<router-link to="/Login" class="mt-3">{{ $t('back') }}</router-link>
|
|
||||||
</b-col>
|
|
||||||
</b-row>
|
|
||||||
</b-container>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
<script>
|
|
||||||
import { checkEmailQuery } from '../../graphql/queries'
|
|
||||||
|
|
||||||
export default {
|
|
||||||
name: 'CheckEmail',
|
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
authenticated: false,
|
|
||||||
sessionId: null,
|
|
||||||
email: null,
|
|
||||||
pending: true,
|
|
||||||
}
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
async authenticate() {
|
|
||||||
const loader = this.$loading.show({
|
|
||||||
container: this.$refs.header,
|
|
||||||
})
|
|
||||||
const optin = this.$route.params.optin
|
|
||||||
this.$apollo
|
|
||||||
.query({
|
|
||||||
query: checkEmailQuery,
|
|
||||||
variables: {
|
|
||||||
optin: optin,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
.then((result) => {
|
|
||||||
this.authenticated = true
|
|
||||||
this.sessionId = result.data.checkEmail.sessionId
|
|
||||||
this.email = result.data.checkEmail.email
|
|
||||||
this.$router.push('/thx/checkEmail')
|
|
||||||
})
|
|
||||||
.catch((error) => {
|
|
||||||
this.$toasted.error(error.message)
|
|
||||||
})
|
|
||||||
loader.hide()
|
|
||||||
this.pending = false
|
|
||||||
},
|
|
||||||
},
|
|
||||||
mounted() {
|
|
||||||
this.authenticate()
|
|
||||||
},
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
<style></style>
|
|
||||||
@ -8,10 +8,14 @@ const localVue = global.localVue
|
|||||||
|
|
||||||
const mockRouterPush = jest.fn()
|
const mockRouterPush = jest.fn()
|
||||||
|
|
||||||
describe('ForgotPassword', () => {
|
const stubs = {
|
||||||
let wrapper
|
RouterLink: RouterLinkStub,
|
||||||
|
}
|
||||||
|
|
||||||
const mocks = {
|
const createMockObject = (comingFrom) => {
|
||||||
|
return {
|
||||||
|
localVue,
|
||||||
|
mocks: {
|
||||||
$t: jest.fn((t) => t),
|
$t: jest.fn((t) => t),
|
||||||
$router: {
|
$router: {
|
||||||
push: mockRouterPush,
|
push: mockRouterPush,
|
||||||
@ -19,19 +23,26 @@ describe('ForgotPassword', () => {
|
|||||||
$apollo: {
|
$apollo: {
|
||||||
query: mockAPIcall,
|
query: mockAPIcall,
|
||||||
},
|
},
|
||||||
|
$route: {
|
||||||
|
params: {
|
||||||
|
comingFrom,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
stubs,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const stubs = {
|
describe('ForgotPassword', () => {
|
||||||
RouterLink: RouterLinkStub,
|
let wrapper
|
||||||
}
|
|
||||||
|
|
||||||
const Wrapper = () => {
|
const Wrapper = (functionN) => {
|
||||||
return mount(ForgotPassword, { localVue, mocks, stubs })
|
return mount(ForgotPassword, functionN)
|
||||||
}
|
}
|
||||||
|
|
||||||
describe('mount', () => {
|
describe('mount', () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
wrapper = Wrapper()
|
wrapper = Wrapper(createMockObject())
|
||||||
})
|
})
|
||||||
|
|
||||||
it('renders the component', () => {
|
it('renders the component', () => {
|
||||||
@ -144,5 +155,15 @@ describe('ForgotPassword', () => {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
describe('comingFrom login', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
wrapper = Wrapper(createMockObject('reset'))
|
||||||
|
})
|
||||||
|
|
||||||
|
it('has another subtitle', () => {
|
||||||
|
expect(wrapper.find('p.text-lead').text()).toEqual('settings.password.resend_subtitle')
|
||||||
|
})
|
||||||
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
@ -5,8 +5,8 @@
|
|||||||
<div class="header-body text-center mb-7">
|
<div class="header-body text-center mb-7">
|
||||||
<b-row class="justify-content-center">
|
<b-row class="justify-content-center">
|
||||||
<b-col xl="5" lg="6" md="8" class="px-2">
|
<b-col xl="5" lg="6" md="8" class="px-2">
|
||||||
<h1>{{ $t('settings.password.reset') }}</h1>
|
<h1>{{ $t(displaySetup.headline) }}</h1>
|
||||||
<p class="text-lead">{{ $t('settings.password.subtitle') }}</p>
|
<p class="text-lead">{{ $t(displaySetup.subtitle) }}</p>
|
||||||
</b-col>
|
</b-col>
|
||||||
</b-row>
|
</b-row>
|
||||||
</div>
|
</div>
|
||||||
@ -22,7 +22,7 @@
|
|||||||
<input-email v-model="form.email"></input-email>
|
<input-email v-model="form.email"></input-email>
|
||||||
<div class="text-center">
|
<div class="text-center">
|
||||||
<b-button type="submit" variant="primary">
|
<b-button type="submit" variant="primary">
|
||||||
{{ $t('settings.password.send_now') }}
|
{{ $t(displaySetup.button) }}
|
||||||
</b-button>
|
</b-button>
|
||||||
</div>
|
</div>
|
||||||
</b-form>
|
</b-form>
|
||||||
@ -41,6 +41,21 @@
|
|||||||
import { sendResetPasswordEmail } from '../../graphql/queries'
|
import { sendResetPasswordEmail } from '../../graphql/queries'
|
||||||
import InputEmail from '../../components/Inputs/InputEmail'
|
import InputEmail from '../../components/Inputs/InputEmail'
|
||||||
|
|
||||||
|
const textFields = {
|
||||||
|
reset: {
|
||||||
|
headline: 'settings.password.reset',
|
||||||
|
subtitle: 'settings.password.resend_subtitle',
|
||||||
|
button: 'settings.password.send_now',
|
||||||
|
cancel: 'back',
|
||||||
|
},
|
||||||
|
login: {
|
||||||
|
headline: 'settings.password.reset',
|
||||||
|
subtitle: 'settings.password.subtitle',
|
||||||
|
button: 'settings.password.send_now',
|
||||||
|
cancel: 'back',
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'password',
|
name: 'password',
|
||||||
components: {
|
components: {
|
||||||
@ -52,6 +67,7 @@ export default {
|
|||||||
form: {
|
form: {
|
||||||
email: '',
|
email: '',
|
||||||
},
|
},
|
||||||
|
displaySetup: {},
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
@ -71,6 +87,13 @@ export default {
|
|||||||
})
|
})
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
created() {
|
||||||
|
if (this.$route.params.comingFrom) {
|
||||||
|
this.displaySetup = textFields[this.$route.params.comingFrom]
|
||||||
|
} else {
|
||||||
|
this.displaySetup = textFields.login
|
||||||
|
}
|
||||||
|
},
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
<style></style>
|
<style></style>
|
||||||
|
|||||||
@ -52,8 +52,10 @@ describe('Login', () => {
|
|||||||
push: mockRouterPush,
|
push: mockRouterPush,
|
||||||
},
|
},
|
||||||
$toasted: {
|
$toasted: {
|
||||||
|
global: {
|
||||||
error: toastErrorMock,
|
error: toastErrorMock,
|
||||||
},
|
},
|
||||||
|
},
|
||||||
$apollo: {
|
$apollo: {
|
||||||
query: apolloQueryMock,
|
query: apolloQueryMock,
|
||||||
},
|
},
|
||||||
@ -238,7 +240,7 @@ describe('Login', () => {
|
|||||||
describe('login fails', () => {
|
describe('login fails', () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
apolloQueryMock.mockRejectedValue({
|
apolloQueryMock.mockRejectedValue({
|
||||||
message: 'Ouch!',
|
message: '..No user with this credentials',
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@ -105,11 +105,11 @@ export default {
|
|||||||
loader.hide()
|
loader.hide()
|
||||||
})
|
})
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
if (!error.message.includes('user email not validated')) {
|
if (error.message.includes('No user with this credentials')) {
|
||||||
this.$toasted.error(this.$t('error.no-account'))
|
this.$toasted.global.error(this.$t('error.no-account'))
|
||||||
} else {
|
} else {
|
||||||
// : this.$t('error.no-email-verify')
|
// : this.$t('error.no-email-verify')
|
||||||
this.$router.push('/thx/login')
|
this.$router.push('/reset/login')
|
||||||
}
|
}
|
||||||
loader.hide()
|
loader.hide()
|
||||||
})
|
})
|
||||||
|
|||||||
@ -49,8 +49,10 @@ describe('Register', () => {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
$toasted: {
|
$toasted: {
|
||||||
|
global: {
|
||||||
error: toastErrorMock,
|
error: toastErrorMock,
|
||||||
},
|
},
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
const stubs = {
|
const stubs = {
|
||||||
@ -151,16 +153,10 @@ describe('Register', () => {
|
|||||||
expect(wrapper.find('#Email-input-field').exists()).toBeTruthy()
|
expect(wrapper.find('#Email-input-field').exists()).toBeTruthy()
|
||||||
})
|
})
|
||||||
|
|
||||||
it('has password input fields', () => {
|
|
||||||
expect(wrapper.find('input[name="form.password"]').exists()).toBeTruthy()
|
|
||||||
})
|
|
||||||
|
|
||||||
it('has password repeat input fields', () => {
|
|
||||||
expect(wrapper.find('input[name="form.passwordRepeat"]').exists()).toBeTruthy()
|
|
||||||
})
|
|
||||||
it('has Language selected field', () => {
|
it('has Language selected field', () => {
|
||||||
expect(wrapper.find('.selectedLanguage').exists()).toBeTruthy()
|
expect(wrapper.find('.selectedLanguage').exists()).toBeTruthy()
|
||||||
})
|
})
|
||||||
|
|
||||||
it('selects Language value en', async () => {
|
it('selects Language value en', async () => {
|
||||||
wrapper.find('.selectedLanguage').findAll('option').at(1).setSelected()
|
wrapper.find('.selectedLanguage').findAll('option').at(1).setSelected()
|
||||||
expect(wrapper.find('.selectedLanguage').element.value).toBe('en')
|
expect(wrapper.find('.selectedLanguage').element.value).toBe('en')
|
||||||
@ -223,8 +219,6 @@ describe('Register', () => {
|
|||||||
wrapper.find('#registerFirstname').setValue('Max')
|
wrapper.find('#registerFirstname').setValue('Max')
|
||||||
wrapper.find('#registerLastname').setValue('Mustermann')
|
wrapper.find('#registerLastname').setValue('Mustermann')
|
||||||
wrapper.find('#Email-input-field').setValue('max.mustermann@gradido.net')
|
wrapper.find('#Email-input-field').setValue('max.mustermann@gradido.net')
|
||||||
wrapper.find('input[name="form.password"]').setValue('Aa123456_')
|
|
||||||
wrapper.find('input[name="form.passwordRepeat"]').setValue('Aa123456_')
|
|
||||||
wrapper.find('.language-switch-select').findAll('option').at(1).setSelected()
|
wrapper.find('.language-switch-select').findAll('option').at(1).setSelected()
|
||||||
wrapper.find('#publisherid').setValue('12345')
|
wrapper.find('#publisherid').setValue('12345')
|
||||||
})
|
})
|
||||||
@ -280,7 +274,6 @@ describe('Register', () => {
|
|||||||
email: 'max.mustermann@gradido.net',
|
email: 'max.mustermann@gradido.net',
|
||||||
firstName: 'Max',
|
firstName: 'Max',
|
||||||
lastName: 'Mustermann',
|
lastName: 'Mustermann',
|
||||||
password: 'Aa123456_',
|
|
||||||
language: 'en',
|
language: 'en',
|
||||||
publisherId: 12345,
|
publisherId: 12345,
|
||||||
},
|
},
|
||||||
|
|||||||
@ -85,10 +85,6 @@
|
|||||||
<input-email v-model="form.email"></input-email>
|
<input-email v-model="form.email"></input-email>
|
||||||
|
|
||||||
<hr />
|
<hr />
|
||||||
<input-password-confirmation
|
|
||||||
v-model="form.password"
|
|
||||||
:register="register"
|
|
||||||
></input-password-confirmation>
|
|
||||||
|
|
||||||
<b-row>
|
<b-row>
|
||||||
<b-col cols="12">
|
<b-col cols="12">
|
||||||
@ -194,14 +190,13 @@
|
|||||||
</template>
|
</template>
|
||||||
<script>
|
<script>
|
||||||
import InputEmail from '../../components/Inputs/InputEmail.vue'
|
import InputEmail from '../../components/Inputs/InputEmail.vue'
|
||||||
import InputPasswordConfirmation from '../../components/Inputs/InputPasswordConfirmation.vue'
|
|
||||||
import LanguageSwitchSelect from '../../components/LanguageSwitchSelect.vue'
|
import LanguageSwitchSelect from '../../components/LanguageSwitchSelect.vue'
|
||||||
import { registerUser } from '../../graphql/mutations'
|
import { createUser } from '../../graphql/mutations'
|
||||||
import { localeChanged } from 'vee-validate'
|
import { localeChanged } from 'vee-validate'
|
||||||
import { getCommunityInfoMixin } from '../../mixins/getCommunityInfo'
|
import { getCommunityInfoMixin } from '../../mixins/getCommunityInfo'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: { InputPasswordConfirmation, InputEmail, LanguageSwitchSelect },
|
components: { InputEmail, LanguageSwitchSelect },
|
||||||
name: 'register',
|
name: 'register',
|
||||||
mixins: [getCommunityInfoMixin],
|
mixins: [getCommunityInfoMixin],
|
||||||
data() {
|
data() {
|
||||||
@ -211,10 +206,6 @@ export default {
|
|||||||
lastname: '',
|
lastname: '',
|
||||||
email: '',
|
email: '',
|
||||||
agree: false,
|
agree: false,
|
||||||
password: {
|
|
||||||
password: '',
|
|
||||||
passwordRepeat: '',
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
language: '',
|
language: '',
|
||||||
submitted: false,
|
submitted: false,
|
||||||
@ -240,12 +231,11 @@ export default {
|
|||||||
async onSubmit() {
|
async onSubmit() {
|
||||||
this.$apollo
|
this.$apollo
|
||||||
.mutate({
|
.mutate({
|
||||||
mutation: registerUser,
|
mutation: createUser,
|
||||||
variables: {
|
variables: {
|
||||||
email: this.form.email,
|
email: this.form.email,
|
||||||
firstName: this.form.firstname,
|
firstName: this.form.firstname,
|
||||||
lastName: this.form.lastname,
|
lastName: this.form.lastname,
|
||||||
password: this.form.password.password,
|
|
||||||
language: this.language,
|
language: this.language,
|
||||||
publisherId: this.$store.state.publisherId,
|
publisherId: this.$store.state.publisherId,
|
||||||
},
|
},
|
||||||
@ -264,8 +254,6 @@ export default {
|
|||||||
this.form.email = ''
|
this.form.email = ''
|
||||||
this.form.firstname = ''
|
this.form.firstname = ''
|
||||||
this.form.lastname = ''
|
this.form.lastname = ''
|
||||||
this.form.password.password = ''
|
|
||||||
this.form.password.passwordRepeat = ''
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
|
|||||||
@ -37,8 +37,10 @@ describe('RegisterCommunity', () => {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
$toasted: {
|
$toasted: {
|
||||||
|
global: {
|
||||||
error: toastErrorMock,
|
error: toastErrorMock,
|
||||||
},
|
},
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
const stubs = {
|
const stubs = {
|
||||||
|
|||||||
@ -79,8 +79,10 @@ describe('RegisterSelectCommunity', () => {
|
|||||||
show: spinnerMock,
|
show: spinnerMock,
|
||||||
},
|
},
|
||||||
$toasted: {
|
$toasted: {
|
||||||
|
global: {
|
||||||
error: toasterMock,
|
error: toasterMock,
|
||||||
},
|
},
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
const stubs = {
|
const stubs = {
|
||||||
|
|||||||
@ -76,7 +76,7 @@ export default {
|
|||||||
)
|
)
|
||||||
})
|
})
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
this.$toasted.error(error.message)
|
this.$toasted.global.error(error.message)
|
||||||
})
|
})
|
||||||
loader.hide()
|
loader.hide()
|
||||||
this.pending = false
|
this.pending = false
|
||||||
|
|||||||
@ -6,16 +6,19 @@ import flushPromises from 'flush-promises'
|
|||||||
|
|
||||||
const localVue = global.localVue
|
const localVue = global.localVue
|
||||||
|
|
||||||
const apolloQueryMock = jest.fn().mockRejectedValue({ message: 'error' })
|
|
||||||
const apolloMutationMock = jest.fn()
|
const apolloMutationMock = jest.fn()
|
||||||
|
|
||||||
const toasterMock = jest.fn()
|
const toasterMock = jest.fn()
|
||||||
const routerPushMock = jest.fn()
|
const routerPushMock = jest.fn()
|
||||||
|
|
||||||
describe('ResetPassword', () => {
|
const stubs = {
|
||||||
let wrapper
|
RouterLink: RouterLinkStub,
|
||||||
|
}
|
||||||
|
|
||||||
const mocks = {
|
const createMockObject = (comingFrom) => {
|
||||||
|
return {
|
||||||
|
localVue,
|
||||||
|
mocks: {
|
||||||
$i18n: {
|
$i18n: {
|
||||||
locale: 'en',
|
locale: 'en',
|
||||||
},
|
},
|
||||||
@ -23,11 +26,14 @@ describe('ResetPassword', () => {
|
|||||||
$route: {
|
$route: {
|
||||||
params: {
|
params: {
|
||||||
optin: '123',
|
optin: '123',
|
||||||
|
comingFrom,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
$toasted: {
|
$toasted: {
|
||||||
|
global: {
|
||||||
error: toasterMock,
|
error: toasterMock,
|
||||||
},
|
},
|
||||||
|
},
|
||||||
$router: {
|
$router: {
|
||||||
push: routerPushMock,
|
push: routerPushMock,
|
||||||
},
|
},
|
||||||
@ -38,63 +44,40 @@ describe('ResetPassword', () => {
|
|||||||
},
|
},
|
||||||
$apollo: {
|
$apollo: {
|
||||||
mutate: apolloMutationMock,
|
mutate: apolloMutationMock,
|
||||||
query: apolloQueryMock,
|
|
||||||
},
|
},
|
||||||
|
},
|
||||||
|
stubs,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const stubs = {
|
describe('ResetPassword', () => {
|
||||||
RouterLink: RouterLinkStub,
|
let wrapper
|
||||||
}
|
|
||||||
|
|
||||||
const Wrapper = () => {
|
const Wrapper = (functionName) => {
|
||||||
return mount(ResetPassword, { localVue, mocks, stubs })
|
return mount(ResetPassword, functionName)
|
||||||
}
|
}
|
||||||
|
|
||||||
describe('mount', () => {
|
describe('mount', () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
wrapper = Wrapper()
|
wrapper = Wrapper(createMockObject())
|
||||||
})
|
|
||||||
|
|
||||||
it('calls the email verification when created', async () => {
|
|
||||||
expect(apolloQueryMock).toBeCalledWith(
|
|
||||||
expect.objectContaining({ variables: { optin: '123' } }),
|
|
||||||
)
|
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('No valid optin', () => {
|
describe('No valid optin', () => {
|
||||||
it('does not render the Reset Password form when not authenticated', () => {
|
it.skip('does not render the Reset Password form when not authenticated', () => {
|
||||||
expect(wrapper.find('form').exists()).toBeFalsy()
|
expect(wrapper.find('form').exists()).toBeFalsy()
|
||||||
})
|
})
|
||||||
|
|
||||||
it('toasts an error when no valid optin is given', () => {
|
it.skip('toasts an error when no valid optin is given', () => {
|
||||||
expect(toasterMock).toHaveBeenCalledWith('error')
|
expect(toasterMock).toHaveBeenCalledWith('error')
|
||||||
})
|
})
|
||||||
|
|
||||||
it('has a message suggesting to contact the support', () => {
|
it.skip('has a message suggesting to contact the support', () => {
|
||||||
expect(wrapper.find('div.header').text()).toContain('settings.password.reset')
|
expect(wrapper.find('div.header').text()).toContain('settings.password.reset')
|
||||||
expect(wrapper.find('div.header').text()).toContain(
|
expect(wrapper.find('div.header').text()).toContain('settings.password.not-authenticated')
|
||||||
'settings.password.reset-password.not-authenticated',
|
|
||||||
)
|
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('is authenticated', () => {
|
describe('is authenticated', () => {
|
||||||
beforeEach(() => {
|
|
||||||
apolloQueryMock.mockResolvedValue({
|
|
||||||
data: {
|
|
||||||
loginViaEmailVerificationCode: {
|
|
||||||
sessionId: 1,
|
|
||||||
email: 'user@example.org',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
it.skip('Has sessionId from API call', async () => {
|
|
||||||
await wrapper.vm.$nextTick()
|
|
||||||
expect(wrapper.vm.sessionId).toBe(1)
|
|
||||||
})
|
|
||||||
|
|
||||||
it('renders the Reset Password form when authenticated', () => {
|
it('renders the Reset Password form when authenticated', () => {
|
||||||
expect(wrapper.find('div.resetpwd-form').exists()).toBeTruthy()
|
expect(wrapper.find('div.resetpwd-form').exists()).toBeTruthy()
|
||||||
})
|
})
|
||||||
@ -114,7 +97,7 @@ describe('ResetPassword', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
it('links to /login when clicking "Back"', async () => {
|
it('links to /login when clicking "Back"', async () => {
|
||||||
expect(wrapper.findAllComponents(RouterLinkStub).at(0).props().to).toBe('/Login')
|
expect(wrapper.findAllComponents(RouterLinkStub).at(0).props().to).toBe('/login')
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -128,7 +111,7 @@ describe('ResetPassword', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
it('toggles the first input field to text when eye icon is clicked', async () => {
|
it('toggles the first input field to text when eye icon is clicked', async () => {
|
||||||
wrapper.findAll('button').at(0).trigger('click')
|
await wrapper.findAll('button').at(0).trigger('click')
|
||||||
await wrapper.vm.$nextTick()
|
await wrapper.vm.$nextTick()
|
||||||
expect(wrapper.findAll('input').at(0).attributes('type')).toBe('text')
|
expect(wrapper.findAll('input').at(0).attributes('type')).toBe('text')
|
||||||
})
|
})
|
||||||
@ -142,37 +125,61 @@ describe('ResetPassword', () => {
|
|||||||
|
|
||||||
describe('submit form', () => {
|
describe('submit form', () => {
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
await wrapper.setData({ authenticated: true, sessionId: 1 })
|
// wrapper = Wrapper(createMockObject())
|
||||||
await wrapper.vm.$nextTick()
|
|
||||||
await wrapper.findAll('input').at(0).setValue('Aa123456_')
|
await wrapper.findAll('input').at(0).setValue('Aa123456_')
|
||||||
await wrapper.findAll('input').at(1).setValue('Aa123456_')
|
await wrapper.findAll('input').at(1).setValue('Aa123456_')
|
||||||
await flushPromises()
|
await flushPromises()
|
||||||
await wrapper.find('form').trigger('submit')
|
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('server response with error', () => {
|
describe('server response with error code > 10min', () => {
|
||||||
beforeEach(() => {
|
beforeEach(async () => {
|
||||||
apolloMutationMock.mockRejectedValue({ message: 'error' })
|
jest.clearAllMocks()
|
||||||
|
apolloMutationMock.mockRejectedValue({ message: '...Code is older than 10 minutes' })
|
||||||
|
await wrapper.find('form').trigger('submit')
|
||||||
|
await flushPromises()
|
||||||
})
|
})
|
||||||
|
|
||||||
it('toasts an error message', () => {
|
it('toasts an error message', () => {
|
||||||
expect(toasterMock).toHaveBeenCalledWith('error')
|
expect(toasterMock).toHaveBeenCalledWith('...Code is older than 10 minutes')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('router pushes to /password/reset', () => {
|
||||||
|
expect(routerPushMock).toHaveBeenCalledWith('/password/reset')
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('server response with error code > 10min', () => {
|
||||||
|
beforeEach(async () => {
|
||||||
|
jest.clearAllMocks()
|
||||||
|
apolloMutationMock.mockRejectedValueOnce({ message: 'Error' })
|
||||||
|
await wrapper.find('form').trigger('submit')
|
||||||
|
await flushPromises()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('toasts an error message', () => {
|
||||||
|
expect(toasterMock).toHaveBeenCalledWith('Error')
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('server response with success', () => {
|
describe('server response with success', () => {
|
||||||
beforeEach(() => {
|
beforeEach(async () => {
|
||||||
apolloMutationMock.mockResolvedValue({
|
apolloMutationMock.mockResolvedValue({
|
||||||
data: {
|
data: {
|
||||||
resetPassword: 'success',
|
resetPassword: 'success',
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
wrapper = Wrapper(createMockObject('checkEmail'))
|
||||||
|
await wrapper.findAll('input').at(0).setValue('Aa123456_')
|
||||||
|
await wrapper.findAll('input').at(1).setValue('Aa123456_')
|
||||||
|
await wrapper.find('form').trigger('submit')
|
||||||
|
await flushPromises()
|
||||||
})
|
})
|
||||||
|
|
||||||
it('calls the API', () => {
|
it('calls the API', () => {
|
||||||
expect(apolloMutationMock).toBeCalledWith(
|
expect(apolloMutationMock).toBeCalledWith(
|
||||||
expect.objectContaining({
|
expect.objectContaining({
|
||||||
variables: {
|
variables: {
|
||||||
sessionId: 1,
|
code: '123',
|
||||||
email: 'user@example.org',
|
|
||||||
password: 'Aa123456_',
|
password: 'Aa123456_',
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
|
|||||||
@ -6,13 +6,10 @@
|
|||||||
<b-row class="justify-content-center">
|
<b-row class="justify-content-center">
|
||||||
<b-col xl="5" lg="6" md="8" class="px-2">
|
<b-col xl="5" lg="6" md="8" class="px-2">
|
||||||
<h1>{{ $t('settings.password.reset') }}</h1>
|
<h1>{{ $t('settings.password.reset') }}</h1>
|
||||||
<div class="pb-4" v-if="!pending">
|
<div class="pb-4">
|
||||||
<span v-if="authenticated">
|
<span>
|
||||||
{{ $t('settings.password.reset-password.text') }}
|
{{ $t('settings.password.reset-password.text') }}
|
||||||
</span>
|
</span>
|
||||||
<span v-else>
|
|
||||||
{{ $t('settings.password.reset-password.not-authenticated') }}
|
|
||||||
</span>
|
|
||||||
</div>
|
</div>
|
||||||
</b-col>
|
</b-col>
|
||||||
</b-row>
|
</b-row>
|
||||||
@ -20,16 +17,16 @@
|
|||||||
</div>
|
</div>
|
||||||
</b-container>
|
</b-container>
|
||||||
<b-container class="mt--8 p-1">
|
<b-container class="mt--8 p-1">
|
||||||
<b-row class="justify-content-center" v-if="authenticated">
|
<b-row class="justify-content-center">
|
||||||
<b-col lg="6" md="8">
|
<b-col lg="6" md="8">
|
||||||
<b-card no-body class="border-0" style="background-color: #ebebeba3 !important">
|
<b-card no-body class="border-0" style="background-color: #ebebeba3 !important">
|
||||||
<b-card-body class="p-4">
|
<b-card-body class="p-4">
|
||||||
<validation-observer ref="observer" v-slot="{ handleSubmit }">
|
<validation-observer ref="observer" v-slot="{ handleSubmit }">
|
||||||
<b-form role="form" @submit.prevent="handleSubmit(onSubmit)">
|
<b-form role="form" @submit.prevent="handleSubmit(onSubmit)">
|
||||||
<input-password-confirmation v-model="form" :register="register" />
|
<input-password-confirmation v-model="form" />
|
||||||
<div class="text-center">
|
<div class="text-center">
|
||||||
<b-button type="submit" variant="primary" class="mt-4">
|
<b-button type="submit" variant="primary" class="mt-4">
|
||||||
{{ $t('settings.password.reset') }}
|
{{ $t(displaySetup.button) }}
|
||||||
</b-button>
|
</b-button>
|
||||||
</div>
|
</div>
|
||||||
</b-form>
|
</b-form>
|
||||||
@ -38,9 +35,9 @@
|
|||||||
</b-card>
|
</b-card>
|
||||||
</b-col>
|
</b-col>
|
||||||
</b-row>
|
</b-row>
|
||||||
<b-row>
|
<b-row v-if="displaySetup.linkTo">
|
||||||
<b-col class="text-center py-lg-4">
|
<b-col class="text-center py-lg-4">
|
||||||
<router-link to="/Login" class="mt-3">{{ $t('back') }}</router-link>
|
<router-link :to="displaySetup.linkTo" class="mt-3">{{ $t('back') }}</router-link>
|
||||||
</b-col>
|
</b-col>
|
||||||
</b-row>
|
</b-row>
|
||||||
</b-container>
|
</b-container>
|
||||||
@ -48,8 +45,26 @@
|
|||||||
</template>
|
</template>
|
||||||
<script>
|
<script>
|
||||||
import InputPasswordConfirmation from '../../components/Inputs/InputPasswordConfirmation'
|
import InputPasswordConfirmation from '../../components/Inputs/InputPasswordConfirmation'
|
||||||
import { loginViaEmailVerificationCode } from '../../graphql/queries'
|
import { setPassword } from '../../graphql/mutations'
|
||||||
import { resetPassword } from '../../graphql/mutations'
|
|
||||||
|
const textFields = {
|
||||||
|
reset: {
|
||||||
|
authenticated: 'settings.password.reset-password.text',
|
||||||
|
notAuthenticated: 'settings.password.not-authenticated',
|
||||||
|
button: 'settings.password.reset',
|
||||||
|
linkTo: '/login',
|
||||||
|
},
|
||||||
|
checkEmail: {
|
||||||
|
authenticated: 'settings.password.set-password.text',
|
||||||
|
notAuthenticated: 'settings.password.not-authenticated',
|
||||||
|
button: 'settings.password.set',
|
||||||
|
linkTo: '/login',
|
||||||
|
},
|
||||||
|
login: {
|
||||||
|
headline: 'site.thx.errorTitle',
|
||||||
|
subtitle: 'site.thx.activateEmail',
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'ResetPassword',
|
name: 'ResetPassword',
|
||||||
@ -62,21 +77,16 @@ export default {
|
|||||||
password: '',
|
password: '',
|
||||||
passwordRepeat: '',
|
passwordRepeat: '',
|
||||||
},
|
},
|
||||||
authenticated: false,
|
displaySetup: {},
|
||||||
sessionId: null,
|
|
||||||
email: null,
|
|
||||||
pending: true,
|
|
||||||
register: false,
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
async onSubmit() {
|
async onSubmit() {
|
||||||
this.$apollo
|
this.$apollo
|
||||||
.mutate({
|
.mutate({
|
||||||
mutation: resetPassword,
|
mutation: setPassword,
|
||||||
variables: {
|
variables: {
|
||||||
sessionId: this.sessionId,
|
code: this.$route.params.optin,
|
||||||
email: this.email,
|
|
||||||
password: this.form.password,
|
password: this.form.password,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
@ -85,35 +95,24 @@ export default {
|
|||||||
this.$router.push('/thx/reset')
|
this.$router.push('/thx/reset')
|
||||||
})
|
})
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
this.$toasted.error(error.message)
|
if (error.message.includes('Code is older than 10 minutes')) {
|
||||||
|
this.$toasted.global.error(error.message)
|
||||||
|
this.$router.push('/password/reset')
|
||||||
|
} else {
|
||||||
|
this.$toasted.global.error(error.message)
|
||||||
|
}
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
async authenticate() {
|
setDisplaySetup() {
|
||||||
const loader = this.$loading.show({
|
if (!this.$route.params.comingFrom) {
|
||||||
container: this.$refs.header,
|
this.displaySetup = textFields.reset
|
||||||
})
|
} else {
|
||||||
const optin = this.$route.params.optin
|
this.displaySetup = textFields[this.$route.params.comingFrom]
|
||||||
this.$apollo
|
}
|
||||||
.query({
|
|
||||||
query: loginViaEmailVerificationCode,
|
|
||||||
variables: {
|
|
||||||
optin: optin,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
.then((result) => {
|
|
||||||
this.authenticated = true
|
|
||||||
this.sessionId = result.data.loginViaEmailVerificationCode.sessionId
|
|
||||||
this.email = result.data.loginViaEmailVerificationCode.email
|
|
||||||
})
|
|
||||||
.catch((error) => {
|
|
||||||
this.$toasted.error(error.message)
|
|
||||||
})
|
|
||||||
loader.hide()
|
|
||||||
this.pending = false
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
mounted() {
|
created() {
|
||||||
this.authenticate()
|
this.setDisplaySetup()
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@ -24,8 +24,10 @@ describe('UserCard_CoinAnimation', () => {
|
|||||||
},
|
},
|
||||||
$toasted: {
|
$toasted: {
|
||||||
success: toastSuccessMock,
|
success: toastSuccessMock,
|
||||||
|
global: {
|
||||||
error: toastErrorMock,
|
error: toastErrorMock,
|
||||||
},
|
},
|
||||||
|
},
|
||||||
$apollo: {
|
$apollo: {
|
||||||
mutate: mockAPIcall,
|
mutate: mockAPIcall,
|
||||||
},
|
},
|
||||||
|
|||||||
@ -58,7 +58,7 @@ export default {
|
|||||||
})
|
})
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
this.CoinAnimationStatus = this.$store.state.coinanimation
|
this.CoinAnimationStatus = this.$store.state.coinanimation
|
||||||
this.$toasted.error(error.message)
|
this.$toasted.global.error(error.message)
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
@ -25,8 +25,10 @@ describe('UserCard_FormUserData', () => {
|
|||||||
},
|
},
|
||||||
$toasted: {
|
$toasted: {
|
||||||
success: toastSuccessMock,
|
success: toastSuccessMock,
|
||||||
|
global: {
|
||||||
error: toastErrorMock,
|
error: toastErrorMock,
|
||||||
},
|
},
|
||||||
|
},
|
||||||
$apollo: {
|
$apollo: {
|
||||||
mutate: mockAPIcall,
|
mutate: mockAPIcall,
|
||||||
},
|
},
|
||||||
|
|||||||
@ -124,7 +124,7 @@ export default {
|
|||||||
this.$toasted.success(this.$t('settings.name.change-success'))
|
this.$toasted.success(this.$t('settings.name.change-success'))
|
||||||
})
|
})
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
this.$toasted.error(error.message)
|
this.$toasted.global.error(error.message)
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
@ -17,8 +17,10 @@ describe('UserCard_FormUserPasswort', () => {
|
|||||||
$t: jest.fn((t) => t),
|
$t: jest.fn((t) => t),
|
||||||
$toasted: {
|
$toasted: {
|
||||||
success: toastSuccessMock,
|
success: toastSuccessMock,
|
||||||
|
global: {
|
||||||
error: toastErrorMock,
|
error: toastErrorMock,
|
||||||
},
|
},
|
||||||
|
},
|
||||||
$apollo: {
|
$apollo: {
|
||||||
mutate: changePasswordProfileMock,
|
mutate: changePasswordProfileMock,
|
||||||
},
|
},
|
||||||
|
|||||||
@ -85,7 +85,7 @@ export default {
|
|||||||
this.cancelEdit()
|
this.cancelEdit()
|
||||||
})
|
})
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
this.$toasted.error(error.message)
|
this.$toasted.global.error(error.message)
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
@ -31,8 +31,10 @@ describe('UserCard_FormUsername', () => {
|
|||||||
},
|
},
|
||||||
$toasted: {
|
$toasted: {
|
||||||
success: toastSuccessMock,
|
success: toastSuccessMock,
|
||||||
|
global: {
|
||||||
error: toastErrorMock,
|
error: toastErrorMock,
|
||||||
},
|
},
|
||||||
|
},
|
||||||
$apollo: {
|
$apollo: {
|
||||||
mutate: mockAPIcall,
|
mutate: mockAPIcall,
|
||||||
},
|
},
|
||||||
|
|||||||
@ -100,7 +100,7 @@ export default {
|
|||||||
this.$toasted.success(this.$t('settings.name.change-success'))
|
this.$toasted.success(this.$t('settings.name.change-success'))
|
||||||
})
|
})
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
this.$toasted.error(error.message)
|
this.$toasted.global.error(error.message)
|
||||||
this.showUsername = true
|
this.showUsername = true
|
||||||
this.username = this.$store.state.username
|
this.username = this.$store.state.username
|
||||||
this.form.username = this.$store.state.username
|
this.form.username = this.$store.state.username
|
||||||
|
|||||||
@ -28,8 +28,10 @@ describe('UserCard_Language', () => {
|
|||||||
},
|
},
|
||||||
$toasted: {
|
$toasted: {
|
||||||
success: toastSuccessMock,
|
success: toastSuccessMock,
|
||||||
|
global: {
|
||||||
error: toastErrorMock,
|
error: toastErrorMock,
|
||||||
},
|
},
|
||||||
|
},
|
||||||
$apollo: {
|
$apollo: {
|
||||||
mutate: mockAPIcall,
|
mutate: mockAPIcall,
|
||||||
},
|
},
|
||||||
|
|||||||
@ -101,7 +101,7 @@ export default {
|
|||||||
})
|
})
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
this.language = this.$store.state.language
|
this.language = this.$store.state.language
|
||||||
this.$toasted.error(error.message)
|
this.$toasted.global.error(error.message)
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
buildTagFromLanguageString() {
|
buildTagFromLanguageString() {
|
||||||
|
|||||||
@ -25,8 +25,10 @@ describe('UserCard_Newsletter', () => {
|
|||||||
},
|
},
|
||||||
$toasted: {
|
$toasted: {
|
||||||
success: toastSuccessMock,
|
success: toastSuccessMock,
|
||||||
|
global: {
|
||||||
error: toastErrorMock,
|
error: toastErrorMock,
|
||||||
},
|
},
|
||||||
|
},
|
||||||
$apollo: {
|
$apollo: {
|
||||||
mutate: mockAPIcall,
|
mutate: mockAPIcall,
|
||||||
},
|
},
|
||||||
|
|||||||
@ -56,7 +56,7 @@ export default {
|
|||||||
})
|
})
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
this.newsletterState = this.$store.state.newsletterState
|
this.newsletterState = this.$store.state.newsletterState
|
||||||
this.$toasted.error(error.message)
|
this.$toasted.global.error(error.message)
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@ -491,7 +491,7 @@ namespace model {
|
|||||||
break;
|
break;
|
||||||
case TRANSACTION_VALID_INVALID_TARGET_DATE:
|
case TRANSACTION_VALID_INVALID_TARGET_DATE:
|
||||||
error_name = t->gettext_str("Creation Error");
|
error_name = t->gettext_str("Creation Error");
|
||||||
error_description = t->gettext_str("Invalid target date! No future and only 3 month in the past.");
|
error_description = t->gettext_str("Invalid target date! No future and only 2 month in the past.");
|
||||||
break;
|
break;
|
||||||
case TRANSACTION_VALID_CREATION_OUT_OF_BORDER:
|
case TRANSACTION_VALID_CREATION_OUT_OF_BORDER:
|
||||||
error_name = t->gettext_str("Creation Error");
|
error_name = t->gettext_str("Creation Error");
|
||||||
|
|||||||
@ -74,8 +74,8 @@ namespace model {
|
|||||||
auto now = Poco::DateTime();
|
auto now = Poco::DateTime();
|
||||||
if (target_date.year() == now.year())
|
if (target_date.year() == now.year())
|
||||||
{
|
{
|
||||||
if (target_date.month() + 3 < now.month()) {
|
if (target_date.month() + 2 < now.month()) {
|
||||||
addError(new Error(function_name, "year is the same, target date month is more than 3 month in past"));
|
addError(new Error(function_name, "year is the same, target date month is more than 2 month in past"));
|
||||||
return TRANSACTION_VALID_INVALID_TARGET_DATE;
|
return TRANSACTION_VALID_INVALID_TARGET_DATE;
|
||||||
}
|
}
|
||||||
if (target_date.month() > now.month()) {
|
if (target_date.month() > now.month()) {
|
||||||
@ -96,8 +96,8 @@ namespace model {
|
|||||||
else
|
else
|
||||||
{
|
{
|
||||||
// target_date.year +1 == now.year
|
// target_date.year +1 == now.year
|
||||||
if (target_date.month() + 3 < now.month() + 12) {
|
if (target_date.month() + 2 < now.month() + 12) {
|
||||||
addError(new Error(function_name, "target date is more than 3 month in past"));
|
addError(new Error(function_name, "target date is more than 2 month in past"));
|
||||||
return TRANSACTION_VALID_INVALID_TARGET_DATE;
|
return TRANSACTION_VALID_INVALID_TARGET_DATE;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user