diff --git a/backend/src/auth/auth.ts b/backend/src/auth/auth.ts new file mode 100644 index 000000000..0d9014b15 --- /dev/null +++ b/backend/src/auth/auth.ts @@ -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 = 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 +} diff --git a/backend/src/graphql/inputs/GdtInputs.ts b/backend/src/graphql/inputs/GdtInputs.ts index 1404fbf14..e11ce24c1 100644 --- a/backend/src/graphql/inputs/GdtInputs.ts +++ b/backend/src/graphql/inputs/GdtInputs.ts @@ -17,9 +17,6 @@ export class GdtTransactionInput { @ArgsType() export class GdtTransactionSessionIdInput { - @Field(() => Number) - sessionId: number - @Field(() => Int, { nullable: true }) currentPage?: number diff --git a/backend/src/graphql/inputs/LoginUserInput.ts b/backend/src/graphql/inputs/LoginUserInput.ts index 716b19403..4a60d00e1 100644 --- a/backend/src/graphql/inputs/LoginUserInput.ts +++ b/backend/src/graphql/inputs/LoginUserInput.ts @@ -41,9 +41,6 @@ export class ChangePasswordArgs { @ArgsType() export class UpdateUserInfosArgs { - @Field(() => Number) - sessionId!: number - @Field(() => String) email!: string diff --git a/backend/src/graphql/inputs/TransactionInput.ts b/backend/src/graphql/inputs/TransactionInput.ts index 7f83cd82d..a62d86a65 100644 --- a/backend/src/graphql/inputs/TransactionInput.ts +++ b/backend/src/graphql/inputs/TransactionInput.ts @@ -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 diff --git a/backend/src/graphql/models/GdtEntry.ts b/backend/src/graphql/models/GdtEntry.ts index 09ee35a70..7935d4181 100644 --- a/backend/src/graphql/models/GdtEntry.ts +++ b/backend/src/graphql/models/GdtEntry.ts @@ -15,6 +15,7 @@ export enum GdtEntryType { @ObjectType() export class GdtEntry { constructor(json: any) { + this.id = json.id this.amount = json.amount this.date = json.date this.email = json.email @@ -27,6 +28,9 @@ export class GdtEntry { this.gdt = json.gdt } + @Field(() => Number) + id: number + @Field(() => Number) amount: number diff --git a/backend/src/graphql/models/LoginResponse.ts b/backend/src/graphql/models/LoginResponse.ts deleted file mode 100644 index 258784b75..000000000 --- a/backend/src/graphql/models/LoginResponse.ts +++ /dev/null @@ -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 -} diff --git a/backend/src/graphql/resolvers/BalanceResolver.ts b/backend/src/graphql/resolvers/BalanceResolver.ts index 1eea71e66..aafa767b0 100644 --- a/backend/src/graphql/resolvers/BalanceResolver.ts +++ b/backend/src/graphql/resolvers/BalanceResolver.ts @@ -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/HttpRequest' @Resolver() export class BalanceResolver { + @Authorized() @Query(() => Balance) - async balance(@Arg('sessionId') sessionId: number): Promise { - const result = await apiGet(CONFIG.COMMUNITY_API_URL + 'getBalance/' + sessionId) + async balance(@Ctx() context: any): Promise { + const result = await apiGet(CONFIG.COMMUNITY_API_URL + 'getBalance/' + context.sessionId) if (!result.success) throw new Error(result.data) return new Balance(result.data) } diff --git a/backend/src/graphql/resolvers/GdtResolver.ts b/backend/src/graphql/resolvers/GdtResolver.ts index 6ff8b7ae4..18365e9f9 100644 --- a/backend/src/graphql/resolvers/GdtResolver.ts +++ b/backend/src/graphql/resolvers/GdtResolver.ts @@ -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/HttpRequest' @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 { 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) diff --git a/backend/src/graphql/resolvers/TransactionResolver.ts b/backend/src/graphql/resolvers/TransactionResolver.ts index 40dd9eb6f..d18c7c300 100644 --- a/backend/src/graphql/resolvers/TransactionResolver.ts +++ b/backend/src/graphql/resolvers/TransactionResolver.ts @@ -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/HttpRequest' @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 { 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 { const payload = { - session_id: sessionId, + session_id: context.sessionId, target_email: email, amount: amount * 10000, memo, diff --git a/backend/src/graphql/resolvers/UserResolver.ts b/backend/src/graphql/resolvers/UserResolver.ts index 87979c353..4d887286b 100644 --- a/backend/src/graphql/resolvers/UserResolver.ts +++ b/backend/src/graphql/resolvers/UserResolver.ts @@ -1,8 +1,9 @@ -// import jwt from 'jsonwebtoken' -import { Resolver, Query, Args, Arg, UseMiddleware } 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, UseMiddleware } 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' @@ -16,13 +17,14 @@ import { import { apiPost, apiGet } from '../../apis/HttpRequest' import { KlicktippController } from '../../apis/KlicktippController' import { registerMiddleware } from '../../middleware/registerMiddleware' +import encode from '../../jwt/encode' @Resolver() export class UserResolver { private connector: KlicktippController = new KlicktippController(CONFIG.KLICKTTIPP_API_URL) - @Query(() => LoginResponse) - async login(@Args() { email, password }: UnsecureLoginArgs): Promise { + @Query(() => String) + async login(@Args() { email, password }: UnsecureLoginArgs): Promise { email = email.trim().toLowerCase() const result = await apiPost(CONFIG.LOGIN_API_URL + 'unsecureLogin', { email, password }) @@ -31,21 +33,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) @@ -63,9 +54,10 @@ export class UserResolver { return new LoginViaVerificationCode(result.data) } + @Authorized() @Query(() => String) - async logout(@Arg('sessionId') sessionId: number): Promise { - const payload = { session_id: sessionId } + async logout(@Ctx() context: any): Promise { + const payload = { session_id: context.sessionId } const result = await apiPost(CONFIG.LOGIN_API_URL + 'logout', payload) if (!result.success) { throw new Error(result.data) @@ -128,11 +120,11 @@ export class UserResolver { return 'sucess' } + @Authorized() @Query(() => UpdateUserInfosResponse) async updateUserInfos( @Args() { - sessionId, email, firstName, lastName, @@ -142,9 +134,10 @@ export class UserResolver { password, passwordNew, }: UpdateUserInfosArgs, + @Ctx() context: any, ): Promise { const payload = { - session_id: sessionId, + session_id: context.sessionId, email, update: { 'User.first_name': firstName || undefined, diff --git a/backend/src/index.ts b/backend/src/index.ts index 6e51ed2f4..067403508 100644 --- a/backend/src/index.ts +++ b/backend/src/index.ts @@ -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 diff --git a/backend/src/jwt/decode.ts b/backend/src/jwt/decode.ts new file mode 100644 index 000000000..a414e0c41 --- /dev/null +++ b/backend/src/jwt/decode.ts @@ -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 + } +} diff --git a/backend/src/jwt/encode.ts b/backend/src/jwt/encode.ts new file mode 100644 index 000000000..477644dc7 --- /dev/null +++ b/backend/src/jwt/encode.ts @@ -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 +} diff --git a/community_server/src/Template/StateBalances/overview_gdt.ctp b/community_server/src/Template/StateBalances/overview_gdt.ctp index eb67626b0..f076e26c8 100644 --- a/community_server/src/Template/StateBalances/overview_gdt.ctp +++ b/community_server/src/Template/StateBalances/overview_gdt.ctp @@ -21,7 +21,7 @@ $this->assign('title', __('GDT Kontoübersicht')); $header = '

' . __('Zur Verfügung: ') . '

'; if($gdtSum > 0){ - $header .= '

'.$this->element('printGDT', ['number' => $gdtSum]).'

'; + $header .= '

'.$this->element('printGDT', ['number' => $gdtSum*100.0]).'

'; } if($moreEntrysAsShown) { $header .= ''. __('Nur die letzten 100 Einträge werden angezeigt!') . ''; @@ -56,7 +56,7 @@ $this->assign('header', $header); Number->format($entry['factor2']) ?> -
element('printGDT', ['number' => $entry['gdt']]) ?>
+
element('printGDT', ['number' => $entry['gdt']*100.0]) ?>
@@ -98,7 +98,7 @@ $this->assign('header', $header); Number->format($gdtEntry['factor2']) ?> -
element('printGDT', ['number' => $gdtEntry['gdt']]) ?>
+
element('printGDT', ['number' => $gdtEntry['gdt'] * 100.0]) ?>
diff --git a/docu/Concepts/Snippets/1-Blockchain/Roles.md b/docu/Concepts/Snippets/1-Blockchain/Roles.md new file mode 100644 index 000000000..d21b3814a --- /dev/null +++ b/docu/Concepts/Snippets/1-Blockchain/Roles.md @@ -0,0 +1,53 @@ +# Roles +User Roles also handled by blockchain and node servers + +My Goal is to save not only the gradido transactions in blockchain but also +who is allowed to create new gradidos, +who allow joining new user to the group, +who allow connect to another group and +how the community decide which one is allowed to do this things. + +## Why? +If this would be handled only by community-server everyone could be easly +overwrite this rules by using a modified client to send his transactions direct over +hedera or iota bypassing the community-server rules. +With hedera it is possible to only allow sending messages to a topic with the correct admin key, +but then is the admin the single point of failure. Also must the key saved on server to allow everyone +sending gradidos or the transactions will only be proccessed, when admin is logging in. +If we don't use blockchain technologie at all, we have a big single point of failure. +The Community-Server and everyone who has direct access to server and the admins of course. +But it would be much much simpler of course :) + +In summary it is to make sure that the community is in power and no one can take over. + +## How? +There is a special type of transactions with which users determine who can determine what. +This transaction control which signatures are neccessary for things like creation and so one. +For this I think different types are needed. +- *one*: The founder of group (or someone other choosen) decide everything, this is default from start +- *some*: a number of user must sign, set absolute count or relative count +- *most*: more than 1/2 or 3/4 must sign +- *all*: all member must sign, default for choose which mode will be used +- *one-council*: one member of council +- *some-council*: absolute or relative number of council members must sign +- *most-council*: more than 1/2 or 3/4 from council members must sign +- *all-council*: all members of council must sign + +this configuration can be done for different types of action, +so the voting-mode for creation may differ from voting mode for +add new members to community. Also the council members for different actions +may differ. +Also how to vote for council members is an extra type of action. + +## Veto +Especially for *some* and *some-council* maybe also for other types. +The users there could vote but haven't yet and not all need to vote, +can make a Veto with Explanation which reset all existing signs of current vote, +and is needed to sign by all which again vote for the case. + +## Summary +With that setup all community consense models should be possible except Democracy. +Democracy needs a secret ballot. The votes on blockchain are open (at least if someone knows which +public-key belongs to which user). A secret ballot on blockchain is really hard. By my last +recherche I haven't found on. But maybe this can do the trick: https://secure.vote/ + diff --git a/docu/Concepts/Snippets/Iota/ColoredCoins.md b/docu/Concepts/Snippets/Iota/ColoredCoins.md new file mode 100644 index 000000000..33d3a891e --- /dev/null +++ b/docu/Concepts/Snippets/Iota/ColoredCoins.md @@ -0,0 +1,42 @@ +# Wie Colored Coins in Iota funktionieren +## Schöpfung +- Colored Coins werden bei Iota mit einer speziellen Transaktion erzeugt +- Die Farbe des neuen Coins wird durch den Transaktionshash beschrieben +- Die einmal erzeugte Menge von Colored Coins ist fest +- Um die Menge zu erhöhen müssten neue Colored Coin erzeugt werden und die alten damit ausgetauscht werden (z.B. mittels SmartContract) + +## Geldmenge +- Colored Coins basierend auf den normalen Iota-Coins somit werden soviele Iota-Coins benötigt wie man Colored-Coins braucht +- 2.779.530.283.000.000 Iota maximale Coinmenge +- Weltbevölkerung 2021: 7.915.559.780 +- Pro Kopf Geldmenge Gradido: 53.476 +- Benötigte Iota Coins für Gradido mit 4 Nachkommastellen mit 25% Puffer: +- 7.915.559.780 * 534.760.000 * 1.25 = 5.291.155.935.000.000.000 +- Vorhandene Iota coins: 2.779.530.283.000.000 +- Es sind nicht genügend Iota Coins vorhanden im die ganze Welt mit Gradido auf Basis von Iota versorgen zu können + +## Kosten +- Kurs am 30.08.2021: 1 Miota = 0,84 +- Bei Verwendung von 4 Nachkommastellen braucht es 10 Miota für 1.000 Colored Coins (Gradido) also Miota im Wert von 8,40€ +- Aktuell (30.08.2021) geschöpfte Gradido Cent: 17.001.990.500 +- Notwendige Miota: 17.002.0, Wert: 14.286,73 € +- Solange die Benutzer Kontrolle über ihre Keys haben können sie mit einem regulärem Iota Wallet die Gradidos wieder in Iota umwandeln (um z.B. der Vergänglichkeit zu entgehen) +- Mit 2 Nachkommastellen wird die Vergänglichkeit schon bei 100 Gradido und 1 Stunde ungenau +- 1 Stunde, 100 Gradido, Vergänglichkeit: 0,00576, Gradidos nach einer Stunde: 99,99424 GDD +- 1 Minute, 100 Gradido, Vergänglichkeit: 0,000096, Gradidos nach einer Minute: 99,999904 GDD + +## Dust-Protection +- Iota erlaubt bei leeren Adressen nur Transaktionen von mindestens 1 Miota +- Nachdem 1 Miota da ist sind bis zu 10 Transaktione < 1 Miota erlaubt, bevor ein weitere Transaktion mit mindestens 1 Miota eingehen muss +- Bei Verwendung von 4 Nachkommastellen entspricht das 100 GDD, bei 2 Nachkommastellen 10.000 GDD + +## Lösung +Wir können nur 3 Nachkommastellen verwenden. +### Kosten +- 0,84 € für 1.000 GDD +- 1.428 Euro für alle bisherigen geschöpften Gradidos +### Dust-Protection +- 1.000 GDD entspricht 1 Miota, die erste Schöpfung muss also zwangsläufig 1k Gradido betragen +- Jeder kann nur maximal 10 Transaktionen < 1k GDD empfangen bevor er die GDD weitergesendet haben muss um neue erhalten zu können oder eine neue Schöpfung von 1k GDD bekommen hat + + diff --git a/docu/Concepts/Snippets/images/classes.drawio b/docu/Concepts/Snippets/images/classes.drawio new file mode 100644 index 000000000..ad4f88aa2 --- /dev/null +++ b/docu/Concepts/Snippets/images/classes.drawio @@ -0,0 +1 @@ +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 \ No newline at end of file diff --git a/docu/Concepts/Snippets/images/classes.png b/docu/Concepts/Snippets/images/classes.png new file mode 100644 index 000000000..1cf30c437 Binary files /dev/null and b/docu/Concepts/Snippets/images/classes.png differ diff --git a/docu/Concepts/Snippets/images/example_blockchain.drawio b/docu/Concepts/Snippets/images/example_blockchain.drawio new file mode 100644 index 000000000..ac043b21b --- /dev/null +++ b/docu/Concepts/Snippets/images/example_blockchain.drawio @@ -0,0 +1 @@ +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 \ No newline at end of file diff --git a/docu/Concepts/Snippets/images/example_blockchain.png b/docu/Concepts/Snippets/images/example_blockchain.png new file mode 100644 index 000000000..f2771b388 Binary files /dev/null and b/docu/Concepts/Snippets/images/example_blockchain.png differ diff --git a/docu/Concepts/Snippets/images/iota.drawio b/docu/Concepts/Snippets/images/iota.drawio new file mode 100644 index 000000000..5e491aefa --- /dev/null +++ b/docu/Concepts/Snippets/images/iota.drawio @@ -0,0 +1 @@ +zVlLc+MoEP41Os6U9bRz9GMym9qaSmqTqp3sjUjYYoOEF6HYmV+/DQJLCHnieJRkLjK0oNV099cP7IXLYv+Vo23+jWWYesEk23vhyguCaRLAUxKeNWEaN4QNJ1lD8lvCLfmBNXGiqTXJcGUtFIxRQbY2MWVliVNh0RDnbGcvWzNqf3WLNtgh3KaIutS/SSbyhjoLpi39D0w2ufmyn1w0bwpkFuuTVDnK2K5DCr944ZIzJppRsV9iKnVn9NLsuzzy9iAYx6U4aYNW/BOitT7c1fXdXAsnns2JYRcoFyYLkHgriSllNTBZ7HIi8O0WpZK4A3MDLRcFhZkPQ1cgLeMT5gLvOyQt4FfMCiz4MyzRb8MLrSztLWa6a1XvG33mHbXPNA1pa28OnFuFwEDr5Ih+Ekc/4NIZydgdR2WFUkFY6WiLs7rMsGQxeRcNzWwF+ZMTNZSMoaGpoyHXe8psLmGn3AZVFUltFeA9Ed8743upuc+xnq32WpFq8mwmJQj6XS0EI+m53hibebtVzay9N5gTOC3mmnjUDhWreYptfxCIb7BZpjGEMytquNbqmCMesIahcUyRIE92rBkykf7CDSMg8TFnCKKekZvj6E3d0NDj44c9r7roMWp04DBSDnM49Wk+NHNR9td8dbW6/uwFCSokYihoaYEoga8b0kYczNZxNsCMsN2L44r8QA9qgbT0VoqsDhEvvHgl+daCVU2a8ZvPbEoYU7yWrCQQCUT/uSYLJr9eAaJJubmTk9WnaBwkB7OoB+WZC2V/wHmCEaBseHTMQEklcKnyKzxeYZREvXrgMFIUi5FeWj5U8qcgFFeClZDRg0tI2WvCCwiefbuaxLPlLMVV9XJgfUDp40aF4utaUFIa646RknrYCEI34gZDZhoj4poA+ysR97Rg91sHtrCf308NbFEQfp5Mkwswmnr6w6YcP8wFoWO4GzCUDCJtMVEddXwQAPMS0VvBuCpQPxAA8cv+7ydvVJMF0Xv5/0CyNzb8TTDhJOnpudm+Xza8IQzcnsPki9w3+eKfGlY8Um8ZevOIy8zRrADm7SJD3BrCn6woVJbJCIbnN8gTgJRKfkyW6ZMrJpDsHykmaQ6DEqmfDBfS9GoPYKAC78FSN7uaZ4odQEgeAJhzxYsfNh2SlxcsFZEfOD3Ai0Z+QTaSXY7kkzVrHo2ozWJUK0kKpBplpAQLLzun3raHfrHgqQRnj3jJKJPFbSmlCxdrQmmPpEsYIMROQRMca10YaGhNFbRykkn9QIVlNTxjxJfIdsckdPPrJHZh5ffd9qwA45ajxhCgo9LSfvJfLXv1RlOfqkZVc+lajCtjmgXGfGvpEouldIF/cSZNrxtKGFk95WSHaZpj5XfSl1tHcz1LOWrHWRoxj/jLGd2pY7gB8x4vluLYTtzRicnCH6OoDd2i9tXZwvSnbU9633kz3J8OpokXU445sZVyZh+ZYXqZPj67nZzYjKK3aydNRfczk787CCIbBLE/AILZgBnH6BjCETqGzn3L4Xrl3uvergzftZyJg5mLg/BDK60eDpJ+bDoXB3G/YhsRB0P9BmSlkqm8s8w5K0kqHeByxKLCXJ6kuFSXa871SQFVAz3WvgxVEr8EvCjqV8ixC7zJT/zmFcCDaXt13xis/f8j/PI/ \ No newline at end of file diff --git a/docu/Concepts/Snippets/images/iota.png b/docu/Concepts/Snippets/images/iota.png new file mode 100644 index 000000000..b8f7fb7e5 Binary files /dev/null and b/docu/Concepts/Snippets/images/iota.png differ diff --git a/frontend/package.json b/frontend/package.json index 4bc621916..59b34cc9d 100755 --- a/frontend/package.json +++ b/frontend/package.json @@ -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", diff --git a/frontend/src/components/LanguageSwitch.spec.js b/frontend/src/components/LanguageSwitch.spec.js index 72771bd37..afe98b357 100644 --- a/frontend/src/components/LanguageSwitch.spec.js +++ b/frontend/src/components/LanguageSwitch.spec.js @@ -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', }, diff --git a/frontend/src/components/LanguageSwitch.vue b/frontend/src/components/LanguageSwitch.vue index e6f9c86c3..f0528a935 100644 --- a/frontend/src/components/LanguageSwitch.vue +++ b/frontend/src/components/LanguageSwitch.vue @@ -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, }, diff --git a/frontend/src/graphql/queries.js b/frontend/src/graphql/queries.js index e7cfa2182..eacdc7618 100644 --- a/frontend/src/graphql/queries.js +++ b/frontend/src/graphql/queries.js @@ -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 @@ -115,8 +103,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) } ` @@ -137,10 +125,11 @@ 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 amount date comment diff --git a/frontend/src/locales/de.json b/frontend/src/locales/de.json index 5db4bc337..ae1822396 100644 --- a/frontend/src/locales/de.json +++ b/frontend/src/locales/de.json @@ -18,6 +18,7 @@ "de": "Deutsch", "en": "English" }, + "select_language": "Bitte wähle eine Sprache für die App und Newsletter", "decay": { "decay": "Vergänglichkeit", "decay_since_last_transaction":"Vergänglichkeit seit der letzten Transaktion", diff --git a/frontend/src/locales/en.json b/frontend/src/locales/en.json index 323f96b4d..4bd04116d 100644 --- a/frontend/src/locales/en.json +++ b/frontend/src/locales/en.json @@ -18,6 +18,7 @@ "de": "Deutsch", "en": "English" }, + "select_language": "Please choose a language for the app and newsletter", "decay": { "decay": "Decay", "decay_since_last_transaction":"Decay since the last transaction", diff --git a/frontend/src/main.js b/frontend/src/main.js index 31ccd8de7..823df516c 100755 --- a/frontend/src/main.js +++ b/frontend/src/main.js @@ -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() diff --git a/frontend/src/store/store.js b/frontend/src/store/store.js index 03be5e75c..77c7096ad 100644 --- a/frontend/src/store/store.js +++ b/frontend/src/store/store.js @@ -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 diff --git a/frontend/src/store/store.test.js b/frontend/src/store/store.test.js index a67dff5ce..99a37451e 100644 --- a/frontend/src/store/store.test.js +++ b/frontend/src/store/store.test.js @@ -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 { diff --git a/frontend/src/views/Layout/DashboardLayout_gdd.spec.js b/frontend/src/views/Layout/DashboardLayout_gdd.spec.js index f0f08f6a2..614ddf9c4 100644 --- a/frontend/src/views/Layout/DashboardLayout_gdd.spec.js +++ b/frontend/src/views/Layout/DashboardLayout_gdd.spec.js @@ -41,7 +41,6 @@ describe('DashboardLayoutGdd', () => { }, $store: { state: { - sessionId: 1, email: 'user@example.org', }, dispatch: storeDispatchMock, @@ -128,16 +127,17 @@ describe('DashboardLayoutGdd', () => { describe('logout', () => { beforeEach(async () => { + await apolloMock.mockResolvedValue({ + data: { + logout: 'success', + }, + }) await wrapper.findComponent({ name: 'sidebar' }).vm.$emit('logout') await flushPromises() }) it('calls the API', async () => { - expect(apolloMock).toBeCalledWith( - expect.objectContaining({ - variables: { sessionId: 1 }, - }), - ) + await expect(apolloMock).toBeCalled() }) it('dispatches logout to store', () => { @@ -196,7 +196,6 @@ describe('DashboardLayoutGdd', () => { expect(apolloMock).toBeCalledWith( expect.objectContaining({ variables: { - sessionId: 1, firstPage: 2, items: 5, }, diff --git a/frontend/src/views/Layout/DashboardLayout_gdd.vue b/frontend/src/views/Layout/DashboardLayout_gdd.vue index 4081f4ceb..35e7bb8af 100755 --- a/frontend/src/views/Layout/DashboardLayout_gdd.vue +++ b/frontend/src/views/Layout/DashboardLayout_gdd.vue @@ -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, }, diff --git a/frontend/src/views/Pages/AccountOverview.spec.js b/frontend/src/views/Pages/AccountOverview.spec.js index b890f1471..df964ed2c 100644 --- a/frontend/src/views/Pages/AccountOverview.spec.js +++ b/frontend/src/views/Pages/AccountOverview.spec.js @@ -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!', diff --git a/frontend/src/views/Pages/AccountOverview.vue b/frontend/src/views/Pages/AccountOverview.vue index b89955a6a..8e5362d8c 100644 --- a/frontend/src/views/Pages/AccountOverview.vue +++ b/frontend/src/views/Pages/AccountOverview.vue @@ -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 diff --git a/frontend/src/views/Pages/AccountOverview/GddSend.spec.js b/frontend/src/views/Pages/AccountOverview/GddSend.spec.js index c7005ddf5..15beac511 100644 --- a/frontend/src/views/Pages/AccountOverview/GddSend.spec.js +++ b/frontend/src/views/Pages/AccountOverview/GddSend.spec.js @@ -8,11 +8,6 @@ describe('GddSend', () => { const mocks = { $t: jest.fn((t) => t), - $store: { - state: { - sessionId: 1234, - }, - }, $i18n: { locale: jest.fn(() => 'en'), }, diff --git a/frontend/src/views/Pages/AccountOverview/GdtTransactionList.spec.js b/frontend/src/views/Pages/AccountOverview/GdtTransactionList.spec.js index 5ad12be97..a3c737d10 100644 --- a/frontend/src/views/Pages/AccountOverview/GdtTransactionList.spec.js +++ b/frontend/src/views/Pages/AccountOverview/GdtTransactionList.spec.js @@ -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, }, diff --git a/frontend/src/views/Pages/AccountOverview/GdtTransactionList.vue b/frontend/src/views/Pages/AccountOverview/GdtTransactionList.vue index 8193717cf..3bdc9f42c 100644 --- a/frontend/src/views/Pages/AccountOverview/GdtTransactionList.vue +++ b/frontend/src/views/Pages/AccountOverview/GdtTransactionList.vue @@ -163,10 +163,19 @@ import { listGDTEntriesQuery } from '../../../graphql/queries' import PaginationButtons from '../../../components/PaginationButtons' -const iconsByType = { - 1: { icon: 'heart', classes: 'gradido-global-color-accent' }, - 4: { icon: 'person-check', classes: 'gradido-global-color-accent' }, - 7: { icon: 'gift', classes: 'gradido-global-color-accent' }, +function iconByType(typeId) { + switch (typeId) { + case 1: + case 2: + case 3: + case 5: + case 6: + return { icon: 'heart', classes: 'gradido-global-color-accent' } + case 4: + return { icon: 'person-check', classes: 'gradido-global-color-accent' } + case 7: + return { icon: 'gift', classes: 'gradido-global-color-accent' } + } } export default { @@ -199,7 +208,6 @@ export default { .query({ query: listGDTEntriesQuery, variables: { - sessionId: this.$store.state.sessionId, currentPage: this.currentPage, pageSize: this.pageSize, }, @@ -216,7 +224,7 @@ export default { }) }, getIcon(givenType) { - const type = iconsByType[givenType] + const type = iconByType(givenType) if (type) return { icon: type.icon, diff --git a/frontend/src/views/Pages/Login.spec.js b/frontend/src/views/Pages/Login.spec.js index 7cbe5f003..20eead7a3 100644 --- a/frontend/src/views/Pages/Login.spec.js +++ b/frontend/src/views/Pages/Login.spec.js @@ -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', () => { diff --git a/frontend/src/views/Pages/Register.spec.js b/frontend/src/views/Pages/Register.spec.js index 83ff177d3..1a9549d41 100644 --- a/frontend/src/views/Pages/Register.spec.js +++ b/frontend/src/views/Pages/Register.spec.js @@ -85,6 +85,13 @@ describe('Register', () => { it('has password repeat input fields', () => { expect(wrapper.find('input[name="form.passwordRepeat"]').exists()).toBeTruthy() }) + it('has Language selected field', () => { + expect(wrapper.find('#selectedLanguage').exists()).toBeTruthy() + }) + it('selected Language value de', async () => { + wrapper.find('#selectedLanguage').findAll('option').at(1).setSelected() + expect(wrapper.find('#selectedLanguage').element.value).toBe('de') + }) it('has 1 checkbox input fields', () => { expect(wrapper.find('#registerCheckbox').exists()).toBeTruthy() @@ -126,9 +133,16 @@ describe('Register', () => { 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('#selectedLanguage').findAll('option').at(1).setSelected() wrapper.find('input[name="site.signup.agree"]').setChecked(true) }) + it('reset selected value language', async () => { + await wrapper.find('button.ml-2').trigger('click') + await flushPromises() + expect(wrapper.find('#selectedLanguage').element.value).toBe('') + }) + it('resets the firstName field after clicking the reset button', async () => { await wrapper.find('button.ml-2').trigger('click') await flushPromises() @@ -173,6 +187,7 @@ describe('Register', () => { 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('#selectedLanguage').findAll('option').at(1).setSelected() }) describe('server sends back error', () => { diff --git a/frontend/src/views/Pages/Register.vue b/frontend/src/views/Pages/Register.vue index 2680c5ecf..f8e7eefd0 100755 --- a/frontend/src/views/Pages/Register.vue +++ b/frontend/src/views/Pages/Register.vue @@ -84,6 +84,18 @@ :register="register" > + + + {{ $t('language') }} + + + + -
+
{{ $t('form.reset') }} {{ $t('signup') }} @@ -147,6 +162,12 @@ export default { passwordRepeat: '', }, }, + selected: null, + options: [ + { value: null, text: this.$t('select_language') }, + { value: 'de', text: this.$t('languages.de') }, + { value: 'en', text: this.$t('languages.en') }, + ], submitted: false, showError: false, messageError: '', @@ -168,8 +189,7 @@ export default { }, agree: false, } - this.form.password.password = '' - this.form.password.passwordRepeat = '' + this.selected = null this.$nextTick(() => { this.$refs.observer.reset() }) @@ -183,7 +203,7 @@ export default { firstName: this.form.firstname, lastName: this.form.lastname, password: this.form.password.password, - language: this.$store.state.language, + language: this.selected, }, }) .then(() => { @@ -192,6 +212,7 @@ export default { this.form.lastname = '' this.form.password.password = '' this.form.password.passwordRepeat = '' + this.selected = null this.$router.push('/thx/register') }) .catch((error) => { @@ -207,6 +228,7 @@ export default { this.form.lastname = '' this.form.password.password = '' this.form.password.passwordRepeat = '' + this.selected = null }, }, computed: { @@ -221,6 +243,9 @@ export default { emailFilled() { return this.form.email !== '' }, + languageFilled() { + return this.selected !== null + }, }, } diff --git a/frontend/src/views/Pages/UserProfile/UserCard_FormUserData.spec.js b/frontend/src/views/Pages/UserProfile/UserCard_FormUserData.spec.js index 847185741..b3e014e57 100644 --- a/frontend/src/views/Pages/UserProfile/UserCard_FormUserData.spec.js +++ b/frontend/src/views/Pages/UserProfile/UserCard_FormUserData.spec.js @@ -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', diff --git a/frontend/src/views/Pages/UserProfile/UserCard_FormUserData.vue b/frontend/src/views/Pages/UserProfile/UserCard_FormUserData.vue index 369d30074..3726b5298 100644 --- a/frontend/src/views/Pages/UserProfile/UserCard_FormUserData.vue +++ b/frontend/src/views/Pages/UserProfile/UserCard_FormUserData.vue @@ -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, diff --git a/frontend/src/views/Pages/UserProfile/UserCard_FormUserMail.spec.js b/frontend/src/views/Pages/UserProfile/UserCard_FormUserMail.spec.js index 93477bdfa..06f26877a 100644 --- a/frontend/src/views/Pages/UserProfile/UserCard_FormUserMail.spec.js +++ b/frontend/src/views/Pages/UserProfile/UserCard_FormUserMail.spec.js @@ -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', }, diff --git a/frontend/src/views/Pages/UserProfile/UserCard_FormUserMail.vue b/frontend/src/views/Pages/UserProfile/UserCard_FormUserMail.vue index b5c4f30bb..ed88425c7 100644 --- a/frontend/src/views/Pages/UserProfile/UserCard_FormUserMail.vue +++ b/frontend/src/views/Pages/UserProfile/UserCard_FormUserMail.vue @@ -48,7 +48,6 @@ export default { .query({ query: updateUserInfos, variables: { - sessionId: this.$store.state.sessionId, email: this.$store.state.email, newEmail: this.newEmail, }, diff --git a/frontend/src/views/Pages/UserProfile/UserCard_FormUserPasswort.spec.js b/frontend/src/views/Pages/UserProfile/UserCard_FormUserPasswort.spec.js index c44f7d300..fa4aceb0c 100644 --- a/frontend/src/views/Pages/UserProfile/UserCard_FormUserPasswort.spec.js +++ b/frontend/src/views/Pages/UserProfile/UserCard_FormUserPasswort.spec.js @@ -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', diff --git a/frontend/src/views/Pages/UserProfile/UserCard_FormUserPasswort.vue b/frontend/src/views/Pages/UserProfile/UserCard_FormUserPasswort.vue index a9be983c1..4678defaf 100644 --- a/frontend/src/views/Pages/UserProfile/UserCard_FormUserPasswort.vue +++ b/frontend/src/views/Pages/UserProfile/UserCard_FormUserPasswort.vue @@ -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, diff --git a/frontend/src/views/Pages/UserProfile/UserCard_FormUsername.spec.js b/frontend/src/views/Pages/UserProfile/UserCard_FormUsername.spec.js index b6ab05902..8a69ee7eb 100644 --- a/frontend/src/views/Pages/UserProfile/UserCard_FormUsername.spec.js +++ b/frontend/src/views/Pages/UserProfile/UserCard_FormUsername.spec.js @@ -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', }, }), diff --git a/frontend/src/views/Pages/UserProfile/UserCard_FormUsername.vue b/frontend/src/views/Pages/UserProfile/UserCard_FormUsername.vue index 1c269ba53..f6587b968 100644 --- a/frontend/src/views/Pages/UserProfile/UserCard_FormUsername.vue +++ b/frontend/src/views/Pages/UserProfile/UserCard_FormUsername.vue @@ -90,7 +90,6 @@ export default { .query({ query: updateUserInfos, variables: { - sessionId: this.$store.state.sessionId, email: this.$store.state.email, username: this.form.username, }, diff --git a/frontend/src/views/Pages/UserProfileTransactionList.spec.js b/frontend/src/views/Pages/UserProfileTransactionList.spec.js index 484dd10f1..8e4235fbe 100644 --- a/frontend/src/views/Pages/UserProfileTransactionList.spec.js +++ b/frontend/src/views/Pages/UserProfileTransactionList.spec.js @@ -13,11 +13,6 @@ describe('UserProfileTransactionList', () => { $i18n: { locale: jest.fn(() => 'en'), }, - $store: { - state: { - sessionId: 1, - }, - }, } const stubs = { diff --git a/frontend/yarn.lock b/frontend/yarn.lock index eff78ada1..c910ea9de 100644 --- a/frontend/yarn.lock +++ b/frontend/yarn.lock @@ -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"