mirror of
https://github.com/IT4Change/gradido.git
synced 2025-12-13 07:45:54 +00:00
Merge pull request #777 from gradido/jwt-setup
feat: JSON Web Token for Authentification
This commit is contained in:
commit
8c729c1723
21
backend/src/auth/auth.ts
Normal file
21
backend/src/auth/auth.ts
Normal file
@ -0,0 +1,21 @@
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
|
||||
import { AuthChecker } from 'type-graphql'
|
||||
import decode from '../jwt/decode'
|
||||
import { apiGet } from '../apis/loginAPI'
|
||||
import CONFIG from '../config'
|
||||
|
||||
/* eslint-disable-next-line @typescript-eslint/no-unused-vars */
|
||||
export const isAuthorized: AuthChecker<any> = async ({ root, args, context, info }, roles) => {
|
||||
if (context.token) {
|
||||
const decoded = decode(context.token)
|
||||
if (decoded.sessionId && decoded.sessionId !== 0) {
|
||||
const result = await apiGet(
|
||||
`${CONFIG.LOGIN_API_URL}checkSessionState?session_id=${decoded.sessionId}`,
|
||||
)
|
||||
context.sessionId = decoded.sessionId
|
||||
return result.success
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
@ -17,9 +17,6 @@ export class GdtTransactionInput {
|
||||
|
||||
@ArgsType()
|
||||
export class GdtTransactionSessionIdInput {
|
||||
@Field(() => Number)
|
||||
sessionId: number
|
||||
|
||||
@Field(() => Int, { nullable: true })
|
||||
currentPage?: number
|
||||
|
||||
|
||||
@ -38,9 +38,6 @@ export class ChangePasswordArgs {
|
||||
|
||||
@ArgsType()
|
||||
export class UpdateUserInfosArgs {
|
||||
@Field(() => Number)
|
||||
sessionId!: number
|
||||
|
||||
@Field(() => String)
|
||||
email!: string
|
||||
|
||||
|
||||
@ -2,9 +2,6 @@ import { ArgsType, Field, Int } from 'type-graphql'
|
||||
|
||||
@ArgsType()
|
||||
export class TransactionListInput {
|
||||
@Field(() => Number)
|
||||
sessionId: number
|
||||
|
||||
@Field(() => Int)
|
||||
firstPage: number
|
||||
|
||||
@ -17,9 +14,6 @@ export class TransactionListInput {
|
||||
|
||||
@ArgsType()
|
||||
export class TransactionSendArgs {
|
||||
@Field(() => Number)
|
||||
sessionId: number
|
||||
|
||||
@Field(() => String)
|
||||
email: string
|
||||
|
||||
|
||||
@ -1,20 +0,0 @@
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
/* eslint-disable @typescript-eslint/explicit-module-boundary-types */
|
||||
import { ObjectType, Field } from 'type-graphql'
|
||||
import { User } from './User'
|
||||
|
||||
// temporaray solution until we have JWT implemented
|
||||
|
||||
@ObjectType()
|
||||
export class LoginResponse {
|
||||
constructor(json: any) {
|
||||
this.sessionId = json.session_id
|
||||
this.user = new User(json.user)
|
||||
}
|
||||
|
||||
@Field(() => Number)
|
||||
sessionId: number
|
||||
|
||||
@Field(() => User)
|
||||
user: User
|
||||
}
|
||||
@ -1,13 +1,17 @@
|
||||
import { Resolver, Query, /* Mutation, */ Arg } from 'type-graphql'
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
/* eslint-disable @typescript-eslint/explicit-module-boundary-types */
|
||||
|
||||
import { Resolver, Query, Ctx, Authorized } from 'type-graphql'
|
||||
import CONFIG from '../../config'
|
||||
import { Balance } from '../models/Balance'
|
||||
import { apiGet } from '../../apis/loginAPI'
|
||||
|
||||
@Resolver()
|
||||
export class BalanceResolver {
|
||||
@Authorized()
|
||||
@Query(() => Balance)
|
||||
async balance(@Arg('sessionId') sessionId: number): Promise<Balance> {
|
||||
const result = await apiGet(CONFIG.COMMUNITY_API_URL + 'getBalance/' + sessionId)
|
||||
async balance(@Ctx() context: any): Promise<Balance> {
|
||||
const result = await apiGet(CONFIG.COMMUNITY_API_URL + 'getBalance/' + context.sessionId)
|
||||
if (!result.success) throw new Error(result.data)
|
||||
return new Balance(result.data)
|
||||
}
|
||||
|
||||
@ -1,5 +1,7 @@
|
||||
// import jwt from 'jsonwebtoken'
|
||||
import { Resolver, Query, /* Mutation, */ Args } from 'type-graphql'
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
/* eslint-disable @typescript-eslint/explicit-module-boundary-types */
|
||||
|
||||
import { Resolver, Query, Args, Ctx, Authorized } from 'type-graphql'
|
||||
import CONFIG from '../../config'
|
||||
import { GdtEntryList } from '../models/GdtEntryList'
|
||||
import { GdtTransactionSessionIdInput } from '../inputs/GdtInputs'
|
||||
@ -7,14 +9,16 @@ import { apiGet } from '../../apis/loginAPI'
|
||||
|
||||
@Resolver()
|
||||
export class GdtResolver {
|
||||
@Authorized()
|
||||
@Query(() => GdtEntryList)
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
async listGDTEntries(
|
||||
@Args()
|
||||
{ currentPage = 1, pageSize = 5, order = 'DESC', sessionId }: GdtTransactionSessionIdInput,
|
||||
{ currentPage = 1, pageSize = 5, order = 'DESC' }: GdtTransactionSessionIdInput,
|
||||
@Ctx() context: any,
|
||||
): Promise<GdtEntryList> {
|
||||
const result = await apiGet(
|
||||
`${CONFIG.COMMUNITY_API_URL}listGDTTransactions/${currentPage}/${pageSize}/${order}/${sessionId}`,
|
||||
`${CONFIG.COMMUNITY_API_URL}listGDTTransactions/${currentPage}/${pageSize}/${order}/${context.sessionId}`,
|
||||
)
|
||||
if (!result.success) {
|
||||
throw new Error(result.data)
|
||||
|
||||
@ -1,4 +1,7 @@
|
||||
import { Resolver, Query, Args } from 'type-graphql'
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
/* eslint-disable @typescript-eslint/explicit-module-boundary-types */
|
||||
|
||||
import { Resolver, Query, Args, Authorized, Ctx } from 'type-graphql'
|
||||
import CONFIG from '../../config'
|
||||
import { TransactionList } from '../models/Transaction'
|
||||
import { TransactionListInput, TransactionSendArgs } from '../inputs/TransactionInput'
|
||||
@ -6,23 +9,27 @@ import { apiGet, apiPost } from '../../apis/loginAPI'
|
||||
|
||||
@Resolver()
|
||||
export class TransactionResolver {
|
||||
@Authorized()
|
||||
@Query(() => TransactionList)
|
||||
async transactionList(
|
||||
@Args() { sessionId, firstPage = 1, items = 25, order = 'DESC' }: TransactionListInput,
|
||||
@Args() { firstPage = 1, items = 25, order = 'DESC' }: TransactionListInput,
|
||||
@Ctx() context: any,
|
||||
): Promise<TransactionList> {
|
||||
const result = await apiGet(
|
||||
`${CONFIG.COMMUNITY_API_URL}listTransactions/${firstPage}/${items}/${order}/${sessionId}`,
|
||||
`${CONFIG.COMMUNITY_API_URL}listTransactions/${firstPage}/${items}/${order}/${context.sessionId}`,
|
||||
)
|
||||
if (!result.success) throw new Error(result.data)
|
||||
return new TransactionList(result.data)
|
||||
}
|
||||
|
||||
@Authorized()
|
||||
@Query(() => String)
|
||||
async sendCoins(
|
||||
@Args() { sessionId, email, amount, memo }: TransactionSendArgs,
|
||||
@Args() { email, amount, memo }: TransactionSendArgs,
|
||||
@Ctx() context: any,
|
||||
): Promise<string> {
|
||||
const payload = {
|
||||
session_id: sessionId,
|
||||
session_id: context.sessionId,
|
||||
target_email: email,
|
||||
amount: amount * 10000,
|
||||
memo,
|
||||
|
||||
@ -1,8 +1,10 @@
|
||||
// import jwt from 'jsonwebtoken'
|
||||
import { Resolver, Query, Args, Arg } from 'type-graphql'
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
/* eslint-disable @typescript-eslint/explicit-module-boundary-types */
|
||||
|
||||
import { Resolver, Query, Args, Arg, Authorized, Ctx } from 'type-graphql'
|
||||
import CONFIG from '../../config'
|
||||
import { CheckUsernameResponse } from '../models/CheckUsernameResponse'
|
||||
import { LoginResponse } from '../models/LoginResponse'
|
||||
import { User } from '../models/User'
|
||||
import { LoginViaVerificationCode } from '../models/LoginViaVerificationCode'
|
||||
import { SendPasswordResetEmailResponse } from '../models/SendPasswordResetEmailResponse'
|
||||
import { UpdateUserInfosResponse } from '../models/UpdateUserInfosResponse'
|
||||
@ -14,11 +16,12 @@ import {
|
||||
UpdateUserInfosArgs,
|
||||
} from '../inputs/LoginUserInput'
|
||||
import { apiPost, apiGet } from '../../apis/loginAPI'
|
||||
import encode from '../../jwt/encode'
|
||||
|
||||
@Resolver()
|
||||
export class UserResolver {
|
||||
@Query(() => LoginResponse)
|
||||
async login(@Args() { email, password }: UnsecureLoginArgs): Promise<LoginResponse> {
|
||||
@Query(() => String)
|
||||
async login(@Args() { email, password }: UnsecureLoginArgs): Promise<string> {
|
||||
email = email.trim().toLowerCase()
|
||||
const result = await apiPost(CONFIG.LOGIN_API_URL + 'unsecureLogin', { email, password })
|
||||
|
||||
@ -27,21 +30,10 @@ export class UserResolver {
|
||||
throw new Error(result.data)
|
||||
}
|
||||
|
||||
// temporary solution until we have JWT implemented
|
||||
return new LoginResponse(result.data)
|
||||
|
||||
// create and return the json web token
|
||||
// The expire doesn't help us here. The client needs to track when the token expires on its own,
|
||||
// since every action prolongs the time the session is valid.
|
||||
/*
|
||||
return jwt.sign(
|
||||
{ result, role: 'todo' },
|
||||
CONFIG.JWT_SECRET, // * , { expiresIn: CONFIG.JWT_EXPIRES_IN } ,
|
||||
)
|
||||
*/
|
||||
// return (await apiPost(CONFIG.LOGIN_API_URL + 'unsecureLogin', login)).result.data
|
||||
// const loginResult: LoginResult = await loginAPI.login(data)
|
||||
// return loginResult.user ? loginResult.user : new User()
|
||||
const data = result.data
|
||||
const sessionId = data.session_id
|
||||
delete data.session_id
|
||||
return encode({ sessionId, user: new User(data.user) })
|
||||
}
|
||||
|
||||
@Query(() => LoginViaVerificationCode)
|
||||
@ -59,9 +51,10 @@ export class UserResolver {
|
||||
return new LoginViaVerificationCode(result.data)
|
||||
}
|
||||
|
||||
@Authorized()
|
||||
@Query(() => String)
|
||||
async logout(@Arg('sessionId') sessionId: number): Promise<string> {
|
||||
const payload = { session_id: sessionId }
|
||||
async logout(@Ctx() context: any): Promise<string> {
|
||||
const payload = { session_id: context.sessionId }
|
||||
const result = await apiPost(CONFIG.LOGIN_API_URL + 'logout', payload)
|
||||
if (!result.success) {
|
||||
throw new Error(result.data)
|
||||
@ -115,11 +108,11 @@ export class UserResolver {
|
||||
return 'sucess'
|
||||
}
|
||||
|
||||
@Authorized()
|
||||
@Query(() => UpdateUserInfosResponse)
|
||||
async updateUserInfos(
|
||||
@Args()
|
||||
{
|
||||
sessionId,
|
||||
email,
|
||||
firstName,
|
||||
lastName,
|
||||
@ -129,9 +122,10 @@ export class UserResolver {
|
||||
password,
|
||||
passwordNew,
|
||||
}: UpdateUserInfosArgs,
|
||||
@Ctx() context: any,
|
||||
): Promise<UpdateUserInfosResponse> {
|
||||
const payload = {
|
||||
session_id: sessionId,
|
||||
session_id: context.sessionId,
|
||||
email,
|
||||
update: {
|
||||
'User.first_name': firstName || undefined,
|
||||
|
||||
@ -1,3 +1,5 @@
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
|
||||
import 'reflect-metadata'
|
||||
import express from 'express'
|
||||
import { buildSchema } from 'type-graphql'
|
||||
@ -8,17 +10,30 @@ import connection from './database/connection'
|
||||
import CONFIG from './config'
|
||||
|
||||
// TODO move to extern
|
||||
// import { BookResolver } from './graphql/resolvers/BookResolver'
|
||||
import { UserResolver } from './graphql/resolvers/UserResolver'
|
||||
import { BalanceResolver } from './graphql/resolvers/BalanceResolver'
|
||||
import { GdtResolver } from './graphql/resolvers/GdtResolver'
|
||||
import { TransactionResolver } from './graphql/resolvers/TransactionResolver'
|
||||
|
||||
import { isAuthorized } from './auth/auth'
|
||||
|
||||
// TODO implement
|
||||
// import queryComplexity, { simpleEstimator, fieldConfigEstimator } from "graphql-query-complexity";
|
||||
|
||||
const DB_VERSION = '0001-init_db'
|
||||
|
||||
const context = (req: any) => {
|
||||
const authorization = req.req.headers.authorization
|
||||
let token = null
|
||||
if (authorization) {
|
||||
token = req.req.headers.authorization.replace(/^Bearer /, '')
|
||||
}
|
||||
const context = {
|
||||
token,
|
||||
}
|
||||
return context
|
||||
}
|
||||
|
||||
async function main() {
|
||||
// check for correct database version
|
||||
const con = await connection()
|
||||
@ -34,6 +49,7 @@ async function main() {
|
||||
// const connection = await createConnection()
|
||||
const schema = await buildSchema({
|
||||
resolvers: [UserResolver, BalanceResolver, TransactionResolver, GdtResolver],
|
||||
authChecker: isAuthorized,
|
||||
})
|
||||
|
||||
// Graphiql interface
|
||||
@ -46,7 +62,7 @@ async function main() {
|
||||
const server = express()
|
||||
|
||||
// Apollo Server
|
||||
const apollo = new ApolloServer({ schema, playground })
|
||||
const apollo = new ApolloServer({ schema, playground, context })
|
||||
apollo.applyMiddleware({ app: server })
|
||||
|
||||
// Start Server
|
||||
|
||||
23
backend/src/jwt/decode.ts
Normal file
23
backend/src/jwt/decode.ts
Normal file
@ -0,0 +1,23 @@
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
/* eslint-disable @typescript-eslint/explicit-module-boundary-types */
|
||||
|
||||
import jwt from 'jsonwebtoken'
|
||||
import CONFIG from '../config/'
|
||||
|
||||
export default (token: string): any => {
|
||||
if (!token) return null
|
||||
let sessionId = null
|
||||
const email = null
|
||||
try {
|
||||
const decoded = jwt.verify(token, CONFIG.JWT_SECRET)
|
||||
sessionId = decoded.sub
|
||||
// email = decoded.email
|
||||
return {
|
||||
token,
|
||||
sessionId,
|
||||
email,
|
||||
}
|
||||
} catch (err) {
|
||||
return null
|
||||
}
|
||||
}
|
||||
18
backend/src/jwt/encode.ts
Normal file
18
backend/src/jwt/encode.ts
Normal file
@ -0,0 +1,18 @@
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
/* eslint-disable @typescript-eslint/explicit-module-boundary-types */
|
||||
|
||||
import jwt from 'jsonwebtoken'
|
||||
import CONFIG from '../config/'
|
||||
|
||||
// Generate an Access Token
|
||||
export default function encode(data: any): string {
|
||||
const { user, sessionId } = data
|
||||
const { email, language, firstName, lastName } = user
|
||||
const token = jwt.sign({ email, language, firstName, lastName, sessionId }, CONFIG.JWT_SECRET, {
|
||||
expiresIn: CONFIG.JWT_EXPIRES_IN,
|
||||
// issuer: CONFIG.GRAPHQL_URI,
|
||||
// audience: CONFIG.CLIENT_URI,
|
||||
subject: sessionId.toString(),
|
||||
})
|
||||
return token
|
||||
}
|
||||
@ -72,6 +72,7 @@
|
||||
"vue-good-table": "^2.21.3",
|
||||
"vue-i18n": "^8.22.4",
|
||||
"vue-jest": "^3.0.7",
|
||||
"vue-jwt-decode": "^0.1.0",
|
||||
"vue-loading-overlay": "^3.4.2",
|
||||
"vue-moment": "^4.1.0",
|
||||
"vue-qrcode": "^0.3.5",
|
||||
|
||||
@ -15,7 +15,6 @@ describe('LanguageSwitch', () => {
|
||||
let wrapper
|
||||
|
||||
const state = {
|
||||
sessionId: 1234,
|
||||
email: 'he@ho.he',
|
||||
language: null,
|
||||
}
|
||||
@ -123,7 +122,6 @@ describe('LanguageSwitch', () => {
|
||||
expect(updateUserInfosQueryMock).toBeCalledWith(
|
||||
expect.objectContaining({
|
||||
variables: {
|
||||
sessionId: 1234,
|
||||
email: 'he@ho.he',
|
||||
locale: 'en',
|
||||
},
|
||||
@ -136,7 +134,6 @@ describe('LanguageSwitch', () => {
|
||||
expect(updateUserInfosQueryMock).toBeCalledWith(
|
||||
expect.objectContaining({
|
||||
variables: {
|
||||
sessionId: 1234,
|
||||
email: 'he@ho.he',
|
||||
locale: 'de',
|
||||
},
|
||||
|
||||
@ -32,13 +32,13 @@ export default {
|
||||
localeChanged(locale)
|
||||
},
|
||||
async saveLocale(locale) {
|
||||
// if (this.$i18n.locale === locale) return
|
||||
this.setLocale(locale)
|
||||
if (this.$store.state.sessionId && this.$store.state.email) {
|
||||
if (this.$store.state.email) {
|
||||
this.$apollo
|
||||
.query({
|
||||
query: updateUserInfos,
|
||||
variables: {
|
||||
sessionId: this.$store.state.sessionId,
|
||||
email: this.$store.state.email,
|
||||
locale: locale,
|
||||
},
|
||||
|
||||
@ -2,23 +2,13 @@ import gql from 'graphql-tag'
|
||||
|
||||
export const login = gql`
|
||||
query($email: String!, $password: String!) {
|
||||
login(email: $email, password: $password) {
|
||||
sessionId
|
||||
user {
|
||||
email
|
||||
firstName
|
||||
lastName
|
||||
language
|
||||
username
|
||||
description
|
||||
}
|
||||
}
|
||||
login(email: $email, password: $password)
|
||||
}
|
||||
`
|
||||
|
||||
export const logout = gql`
|
||||
query($sessionId: Float!) {
|
||||
logout(sessionId: $sessionId)
|
||||
query {
|
||||
logout
|
||||
}
|
||||
`
|
||||
|
||||
@ -39,7 +29,6 @@ export const loginViaEmailVerificationCode = gql`
|
||||
|
||||
export const updateUserInfos = gql`
|
||||
query(
|
||||
$sessionId: Float!
|
||||
$email: String!
|
||||
$firstName: String
|
||||
$lastName: String
|
||||
@ -50,7 +39,6 @@ export const updateUserInfos = gql`
|
||||
$locale: String
|
||||
) {
|
||||
updateUserInfos(
|
||||
sessionId: $sessionId
|
||||
email: $email
|
||||
firstName: $firstName
|
||||
lastName: $lastName
|
||||
@ -66,8 +54,8 @@ export const updateUserInfos = gql`
|
||||
`
|
||||
|
||||
export const transactionsQuery = gql`
|
||||
query($sessionId: Float!, $firstPage: Int = 1, $items: Int = 25, $order: String = "DESC") {
|
||||
transactionList(sessionId: $sessionId, firstPage: $firstPage, items: $items, order: $order) {
|
||||
query($firstPage: Int = 1, $items: Int = 25, $order: String = "DESC") {
|
||||
transactionList(firstPage: $firstPage, items: $items, order: $order) {
|
||||
gdtSum
|
||||
count
|
||||
balance
|
||||
@ -103,8 +91,8 @@ export const resgisterUserQuery = gql`
|
||||
`
|
||||
|
||||
export const sendCoins = gql`
|
||||
query($sessionId: Float!, $email: String!, $amount: Float!, $memo: String!) {
|
||||
sendCoins(sessionId: $sessionId, email: $email, amount: $amount, memo: $memo)
|
||||
query($email: String!, $amount: Float!, $memo: String!) {
|
||||
sendCoins(email: $email, amount: $amount, memo: $memo)
|
||||
}
|
||||
`
|
||||
|
||||
@ -125,8 +113,8 @@ export const checkUsername = gql`
|
||||
`
|
||||
|
||||
export const listGDTEntriesQuery = gql`
|
||||
query($currentPage: Int!, $pageSize: Int!, $sessionId: Float!) {
|
||||
listGDTEntries(currentPage: $currentPage, pageSize: $pageSize, sessionId: $sessionId) {
|
||||
query($currentPage: Int!, $pageSize: Int!) {
|
||||
listGDTEntries(currentPage: $currentPage, pageSize: $pageSize) {
|
||||
count
|
||||
gdtEntries {
|
||||
id
|
||||
|
||||
@ -3,7 +3,7 @@ import DashboardPlugin from './plugins/dashboard-plugin'
|
||||
import App from './App.vue'
|
||||
import i18n from './i18n.js'
|
||||
import { loadAllRules } from './validation-rules'
|
||||
import ApolloClient from 'apollo-boost'
|
||||
import { ApolloClient, ApolloLink, InMemoryCache, HttpLink } from 'apollo-boost'
|
||||
import VueApollo from 'vue-apollo'
|
||||
import CONFIG from './config'
|
||||
|
||||
@ -11,7 +11,21 @@ import { store } from './store/store'
|
||||
|
||||
import router from './routes/router'
|
||||
|
||||
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)
|
||||
})
|
||||
|
||||
const apolloClient = new ApolloClient({
|
||||
link: authLink.concat(httpLink),
|
||||
cache: new InMemoryCache(),
|
||||
uri: CONFIG.GRAPHQL_URI,
|
||||
})
|
||||
|
||||
@ -26,7 +40,7 @@ Vue.config.productionTip = false
|
||||
loadAllRules(i18n)
|
||||
|
||||
router.beforeEach((to, from, next) => {
|
||||
if (to.meta.requiresAuth && !store.state.sessionId) {
|
||||
if (to.meta.requiresAuth && !store.state.token) {
|
||||
next({ path: '/login' })
|
||||
} else {
|
||||
next()
|
||||
|
||||
@ -1,6 +1,8 @@
|
||||
import Vue from 'vue'
|
||||
import Vuex from 'vuex'
|
||||
import createPersistedState from 'vuex-persistedstate'
|
||||
import VueJwtDecode from 'vue-jwt-decode'
|
||||
|
||||
Vue.use(Vuex)
|
||||
|
||||
export const mutations = {
|
||||
@ -10,9 +12,6 @@ export const mutations = {
|
||||
email: (state, email) => {
|
||||
state.email = email
|
||||
},
|
||||
sessionId: (state, sessionId) => {
|
||||
state.sessionId = sessionId
|
||||
},
|
||||
username: (state, username) => {
|
||||
state.username = username
|
||||
},
|
||||
@ -25,43 +24,47 @@ export const mutations = {
|
||||
description: (state, description) => {
|
||||
state.description = description
|
||||
},
|
||||
token: (state, token) => {
|
||||
state.token = token
|
||||
},
|
||||
}
|
||||
|
||||
export const actions = {
|
||||
login: ({ dispatch, commit }, data) => {
|
||||
commit('sessionId', data.sessionId)
|
||||
commit('email', data.user.email)
|
||||
commit('language', data.user.language)
|
||||
commit('username', data.user.username)
|
||||
commit('firstName', data.user.firstName)
|
||||
commit('lastName', data.user.lastName)
|
||||
commit('description', data.user.description)
|
||||
login: ({ dispatch, commit }, token) => {
|
||||
const decoded = VueJwtDecode.decode(token)
|
||||
commit('token', token)
|
||||
commit('email', decoded.email)
|
||||
commit('language', decoded.language)
|
||||
commit('username', decoded.username)
|
||||
commit('firstName', decoded.firstName)
|
||||
commit('lastName', decoded.lastName)
|
||||
commit('description', decoded.description)
|
||||
},
|
||||
logout: ({ commit, state }) => {
|
||||
commit('sessionId', null)
|
||||
commit('token', null)
|
||||
commit('email', null)
|
||||
commit('username', '')
|
||||
commit('firstName', '')
|
||||
commit('lastName', '')
|
||||
commit('description', '')
|
||||
sessionStorage.clear()
|
||||
localStorage.clear()
|
||||
},
|
||||
}
|
||||
|
||||
export const store = new Vuex.Store({
|
||||
plugins: [
|
||||
createPersistedState({
|
||||
storage: window.sessionStorage,
|
||||
storage: window.localStorage,
|
||||
}),
|
||||
],
|
||||
state: {
|
||||
sessionId: null,
|
||||
email: '',
|
||||
language: null,
|
||||
firstName: '',
|
||||
lastName: '',
|
||||
username: '',
|
||||
description: '',
|
||||
token: null,
|
||||
},
|
||||
getters: {},
|
||||
// Syncronous mutation of the state
|
||||
|
||||
@ -1,6 +1,17 @@
|
||||
import { mutations, actions } from './store'
|
||||
import VueJwtDecode from 'vue-jwt-decode'
|
||||
|
||||
const { language, email, sessionId, username, firstName, lastName, description } = mutations
|
||||
jest.mock('vue-jwt-decode')
|
||||
VueJwtDecode.decode.mockReturnValue({
|
||||
email: 'user@example.org',
|
||||
language: 'de',
|
||||
username: 'peter',
|
||||
firstName: 'Peter',
|
||||
lastName: 'Lustig',
|
||||
description: 'Nickelbrille',
|
||||
})
|
||||
|
||||
const { language, email, token, username, firstName, lastName, description } = mutations
|
||||
const { login, logout } = actions
|
||||
|
||||
describe('Vuex store', () => {
|
||||
@ -21,11 +32,11 @@ describe('Vuex store', () => {
|
||||
})
|
||||
})
|
||||
|
||||
describe('sessionId', () => {
|
||||
it('sets the state of sessionId', () => {
|
||||
const state = { sessionId: null }
|
||||
sessionId(state, '1234')
|
||||
expect(state.sessionId).toEqual('1234')
|
||||
describe('token', () => {
|
||||
it('sets the state of token', () => {
|
||||
const state = { token: null }
|
||||
token(state, '1234')
|
||||
expect(state.token).toEqual('1234')
|
||||
})
|
||||
})
|
||||
|
||||
@ -66,41 +77,31 @@ describe('Vuex store', () => {
|
||||
describe('login', () => {
|
||||
const commit = jest.fn()
|
||||
const state = {}
|
||||
const commitedData = {
|
||||
sessionId: 1234,
|
||||
user: {
|
||||
email: 'someone@there.is',
|
||||
language: 'en',
|
||||
username: 'user',
|
||||
firstName: 'Peter',
|
||||
lastName: 'Lustig',
|
||||
description: 'Nickelbrille',
|
||||
},
|
||||
}
|
||||
const commitedData = 'token'
|
||||
|
||||
it('calls seven commits', () => {
|
||||
login({ commit, state }, commitedData)
|
||||
expect(commit).toHaveBeenCalledTimes(7)
|
||||
})
|
||||
|
||||
it('commits sessionId', () => {
|
||||
it('commits token', () => {
|
||||
login({ commit, state }, commitedData)
|
||||
expect(commit).toHaveBeenNthCalledWith(1, 'sessionId', 1234)
|
||||
expect(commit).toHaveBeenNthCalledWith(1, 'token', 'token')
|
||||
})
|
||||
|
||||
it('commits email', () => {
|
||||
login({ commit, state }, commitedData)
|
||||
expect(commit).toHaveBeenNthCalledWith(2, 'email', 'someone@there.is')
|
||||
expect(commit).toHaveBeenNthCalledWith(2, 'email', 'user@example.org')
|
||||
})
|
||||
|
||||
it('commits language', () => {
|
||||
login({ commit, state }, commitedData)
|
||||
expect(commit).toHaveBeenNthCalledWith(3, 'language', 'en')
|
||||
expect(commit).toHaveBeenNthCalledWith(3, 'language', 'de')
|
||||
})
|
||||
|
||||
it('commits username', () => {
|
||||
login({ commit, state }, commitedData)
|
||||
expect(commit).toHaveBeenNthCalledWith(4, 'username', 'user')
|
||||
expect(commit).toHaveBeenNthCalledWith(4, 'username', 'peter')
|
||||
})
|
||||
|
||||
it('commits firstName', () => {
|
||||
@ -128,9 +129,9 @@ describe('Vuex store', () => {
|
||||
expect(commit).toHaveBeenCalledTimes(6)
|
||||
})
|
||||
|
||||
it('commits sessionId', () => {
|
||||
it('commits token', () => {
|
||||
logout({ commit, state })
|
||||
expect(commit).toHaveBeenNthCalledWith(1, 'sessionId', null)
|
||||
expect(commit).toHaveBeenNthCalledWith(1, 'token', null)
|
||||
})
|
||||
|
||||
it('commits email', () => {
|
||||
@ -159,7 +160,7 @@ describe('Vuex store', () => {
|
||||
})
|
||||
|
||||
// how to get this working?
|
||||
it.skip('calls sessionStorage.clear()', () => {
|
||||
it.skip('calls localStorage.clear()', () => {
|
||||
const clearStorageMock = jest.fn()
|
||||
global.sessionStorage = jest.fn(() => {
|
||||
return {
|
||||
|
||||
@ -41,7 +41,6 @@ describe('DashboardLayoutGdd', () => {
|
||||
},
|
||||
$store: {
|
||||
state: {
|
||||
sessionId: 1,
|
||||
email: 'user@example.org',
|
||||
},
|
||||
dispatch: storeDispatchMock,
|
||||
@ -133,11 +132,7 @@ describe('DashboardLayoutGdd', () => {
|
||||
})
|
||||
|
||||
it('calls the API', async () => {
|
||||
expect(apolloMock).toBeCalledWith(
|
||||
expect.objectContaining({
|
||||
variables: { sessionId: 1 },
|
||||
}),
|
||||
)
|
||||
expect(apolloMock).toBeCalled()
|
||||
})
|
||||
|
||||
it('dispatches logout to store', () => {
|
||||
@ -196,7 +191,6 @@ describe('DashboardLayoutGdd', () => {
|
||||
expect(apolloMock).toBeCalledWith(
|
||||
expect.objectContaining({
|
||||
variables: {
|
||||
sessionId: 1,
|
||||
firstPage: 2,
|
||||
items: 5,
|
||||
},
|
||||
|
||||
@ -92,7 +92,6 @@ export default {
|
||||
this.$apollo
|
||||
.query({
|
||||
query: logout,
|
||||
variables: { sessionId: this.$store.state.sessionId },
|
||||
})
|
||||
.then(() => {
|
||||
this.$sidebar.displaySidebar(false)
|
||||
@ -111,7 +110,6 @@ export default {
|
||||
.query({
|
||||
query: transactionsQuery,
|
||||
variables: {
|
||||
sessionId: this.$store.state.sessionId,
|
||||
firstPage: pagination.firstPage,
|
||||
items: pagination.items,
|
||||
},
|
||||
|
||||
@ -16,12 +16,12 @@ describe('AccountOverview', () => {
|
||||
|
||||
const mocks = {
|
||||
$t: jest.fn((t) => t),
|
||||
$n: jest.fn((n) => String(n)),
|
||||
$store: {
|
||||
state: {
|
||||
sessionId: 1,
|
||||
email: 'sender@example.org',
|
||||
},
|
||||
},
|
||||
$n: jest.fn((n) => String(n)),
|
||||
$apollo: {
|
||||
query: sendMock,
|
||||
},
|
||||
@ -93,7 +93,6 @@ describe('AccountOverview', () => {
|
||||
expect(sendMock).toBeCalledWith(
|
||||
expect.objectContaining({
|
||||
variables: {
|
||||
sessionId: 1,
|
||||
email: 'user@example.org',
|
||||
amount: 23.45,
|
||||
memo: 'Make the best of it!',
|
||||
|
||||
@ -107,10 +107,7 @@ export default {
|
||||
this.$apollo
|
||||
.query({
|
||||
query: sendCoins,
|
||||
variables: {
|
||||
sessionId: this.$store.state.sessionId,
|
||||
...this.transactionData,
|
||||
},
|
||||
variables: this.transactionData,
|
||||
})
|
||||
.then(() => {
|
||||
this.error = false
|
||||
|
||||
@ -8,11 +8,6 @@ describe('GddSend', () => {
|
||||
|
||||
const mocks = {
|
||||
$t: jest.fn((t) => t),
|
||||
$store: {
|
||||
state: {
|
||||
sessionId: 1234,
|
||||
},
|
||||
},
|
||||
$i18n: {
|
||||
locale: jest.fn(() => 'en'),
|
||||
},
|
||||
|
||||
@ -57,11 +57,6 @@ describe('GdtTransactionList', () => {
|
||||
$t: jest.fn((t) => t),
|
||||
$n: jest.fn((n) => n),
|
||||
$d: jest.fn((d) => d),
|
||||
$store: {
|
||||
state: {
|
||||
sessionId: 1,
|
||||
},
|
||||
},
|
||||
$toasted: {
|
||||
error: toastErrorMock,
|
||||
},
|
||||
@ -89,7 +84,6 @@ describe('GdtTransactionList', () => {
|
||||
expect(apolloMock).toBeCalledWith(
|
||||
expect.objectContaining({
|
||||
variables: {
|
||||
sessionId: 1,
|
||||
currentPage: 1,
|
||||
pageSize: 25,
|
||||
},
|
||||
|
||||
@ -208,7 +208,6 @@ export default {
|
||||
.query({
|
||||
query: listGDTEntriesQuery,
|
||||
variables: {
|
||||
sessionId: this.$store.state.sessionId,
|
||||
currentPage: this.currentPage,
|
||||
pageSize: this.pageSize,
|
||||
},
|
||||
|
||||
@ -6,12 +6,7 @@ const localVue = global.localVue
|
||||
|
||||
const loginQueryMock = jest.fn().mockResolvedValue({
|
||||
data: {
|
||||
login: {
|
||||
sessionId: 1,
|
||||
user: {
|
||||
name: 'Peter Lustig',
|
||||
},
|
||||
},
|
||||
login: 'token',
|
||||
},
|
||||
})
|
||||
|
||||
@ -159,10 +154,7 @@ describe('Login', () => {
|
||||
|
||||
describe('login success', () => {
|
||||
it('dispatches server response to store', () => {
|
||||
expect(mockStoreDispach).toBeCalledWith('login', {
|
||||
sessionId: 1,
|
||||
user: { name: 'Peter Lustig' },
|
||||
})
|
||||
expect(mockStoreDispach).toBeCalledWith('login', 'token')
|
||||
})
|
||||
|
||||
it('redirects to overview page', () => {
|
||||
|
||||
@ -17,7 +17,6 @@ describe('UserCard_FormUserData', () => {
|
||||
$t: jest.fn((t) => t),
|
||||
$store: {
|
||||
state: {
|
||||
sessionId: 1,
|
||||
email: 'user@example.org',
|
||||
firstName: 'Peter',
|
||||
lastName: 'Lustig',
|
||||
@ -118,7 +117,6 @@ describe('UserCard_FormUserData', () => {
|
||||
expect(mockAPIcall).toBeCalledWith(
|
||||
expect.objectContaining({
|
||||
variables: {
|
||||
sessionId: 1,
|
||||
email: 'user@example.org',
|
||||
firstName: 'Petra',
|
||||
lastName: 'Lustiger',
|
||||
@ -167,7 +165,6 @@ describe('UserCard_FormUserData', () => {
|
||||
expect(mockAPIcall).toBeCalledWith(
|
||||
expect.objectContaining({
|
||||
variables: {
|
||||
sessionId: 1,
|
||||
email: 'user@example.org',
|
||||
firstName: 'Petra',
|
||||
lastName: 'Lustiger',
|
||||
|
||||
@ -85,7 +85,6 @@ export default {
|
||||
data() {
|
||||
return {
|
||||
showUserData: true,
|
||||
sessionId: this.$store.state.sessionId,
|
||||
form: {
|
||||
firstName: this.$store.state.firstName,
|
||||
lastName: this.$store.state.lastName,
|
||||
@ -118,7 +117,6 @@ export default {
|
||||
.query({
|
||||
query: updateUserInfos,
|
||||
variables: {
|
||||
sessionId: this.$store.state.sessionId,
|
||||
email: this.$store.state.email,
|
||||
firstName: this.form.firstName,
|
||||
lastName: this.form.lastName,
|
||||
|
||||
@ -14,7 +14,6 @@ describe('UserCard_FormUserMail', () => {
|
||||
$t: jest.fn((t) => t),
|
||||
$store: {
|
||||
state: {
|
||||
sessionId: 1,
|
||||
email: 'user@example.org',
|
||||
firstName: 'Peter',
|
||||
lastName: 'Lustig',
|
||||
@ -76,7 +75,6 @@ describe('UserCard_FormUserMail', () => {
|
||||
expect(mockAPIcall).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
variables: {
|
||||
sessionId: 1,
|
||||
email: 'user@example.org',
|
||||
newEmail: 'test@example.org',
|
||||
},
|
||||
@ -106,7 +104,6 @@ describe('UserCard_FormUserMail', () => {
|
||||
expect(mockAPIcall).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
variables: {
|
||||
sessionId: 1,
|
||||
email: 'user@example.org',
|
||||
newEmail: 'test@example.org',
|
||||
},
|
||||
|
||||
@ -48,7 +48,6 @@ export default {
|
||||
.query({
|
||||
query: updateUserInfos,
|
||||
variables: {
|
||||
sessionId: this.$store.state.sessionId,
|
||||
email: this.$store.state.email,
|
||||
newEmail: this.newEmail,
|
||||
},
|
||||
|
||||
@ -17,7 +17,6 @@ describe('UserCard_FormUserPasswort', () => {
|
||||
$t: jest.fn((t) => t),
|
||||
$store: {
|
||||
state: {
|
||||
sessionId: 1,
|
||||
email: 'user@example.org',
|
||||
},
|
||||
},
|
||||
@ -175,7 +174,6 @@ describe('UserCard_FormUserPasswort', () => {
|
||||
expect(changePasswordProfileMock).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
variables: {
|
||||
sessionId: 1,
|
||||
email: 'user@example.org',
|
||||
password: '1234',
|
||||
passwordNew: 'Aa123456',
|
||||
|
||||
@ -81,7 +81,6 @@ export default {
|
||||
.query({
|
||||
query: updateUserInfos,
|
||||
variables: {
|
||||
sessionId: this.$store.state.sessionId,
|
||||
email: this.$store.state.email,
|
||||
password: this.form.password,
|
||||
passwordNew: this.form.newPassword.password,
|
||||
|
||||
@ -25,7 +25,6 @@ describe('UserCard_FormUsername', () => {
|
||||
$t: jest.fn((t) => t),
|
||||
$store: {
|
||||
state: {
|
||||
sessionId: 1,
|
||||
email: 'user@example.org',
|
||||
username: '',
|
||||
},
|
||||
@ -111,7 +110,6 @@ describe('UserCard_FormUsername', () => {
|
||||
expect.objectContaining({
|
||||
variables: {
|
||||
email: 'user@example.org',
|
||||
sessionId: 1,
|
||||
username: 'username',
|
||||
},
|
||||
}),
|
||||
@ -151,7 +149,6 @@ describe('UserCard_FormUsername', () => {
|
||||
expect.objectContaining({
|
||||
variables: {
|
||||
email: 'user@example.org',
|
||||
sessionId: 1,
|
||||
username: 'username',
|
||||
},
|
||||
}),
|
||||
|
||||
@ -90,7 +90,6 @@ export default {
|
||||
.query({
|
||||
query: updateUserInfos,
|
||||
variables: {
|
||||
sessionId: this.$store.state.sessionId,
|
||||
email: this.$store.state.email,
|
||||
username: this.form.username,
|
||||
},
|
||||
|
||||
@ -13,11 +13,6 @@ describe('UserProfileTransactionList', () => {
|
||||
$i18n: {
|
||||
locale: jest.fn(() => 'en'),
|
||||
},
|
||||
$store: {
|
||||
state: {
|
||||
sessionId: 1,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
const stubs = {
|
||||
|
||||
@ -13576,6 +13576,13 @@ vue-jest@^3.0.5, vue-jest@^3.0.7:
|
||||
tsconfig "^7.0.0"
|
||||
vue-template-es2015-compiler "^1.6.0"
|
||||
|
||||
vue-jwt-decode@^0.1.0:
|
||||
version "0.1.0"
|
||||
resolved "https://registry.yarnpkg.com/vue-jwt-decode/-/vue-jwt-decode-0.1.0.tgz#f9caf7b9030d5459cc567b1c3117d9d1f291458f"
|
||||
integrity sha512-4iP0NzYHkAF7G13tYPc/nudk4oNpB8GCVZupc7lekxXok1XKEgefNaGTpDT14g7RKe5H9GaMphPduDj4UVfZwQ==
|
||||
dependencies:
|
||||
vue "^2.3.3"
|
||||
|
||||
vue-loader@^15.7.0:
|
||||
version "15.9.6"
|
||||
resolved "https://registry.yarnpkg.com/vue-loader/-/vue-loader-15.9.6.tgz#f4bb9ae20c3a8370af3ecf09b8126d38ffdb6b8b"
|
||||
@ -13655,6 +13662,11 @@ vue@^2.2.6, vue@^2.5.17, vue@^2.6.11:
|
||||
resolved "https://registry.yarnpkg.com/vue/-/vue-2.6.12.tgz#f5ebd4fa6bd2869403e29a896aed4904456c9123"
|
||||
integrity sha512-uhmLFETqPPNyuLLbsKz6ioJ4q7AZHzD8ZVFNATNyICSZouqP2Sz0rotWQC8UNBF6VGSCs5abnKJoStA6JbCbfg==
|
||||
|
||||
vue@^2.3.3:
|
||||
version "2.6.14"
|
||||
resolved "https://registry.yarnpkg.com/vue/-/vue-2.6.14.tgz#e51aa5250250d569a3fbad3a8a5a687d6036e235"
|
||||
integrity sha512-x2284lgYvjOMj3Za7kqzRcUSxBboHqtgRE2zlos1qWaOye5yUmHn42LB1250NJBLRwEcdrB0JRwyPTEPhfQjiQ==
|
||||
|
||||
vuex-persistedstate@^4.0.0-beta.3:
|
||||
version "4.0.0-beta.3"
|
||||
resolved "https://registry.yarnpkg.com/vuex-persistedstate/-/vuex-persistedstate-4.0.0-beta.3.tgz#89dd712de72d28e85cc95467d066002c1405f277"
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user