mirror of
https://github.com/IT4Change/gradido.git
synced 2025-12-13 07:45:54 +00:00
Merge remote-tracking branch 'origin/master' into
2420-feature-federation-implement-exchange-of-api-versions-persist-in-table
This commit is contained in:
commit
26dec0184a
3
.github/workflows/test.yml
vendored
3
.github/workflows/test.yml
vendored
@ -139,7 +139,6 @@ jobs:
|
|||||||
build_test_nginx:
|
build_test_nginx:
|
||||||
name: Docker Build Test - Nginx
|
name: Docker Build Test - Nginx
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
needs: [build_test_backend, build_test_admin, build_test_frontend]
|
|
||||||
steps:
|
steps:
|
||||||
##########################################################################
|
##########################################################################
|
||||||
# CHECKOUT CODE ##########################################################
|
# CHECKOUT CODE ##########################################################
|
||||||
@ -528,7 +527,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: 74
|
min_coverage: 76
|
||||||
token: ${{ github.token }}
|
token: ${{ github.token }}
|
||||||
|
|
||||||
##########################################################################
|
##########################################################################
|
||||||
|
|||||||
22
CHANGELOG.md
22
CHANGELOG.md
@ -4,8 +4,30 @@ All notable changes to this project will be documented in this file. Dates are d
|
|||||||
|
|
||||||
Generated by [`auto-changelog`](https://github.com/CookPete/auto-changelog).
|
Generated by [`auto-changelog`](https://github.com/CookPete/auto-changelog).
|
||||||
|
|
||||||
|
#### [1.16.0](https://github.com/gradido/gradido/compare/1.15.0...1.16.0)
|
||||||
|
|
||||||
|
- refactor(backend): cleaning user related old password junk [`#2426`](https://github.com/gradido/gradido/pull/2426)
|
||||||
|
- fix(database): consistent transaction table [`#2453`](https://github.com/gradido/gradido/pull/2453)
|
||||||
|
- refactor(backend): dissolve admin resolver [`#2416`](https://github.com/gradido/gradido/pull/2416)
|
||||||
|
- fix(backend): email verification code never expired [`#2418`](https://github.com/gradido/gradido/pull/2418)
|
||||||
|
- fix(database): consistent deleted at bewteen users and user contacts [`#2451`](https://github.com/gradido/gradido/pull/2451)
|
||||||
|
- feat(backend): log client timezone offset [`#2454`](https://github.com/gradido/gradido/pull/2454)
|
||||||
|
- refactor(backend): refactor more emails to translatables [`#2398`](https://github.com/gradido/gradido/pull/2398)
|
||||||
|
- fix(backend): delete / undelete email contact as well [`#2444`](https://github.com/gradido/gradido/pull/2444)
|
||||||
|
- feat(backend): 🍰 Mark creation via link [`#2363`](https://github.com/gradido/gradido/pull/2363)
|
||||||
|
- fix(backend): run all timers for high values [`#2452`](https://github.com/gradido/gradido/pull/2452)
|
||||||
|
- fix(backend): critical bug [`#2443`](https://github.com/gradido/gradido/pull/2443)
|
||||||
|
- fix(other): missing files for docker production build [`#2442`](https://github.com/gradido/gradido/pull/2442)
|
||||||
|
- fix(frontend): in contribution messages formular a message can be send twice, when clicking the submit button fast [`#2424`](https://github.com/gradido/gradido/pull/2424)
|
||||||
|
- fix(backend): wrong month for contribution near turn of month [`#2201`](https://github.com/gradido/gradido/pull/2201)
|
||||||
|
- feat(backend): add federation config properties [`#2374`](https://github.com/gradido/gradido/pull/2374)
|
||||||
|
- fix(backend): moved all jest & type-definition related packages into the `devDependencies` section [`#2385`](https://github.com/gradido/gradido/pull/2385)
|
||||||
|
|
||||||
#### [1.15.0](https://github.com/gradido/gradido/compare/1.14.1...1.15.0)
|
#### [1.15.0](https://github.com/gradido/gradido/compare/1.14.1...1.15.0)
|
||||||
|
|
||||||
|
> 26 November 2022
|
||||||
|
|
||||||
|
- chore(release): v1.15.0 [`#2425`](https://github.com/gradido/gradido/pull/2425)
|
||||||
- fix(database): wrong balance and decay values [`#2423`](https://github.com/gradido/gradido/pull/2423)
|
- fix(database): wrong balance and decay values [`#2423`](https://github.com/gradido/gradido/pull/2423)
|
||||||
- fix(backend): wrong balance after transaction receive [`#2422`](https://github.com/gradido/gradido/pull/2422)
|
- fix(backend): wrong balance after transaction receive [`#2422`](https://github.com/gradido/gradido/pull/2422)
|
||||||
- feat(other): feature gradido roadmap [`#2301`](https://github.com/gradido/gradido/pull/2301)
|
- feat(other): feature gradido roadmap [`#2301`](https://github.com/gradido/gradido/pull/2301)
|
||||||
|
|||||||
@ -3,7 +3,7 @@
|
|||||||
"description": "Administraion Interface for Gradido",
|
"description": "Administraion Interface for Gradido",
|
||||||
"main": "index.js",
|
"main": "index.js",
|
||||||
"author": "Moriz Wahl",
|
"author": "Moriz Wahl",
|
||||||
"version": "1.15.0",
|
"version": "1.16.0",
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"private": false,
|
"private": false,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
|||||||
@ -65,7 +65,6 @@ export default {
|
|||||||
},
|
},
|
||||||
})
|
})
|
||||||
.then((result) => {
|
.then((result) => {
|
||||||
this.toastSuccess(this.$t('user_recovered'))
|
|
||||||
this.$emit('updateDeletedAt', {
|
this.$emit('updateDeletedAt', {
|
||||||
userId: this.item.userId,
|
userId: this.item.userId,
|
||||||
deletedAt: result.data.unDeleteUser,
|
deletedAt: result.data.unDeleteUser,
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
CONFIG_VERSION=v13.2022-11-25
|
CONFIG_VERSION=v14.2022-12-22
|
||||||
|
|
||||||
# Server
|
# Server
|
||||||
PORT=4000
|
PORT=4000
|
||||||
@ -30,6 +30,7 @@ COMMUNITY_REGISTER_URL=http://localhost/register
|
|||||||
COMMUNITY_REDEEM_URL=http://localhost/redeem/{code}
|
COMMUNITY_REDEEM_URL=http://localhost/redeem/{code}
|
||||||
COMMUNITY_REDEEM_CONTRIBUTION_URL=http://localhost/redeem/CL-{code}
|
COMMUNITY_REDEEM_CONTRIBUTION_URL=http://localhost/redeem/CL-{code}
|
||||||
COMMUNITY_DESCRIPTION=Die lokale Entwicklungsumgebung von Gradido.
|
COMMUNITY_DESCRIPTION=Die lokale Entwicklungsumgebung von Gradido.
|
||||||
|
COMMUNITY_SUPPORT_MAIL=support@supportmail.com
|
||||||
|
|
||||||
# Login Server
|
# Login Server
|
||||||
LOGIN_APP_SECRET=21ffbbc616fe
|
LOGIN_APP_SECRET=21ffbbc616fe
|
||||||
@ -66,5 +67,4 @@ EVENT_PROTOCOL_DISABLED=false
|
|||||||
# on an hash created from this topic
|
# on an hash created from this topic
|
||||||
# FEDERATION_DHT_TOPIC=GRADIDO_HUB
|
# FEDERATION_DHT_TOPIC=GRADIDO_HUB
|
||||||
# FEDERATION_DHT_SEED=64ebcb0e3ad547848fef4197c6e2332f
|
# FEDERATION_DHT_SEED=64ebcb0e3ad547848fef4197c6e2332f
|
||||||
# FEDERATION_DHT_TEST_SOCKET=false
|
|
||||||
# FEDERATION_COMMUNITY_URL=http://localhost:4000/api
|
# FEDERATION_COMMUNITY_URL=http://localhost:4000/api
|
||||||
|
|||||||
@ -29,6 +29,7 @@ COMMUNITY_REGISTER_URL=$COMMUNITY_REGISTER_URL
|
|||||||
COMMUNITY_REDEEM_URL=$COMMUNITY_REDEEM_URL
|
COMMUNITY_REDEEM_URL=$COMMUNITY_REDEEM_URL
|
||||||
COMMUNITY_REDEEM_CONTRIBUTION_URL=$COMMUNITY_REDEEM_CONTRIBUTION_URL
|
COMMUNITY_REDEEM_CONTRIBUTION_URL=$COMMUNITY_REDEEM_CONTRIBUTION_URL
|
||||||
COMMUNITY_DESCRIPTION=$COMMUNITY_DESCRIPTION
|
COMMUNITY_DESCRIPTION=$COMMUNITY_DESCRIPTION
|
||||||
|
COMMUNITY_SUPPORT_MAIL=$COMMUNITY_SUPPORT_MAIL
|
||||||
|
|
||||||
# Login Server
|
# Login Server
|
||||||
LOGIN_APP_SECRET=21ffbbc616fe
|
LOGIN_APP_SECRET=21ffbbc616fe
|
||||||
|
|||||||
@ -107,9 +107,7 @@ COPY --from=build ${DOCKER_WORKDIR}/package.json ./package.json
|
|||||||
COPY --from=build ${DOCKER_WORKDIR}/tsconfig.json ./tsconfig.json
|
COPY --from=build ${DOCKER_WORKDIR}/tsconfig.json ./tsconfig.json
|
||||||
# Copy log4js-config.json to provide log configuration
|
# Copy log4js-config.json to provide log configuration
|
||||||
COPY --from=build ${DOCKER_WORKDIR}/log4js-config.json ./log4js-config.json
|
COPY --from=build ${DOCKER_WORKDIR}/log4js-config.json ./log4js-config.json
|
||||||
# Copy memonic type since its referenced in the sources
|
|
||||||
# TODO: remove
|
|
||||||
COPY --from=build ${DOCKER_WORKDIR}/src/config/mnemonic.uncompressed_buffer13116.txt ./src/config/mnemonic.uncompressed_buffer13116.txt
|
|
||||||
# Copy run scripts run/
|
# Copy run scripts run/
|
||||||
# COPY --from=build ${DOCKER_WORKDIR}/run ./run
|
# COPY --from=build ${DOCKER_WORKDIR}/run ./run
|
||||||
|
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "gradido-backend",
|
"name": "gradido-backend",
|
||||||
"version": "1.15.0",
|
"version": "1.16.0",
|
||||||
"description": "Gradido unified backend providing an API-Service for Gradido Transactions",
|
"description": "Gradido unified backend providing an API-Service for Gradido Transactions",
|
||||||
"main": "src/index.ts",
|
"main": "src/index.ts",
|
||||||
"repository": "https://github.com/gradido/gradido/backend",
|
"repository": "https://github.com/gradido/gradido/backend",
|
||||||
@ -20,6 +20,7 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@hyperswarm/dht": "^6.2.0",
|
"@hyperswarm/dht": "^6.2.0",
|
||||||
"apollo-server-express": "^2.25.2",
|
"apollo-server-express": "^2.25.2",
|
||||||
|
"await-semaphore": "^0.1.3",
|
||||||
"axios": "^0.21.1",
|
"axios": "^0.21.1",
|
||||||
"class-validator": "^0.13.1",
|
"class-validator": "^0.13.1",
|
||||||
"cors": "^2.8.5",
|
"cors": "^2.8.5",
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
import { JwtPayload } from 'jsonwebtoken'
|
import { JwtPayload } from 'jsonwebtoken'
|
||||||
|
|
||||||
export interface CustomJwtPayload extends JwtPayload {
|
export interface CustomJwtPayload extends JwtPayload {
|
||||||
pubKey: Buffer
|
gradidoID: string
|
||||||
}
|
}
|
||||||
|
|||||||
@ -11,8 +11,8 @@ export const decode = (token: string): CustomJwtPayload | null => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const encode = (pubKey: Buffer): string => {
|
export const encode = (gradidoID: string): string => {
|
||||||
const token = jwt.sign({ pubKey }, CONFIG.JWT_SECRET, {
|
const token = jwt.sign({ gradidoID }, CONFIG.JWT_SECRET, {
|
||||||
expiresIn: CONFIG.JWT_EXPIRES_IN,
|
expiresIn: CONFIG.JWT_EXPIRES_IN,
|
||||||
})
|
})
|
||||||
return token
|
return token
|
||||||
|
|||||||
@ -10,14 +10,14 @@ Decimal.set({
|
|||||||
})
|
})
|
||||||
|
|
||||||
const constants = {
|
const constants = {
|
||||||
DB_VERSION: '0056-add_communities_table',
|
DB_VERSION: '0058-add_communities_table',
|
||||||
DECAY_START_TIME: new Date('2021-05-13 17:46:31-0000'), // GMT+0
|
DECAY_START_TIME: new Date('2021-05-13 17:46:31-0000'), // GMT+0
|
||||||
LOG4JS_CONFIG: 'log4js-config.json',
|
LOG4JS_CONFIG: 'log4js-config.json',
|
||||||
// default log level on production should be info
|
// default log level on production should be info
|
||||||
LOG_LEVEL: process.env.LOG_LEVEL || 'info',
|
LOG_LEVEL: process.env.LOG_LEVEL || 'info',
|
||||||
CONFIG_VERSION: {
|
CONFIG_VERSION: {
|
||||||
DEFAULT: 'DEFAULT',
|
DEFAULT: 'DEFAULT',
|
||||||
EXPECTED: 'v13.2022-11-25',
|
EXPECTED: 'v14.2022-11-22',
|
||||||
CURRENT: '',
|
CURRENT: '',
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@ -58,6 +58,7 @@ const community = {
|
|||||||
process.env.COMMUNITY_REDEEM_CONTRIBUTION_URL || 'http://localhost/redeem/CL-{code}',
|
process.env.COMMUNITY_REDEEM_CONTRIBUTION_URL || 'http://localhost/redeem/CL-{code}',
|
||||||
COMMUNITY_DESCRIPTION:
|
COMMUNITY_DESCRIPTION:
|
||||||
process.env.COMMUNITY_DESCRIPTION || 'Die lokale Entwicklungsumgebung von Gradido.',
|
process.env.COMMUNITY_DESCRIPTION || 'Die lokale Entwicklungsumgebung von Gradido.',
|
||||||
|
COMMUNITY_SUPPORT_MAIL: process.env.COMMUNITY_SUPPORT_MAIL || 'support@supportmail.com',
|
||||||
}
|
}
|
||||||
|
|
||||||
const loginServer = {
|
const loginServer = {
|
||||||
@ -119,7 +120,6 @@ if (
|
|||||||
const federation = {
|
const federation = {
|
||||||
FEDERATION_DHT_TOPIC: process.env.FEDERATION_DHT_TOPIC || null,
|
FEDERATION_DHT_TOPIC: process.env.FEDERATION_DHT_TOPIC || null,
|
||||||
FEDERATION_DHT_SEED: process.env.FEDERATION_DHT_SEED || null,
|
FEDERATION_DHT_SEED: process.env.FEDERATION_DHT_SEED || null,
|
||||||
FEDERATION_DHT_TEST_SOCKET: process.env.FEDERATION_DHT_TEST_SOCKET === 'true' || false,
|
|
||||||
FEDERATION_COMMUNITY_URL:
|
FEDERATION_COMMUNITY_URL:
|
||||||
process.env.FEDERATION_COMMUNITY_URL === undefined
|
process.env.FEDERATION_COMMUNITY_URL === undefined
|
||||||
? null
|
? null
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
@ -70,6 +70,8 @@ describe('sendEmailVariants', () => {
|
|||||||
senderLastName: 'Bloxberg',
|
senderLastName: 'Bloxberg',
|
||||||
contributionMemo: 'My contribution.',
|
contributionMemo: 'My contribution.',
|
||||||
overviewURL: CONFIG.EMAIL_LINK_OVERVIEW,
|
overviewURL: CONFIG.EMAIL_LINK_OVERVIEW,
|
||||||
|
supportEmail: CONFIG.COMMUNITY_SUPPORT_MAIL,
|
||||||
|
communityURL: CONFIG.COMMUNITY_URL,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
@ -106,10 +108,14 @@ describe('sendEmailVariants', () => {
|
|||||||
'To view and reply to the message, go to the “Community” menu in your Gradido account and click on the “My contributions to the common good” tab!',
|
'To view and reply to the message, go to the “Community” menu in your Gradido account and click on the “My contributions to the common good” tab!',
|
||||||
)
|
)
|
||||||
expect(result.originalMessage.html).toContain(
|
expect(result.originalMessage.html).toContain(
|
||||||
`Link to your account:<span> </span><a href="${CONFIG.EMAIL_LINK_OVERVIEW}">${CONFIG.EMAIL_LINK_OVERVIEW}</a>`,
|
`Link to your account: <a href="${CONFIG.EMAIL_LINK_OVERVIEW}">${CONFIG.EMAIL_LINK_OVERVIEW}</a>`,
|
||||||
)
|
)
|
||||||
expect(result.originalMessage.html).toContain('Please do not reply to this email!')
|
expect(result.originalMessage.html).toContain('Please do not reply to this email!')
|
||||||
expect(result.originalMessage.html).toContain('Kind regards,<br><span>your Gradido team')
|
expect(result.originalMessage.html).toContain('Kind regards,<br>your Gradido team')
|
||||||
|
expect(result.originalMessage.html).toContain('—————')
|
||||||
|
expect(result.originalMessage.html).toContain(
|
||||||
|
'<div style="position: relative; left: -22px;"><img src="https://gdd.gradido.net/img/brand/green.png" width="200" alt="Gradido-Akademie Logo"></div><br>Gradido-Akademie<br>Institut für Wirtschaftsbionik<br>Pfarrweg 2<br>74653 Künzelsau<br>Deutschland<br><a href="mailto:support@supportmail.com">support@supportmail.com</a><br><a href="http://localhost/">http://localhost/</a>',
|
||||||
|
)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
@ -140,6 +146,8 @@ describe('sendEmailVariants', () => {
|
|||||||
activationLink: 'http://localhost/checkEmail/6627633878930542284',
|
activationLink: 'http://localhost/checkEmail/6627633878930542284',
|
||||||
timeDurationObject: { hours: 23, minutes: 30 },
|
timeDurationObject: { hours: 23, minutes: 30 },
|
||||||
resendLink: CONFIG.EMAIL_LINK_FORGOTPASSWORD,
|
resendLink: CONFIG.EMAIL_LINK_FORGOTPASSWORD,
|
||||||
|
supportEmail: CONFIG.COMMUNITY_SUPPORT_MAIL,
|
||||||
|
communityURL: CONFIG.COMMUNITY_URL,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
@ -178,12 +186,16 @@ describe('sendEmailVariants', () => {
|
|||||||
'or copy the link above into your browser window.',
|
'or copy the link above into your browser window.',
|
||||||
)
|
)
|
||||||
expect(result.originalMessage.html).toContain(
|
expect(result.originalMessage.html).toContain(
|
||||||
'The link has a validity of 23 hours and 30 minutes. If the validity of the link has already expired, you can have a new link sent to you here by entering your email address:',
|
'The link has a validity of 23 hours and 30 minutes. If the validity of the link has already expired, you can have a new link sent to you here:',
|
||||||
)
|
)
|
||||||
expect(result.originalMessage.html).toContain(
|
expect(result.originalMessage.html).toContain(
|
||||||
`<a href="${CONFIG.EMAIL_LINK_FORGOTPASSWORD}">${CONFIG.EMAIL_LINK_FORGOTPASSWORD}</a>`,
|
`<a href="${CONFIG.EMAIL_LINK_FORGOTPASSWORD}">${CONFIG.EMAIL_LINK_FORGOTPASSWORD}</a>`,
|
||||||
)
|
)
|
||||||
expect(result.originalMessage.html).toContain('Kind regards,<br><span>your Gradido team')
|
expect(result.originalMessage.html).toContain('Kind regards,<br>your Gradido team')
|
||||||
|
expect(result.originalMessage.html).toContain('—————')
|
||||||
|
expect(result.originalMessage.html).toContain(
|
||||||
|
'<div style="position: relative; left: -22px;"><img src="https://gdd.gradido.net/img/brand/green.png" width="200" alt="Gradido-Akademie Logo"></div><br>Gradido-Akademie<br>Institut für Wirtschaftsbionik<br>Pfarrweg 2<br>74653 Künzelsau<br>Deutschland<br><a href="mailto:support@supportmail.com">support@supportmail.com</a><br><a href="http://localhost/">http://localhost/</a>',
|
||||||
|
)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
@ -210,6 +222,8 @@ describe('sendEmailVariants', () => {
|
|||||||
lastName: 'Lustig',
|
lastName: 'Lustig',
|
||||||
locale: 'en',
|
locale: 'en',
|
||||||
resendLink: CONFIG.EMAIL_LINK_FORGOTPASSWORD,
|
resendLink: CONFIG.EMAIL_LINK_FORGOTPASSWORD,
|
||||||
|
supportEmail: CONFIG.COMMUNITY_SUPPORT_MAIL,
|
||||||
|
communityURL: CONFIG.COMMUNITY_URL,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
@ -255,9 +269,13 @@ describe('sendEmailVariants', () => {
|
|||||||
'or copy the link above into your browser window.',
|
'or copy the link above into your browser window.',
|
||||||
)
|
)
|
||||||
expect(result.originalMessage.html).toContain(
|
expect(result.originalMessage.html).toContain(
|
||||||
'If you are not the one who tried to register again, please contact our support:',
|
'If you are not the one who tried to register again, please contact our support:<br><a href="mailto:support@supportmail.com">support@supportmail.com</a>',
|
||||||
|
)
|
||||||
|
expect(result.originalMessage.html).toContain('Kind regards,<br>your Gradido team')
|
||||||
|
expect(result.originalMessage.html).toContain('—————')
|
||||||
|
expect(result.originalMessage.html).toContain(
|
||||||
|
'<div style="position: relative; left: -22px;"><img src="https://gdd.gradido.net/img/brand/green.png" width="200" alt="Gradido-Akademie Logo"></div><br>Gradido-Akademie<br>Institut für Wirtschaftsbionik<br>Pfarrweg 2<br>74653 Künzelsau<br>Deutschland<br><a href="mailto:support@supportmail.com">support@supportmail.com</a><br><a href="http://localhost/">http://localhost/</a>',
|
||||||
)
|
)
|
||||||
expect(result.originalMessage.html).toContain('Kind regards,<br><span>your Gradido team')
|
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
@ -292,6 +310,8 @@ describe('sendEmailVariants', () => {
|
|||||||
contributionMemo: 'My contribution.',
|
contributionMemo: 'My contribution.',
|
||||||
contributionAmount: '23.54',
|
contributionAmount: '23.54',
|
||||||
overviewURL: CONFIG.EMAIL_LINK_OVERVIEW,
|
overviewURL: CONFIG.EMAIL_LINK_OVERVIEW,
|
||||||
|
supportEmail: CONFIG.COMMUNITY_SUPPORT_MAIL,
|
||||||
|
communityURL: CONFIG.COMMUNITY_URL,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
@ -326,10 +346,14 @@ describe('sendEmailVariants', () => {
|
|||||||
)
|
)
|
||||||
expect(result.originalMessage.html).toContain('Amount: 23.54 GDD')
|
expect(result.originalMessage.html).toContain('Amount: 23.54 GDD')
|
||||||
expect(result.originalMessage.html).toContain(
|
expect(result.originalMessage.html).toContain(
|
||||||
`Link to your account:<span> </span><a href="${CONFIG.EMAIL_LINK_OVERVIEW}">${CONFIG.EMAIL_LINK_OVERVIEW}</a>`,
|
`Link to your account: <a href="${CONFIG.EMAIL_LINK_OVERVIEW}">${CONFIG.EMAIL_LINK_OVERVIEW}</a>`,
|
||||||
)
|
)
|
||||||
expect(result.originalMessage.html).toContain('Please do not reply to this email!')
|
expect(result.originalMessage.html).toContain('Please do not reply to this email!')
|
||||||
expect(result.originalMessage.html).toContain('Kind regards,<br><span>your Gradido team')
|
expect(result.originalMessage.html).toContain('Kind regards,<br>your Gradido team')
|
||||||
|
expect(result.originalMessage.html).toContain('—————')
|
||||||
|
expect(result.originalMessage.html).toContain(
|
||||||
|
'<div style="position: relative; left: -22px;"><img src="https://gdd.gradido.net/img/brand/green.png" width="200" alt="Gradido-Akademie Logo"></div><br>Gradido-Akademie<br>Institut für Wirtschaftsbionik<br>Pfarrweg 2<br>74653 Künzelsau<br>Deutschland<br><a href="mailto:support@supportmail.com">support@supportmail.com</a><br><a href="http://localhost/">http://localhost/</a>',
|
||||||
|
)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
@ -362,6 +386,8 @@ describe('sendEmailVariants', () => {
|
|||||||
senderLastName: 'Bloxberg',
|
senderLastName: 'Bloxberg',
|
||||||
contributionMemo: 'My contribution.',
|
contributionMemo: 'My contribution.',
|
||||||
overviewURL: CONFIG.EMAIL_LINK_OVERVIEW,
|
overviewURL: CONFIG.EMAIL_LINK_OVERVIEW,
|
||||||
|
supportEmail: CONFIG.COMMUNITY_SUPPORT_MAIL,
|
||||||
|
communityURL: CONFIG.COMMUNITY_URL,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
@ -398,10 +424,14 @@ describe('sendEmailVariants', () => {
|
|||||||
'To see your common good contributions and related messages, go to the “Community” menu in your Gradido account and click on the “My contributions to the common good” tab!',
|
'To see your common good contributions and related messages, go to the “Community” menu in your Gradido account and click on the “My contributions to the common good” tab!',
|
||||||
)
|
)
|
||||||
expect(result.originalMessage.html).toContain(
|
expect(result.originalMessage.html).toContain(
|
||||||
`Link to your account:<span> </span><a href="${CONFIG.EMAIL_LINK_OVERVIEW}">${CONFIG.EMAIL_LINK_OVERVIEW}</a>`,
|
`Link to your account: <a href="${CONFIG.EMAIL_LINK_OVERVIEW}">${CONFIG.EMAIL_LINK_OVERVIEW}</a>`,
|
||||||
)
|
)
|
||||||
expect(result.originalMessage.html).toContain('Please do not reply to this email!')
|
expect(result.originalMessage.html).toContain('Please do not reply to this email!')
|
||||||
expect(result.originalMessage.html).toContain('Kind regards,<br><span>your Gradido team')
|
expect(result.originalMessage.html).toContain('Kind regards,<br>your Gradido team')
|
||||||
|
expect(result.originalMessage.html).toContain('—————')
|
||||||
|
expect(result.originalMessage.html).toContain(
|
||||||
|
'<div style="position: relative; left: -22px;"><img src="https://gdd.gradido.net/img/brand/green.png" width="200" alt="Gradido-Akademie Logo"></div><br>Gradido-Akademie<br>Institut für Wirtschaftsbionik<br>Pfarrweg 2<br>74653 Künzelsau<br>Deutschland<br><a href="mailto:support@supportmail.com">support@supportmail.com</a><br><a href="http://localhost/">http://localhost/</a>',
|
||||||
|
)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
@ -432,6 +462,8 @@ describe('sendEmailVariants', () => {
|
|||||||
resetLink: 'http://localhost/reset-password/3762660021544901417',
|
resetLink: 'http://localhost/reset-password/3762660021544901417',
|
||||||
timeDurationObject: { hours: 23, minutes: 30 },
|
timeDurationObject: { hours: 23, minutes: 30 },
|
||||||
resendLink: CONFIG.EMAIL_LINK_FORGOTPASSWORD,
|
resendLink: CONFIG.EMAIL_LINK_FORGOTPASSWORD,
|
||||||
|
supportEmail: CONFIG.COMMUNITY_SUPPORT_MAIL,
|
||||||
|
communityURL: CONFIG.COMMUNITY_URL,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
@ -468,12 +500,16 @@ describe('sendEmailVariants', () => {
|
|||||||
'or copy the link above into your browser window.',
|
'or copy the link above into your browser window.',
|
||||||
)
|
)
|
||||||
expect(result.originalMessage.html).toContain(
|
expect(result.originalMessage.html).toContain(
|
||||||
'The link has a validity of 23 hours and 30 minutes. If the validity of the link has already expired, you can have a new link sent to you here by entering your email address:',
|
'The link has a validity of 23 hours and 30 minutes. If the validity of the link has already expired, you can have a new link sent to you here:',
|
||||||
)
|
)
|
||||||
expect(result.originalMessage.html).toContain(
|
expect(result.originalMessage.html).toContain(
|
||||||
`<a href="${CONFIG.EMAIL_LINK_FORGOTPASSWORD}">${CONFIG.EMAIL_LINK_FORGOTPASSWORD}</a>`,
|
`<a href="${CONFIG.EMAIL_LINK_FORGOTPASSWORD}">${CONFIG.EMAIL_LINK_FORGOTPASSWORD}</a>`,
|
||||||
)
|
)
|
||||||
expect(result.originalMessage.html).toContain('Kind regards,<br><span>your Gradido team')
|
expect(result.originalMessage.html).toContain('Kind regards,<br>your Gradido team')
|
||||||
|
expect(result.originalMessage.html).toContain('—————')
|
||||||
|
expect(result.originalMessage.html).toContain(
|
||||||
|
'<div style="position: relative; left: -22px;"><img src="https://gdd.gradido.net/img/brand/green.png" width="200" alt="Gradido-Akademie Logo"></div><br>Gradido-Akademie<br>Institut für Wirtschaftsbionik<br>Pfarrweg 2<br>74653 Künzelsau<br>Deutschland<br><a href="mailto:support@supportmail.com">support@supportmail.com</a><br><a href="http://localhost/">http://localhost/</a>',
|
||||||
|
)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
@ -510,6 +546,8 @@ describe('sendEmailVariants', () => {
|
|||||||
transactionMemo: 'You deserve it! 🙏🏼',
|
transactionMemo: 'You deserve it! 🙏🏼',
|
||||||
transactionAmount: '17.65',
|
transactionAmount: '17.65',
|
||||||
overviewURL: CONFIG.EMAIL_LINK_OVERVIEW,
|
overviewURL: CONFIG.EMAIL_LINK_OVERVIEW,
|
||||||
|
supportEmail: CONFIG.COMMUNITY_SUPPORT_MAIL,
|
||||||
|
communityURL: CONFIG.COMMUNITY_URL,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
@ -543,12 +581,16 @@ describe('sendEmailVariants', () => {
|
|||||||
'Bibi Bloxberg (bibi@bloxberg.de) has just redeemed your link.',
|
'Bibi Bloxberg (bibi@bloxberg.de) has just redeemed your link.',
|
||||||
)
|
)
|
||||||
expect(result.originalMessage.html).toContain('Amount: 17.65 GDD')
|
expect(result.originalMessage.html).toContain('Amount: 17.65 GDD')
|
||||||
expect(result.originalMessage.html).toContain('Memo: You deserve it! 🙏🏼')
|
expect(result.originalMessage.html).toContain('Message: You deserve it! 🙏🏼')
|
||||||
expect(result.originalMessage.html).toContain(
|
expect(result.originalMessage.html).toContain(
|
||||||
`You can find transaction details in your Gradido account:<span> </span><a href="${CONFIG.EMAIL_LINK_OVERVIEW}">${CONFIG.EMAIL_LINK_OVERVIEW}</a>`,
|
`You can find transaction details in your Gradido account: <a href="${CONFIG.EMAIL_LINK_OVERVIEW}">${CONFIG.EMAIL_LINK_OVERVIEW}</a>`,
|
||||||
)
|
)
|
||||||
expect(result.originalMessage.html).toContain('Please do not reply to this email!')
|
expect(result.originalMessage.html).toContain('Please do not reply to this email!')
|
||||||
expect(result.originalMessage.html).toContain('Kind regards,<br><span>your Gradido team')
|
expect(result.originalMessage.html).toContain('Kind regards,<br>your Gradido team')
|
||||||
|
expect(result.originalMessage.html).toContain('—————')
|
||||||
|
expect(result.originalMessage.html).toContain(
|
||||||
|
'<div style="position: relative; left: -22px;"><img src="https://gdd.gradido.net/img/brand/green.png" width="200" alt="Gradido-Akademie Logo"></div><br>Gradido-Akademie<br>Institut für Wirtschaftsbionik<br>Pfarrweg 2<br>74653 Künzelsau<br>Deutschland<br><a href="mailto:support@supportmail.com">support@supportmail.com</a><br><a href="http://localhost/">http://localhost/</a>',
|
||||||
|
)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
@ -583,6 +625,8 @@ describe('sendEmailVariants', () => {
|
|||||||
senderEmail: 'bibi@bloxberg.de',
|
senderEmail: 'bibi@bloxberg.de',
|
||||||
transactionAmount: '37.40',
|
transactionAmount: '37.40',
|
||||||
overviewURL: CONFIG.EMAIL_LINK_OVERVIEW,
|
overviewURL: CONFIG.EMAIL_LINK_OVERVIEW,
|
||||||
|
supportEmail: CONFIG.COMMUNITY_SUPPORT_MAIL,
|
||||||
|
communityURL: CONFIG.COMMUNITY_URL,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
@ -598,26 +642,32 @@ describe('sendEmailVariants', () => {
|
|||||||
to: 'Peter Lustig <peter@lustig.de>',
|
to: 'Peter Lustig <peter@lustig.de>',
|
||||||
from: 'Gradido (do not answer) <info@gradido.net>',
|
from: 'Gradido (do not answer) <info@gradido.net>',
|
||||||
attachments: [],
|
attachments: [],
|
||||||
subject: 'Gradido: You have received Gradidos',
|
subject: 'Gradido: Bibi Bloxberg has sent you 37.40 Gradido',
|
||||||
html: expect.any(String),
|
html: expect.any(String),
|
||||||
text: expect.stringContaining('GRADIDO: YOU HAVE RECEIVED GRADIDOS'),
|
text: expect.stringContaining('GRADIDO: BIBI BLOXBERG HAS SENT YOU 37.40 GRADIDO'),
|
||||||
}),
|
}),
|
||||||
})
|
})
|
||||||
expect(result.originalMessage.html).toContain('<!DOCTYPE html>')
|
expect(result.originalMessage.html).toContain('<!DOCTYPE html>')
|
||||||
expect(result.originalMessage.html).toContain('<html lang="en">')
|
expect(result.originalMessage.html).toContain('<html lang="en">')
|
||||||
expect(result.originalMessage.html).toContain(
|
expect(result.originalMessage.html).toContain(
|
||||||
'<title>Gradido: You have received Gradidos</title>',
|
'<title>Gradido: Bibi Bloxberg has sent you 37.40 Gradido</title>',
|
||||||
|
)
|
||||||
|
expect(result.originalMessage.html).toContain(
|
||||||
|
'>Gradido: Bibi Bloxberg has sent you 37.40 Gradido</h1>',
|
||||||
)
|
)
|
||||||
expect(result.originalMessage.html).toContain('>Gradido: You have received Gradidos</h1>')
|
|
||||||
expect(result.originalMessage.html).toContain('Hello Peter Lustig')
|
expect(result.originalMessage.html).toContain('Hello Peter Lustig')
|
||||||
expect(result.originalMessage.html).toContain(
|
expect(result.originalMessage.html).toContain(
|
||||||
'You have just received 37.40 GDD from Bibi Bloxberg (bibi@bloxberg.de).',
|
'You have just received 37.40 GDD from Bibi Bloxberg (bibi@bloxberg.de).',
|
||||||
)
|
)
|
||||||
expect(result.originalMessage.html).toContain(
|
expect(result.originalMessage.html).toContain(
|
||||||
`You can find transaction details in your Gradido account:<span> </span><a href="${CONFIG.EMAIL_LINK_OVERVIEW}">${CONFIG.EMAIL_LINK_OVERVIEW}</a>`,
|
`You can find transaction details in your Gradido account: <a href="${CONFIG.EMAIL_LINK_OVERVIEW}">${CONFIG.EMAIL_LINK_OVERVIEW}</a>`,
|
||||||
)
|
)
|
||||||
expect(result.originalMessage.html).toContain('Please do not reply to this email!')
|
expect(result.originalMessage.html).toContain('Please do not reply to this email!')
|
||||||
expect(result.originalMessage.html).toContain('Kind regards,<br><span>your Gradido team')
|
expect(result.originalMessage.html).toContain('Kind regards,<br>your Gradido team')
|
||||||
|
expect(result.originalMessage.html).toContain('—————')
|
||||||
|
expect(result.originalMessage.html).toContain(
|
||||||
|
'<div style="position: relative; left: -22px;"><img src="https://gdd.gradido.net/img/brand/green.png" width="200" alt="Gradido-Akademie Logo"></div><br>Gradido-Akademie<br>Institut für Wirtschaftsbionik<br>Pfarrweg 2<br>74653 Künzelsau<br>Deutschland<br><a href="mailto:support@supportmail.com">support@supportmail.com</a><br><a href="http://localhost/">http://localhost/</a>',
|
||||||
|
)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
@ -25,6 +25,8 @@ export const sendAddedContributionMessageEmail = (data: {
|
|||||||
senderLastName: data.senderLastName,
|
senderLastName: data.senderLastName,
|
||||||
contributionMemo: data.contributionMemo,
|
contributionMemo: data.contributionMemo,
|
||||||
overviewURL: CONFIG.EMAIL_LINK_OVERVIEW,
|
overviewURL: CONFIG.EMAIL_LINK_OVERVIEW,
|
||||||
|
supportEmail: CONFIG.COMMUNITY_SUPPORT_MAIL,
|
||||||
|
communityURL: CONFIG.COMMUNITY_URL,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@ -47,6 +49,8 @@ export const sendAccountActivationEmail = (data: {
|
|||||||
activationLink: data.activationLink,
|
activationLink: data.activationLink,
|
||||||
timeDurationObject: data.timeDurationObject,
|
timeDurationObject: data.timeDurationObject,
|
||||||
resendLink: CONFIG.EMAIL_LINK_FORGOTPASSWORD,
|
resendLink: CONFIG.EMAIL_LINK_FORGOTPASSWORD,
|
||||||
|
supportEmail: CONFIG.COMMUNITY_SUPPORT_MAIL,
|
||||||
|
communityURL: CONFIG.COMMUNITY_URL,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@ -65,6 +69,8 @@ export const sendAccountMultiRegistrationEmail = (data: {
|
|||||||
lastName: data.lastName,
|
lastName: data.lastName,
|
||||||
locale: data.language,
|
locale: data.language,
|
||||||
resendLink: CONFIG.EMAIL_LINK_FORGOTPASSWORD,
|
resendLink: CONFIG.EMAIL_LINK_FORGOTPASSWORD,
|
||||||
|
supportEmail: CONFIG.COMMUNITY_SUPPORT_MAIL,
|
||||||
|
communityURL: CONFIG.COMMUNITY_URL,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@ -91,6 +97,8 @@ export const sendContributionConfirmedEmail = (data: {
|
|||||||
contributionMemo: data.contributionMemo,
|
contributionMemo: data.contributionMemo,
|
||||||
contributionAmount: decimalSeparatorByLanguage(data.contributionAmount, data.language),
|
contributionAmount: decimalSeparatorByLanguage(data.contributionAmount, data.language),
|
||||||
overviewURL: CONFIG.EMAIL_LINK_OVERVIEW,
|
overviewURL: CONFIG.EMAIL_LINK_OVERVIEW,
|
||||||
|
supportEmail: CONFIG.COMMUNITY_SUPPORT_MAIL,
|
||||||
|
communityURL: CONFIG.COMMUNITY_URL,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@ -115,6 +123,8 @@ export const sendContributionRejectedEmail = (data: {
|
|||||||
senderLastName: data.senderLastName,
|
senderLastName: data.senderLastName,
|
||||||
contributionMemo: data.contributionMemo,
|
contributionMemo: data.contributionMemo,
|
||||||
overviewURL: CONFIG.EMAIL_LINK_OVERVIEW,
|
overviewURL: CONFIG.EMAIL_LINK_OVERVIEW,
|
||||||
|
supportEmail: CONFIG.COMMUNITY_SUPPORT_MAIL,
|
||||||
|
communityURL: CONFIG.COMMUNITY_URL,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@ -137,6 +147,8 @@ export const sendResetPasswordEmail = (data: {
|
|||||||
resetLink: data.resetLink,
|
resetLink: data.resetLink,
|
||||||
timeDurationObject: data.timeDurationObject,
|
timeDurationObject: data.timeDurationObject,
|
||||||
resendLink: CONFIG.EMAIL_LINK_FORGOTPASSWORD,
|
resendLink: CONFIG.EMAIL_LINK_FORGOTPASSWORD,
|
||||||
|
supportEmail: CONFIG.COMMUNITY_SUPPORT_MAIL,
|
||||||
|
communityURL: CONFIG.COMMUNITY_URL,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@ -165,6 +177,8 @@ export const sendTransactionLinkRedeemedEmail = (data: {
|
|||||||
transactionMemo: data.transactionMemo,
|
transactionMemo: data.transactionMemo,
|
||||||
transactionAmount: decimalSeparatorByLanguage(data.transactionAmount, data.language),
|
transactionAmount: decimalSeparatorByLanguage(data.transactionAmount, data.language),
|
||||||
overviewURL: CONFIG.EMAIL_LINK_OVERVIEW,
|
overviewURL: CONFIG.EMAIL_LINK_OVERVIEW,
|
||||||
|
supportEmail: CONFIG.COMMUNITY_SUPPORT_MAIL,
|
||||||
|
communityURL: CONFIG.COMMUNITY_URL,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@ -191,6 +205,8 @@ export const sendTransactionReceivedEmail = (data: {
|
|||||||
senderEmail: data.senderEmail,
|
senderEmail: data.senderEmail,
|
||||||
transactionAmount: decimalSeparatorByLanguage(data.transactionAmount, data.language),
|
transactionAmount: decimalSeparatorByLanguage(data.transactionAmount, data.language),
|
||||||
overviewURL: CONFIG.EMAIL_LINK_OVERVIEW,
|
overviewURL: CONFIG.EMAIL_LINK_OVERVIEW,
|
||||||
|
supportEmail: CONFIG.COMMUNITY_SUPPORT_MAIL,
|
||||||
|
communityURL: CONFIG.COMMUNITY_URL,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@ -5,16 +5,16 @@ html(lang=locale)
|
|||||||
body
|
body
|
||||||
h1(style='margin-bottom: 24px;')= t('emails.accountActivation.subject')
|
h1(style='margin-bottom: 24px;')= t('emails.accountActivation.subject')
|
||||||
#container.col
|
#container.col
|
||||||
p(style='margin-bottom: 24px;')= t('emails.general.helloName', { firstName, lastName })
|
include ../hello.pug
|
||||||
p= t('emails.accountActivation.emailRegistered')
|
p= t('emails.accountActivation.emailRegistered')
|
||||||
p= t('emails.accountActivation.pleaseClickLink')
|
p
|
||||||
|
= t('emails.accountActivation.pleaseClickLink')
|
||||||
br
|
br
|
||||||
a(href=activationLink) #{activationLink}
|
a(href=activationLink) #{activationLink}
|
||||||
br
|
br
|
||||||
span= t('emails.general.orCopyLink')
|
= t('emails.general.orCopyLink')
|
||||||
p= t('emails.accountActivation.duration', { hours: timeDurationObject.hours, minutes: timeDurationObject.minutes })
|
p
|
||||||
|
= t('emails.accountActivation.duration', { hours: timeDurationObject.hours, minutes: timeDurationObject.minutes })
|
||||||
br
|
br
|
||||||
a(href=resendLink) #{resendLink}
|
a(href=resendLink) #{resendLink}
|
||||||
p(style='margin-top: 24px;')= t('emails.general.sincerelyYours')
|
include ../greatingFormularImprint.pug
|
||||||
br
|
|
||||||
span= t('emails.general.yourGradidoTeam')
|
|
||||||
|
|||||||
@ -5,18 +5,19 @@ html(lang=locale)
|
|||||||
body
|
body
|
||||||
h1(style='margin-bottom: 24px;')= t('emails.accountMultiRegistration.subject')
|
h1(style='margin-bottom: 24px;')= t('emails.accountMultiRegistration.subject')
|
||||||
#container.col
|
#container.col
|
||||||
p(style='margin-bottom: 24px;')= t('emails.general.helloName', { firstName, lastName })
|
include ../hello.pug
|
||||||
p= t('emails.accountMultiRegistration.emailReused')
|
p
|
||||||
|
= t('emails.accountMultiRegistration.emailReused')
|
||||||
br
|
br
|
||||||
span= t('emails.accountMultiRegistration.emailExists')
|
= t('emails.accountMultiRegistration.emailExists')
|
||||||
p= t('emails.accountMultiRegistration.onForgottenPasswordClickLink')
|
p
|
||||||
|
= t('emails.accountMultiRegistration.onForgottenPasswordClickLink')
|
||||||
br
|
br
|
||||||
a(href=resendLink) #{resendLink}
|
a(href=resendLink) #{resendLink}
|
||||||
br
|
br
|
||||||
span= t('emails.accountMultiRegistration.onForgottenPasswordCopyLink')
|
= t('emails.accountMultiRegistration.onForgottenPasswordCopyLink')
|
||||||
p= t('emails.accountMultiRegistration.ifYouAreNotTheOne')
|
p
|
||||||
|
= t('emails.accountMultiRegistration.ifYouAreNotTheOne')
|
||||||
br
|
br
|
||||||
a(href='https://gradido.net/de/contact/') https://gradido.net/de/contact/
|
a(href='mailto:' + supportEmail)= supportEmail
|
||||||
p(style='margin-top: 24px;')= t('emails.general.sincerelyYours')
|
include ../greatingFormularImprint.pug
|
||||||
br
|
|
||||||
span= t('emails.general.yourGradidoTeam')
|
|
||||||
|
|||||||
@ -5,13 +5,12 @@ html(lang=locale)
|
|||||||
body
|
body
|
||||||
h1(style='margin-bottom: 24px;')= t('emails.addedContributionMessage.subject')
|
h1(style='margin-bottom: 24px;')= t('emails.addedContributionMessage.subject')
|
||||||
#container.col
|
#container.col
|
||||||
p(style='margin-bottom: 24px;')= t('emails.general.helloName', { firstName, lastName })
|
include ../hello.pug
|
||||||
p= t('emails.addedContributionMessage.commonGoodContributionMessage', { senderFirstName, senderLastName, contributionMemo })
|
p= t('emails.addedContributionMessage.commonGoodContributionMessage', { senderFirstName, senderLastName, contributionMemo })
|
||||||
p= t('emails.addedContributionMessage.toSeeAndAnswerMessage')
|
p= t('emails.addedContributionMessage.toSeeAndAnswerMessage')
|
||||||
p= t('emails.general.linkToYourAccount')
|
p
|
||||||
span= " "
|
= t('emails.general.linkToYourAccount')
|
||||||
|
= " "
|
||||||
a(href=overviewURL) #{overviewURL}
|
a(href=overviewURL) #{overviewURL}
|
||||||
p= t('emails.general.pleaseDoNotReply')
|
p= t('emails.general.pleaseDoNotReply')
|
||||||
p(style='margin-top: 24px;')= t('emails.general.sincerelyYours')
|
include ../greatingFormularImprint.pug
|
||||||
br
|
|
||||||
span= t('emails.general.yourGradidoTeam')
|
|
||||||
|
|||||||
@ -5,13 +5,12 @@ html(lang=locale)
|
|||||||
body
|
body
|
||||||
h1(style='margin-bottom: 24px;')= t('emails.contributionConfirmed.subject')
|
h1(style='margin-bottom: 24px;')= t('emails.contributionConfirmed.subject')
|
||||||
#container.col
|
#container.col
|
||||||
p(style='margin-bottom: 24px;')= t('emails.general.helloName', { firstName, lastName })
|
include ../hello.pug
|
||||||
p= t('emails.contributionConfirmed.commonGoodContributionConfirmed', { senderFirstName, senderLastName, contributionMemo })
|
p= t('emails.contributionConfirmed.commonGoodContributionConfirmed', { senderFirstName, senderLastName, contributionMemo })
|
||||||
p= t('emails.general.amountGDD', { amountGDD: contributionAmount })
|
p= t('emails.general.amountGDD', { amountGDD: contributionAmount })
|
||||||
p= t('emails.general.linkToYourAccount')
|
p
|
||||||
span= " "
|
= t('emails.general.linkToYourAccount')
|
||||||
|
= " "
|
||||||
a(href=overviewURL) #{overviewURL}
|
a(href=overviewURL) #{overviewURL}
|
||||||
p= t('emails.general.pleaseDoNotReply')
|
p= t('emails.general.pleaseDoNotReply')
|
||||||
p(style='margin-top: 24px;')= t('emails.general.sincerelyYours')
|
include ../greatingFormularImprint.pug
|
||||||
br
|
|
||||||
span= t('emails.general.yourGradidoTeam')
|
|
||||||
|
|||||||
@ -5,13 +5,12 @@ html(lang=locale)
|
|||||||
body
|
body
|
||||||
h1(style='margin-bottom: 24px;')= t('emails.contributionRejected.subject')
|
h1(style='margin-bottom: 24px;')= t('emails.contributionRejected.subject')
|
||||||
#container.col
|
#container.col
|
||||||
p(style='margin-bottom: 24px;')= t('emails.general.helloName', { firstName, lastName })
|
include ../hello.pug
|
||||||
p= t('emails.contributionRejected.commonGoodContributionRejected', { senderFirstName, senderLastName, contributionMemo })
|
p= t('emails.contributionRejected.commonGoodContributionRejected', { senderFirstName, senderLastName, contributionMemo })
|
||||||
p= t('emails.contributionRejected.toSeeContributionsAndMessages')
|
p= t('emails.contributionRejected.toSeeContributionsAndMessages')
|
||||||
p= t('emails.general.linkToYourAccount')
|
p
|
||||||
span= " "
|
= t('emails.general.linkToYourAccount')
|
||||||
|
= " "
|
||||||
a(href=overviewURL) #{overviewURL}
|
a(href=overviewURL) #{overviewURL}
|
||||||
p= t('emails.general.pleaseDoNotReply')
|
p= t('emails.general.pleaseDoNotReply')
|
||||||
p(style='margin-top: 24px;')= t('emails.general.sincerelyYours')
|
include ../greatingFormularImprint.pug
|
||||||
br
|
|
||||||
span= t('emails.general.yourGradidoTeam')
|
|
||||||
|
|||||||
16
backend/src/emails/templates/greatingFormularImprint.pug
Normal file
16
backend/src/emails/templates/greatingFormularImprint.pug
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
p(style='margin-top: 24px;')
|
||||||
|
= t('emails.general.sincerelyYours')
|
||||||
|
br
|
||||||
|
= t('emails.general.yourGradidoTeam')
|
||||||
|
p(style='margin-top: 24px;')= '—————'
|
||||||
|
p(style='margin-top: 24px;')
|
||||||
|
if t('general.imprintImageURL').length > 0
|
||||||
|
div(style='position: relative; left: -22px;')
|
||||||
|
img(src=t('general.imprintImageURL'), width='200', alt=t('general.imprintImageAlt'))
|
||||||
|
br
|
||||||
|
each line in t('general.imprint').split(/\n/)
|
||||||
|
= line
|
||||||
|
br
|
||||||
|
a(href='mailto:' + supportEmail)= supportEmail
|
||||||
|
br
|
||||||
|
a(href=communityURL)= communityURL
|
||||||
1
backend/src/emails/templates/hello.pug
Normal file
1
backend/src/emails/templates/hello.pug
Normal file
@ -0,0 +1 @@
|
|||||||
|
p= t('emails.general.helloName', { firstName, lastName })
|
||||||
@ -5,16 +5,16 @@ html(lang=locale)
|
|||||||
body
|
body
|
||||||
h1(style='margin-bottom: 24px;')= t('emails.resetPassword.subject')
|
h1(style='margin-bottom: 24px;')= t('emails.resetPassword.subject')
|
||||||
#container.col
|
#container.col
|
||||||
p(style='margin-bottom: 24px;')= t('emails.general.helloName', { firstName, lastName })
|
include ../hello.pug
|
||||||
p= t('emails.resetPassword.youOrSomeoneResetPassword')
|
p= t('emails.resetPassword.youOrSomeoneResetPassword')
|
||||||
p= t('emails.resetPassword.pleaseClickLink')
|
p
|
||||||
|
= t('emails.resetPassword.pleaseClickLink')
|
||||||
br
|
br
|
||||||
a(href=resetLink) #{resetLink}
|
a(href=resetLink) #{resetLink}
|
||||||
br
|
br
|
||||||
span= t('emails.general.orCopyLink')
|
= t('emails.general.orCopyLink')
|
||||||
p= t('emails.resetPassword.duration', { hours: timeDurationObject.hours, minutes: timeDurationObject.minutes })
|
p
|
||||||
|
= t('emails.resetPassword.duration', { hours: timeDurationObject.hours, minutes: timeDurationObject.minutes })
|
||||||
br
|
br
|
||||||
a(href=resendLink) #{resendLink}
|
a(href=resendLink) #{resendLink}
|
||||||
p(style='margin-top: 24px;')= t('emails.general.sincerelyYours')
|
include ../greatingFormularImprint.pug
|
||||||
br
|
|
||||||
span= t('emails.general.yourGradidoTeam')
|
|
||||||
|
|||||||
@ -5,15 +5,15 @@ html(lang=locale)
|
|||||||
body
|
body
|
||||||
h1(style='margin-bottom: 24px;')= t('emails.transactionLinkRedeemed.subject')
|
h1(style='margin-bottom: 24px;')= t('emails.transactionLinkRedeemed.subject')
|
||||||
#container.col
|
#container.col
|
||||||
p(style='margin-bottom: 24px;')= t('emails.general.helloName', { firstName, lastName })
|
include ../hello.pug
|
||||||
p= t('emails.transactionLinkRedeemed.hasRedeemedYourLink', { senderFirstName, senderLastName, senderEmail })
|
p= t('emails.transactionLinkRedeemed.hasRedeemedYourLink', { senderFirstName, senderLastName, senderEmail })
|
||||||
p= t('emails.general.amountGDD', { amountGDD: transactionAmount })
|
p
|
||||||
|
= t('emails.general.amountGDD', { amountGDD: transactionAmount })
|
||||||
br
|
br
|
||||||
span= t('emails.transactionLinkRedeemed.memo', { transactionMemo })
|
= t('emails.transactionLinkRedeemed.memo', { transactionMemo })
|
||||||
p= t('emails.general.detailsYouFindOnLinkToYourAccount')
|
p
|
||||||
span= " "
|
= t('emails.general.detailsYouFindOnLinkToYourAccount')
|
||||||
|
= " "
|
||||||
a(href=overviewURL) #{overviewURL}
|
a(href=overviewURL) #{overviewURL}
|
||||||
p= t('emails.general.pleaseDoNotReply')
|
p= t('emails.general.pleaseDoNotReply')
|
||||||
p(style='margin-top: 24px;')= t('emails.general.sincerelyYours')
|
include ../greatingFormularImprint.pug
|
||||||
br
|
|
||||||
span= t('emails.general.yourGradidoTeam')
|
|
||||||
|
|||||||
@ -1,16 +1,15 @@
|
|||||||
doctype html
|
doctype html
|
||||||
html(lang=locale)
|
html(lang=locale)
|
||||||
head
|
head
|
||||||
title= t('emails.transactionReceived.subject')
|
title= t('emails.transactionReceived.subject', { senderFirstName, senderLastName, transactionAmount })
|
||||||
body
|
body
|
||||||
h1(style='margin-bottom: 24px;')= t('emails.transactionReceived.subject')
|
h1(style='margin-bottom: 24px;')= t('emails.transactionReceived.subject', { senderFirstName, senderLastName, transactionAmount })
|
||||||
#container.col
|
#container.col
|
||||||
p(style='margin-bottom: 24px;')= t('emails.general.helloName', { firstName, lastName })
|
include ../hello.pug
|
||||||
p= t('emails.transactionReceived.haveReceivedAmountGDDFrom', { transactionAmount, senderFirstName, senderLastName, senderEmail })
|
p= t('emails.transactionReceived.haveReceivedAmountGDDFrom', { transactionAmount, senderFirstName, senderLastName, senderEmail })
|
||||||
p= t('emails.general.detailsYouFindOnLinkToYourAccount')
|
p
|
||||||
span= " "
|
= t('emails.general.detailsYouFindOnLinkToYourAccount')
|
||||||
|
= " "
|
||||||
a(href=overviewURL) #{overviewURL}
|
a(href=overviewURL) #{overviewURL}
|
||||||
p= t('emails.general.pleaseDoNotReply')
|
p= t('emails.general.pleaseDoNotReply')
|
||||||
p(style='margin-top: 24px;')= t('emails.general.sincerelyYours')
|
include ../greatingFormularImprint.pug
|
||||||
br
|
|
||||||
span= t('emails.general.yourGradidoTeam')
|
|
||||||
|
|||||||
@ -1 +1 @@
|
|||||||
= t('emails.transactionReceived.subject')
|
= t('emails.transactionReceived.subject', { senderFirstName, senderLastName, transactionAmount })
|
||||||
|
|||||||
@ -9,7 +9,6 @@ import { Community as DbCommunity } from '@entity/Community'
|
|||||||
import { testEnvironment, cleanDB } from '@test/helpers'
|
import { testEnvironment, cleanDB } from '@test/helpers'
|
||||||
|
|
||||||
CONFIG.FEDERATION_DHT_SEED = '64ebcb0e3ad547848fef4197c6e2332f'
|
CONFIG.FEDERATION_DHT_SEED = '64ebcb0e3ad547848fef4197c6e2332f'
|
||||||
CONFIG.FEDERATION_DHT_TEST_SOCKET = false
|
|
||||||
|
|
||||||
jest.mock('@hyperswarm/dht')
|
jest.mock('@hyperswarm/dht')
|
||||||
|
|
||||||
|
|||||||
@ -5,9 +5,8 @@ import { AuthChecker } from 'type-graphql'
|
|||||||
import { decode, encode } from '@/auth/JWT'
|
import { decode, encode } from '@/auth/JWT'
|
||||||
import { ROLE_UNAUTHORIZED, ROLE_USER, ROLE_ADMIN } from '@/auth/ROLES'
|
import { ROLE_UNAUTHORIZED, ROLE_USER, ROLE_ADMIN } from '@/auth/ROLES'
|
||||||
import { RIGHTS } from '@/auth/RIGHTS'
|
import { RIGHTS } from '@/auth/RIGHTS'
|
||||||
import { getCustomRepository } from '@dbTools/typeorm'
|
|
||||||
import { UserRepository } from '@repository/User'
|
|
||||||
import { INALIENABLE_RIGHTS } from '@/auth/INALIENABLE_RIGHTS'
|
import { INALIENABLE_RIGHTS } from '@/auth/INALIENABLE_RIGHTS'
|
||||||
|
import { User } from '@entity/User'
|
||||||
|
|
||||||
const isAuthorized: AuthChecker<any> = async ({ context }, rights) => {
|
const isAuthorized: AuthChecker<any> = async ({ context }, rights) => {
|
||||||
context.role = ROLE_UNAUTHORIZED // unauthorized user
|
context.role = ROLE_UNAUTHORIZED // unauthorized user
|
||||||
@ -26,14 +25,16 @@ const isAuthorized: AuthChecker<any> = async ({ context }, rights) => {
|
|||||||
if (!decoded) {
|
if (!decoded) {
|
||||||
throw new Error('403.13 - Client certificate revoked')
|
throw new Error('403.13 - Client certificate revoked')
|
||||||
}
|
}
|
||||||
// Set context pubKey
|
// Set context gradidoID
|
||||||
context.pubKey = Buffer.from(decoded.pubKey).toString('hex')
|
context.gradidoID = decoded.gradidoID
|
||||||
|
|
||||||
// TODO - load from database dynamically & admin - maybe encode this in the token to prevent many database requests
|
// TODO - load from database dynamically & admin - maybe encode this in the token to prevent many database requests
|
||||||
// TODO this implementation is bullshit - two database queries cause our user identifiers are not aligned and vary between email, id and pubKey
|
// TODO this implementation is bullshit - two database queries cause our user identifiers are not aligned and vary between email, id and pubKey
|
||||||
const userRepository = getCustomRepository(UserRepository)
|
|
||||||
try {
|
try {
|
||||||
const user = await userRepository.findByPubkeyHex(context.pubKey)
|
const user = await User.findOneOrFail({
|
||||||
|
where: { gradidoID: decoded.gradidoID },
|
||||||
|
relations: ['emailContact'],
|
||||||
|
})
|
||||||
context.user = user
|
context.user = user
|
||||||
context.role = user.isAdmin ? ROLE_ADMIN : ROLE_USER
|
context.role = user.isAdmin ? ROLE_ADMIN : ROLE_USER
|
||||||
} catch {
|
} catch {
|
||||||
@ -48,7 +49,7 @@ const isAuthorized: AuthChecker<any> = async ({ context }, rights) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// set new header token
|
// set new header token
|
||||||
context.setHeaders.push({ key: 'token', value: encode(decoded.pubKey) })
|
context.setHeaders.push({ key: 'token', value: encode(decoded.gradidoID) })
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1961,8 +1961,7 @@ describe('ContributionResolver', () => {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
// In the futrue this should not throw anymore
|
it('throws no error for the second confirmation', async () => {
|
||||||
it('throws an error for the second confirmation', async () => {
|
|
||||||
const r1 = mutate({
|
const r1 = mutate({
|
||||||
mutation: confirmContribution,
|
mutation: confirmContribution,
|
||||||
variables: {
|
variables: {
|
||||||
@ -1982,8 +1981,7 @@ describe('ContributionResolver', () => {
|
|||||||
)
|
)
|
||||||
await expect(r2).resolves.toEqual(
|
await expect(r2).resolves.toEqual(
|
||||||
expect.objectContaining({
|
expect.objectContaining({
|
||||||
// data: { confirmContribution: true },
|
data: { confirmContribution: true },
|
||||||
errors: [new GraphQLError('Creation was not successful.')],
|
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|||||||
@ -50,6 +50,7 @@ import {
|
|||||||
sendContributionConfirmedEmail,
|
sendContributionConfirmedEmail,
|
||||||
sendContributionRejectedEmail,
|
sendContributionRejectedEmail,
|
||||||
} from '@/emails/sendEmailVariants'
|
} from '@/emails/sendEmailVariants'
|
||||||
|
import { TRANSACTIONS_LOCK } from '@/util/TRANSACTIONS_LOCK'
|
||||||
|
|
||||||
@Resolver()
|
@Resolver()
|
||||||
export class ContributionResolver {
|
export class ContributionResolver {
|
||||||
@ -579,8 +580,10 @@ export class ContributionResolver {
|
|||||||
clientTimezoneOffset,
|
clientTimezoneOffset,
|
||||||
)
|
)
|
||||||
|
|
||||||
const receivedCallDate = new Date()
|
// acquire lock
|
||||||
|
const releaseLock = await TRANSACTIONS_LOCK.acquire()
|
||||||
|
|
||||||
|
const receivedCallDate = new Date()
|
||||||
const queryRunner = getConnection().createQueryRunner()
|
const queryRunner = getConnection().createQueryRunner()
|
||||||
await queryRunner.connect()
|
await queryRunner.connect()
|
||||||
await queryRunner.startTransaction('REPEATABLE READ') // 'READ COMMITTED')
|
await queryRunner.startTransaction('REPEATABLE READ') // 'READ COMMITTED')
|
||||||
@ -590,7 +593,7 @@ export class ContributionResolver {
|
|||||||
.select('transaction')
|
.select('transaction')
|
||||||
.from(DbTransaction, 'transaction')
|
.from(DbTransaction, 'transaction')
|
||||||
.where('transaction.userId = :id', { id: contribution.userId })
|
.where('transaction.userId = :id', { id: contribution.userId })
|
||||||
.orderBy('transaction.balanceDate', 'DESC')
|
.orderBy('transaction.id', 'DESC')
|
||||||
.getOne()
|
.getOne()
|
||||||
logger.info('lastTransaction ID', lastTransaction ? lastTransaction.id : 'undefined')
|
logger.info('lastTransaction ID', lastTransaction ? lastTransaction.id : 'undefined')
|
||||||
|
|
||||||
@ -639,10 +642,11 @@ export class ContributionResolver {
|
|||||||
})
|
})
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
await queryRunner.rollbackTransaction()
|
await queryRunner.rollbackTransaction()
|
||||||
logger.error(`Creation was not successful: ${e}`)
|
logger.error('Creation was not successful', e)
|
||||||
throw new Error(`Creation was not successful.`)
|
throw new Error('Creation was not successful.')
|
||||||
} finally {
|
} finally {
|
||||||
await queryRunner.release()
|
await queryRunner.release()
|
||||||
|
releaseLock()
|
||||||
}
|
}
|
||||||
|
|
||||||
const event = new Event()
|
const event = new Event()
|
||||||
|
|||||||
@ -23,6 +23,11 @@ import { User } from '@entity/User'
|
|||||||
import { UnconfirmedContribution } from '@model/UnconfirmedContribution'
|
import { UnconfirmedContribution } from '@model/UnconfirmedContribution'
|
||||||
import Decimal from 'decimal.js-light'
|
import Decimal from 'decimal.js-light'
|
||||||
import { GraphQLError } from 'graphql'
|
import { GraphQLError } from 'graphql'
|
||||||
|
import { TRANSACTIONS_LOCK } from '@/util/TRANSACTIONS_LOCK'
|
||||||
|
|
||||||
|
// mock semaphore to allow use fake timers
|
||||||
|
jest.mock('@/util/TRANSACTIONS_LOCK')
|
||||||
|
TRANSACTIONS_LOCK.acquire = jest.fn().mockResolvedValue(jest.fn())
|
||||||
|
|
||||||
let mutate: any, query: any, con: any
|
let mutate: any, query: any, con: any
|
||||||
let testEnv: any
|
let testEnv: any
|
||||||
@ -185,8 +190,7 @@ describe('TransactionLinkResolver', () => {
|
|||||||
describe('after one day', () => {
|
describe('after one day', () => {
|
||||||
beforeAll(async () => {
|
beforeAll(async () => {
|
||||||
jest.useFakeTimers()
|
jest.useFakeTimers()
|
||||||
/* eslint-disable-next-line @typescript-eslint/no-empty-function */
|
setTimeout(jest.fn(), 1000 * 60 * 60 * 24)
|
||||||
setTimeout(() => {}, 1000 * 60 * 60 * 24)
|
|
||||||
jest.runAllTimers()
|
jest.runAllTimers()
|
||||||
await mutate({
|
await mutate({
|
||||||
mutation: login,
|
mutation: login,
|
||||||
|
|||||||
@ -31,6 +31,7 @@ import { calculateDecay } from '@/util/decay'
|
|||||||
import { getUserCreation, validateContribution } from './util/creations'
|
import { getUserCreation, validateContribution } from './util/creations'
|
||||||
import { executeTransaction } from './TransactionResolver'
|
import { executeTransaction } from './TransactionResolver'
|
||||||
import QueryLinkResult from '@union/QueryLinkResult'
|
import QueryLinkResult from '@union/QueryLinkResult'
|
||||||
|
import { TRANSACTIONS_LOCK } from '@/util/TRANSACTIONS_LOCK'
|
||||||
|
|
||||||
// TODO: do not export, test it inside the resolver
|
// TODO: do not export, test it inside the resolver
|
||||||
export const transactionLinkCode = (date: Date): string => {
|
export const transactionLinkCode = (date: Date): string => {
|
||||||
@ -165,10 +166,12 @@ export class TransactionLinkResolver {
|
|||||||
): Promise<boolean> {
|
): Promise<boolean> {
|
||||||
const clientTimezoneOffset = getClientTimezoneOffset(context)
|
const clientTimezoneOffset = getClientTimezoneOffset(context)
|
||||||
const user = getUser(context)
|
const user = getUser(context)
|
||||||
const now = new Date()
|
|
||||||
|
|
||||||
if (code.match(/^CL-/)) {
|
if (code.match(/^CL-/)) {
|
||||||
|
// acquire lock
|
||||||
|
const releaseLock = await TRANSACTIONS_LOCK.acquire()
|
||||||
logger.info('redeem contribution link...')
|
logger.info('redeem contribution link...')
|
||||||
|
const now = new Date()
|
||||||
const queryRunner = getConnection().createQueryRunner()
|
const queryRunner = getConnection().createQueryRunner()
|
||||||
await queryRunner.connect()
|
await queryRunner.connect()
|
||||||
await queryRunner.startTransaction('REPEATABLE READ')
|
await queryRunner.startTransaction('REPEATABLE READ')
|
||||||
@ -273,7 +276,7 @@ export class TransactionLinkResolver {
|
|||||||
.select('transaction')
|
.select('transaction')
|
||||||
.from(DbTransaction, 'transaction')
|
.from(DbTransaction, 'transaction')
|
||||||
.where('transaction.userId = :id', { id: user.id })
|
.where('transaction.userId = :id', { id: user.id })
|
||||||
.orderBy('transaction.balanceDate', 'DESC')
|
.orderBy('transaction.id', 'DESC')
|
||||||
.getOne()
|
.getOne()
|
||||||
let newBalance = new Decimal(0)
|
let newBalance = new Decimal(0)
|
||||||
|
|
||||||
@ -309,9 +312,11 @@ export class TransactionLinkResolver {
|
|||||||
throw new Error(`Creation from contribution link was not successful. ${e}`)
|
throw new Error(`Creation from contribution link was not successful. ${e}`)
|
||||||
} finally {
|
} finally {
|
||||||
await queryRunner.release()
|
await queryRunner.release()
|
||||||
|
releaseLock()
|
||||||
}
|
}
|
||||||
return true
|
return true
|
||||||
} else {
|
} else {
|
||||||
|
const now = new Date()
|
||||||
const transactionLink = await DbTransactionLink.findOneOrFail({ code })
|
const transactionLink = await DbTransactionLink.findOneOrFail({ code })
|
||||||
const linkedUser = await DbUser.findOneOrFail(
|
const linkedUser = await DbUser.findOneOrFail(
|
||||||
{ id: transactionLink.userId },
|
{ id: transactionLink.userId },
|
||||||
@ -322,6 +327,9 @@ export class TransactionLinkResolver {
|
|||||||
throw new Error('Cannot redeem own transaction link.')
|
throw new Error('Cannot redeem own transaction link.')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: The now check should be done within the semaphore lock,
|
||||||
|
// since the program might wait a while till it is ready to proceed
|
||||||
|
// writing the transaction.
|
||||||
if (transactionLink.validUntil.getTime() < now.getTime()) {
|
if (transactionLink.validUntil.getTime() < now.getTime()) {
|
||||||
throw new Error('Transaction Link is not valid anymore.')
|
throw new Error('Transaction Link is not valid anymore.')
|
||||||
}
|
}
|
||||||
|
|||||||
@ -368,5 +368,74 @@ describe('send coins', () => {
|
|||||||
)
|
)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
describe('more transactions to test semaphore', () => {
|
||||||
|
it('sends the coins four times in a row', async () => {
|
||||||
|
await expect(
|
||||||
|
mutate({
|
||||||
|
mutation: sendCoins,
|
||||||
|
variables: {
|
||||||
|
email: 'peter@lustig.de',
|
||||||
|
amount: 10,
|
||||||
|
memo: 'first transaction',
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
).resolves.toEqual(
|
||||||
|
expect.objectContaining({
|
||||||
|
data: {
|
||||||
|
sendCoins: 'true',
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
await expect(
|
||||||
|
mutate({
|
||||||
|
mutation: sendCoins,
|
||||||
|
variables: {
|
||||||
|
email: 'peter@lustig.de',
|
||||||
|
amount: 20,
|
||||||
|
memo: 'second transaction',
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
).resolves.toEqual(
|
||||||
|
expect.objectContaining({
|
||||||
|
data: {
|
||||||
|
sendCoins: 'true',
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
await expect(
|
||||||
|
mutate({
|
||||||
|
mutation: sendCoins,
|
||||||
|
variables: {
|
||||||
|
email: 'peter@lustig.de',
|
||||||
|
amount: 30,
|
||||||
|
memo: 'third transaction',
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
).resolves.toEqual(
|
||||||
|
expect.objectContaining({
|
||||||
|
data: {
|
||||||
|
sendCoins: 'true',
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
await expect(
|
||||||
|
mutate({
|
||||||
|
mutation: sendCoins,
|
||||||
|
variables: {
|
||||||
|
email: 'peter@lustig.de',
|
||||||
|
amount: 40,
|
||||||
|
memo: 'fourth transaction',
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
).resolves.toEqual(
|
||||||
|
expect.objectContaining({
|
||||||
|
data: {
|
||||||
|
sendCoins: 'true',
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
@ -16,12 +16,12 @@ import { Transaction } from '@model/Transaction'
|
|||||||
import { TransactionList } from '@model/TransactionList'
|
import { TransactionList } from '@model/TransactionList'
|
||||||
import { Order } from '@enum/Order'
|
import { Order } from '@enum/Order'
|
||||||
import { TransactionTypeId } from '@enum/TransactionTypeId'
|
import { TransactionTypeId } from '@enum/TransactionTypeId'
|
||||||
|
import { calculateBalance } from '@/util/validate'
|
||||||
import TransactionSendArgs from '@arg/TransactionSendArgs'
|
import TransactionSendArgs from '@arg/TransactionSendArgs'
|
||||||
import Paginated from '@arg/Paginated'
|
import Paginated from '@arg/Paginated'
|
||||||
|
|
||||||
import { backendLogger as logger } from '@/server/logger'
|
import { backendLogger as logger } from '@/server/logger'
|
||||||
import { Context, getUser } from '@/server/context'
|
import { Context, getUser } from '@/server/context'
|
||||||
import { calculateBalance, isHexPublicKey } from '@/util/validate'
|
|
||||||
import { RIGHTS } from '@/auth/RIGHTS'
|
import { RIGHTS } from '@/auth/RIGHTS'
|
||||||
import { communityUser } from '@/util/communityUser'
|
import { communityUser } from '@/util/communityUser'
|
||||||
import { virtualLinkTransaction, virtualDecayTransaction } from '@/util/virtualTransactions'
|
import { virtualLinkTransaction, virtualDecayTransaction } from '@/util/virtualTransactions'
|
||||||
@ -36,6 +36,8 @@ import { BalanceResolver } from './BalanceResolver'
|
|||||||
import { MEMO_MAX_CHARS, MEMO_MIN_CHARS } from './const/const'
|
import { MEMO_MAX_CHARS, MEMO_MIN_CHARS } from './const/const'
|
||||||
import { findUserByEmail } from './UserResolver'
|
import { findUserByEmail } from './UserResolver'
|
||||||
|
|
||||||
|
import { TRANSACTIONS_LOCK } from '@/util/TRANSACTIONS_LOCK'
|
||||||
|
|
||||||
export const executeTransaction = async (
|
export const executeTransaction = async (
|
||||||
amount: Decimal,
|
amount: Decimal,
|
||||||
memo: string,
|
memo: string,
|
||||||
@ -62,124 +64,133 @@ export const executeTransaction = async (
|
|||||||
throw new Error(`memo text is too short (${MEMO_MIN_CHARS} characters minimum)`)
|
throw new Error(`memo text is too short (${MEMO_MIN_CHARS} characters minimum)`)
|
||||||
}
|
}
|
||||||
|
|
||||||
// validate amount
|
// acquire lock
|
||||||
const receivedCallDate = new Date()
|
const releaseLock = await TRANSACTIONS_LOCK.acquire()
|
||||||
const sendBalance = await calculateBalance(
|
|
||||||
sender.id,
|
|
||||||
amount.mul(-1),
|
|
||||||
receivedCallDate,
|
|
||||||
transactionLink,
|
|
||||||
)
|
|
||||||
logger.debug(`calculated Balance=${sendBalance}`)
|
|
||||||
if (!sendBalance) {
|
|
||||||
logger.error(`user hasn't enough GDD or amount is < 0 : balance=${sendBalance}`)
|
|
||||||
throw new Error("user hasn't enough GDD or amount is < 0")
|
|
||||||
}
|
|
||||||
|
|
||||||
const queryRunner = getConnection().createQueryRunner()
|
|
||||||
await queryRunner.connect()
|
|
||||||
await queryRunner.startTransaction('REPEATABLE READ')
|
|
||||||
logger.debug(`open Transaction to write...`)
|
|
||||||
try {
|
try {
|
||||||
// transaction
|
// validate amount
|
||||||
const transactionSend = new dbTransaction()
|
const receivedCallDate = new Date()
|
||||||
transactionSend.typeId = TransactionTypeId.SEND
|
const sendBalance = await calculateBalance(
|
||||||
transactionSend.memo = memo
|
sender.id,
|
||||||
transactionSend.userId = sender.id
|
amount.mul(-1),
|
||||||
transactionSend.linkedUserId = recipient.id
|
receivedCallDate,
|
||||||
transactionSend.amount = amount.mul(-1)
|
transactionLink,
|
||||||
transactionSend.balance = sendBalance.balance
|
)
|
||||||
transactionSend.balanceDate = receivedCallDate
|
logger.debug(`calculated Balance=${sendBalance}`)
|
||||||
transactionSend.decay = sendBalance.decay.decay
|
if (!sendBalance) {
|
||||||
transactionSend.decayStart = sendBalance.decay.start
|
logger.error(`user hasn't enough GDD or amount is < 0 : balance=${sendBalance}`)
|
||||||
transactionSend.previous = sendBalance.lastTransactionId
|
throw new Error("user hasn't enough GDD or amount is < 0")
|
||||||
transactionSend.transactionLinkId = transactionLink ? transactionLink.id : null
|
|
||||||
await queryRunner.manager.insert(dbTransaction, transactionSend)
|
|
||||||
|
|
||||||
logger.debug(`sendTransaction inserted: ${dbTransaction}`)
|
|
||||||
|
|
||||||
const transactionReceive = new dbTransaction()
|
|
||||||
transactionReceive.typeId = TransactionTypeId.RECEIVE
|
|
||||||
transactionReceive.memo = memo
|
|
||||||
transactionReceive.userId = recipient.id
|
|
||||||
transactionReceive.linkedUserId = sender.id
|
|
||||||
transactionReceive.amount = amount
|
|
||||||
const receiveBalance = await calculateBalance(recipient.id, amount, receivedCallDate)
|
|
||||||
transactionReceive.balance = receiveBalance ? receiveBalance.balance : amount
|
|
||||||
transactionReceive.balanceDate = receivedCallDate
|
|
||||||
transactionReceive.decay = receiveBalance ? receiveBalance.decay.decay : new Decimal(0)
|
|
||||||
transactionReceive.decayStart = receiveBalance ? receiveBalance.decay.start : null
|
|
||||||
transactionReceive.previous = receiveBalance ? receiveBalance.lastTransactionId : null
|
|
||||||
transactionReceive.linkedTransactionId = transactionSend.id
|
|
||||||
transactionReceive.transactionLinkId = transactionLink ? transactionLink.id : null
|
|
||||||
await queryRunner.manager.insert(dbTransaction, transactionReceive)
|
|
||||||
logger.debug(`receive Transaction inserted: ${dbTransaction}`)
|
|
||||||
|
|
||||||
// Save linked transaction id for send
|
|
||||||
transactionSend.linkedTransactionId = transactionReceive.id
|
|
||||||
await queryRunner.manager.update(dbTransaction, { id: transactionSend.id }, transactionSend)
|
|
||||||
logger.debug(`send Transaction updated: ${transactionSend}`)
|
|
||||||
|
|
||||||
if (transactionLink) {
|
|
||||||
logger.info(`transactionLink: ${transactionLink}`)
|
|
||||||
transactionLink.redeemedAt = receivedCallDate
|
|
||||||
transactionLink.redeemedBy = recipient.id
|
|
||||||
await queryRunner.manager.update(
|
|
||||||
dbTransactionLink,
|
|
||||||
{ id: transactionLink.id },
|
|
||||||
transactionLink,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
await queryRunner.commitTransaction()
|
const queryRunner = getConnection().createQueryRunner()
|
||||||
logger.info(`commit Transaction successful...`)
|
await queryRunner.connect()
|
||||||
|
await queryRunner.startTransaction('REPEATABLE READ')
|
||||||
|
logger.debug(`open Transaction to write...`)
|
||||||
|
try {
|
||||||
|
// transaction
|
||||||
|
const transactionSend = new dbTransaction()
|
||||||
|
transactionSend.typeId = TransactionTypeId.SEND
|
||||||
|
transactionSend.memo = memo
|
||||||
|
transactionSend.userId = sender.id
|
||||||
|
transactionSend.linkedUserId = recipient.id
|
||||||
|
transactionSend.amount = amount.mul(-1)
|
||||||
|
transactionSend.balance = sendBalance.balance
|
||||||
|
transactionSend.balanceDate = receivedCallDate
|
||||||
|
transactionSend.decay = sendBalance.decay.decay
|
||||||
|
transactionSend.decayStart = sendBalance.decay.start
|
||||||
|
transactionSend.previous = sendBalance.lastTransactionId
|
||||||
|
transactionSend.transactionLinkId = transactionLink ? transactionLink.id : null
|
||||||
|
await queryRunner.manager.insert(dbTransaction, transactionSend)
|
||||||
|
|
||||||
const eventTransactionSend = new EventTransactionSend()
|
logger.debug(`sendTransaction inserted: ${dbTransaction}`)
|
||||||
eventTransactionSend.userId = transactionSend.userId
|
|
||||||
eventTransactionSend.xUserId = transactionSend.linkedUserId
|
|
||||||
eventTransactionSend.transactionId = transactionSend.id
|
|
||||||
eventTransactionSend.amount = transactionSend.amount.mul(-1)
|
|
||||||
await eventProtocol.writeEvent(new Event().setEventTransactionSend(eventTransactionSend))
|
|
||||||
|
|
||||||
const eventTransactionReceive = new EventTransactionReceive()
|
const transactionReceive = new dbTransaction()
|
||||||
eventTransactionReceive.userId = transactionReceive.userId
|
transactionReceive.typeId = TransactionTypeId.RECEIVE
|
||||||
eventTransactionReceive.xUserId = transactionReceive.linkedUserId
|
transactionReceive.memo = memo
|
||||||
eventTransactionReceive.transactionId = transactionReceive.id
|
transactionReceive.userId = recipient.id
|
||||||
eventTransactionReceive.amount = transactionReceive.amount
|
transactionReceive.linkedUserId = sender.id
|
||||||
await eventProtocol.writeEvent(new Event().setEventTransactionReceive(eventTransactionReceive))
|
transactionReceive.amount = amount
|
||||||
} catch (e) {
|
const receiveBalance = await calculateBalance(recipient.id, amount, receivedCallDate)
|
||||||
await queryRunner.rollbackTransaction()
|
transactionReceive.balance = receiveBalance ? receiveBalance.balance : amount
|
||||||
logger.error(`Transaction was not successful: ${e}`)
|
transactionReceive.balanceDate = receivedCallDate
|
||||||
throw new Error(`Transaction was not successful: ${e}`)
|
transactionReceive.decay = receiveBalance ? receiveBalance.decay.decay : new Decimal(0)
|
||||||
} finally {
|
transactionReceive.decayStart = receiveBalance ? receiveBalance.decay.start : null
|
||||||
await queryRunner.release()
|
transactionReceive.previous = receiveBalance ? receiveBalance.lastTransactionId : null
|
||||||
}
|
transactionReceive.linkedTransactionId = transactionSend.id
|
||||||
logger.debug(`prepare Email for transaction received...`)
|
transactionReceive.transactionLinkId = transactionLink ? transactionLink.id : null
|
||||||
await sendTransactionReceivedEmail({
|
await queryRunner.manager.insert(dbTransaction, transactionReceive)
|
||||||
firstName: recipient.firstName,
|
logger.debug(`receive Transaction inserted: ${dbTransaction}`)
|
||||||
lastName: recipient.lastName,
|
|
||||||
email: recipient.emailContact.email,
|
// Save linked transaction id for send
|
||||||
language: recipient.language,
|
transactionSend.linkedTransactionId = transactionReceive.id
|
||||||
senderFirstName: sender.firstName,
|
await queryRunner.manager.update(dbTransaction, { id: transactionSend.id }, transactionSend)
|
||||||
senderLastName: sender.lastName,
|
logger.debug(`send Transaction updated: ${transactionSend}`)
|
||||||
senderEmail: sender.emailContact.email,
|
|
||||||
transactionAmount: amount,
|
if (transactionLink) {
|
||||||
})
|
logger.info(`transactionLink: ${transactionLink}`)
|
||||||
if (transactionLink) {
|
transactionLink.redeemedAt = receivedCallDate
|
||||||
await sendTransactionLinkRedeemedEmail({
|
transactionLink.redeemedBy = recipient.id
|
||||||
firstName: sender.firstName,
|
await queryRunner.manager.update(
|
||||||
lastName: sender.lastName,
|
dbTransactionLink,
|
||||||
email: sender.emailContact.email,
|
{ id: transactionLink.id },
|
||||||
language: sender.language,
|
transactionLink,
|
||||||
senderFirstName: recipient.firstName,
|
)
|
||||||
senderLastName: recipient.lastName,
|
}
|
||||||
senderEmail: recipient.emailContact.email,
|
|
||||||
|
await queryRunner.commitTransaction()
|
||||||
|
logger.info(`commit Transaction successful...`)
|
||||||
|
|
||||||
|
const eventTransactionSend = new EventTransactionSend()
|
||||||
|
eventTransactionSend.userId = transactionSend.userId
|
||||||
|
eventTransactionSend.xUserId = transactionSend.linkedUserId
|
||||||
|
eventTransactionSend.transactionId = transactionSend.id
|
||||||
|
eventTransactionSend.amount = transactionSend.amount.mul(-1)
|
||||||
|
await eventProtocol.writeEvent(new Event().setEventTransactionSend(eventTransactionSend))
|
||||||
|
|
||||||
|
const eventTransactionReceive = new EventTransactionReceive()
|
||||||
|
eventTransactionReceive.userId = transactionReceive.userId
|
||||||
|
eventTransactionReceive.xUserId = transactionReceive.linkedUserId
|
||||||
|
eventTransactionReceive.transactionId = transactionReceive.id
|
||||||
|
eventTransactionReceive.amount = transactionReceive.amount
|
||||||
|
await eventProtocol.writeEvent(
|
||||||
|
new Event().setEventTransactionReceive(eventTransactionReceive),
|
||||||
|
)
|
||||||
|
} catch (e) {
|
||||||
|
await queryRunner.rollbackTransaction()
|
||||||
|
logger.error(`Transaction was not successful: ${e}`)
|
||||||
|
throw new Error(`Transaction was not successful: ${e}`)
|
||||||
|
} finally {
|
||||||
|
await queryRunner.release()
|
||||||
|
}
|
||||||
|
logger.debug(`prepare Email for transaction received...`)
|
||||||
|
await sendTransactionReceivedEmail({
|
||||||
|
firstName: recipient.firstName,
|
||||||
|
lastName: recipient.lastName,
|
||||||
|
email: recipient.emailContact.email,
|
||||||
|
language: recipient.language,
|
||||||
|
senderFirstName: sender.firstName,
|
||||||
|
senderLastName: sender.lastName,
|
||||||
|
senderEmail: sender.emailContact.email,
|
||||||
transactionAmount: amount,
|
transactionAmount: amount,
|
||||||
transactionMemo: memo,
|
|
||||||
})
|
})
|
||||||
|
if (transactionLink) {
|
||||||
|
await sendTransactionLinkRedeemedEmail({
|
||||||
|
firstName: sender.firstName,
|
||||||
|
lastName: sender.lastName,
|
||||||
|
email: sender.emailContact.email,
|
||||||
|
language: sender.language,
|
||||||
|
senderFirstName: recipient.firstName,
|
||||||
|
senderLastName: recipient.lastName,
|
||||||
|
senderEmail: recipient.emailContact.email,
|
||||||
|
transactionAmount: amount,
|
||||||
|
transactionMemo: memo,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
logger.info(`finished executeTransaction successfully`)
|
||||||
|
return true
|
||||||
|
} finally {
|
||||||
|
releaseLock()
|
||||||
}
|
}
|
||||||
logger.info(`finished executeTransaction successfully`)
|
|
||||||
return true
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Resolver()
|
@Resolver()
|
||||||
@ -315,10 +326,6 @@ export class TransactionResolver {
|
|||||||
|
|
||||||
// TODO this is subject to replay attacks
|
// TODO this is subject to replay attacks
|
||||||
const senderUser = getUser(context)
|
const senderUser = getUser(context)
|
||||||
if (senderUser.pubKey.length !== 32) {
|
|
||||||
logger.error(`invalid sender public key:${senderUser.pubKey}`)
|
|
||||||
throw new Error('invalid sender public key')
|
|
||||||
}
|
|
||||||
|
|
||||||
// validate recipient user
|
// validate recipient user
|
||||||
const recipientUser = await findUserByEmail(email)
|
const recipientUser = await findUserByEmail(email)
|
||||||
@ -331,10 +338,6 @@ export class TransactionResolver {
|
|||||||
logger.error(`The recipient account is not activated: recipientUser=${recipientUser}`)
|
logger.error(`The recipient account is not activated: recipientUser=${recipientUser}`)
|
||||||
throw new Error('The recipient account is not activated')
|
throw new Error('The recipient account is not activated')
|
||||||
}
|
}
|
||||||
if (!isHexPublicKey(recipientUser.pubKey.toString('hex'))) {
|
|
||||||
logger.error(`invalid recipient public key: recipientUser=${recipientUser}`)
|
|
||||||
throw new Error('invalid recipient public key')
|
|
||||||
}
|
|
||||||
|
|
||||||
await executeTransaction(amount, memo, senderUser, recipientUser)
|
await executeTransaction(amount, memo, senderUser, recipientUser)
|
||||||
logger.info(
|
logger.info(
|
||||||
|
|||||||
@ -138,12 +138,8 @@ describe('UserResolver', () => {
|
|||||||
firstName: 'Peter',
|
firstName: 'Peter',
|
||||||
lastName: 'Lustig',
|
lastName: 'Lustig',
|
||||||
password: '0',
|
password: '0',
|
||||||
pubKey: null,
|
|
||||||
privKey: null,
|
|
||||||
// emailHash: expect.any(Buffer),
|
|
||||||
createdAt: expect.any(Date),
|
createdAt: expect.any(Date),
|
||||||
// emailChecked: false,
|
// emailChecked: false,
|
||||||
passphrase: expect.any(String),
|
|
||||||
language: 'de',
|
language: 'de',
|
||||||
isAdmin: null,
|
isAdmin: null,
|
||||||
deletedAt: null,
|
deletedAt: null,
|
||||||
|
|||||||
@ -1,4 +1,3 @@
|
|||||||
import fs from 'fs'
|
|
||||||
import i18n from 'i18n'
|
import i18n from 'i18n'
|
||||||
import { v4 as uuidv4 } from 'uuid'
|
import { v4 as uuidv4 } from 'uuid'
|
||||||
import {
|
import {
|
||||||
@ -60,8 +59,8 @@ import {
|
|||||||
EventActivateAccount,
|
EventActivateAccount,
|
||||||
} from '@/event/Event'
|
} from '@/event/Event'
|
||||||
import { getUserCreation, getUserCreations } from './util/creations'
|
import { getUserCreation, getUserCreations } from './util/creations'
|
||||||
|
import { isValidPassword } from '@/password/EncryptorUtils'
|
||||||
import { FULL_CREATION_AVAILABLE } from './const/const'
|
import { FULL_CREATION_AVAILABLE } from './const/const'
|
||||||
import { isValidPassword, SecretKeyCryptographyCreateKey } from '@/password/EncryptorUtils'
|
|
||||||
import { encryptPassword, verifyPassword } from '@/password/PasswordEncryptor'
|
import { encryptPassword, verifyPassword } from '@/password/PasswordEncryptor'
|
||||||
import { PasswordEncryptionType } from '../enum/PasswordEncryptionType'
|
import { PasswordEncryptionType } from '../enum/PasswordEncryptionType'
|
||||||
|
|
||||||
@ -76,79 +75,6 @@ const isLanguage = (language: string): boolean => {
|
|||||||
return LANGUAGES.includes(language)
|
return LANGUAGES.includes(language)
|
||||||
}
|
}
|
||||||
|
|
||||||
const PHRASE_WORD_COUNT = 24
|
|
||||||
const WORDS = fs
|
|
||||||
.readFileSync('src/config/mnemonic.uncompressed_buffer13116.txt')
|
|
||||||
.toString()
|
|
||||||
.split(',')
|
|
||||||
const PassphraseGenerate = (): string[] => {
|
|
||||||
logger.trace('PassphraseGenerate...')
|
|
||||||
const result = []
|
|
||||||
for (let i = 0; i < PHRASE_WORD_COUNT; i++) {
|
|
||||||
result.push(WORDS[sodium.randombytes_random() % 2048])
|
|
||||||
}
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
|
|
||||||
const KeyPairEd25519Create = (passphrase: string[]): Buffer[] => {
|
|
||||||
logger.trace('KeyPairEd25519Create...')
|
|
||||||
if (!passphrase.length || passphrase.length < PHRASE_WORD_COUNT) {
|
|
||||||
logger.error('passphrase empty or to short')
|
|
||||||
throw new Error('passphrase empty or to short')
|
|
||||||
}
|
|
||||||
|
|
||||||
const state = Buffer.alloc(sodium.crypto_hash_sha512_STATEBYTES)
|
|
||||||
sodium.crypto_hash_sha512_init(state)
|
|
||||||
|
|
||||||
// To prevent breaking existing passphrase-hash combinations word indices will be put into 64 Bit Variable to mimic first implementation of algorithms
|
|
||||||
for (let i = 0; i < PHRASE_WORD_COUNT; i++) {
|
|
||||||
const value = Buffer.alloc(8)
|
|
||||||
const wordIndex = WORDS.indexOf(passphrase[i])
|
|
||||||
value.writeBigInt64LE(BigInt(wordIndex))
|
|
||||||
sodium.crypto_hash_sha512_update(state, value)
|
|
||||||
}
|
|
||||||
// trailing space is part of the login_server implementation
|
|
||||||
const clearPassphrase = passphrase.join(' ') + ' '
|
|
||||||
sodium.crypto_hash_sha512_update(state, Buffer.from(clearPassphrase))
|
|
||||||
const outputHashBuffer = Buffer.alloc(sodium.crypto_hash_sha512_BYTES)
|
|
||||||
sodium.crypto_hash_sha512_final(state, outputHashBuffer)
|
|
||||||
|
|
||||||
const pubKey = Buffer.alloc(sodium.crypto_sign_PUBLICKEYBYTES)
|
|
||||||
const privKey = Buffer.alloc(sodium.crypto_sign_SECRETKEYBYTES)
|
|
||||||
|
|
||||||
sodium.crypto_sign_seed_keypair(
|
|
||||||
pubKey,
|
|
||||||
privKey,
|
|
||||||
outputHashBuffer.slice(0, sodium.crypto_sign_SEEDBYTES),
|
|
||||||
)
|
|
||||||
logger.debug(`KeyPair creation ready. pubKey=${pubKey}`)
|
|
||||||
|
|
||||||
return [pubKey, privKey]
|
|
||||||
}
|
|
||||||
|
|
||||||
const SecretKeyCryptographyEncrypt = (message: Buffer, encryptionKey: Buffer): Buffer => {
|
|
||||||
logger.trace('SecretKeyCryptographyEncrypt...')
|
|
||||||
const encrypted = Buffer.alloc(message.length + sodium.crypto_secretbox_MACBYTES)
|
|
||||||
const nonce = Buffer.alloc(sodium.crypto_secretbox_NONCEBYTES)
|
|
||||||
nonce.fill(31) // static nonce
|
|
||||||
|
|
||||||
sodium.crypto_secretbox_easy(encrypted, message, nonce, encryptionKey)
|
|
||||||
logger.debug(`SecretKeyCryptographyEncrypt...successful: ${encrypted}`)
|
|
||||||
return encrypted
|
|
||||||
}
|
|
||||||
|
|
||||||
const SecretKeyCryptographyDecrypt = (encryptedMessage: Buffer, encryptionKey: Buffer): Buffer => {
|
|
||||||
logger.trace('SecretKeyCryptographyDecrypt...')
|
|
||||||
const message = Buffer.alloc(encryptedMessage.length - sodium.crypto_secretbox_MACBYTES)
|
|
||||||
const nonce = Buffer.alloc(sodium.crypto_secretbox_NONCEBYTES)
|
|
||||||
nonce.fill(31) // static nonce
|
|
||||||
|
|
||||||
sodium.crypto_secretbox_open_easy(message, encryptedMessage, nonce, encryptionKey)
|
|
||||||
|
|
||||||
logger.debug(`SecretKeyCryptographyDecrypt...successful: ${message}`)
|
|
||||||
return message
|
|
||||||
}
|
|
||||||
|
|
||||||
const newEmailContact = (email: string, userId: number): DbUserContact => {
|
const newEmailContact = (email: string, userId: number): DbUserContact => {
|
||||||
logger.trace(`newEmailContact...`)
|
logger.trace(`newEmailContact...`)
|
||||||
const emailContact = new DbUserContact()
|
const emailContact = new DbUserContact()
|
||||||
@ -191,7 +117,6 @@ export class UserResolver {
|
|||||||
const clientTimezoneOffset = getClientTimezoneOffset(context)
|
const clientTimezoneOffset = getClientTimezoneOffset(context)
|
||||||
const userEntity = getUser(context)
|
const userEntity = getUser(context)
|
||||||
const user = new User(userEntity, await getUserCreation(userEntity.id, clientTimezoneOffset))
|
const user = new User(userEntity, await getUserCreation(userEntity.id, clientTimezoneOffset))
|
||||||
// user.pubkey = userEntity.pubKey.toString('hex')
|
|
||||||
// Elopage Status & Stored PublisherId
|
// Elopage Status & Stored PublisherId
|
||||||
user.hasElopage = await this.hasElopage(context)
|
user.hasElopage = await this.hasElopage(context)
|
||||||
|
|
||||||
@ -223,11 +148,6 @@ export class UserResolver {
|
|||||||
// TODO we want to catch this on the frontend and ask the user to check his emails or resend code
|
// 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')
|
throw new Error('User has no password set yet')
|
||||||
}
|
}
|
||||||
if (!dbUser.pubKey || !dbUser.privKey) {
|
|
||||||
logger.error('The User has no private or publicKey.')
|
|
||||||
// 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')
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!verifyPassword(dbUser, password)) {
|
if (!verifyPassword(dbUser, password)) {
|
||||||
logger.error('The User has no valid credentials.')
|
logger.error('The User has no valid credentials.')
|
||||||
@ -259,7 +179,7 @@ export class UserResolver {
|
|||||||
|
|
||||||
context.setHeaders.push({
|
context.setHeaders.push({
|
||||||
key: 'token',
|
key: 'token',
|
||||||
value: encode(dbUser.pubKey),
|
value: encode(dbUser.gradidoID),
|
||||||
})
|
})
|
||||||
const ev = new EventLogin()
|
const ev = new EventLogin()
|
||||||
ev.userId = user.id
|
ev.userId = user.id
|
||||||
@ -352,11 +272,6 @@ export class UserResolver {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const passphrase = PassphraseGenerate()
|
|
||||||
// const keyPair = KeyPairEd25519Create(passphrase) // return pub, priv Key
|
|
||||||
// const passwordHash = SecretKeyCryptographyCreateKey(email, password) // return short and long hash
|
|
||||||
// const encryptedPrivkey = SecretKeyCryptographyEncrypt(keyPair[1], passwordHash[1])
|
|
||||||
// const emailHash = getEmailHash(email)
|
|
||||||
const gradidoID = await newGradidoID()
|
const gradidoID = await newGradidoID()
|
||||||
|
|
||||||
const eventRegister = new EventRegister()
|
const eventRegister = new EventRegister()
|
||||||
@ -370,7 +285,6 @@ export class UserResolver {
|
|||||||
dbUser.language = language
|
dbUser.language = language
|
||||||
dbUser.publisherId = publisherId
|
dbUser.publisherId = publisherId
|
||||||
dbUser.passwordEncryptionType = PasswordEncryptionType.NO_PASSWORD
|
dbUser.passwordEncryptionType = PasswordEncryptionType.NO_PASSWORD
|
||||||
dbUser.passphrase = passphrase.join(' ')
|
|
||||||
logger.debug('new dbUser=' + dbUser)
|
logger.debug('new dbUser=' + dbUser)
|
||||||
if (redeemCode) {
|
if (redeemCode) {
|
||||||
if (redeemCode.match(/^CL-/)) {
|
if (redeemCode.match(/^CL-/)) {
|
||||||
@ -391,12 +305,6 @@ export class UserResolver {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// TODO this field has no null allowed unlike the loginServer table
|
|
||||||
// dbUser.pubKey = Buffer.from(randomBytes(32)) // Buffer.alloc(32, 0) default to 0000...
|
|
||||||
// dbUser.pubkey = keyPair[0]
|
|
||||||
// loginUser.password = passwordHash[0].readBigUInt64LE() // using the shorthash
|
|
||||||
// loginUser.pubKey = keyPair[0]
|
|
||||||
// loginUser.privKey = encryptedPrivkey
|
|
||||||
|
|
||||||
const queryRunner = getConnection().createQueryRunner()
|
const queryRunner = getConnection().createQueryRunner()
|
||||||
await queryRunner.connect()
|
await queryRunner.connect()
|
||||||
@ -575,34 +483,12 @@ export class UserResolver {
|
|||||||
const user = userContact.user
|
const user = userContact.user
|
||||||
logger.debug('user with EmailVerificationCode found...')
|
logger.debug('user with EmailVerificationCode found...')
|
||||||
|
|
||||||
// Generate Passphrase if needed
|
|
||||||
if (!user.passphrase) {
|
|
||||||
const passphrase = PassphraseGenerate()
|
|
||||||
user.passphrase = passphrase.join(' ')
|
|
||||||
logger.debug('new Passphrase generated...')
|
|
||||||
}
|
|
||||||
|
|
||||||
const passphrase = user.passphrase.split(' ')
|
|
||||||
if (passphrase.length < PHRASE_WORD_COUNT) {
|
|
||||||
logger.error('Could not load a correct passphrase')
|
|
||||||
// TODO if this can happen we cannot recover from that
|
|
||||||
// this seem to be good on production data, if we dont
|
|
||||||
// make a coding mistake we do not have a problem here
|
|
||||||
throw new Error('Could not load a correct passphrase')
|
|
||||||
}
|
|
||||||
logger.debug('Passphrase is valid...')
|
|
||||||
|
|
||||||
// Activate EMail
|
// Activate EMail
|
||||||
userContact.emailChecked = true
|
userContact.emailChecked = true
|
||||||
|
|
||||||
// Update Password
|
// Update Password
|
||||||
user.passwordEncryptionType = PasswordEncryptionType.GRADIDO_ID
|
user.passwordEncryptionType = PasswordEncryptionType.GRADIDO_ID
|
||||||
const passwordHash = SecretKeyCryptographyCreateKey(userContact.email, password) // return short and long hash
|
|
||||||
const keyPair = KeyPairEd25519Create(passphrase) // return pub, priv Key
|
|
||||||
const encryptedPrivkey = SecretKeyCryptographyEncrypt(keyPair[1], passwordHash[1])
|
|
||||||
user.password = encryptPassword(user, password)
|
user.password = encryptPassword(user, password)
|
||||||
user.pubKey = keyPair[0]
|
|
||||||
user.privKey = encryptedPrivkey
|
|
||||||
logger.debug('User credentials updated ...')
|
logger.debug('User credentials updated ...')
|
||||||
|
|
||||||
const queryRunner = getConnection().createQueryRunner()
|
const queryRunner = getConnection().createQueryRunner()
|
||||||
@ -713,30 +599,14 @@ export class UserResolver {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: This had some error cases defined - like missing private key. This is no longer checked.
|
|
||||||
const oldPasswordHash = SecretKeyCryptographyCreateKey(
|
|
||||||
userEntity.emailContact.email,
|
|
||||||
password,
|
|
||||||
)
|
|
||||||
if (!verifyPassword(userEntity, password)) {
|
if (!verifyPassword(userEntity, password)) {
|
||||||
logger.error(`Old password is invalid`)
|
logger.error(`Old password is invalid`)
|
||||||
throw new Error(`Old password is invalid`)
|
throw new Error(`Old password is invalid`)
|
||||||
}
|
}
|
||||||
|
|
||||||
const privKey = SecretKeyCryptographyDecrypt(userEntity.privKey, oldPasswordHash[1])
|
|
||||||
logger.debug('oldPassword decrypted...')
|
|
||||||
const newPasswordHash = SecretKeyCryptographyCreateKey(
|
|
||||||
userEntity.emailContact.email,
|
|
||||||
passwordNew,
|
|
||||||
) // return short and long hash
|
|
||||||
logger.debug('newPasswordHash created...')
|
|
||||||
const encryptedPrivkey = SecretKeyCryptographyEncrypt(privKey, newPasswordHash[1])
|
|
||||||
logger.debug('PrivateKey encrypted...')
|
|
||||||
|
|
||||||
// Save new password hash and newly encrypted private key
|
// Save new password hash and newly encrypted private key
|
||||||
userEntity.passwordEncryptionType = PasswordEncryptionType.GRADIDO_ID
|
userEntity.passwordEncryptionType = PasswordEncryptionType.GRADIDO_ID
|
||||||
userEntity.password = encryptPassword(userEntity, passwordNew)
|
userEntity.password = encryptPassword(userEntity, passwordNew)
|
||||||
userEntity.privKey = encryptedPrivkey
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const queryRunner = getConnection().createQueryRunner()
|
const queryRunner = getConnection().createQueryRunner()
|
||||||
|
|||||||
190
backend/src/graphql/resolver/semaphore.test.ts
Normal file
190
backend/src/graphql/resolver/semaphore.test.ts
Normal file
@ -0,0 +1,190 @@
|
|||||||
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||||
|
|
||||||
|
import Decimal from 'decimal.js-light'
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||||
|
import { logger } from '@test/testSetup'
|
||||||
|
import { userFactory } from '@/seeds/factory/user'
|
||||||
|
import { bibiBloxberg } from '@/seeds/users/bibi-bloxberg'
|
||||||
|
import { bobBaumeister } from '@/seeds/users/bob-baumeister'
|
||||||
|
import { peterLustig } from '@/seeds/users/peter-lustig'
|
||||||
|
import { creationFactory, nMonthsBefore } from '@/seeds/factory/creation'
|
||||||
|
import { cleanDB, testEnvironment, contributionDateFormatter } from '@test/helpers'
|
||||||
|
import {
|
||||||
|
confirmContribution,
|
||||||
|
createContribution,
|
||||||
|
createTransactionLink,
|
||||||
|
redeemTransactionLink,
|
||||||
|
login,
|
||||||
|
createContributionLink,
|
||||||
|
sendCoins,
|
||||||
|
} from '@/seeds/graphql/mutations'
|
||||||
|
|
||||||
|
let mutate: any, con: any
|
||||||
|
let testEnv: any
|
||||||
|
|
||||||
|
beforeAll(async () => {
|
||||||
|
testEnv = await testEnvironment()
|
||||||
|
mutate = testEnv.mutate
|
||||||
|
con = testEnv.con
|
||||||
|
await cleanDB()
|
||||||
|
})
|
||||||
|
|
||||||
|
afterAll(async () => {
|
||||||
|
await cleanDB()
|
||||||
|
await con.close()
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('semaphore', () => {
|
||||||
|
let contributionLinkCode = ''
|
||||||
|
let bobsTransactionLinkCode = ''
|
||||||
|
let bibisTransactionLinkCode = ''
|
||||||
|
let bibisOpenContributionId = -1
|
||||||
|
let bobsOpenContributionId = -1
|
||||||
|
|
||||||
|
beforeAll(async () => {
|
||||||
|
const now = new Date()
|
||||||
|
await userFactory(testEnv, bibiBloxberg)
|
||||||
|
await userFactory(testEnv, peterLustig)
|
||||||
|
await userFactory(testEnv, bobBaumeister)
|
||||||
|
await creationFactory(testEnv, {
|
||||||
|
email: 'bibi@bloxberg.de',
|
||||||
|
amount: 1000,
|
||||||
|
memo: 'Herzlich Willkommen bei Gradido!',
|
||||||
|
creationDate: nMonthsBefore(new Date()),
|
||||||
|
confirmed: true,
|
||||||
|
})
|
||||||
|
await creationFactory(testEnv, {
|
||||||
|
email: 'bob@baumeister.de',
|
||||||
|
amount: 1000,
|
||||||
|
memo: 'Herzlich Willkommen bei Gradido!',
|
||||||
|
creationDate: nMonthsBefore(new Date()),
|
||||||
|
confirmed: true,
|
||||||
|
})
|
||||||
|
await mutate({
|
||||||
|
mutation: login,
|
||||||
|
variables: { email: 'peter@lustig.de', password: 'Aa12345_' },
|
||||||
|
})
|
||||||
|
const {
|
||||||
|
data: { createContributionLink: contributionLink },
|
||||||
|
} = await mutate({
|
||||||
|
mutation: createContributionLink,
|
||||||
|
variables: {
|
||||||
|
amount: new Decimal(200),
|
||||||
|
name: 'Test Contribution Link',
|
||||||
|
memo: 'Danke für deine Teilnahme an dem Test der Contribution Links',
|
||||||
|
cycle: 'ONCE',
|
||||||
|
validFrom: new Date(2022, 5, 18).toISOString(),
|
||||||
|
validTo: new Date(now.getFullYear() + 1, 7, 14).toISOString(),
|
||||||
|
maxAmountPerMonth: new Decimal(200),
|
||||||
|
maxPerCycle: 1,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
contributionLinkCode = 'CL-' + contributionLink.code
|
||||||
|
await mutate({
|
||||||
|
mutation: login,
|
||||||
|
variables: { email: 'bob@baumeister.de', password: 'Aa12345_' },
|
||||||
|
})
|
||||||
|
const {
|
||||||
|
data: { createTransactionLink: bobsLink },
|
||||||
|
} = await mutate({
|
||||||
|
mutation: createTransactionLink,
|
||||||
|
variables: {
|
||||||
|
email: 'bob@baumeister.de',
|
||||||
|
amount: 20,
|
||||||
|
memo: 'Bobs Link',
|
||||||
|
},
|
||||||
|
})
|
||||||
|
const {
|
||||||
|
data: { createContribution: bobsContribution },
|
||||||
|
} = await mutate({
|
||||||
|
mutation: createContribution,
|
||||||
|
variables: {
|
||||||
|
creationDate: contributionDateFormatter(new Date()),
|
||||||
|
amount: 200,
|
||||||
|
memo: 'Bobs Contribution',
|
||||||
|
},
|
||||||
|
})
|
||||||
|
await mutate({
|
||||||
|
mutation: login,
|
||||||
|
variables: { email: 'bibi@bloxberg.de', password: 'Aa12345_' },
|
||||||
|
})
|
||||||
|
const {
|
||||||
|
data: { createTransactionLink: bibisLink },
|
||||||
|
} = await mutate({
|
||||||
|
mutation: createTransactionLink,
|
||||||
|
variables: {
|
||||||
|
amount: 20,
|
||||||
|
memo: 'Bibis Link',
|
||||||
|
},
|
||||||
|
})
|
||||||
|
const {
|
||||||
|
data: { createContribution: bibisContribution },
|
||||||
|
} = await mutate({
|
||||||
|
mutation: createContribution,
|
||||||
|
variables: {
|
||||||
|
creationDate: contributionDateFormatter(new Date()),
|
||||||
|
amount: 200,
|
||||||
|
memo: 'Bibis Contribution',
|
||||||
|
},
|
||||||
|
})
|
||||||
|
bobsTransactionLinkCode = bobsLink.code
|
||||||
|
bibisTransactionLinkCode = bibisLink.code
|
||||||
|
bibisOpenContributionId = bibisContribution.id
|
||||||
|
bobsOpenContributionId = bobsContribution.id
|
||||||
|
})
|
||||||
|
|
||||||
|
it('creates a lot of transactions without errors', async () => {
|
||||||
|
await mutate({
|
||||||
|
mutation: login,
|
||||||
|
variables: { email: 'bibi@bloxberg.de', password: 'Aa12345_' },
|
||||||
|
})
|
||||||
|
const bibiRedeemContributionLink = mutate({
|
||||||
|
mutation: redeemTransactionLink,
|
||||||
|
variables: { code: contributionLinkCode },
|
||||||
|
})
|
||||||
|
const redeemBobsLink = mutate({
|
||||||
|
mutation: redeemTransactionLink,
|
||||||
|
variables: { code: bobsTransactionLinkCode },
|
||||||
|
})
|
||||||
|
const bibisTransaction = mutate({
|
||||||
|
mutation: sendCoins,
|
||||||
|
variables: { email: 'bob@baumeister.de', amount: '50', memo: 'Das ist für dich, Bob' },
|
||||||
|
})
|
||||||
|
await mutate({
|
||||||
|
mutation: login,
|
||||||
|
variables: { email: 'bob@baumeister.de', password: 'Aa12345_' },
|
||||||
|
})
|
||||||
|
const bobRedeemContributionLink = mutate({
|
||||||
|
mutation: redeemTransactionLink,
|
||||||
|
variables: { code: contributionLinkCode },
|
||||||
|
})
|
||||||
|
const redeemBibisLink = mutate({
|
||||||
|
mutation: redeemTransactionLink,
|
||||||
|
variables: { code: bibisTransactionLinkCode },
|
||||||
|
})
|
||||||
|
const bobsTransaction = mutate({
|
||||||
|
mutation: sendCoins,
|
||||||
|
variables: { email: 'bibi@bloxberg.de', amount: '50', memo: 'Das ist für dich, Bibi' },
|
||||||
|
})
|
||||||
|
await mutate({
|
||||||
|
mutation: login,
|
||||||
|
variables: { email: 'peter@lustig.de', password: 'Aa12345_' },
|
||||||
|
})
|
||||||
|
const confirmBibisContribution = mutate({
|
||||||
|
mutation: confirmContribution,
|
||||||
|
variables: { id: bibisOpenContributionId },
|
||||||
|
})
|
||||||
|
const confirmBobsContribution = mutate({
|
||||||
|
mutation: confirmContribution,
|
||||||
|
variables: { id: bobsOpenContributionId },
|
||||||
|
})
|
||||||
|
await expect(bibiRedeemContributionLink).resolves.toMatchObject({ errors: undefined })
|
||||||
|
await expect(redeemBobsLink).resolves.toMatchObject({ errors: undefined })
|
||||||
|
await expect(bibisTransaction).resolves.toMatchObject({ errors: undefined })
|
||||||
|
await expect(bobRedeemContributionLink).resolves.toMatchObject({ errors: undefined })
|
||||||
|
await expect(redeemBibisLink).resolves.toMatchObject({ errors: undefined })
|
||||||
|
await expect(bobsTransaction).resolves.toMatchObject({ errors: undefined })
|
||||||
|
await expect(confirmBibisContribution).resolves.toMatchObject({ errors: undefined })
|
||||||
|
await expect(confirmBobsContribution).resolves.toMatchObject({ errors: undefined })
|
||||||
|
})
|
||||||
|
})
|
||||||
@ -6,7 +6,7 @@
|
|||||||
"toSeeAndAnswerMessage": "Um die Nachricht zu sehen und darauf zu antworten, gehe in deinem Gradido-Konto ins Menü „Gemeinschaft“ auf den Tab „Meine Beiträge zum Gemeinwohl“!"
|
"toSeeAndAnswerMessage": "Um die Nachricht zu sehen und darauf zu antworten, gehe in deinem Gradido-Konto ins Menü „Gemeinschaft“ auf den Tab „Meine Beiträge zum Gemeinwohl“!"
|
||||||
},
|
},
|
||||||
"accountActivation": {
|
"accountActivation": {
|
||||||
"duration": "Der Link hat eine Gültigkeit von {hours} Stunden und {minutes} Minuten. Sollte die Gültigkeit des Links bereits abgelaufen sein, kannst du dir hier einen neuen Link schicken lassen, in dem du deine E-Mail-Adresse eingibst:",
|
"duration": "Der Link hat eine Gültigkeit von {hours} Stunden und {minutes} Minuten. Sollte die Gültigkeit des Links bereits abgelaufen sein, kannst du dir hier einen neuen Link schicken lassen:",
|
||||||
"emailRegistered": "deine E-Mail-Adresse wurde soeben bei Gradido registriert.",
|
"emailRegistered": "deine E-Mail-Adresse wurde soeben bei Gradido registriert.",
|
||||||
"pleaseClickLink": "Klicke bitte auf diesen Link, um die Registrierung abzuschließen und dein Gradido-Konto zu aktivieren:",
|
"pleaseClickLink": "Klicke bitte auf diesen Link, um die Registrierung abzuschließen und dein Gradido-Konto zu aktivieren:",
|
||||||
"subject": "Gradido: E-Mail Überprüfung"
|
"subject": "Gradido: E-Mail Überprüfung"
|
||||||
@ -14,7 +14,7 @@
|
|||||||
"accountMultiRegistration": {
|
"accountMultiRegistration": {
|
||||||
"emailExists": "Es existiert jedoch zu deiner E-Mail-Adresse schon ein Konto.",
|
"emailExists": "Es existiert jedoch zu deiner E-Mail-Adresse schon ein Konto.",
|
||||||
"emailReused": "deine E-Mail-Adresse wurde soeben erneut benutzt, um bei Gradido ein Konto zu registrieren.",
|
"emailReused": "deine E-Mail-Adresse wurde soeben erneut benutzt, um bei Gradido ein Konto zu registrieren.",
|
||||||
"ifYouAreNotTheOne": "Wenn du nicht derjenige bist, der sich versucht hat erneut zu registrieren, wende dich bitte an unseren support:",
|
"ifYouAreNotTheOne": "Wenn du nicht derjenige bist, der versucht hat sich erneut zu registrieren, wende dich bitte an unseren Support:",
|
||||||
"onForgottenPasswordClickLink": "Klicke bitte auf den folgenden Link, falls du dein Passwort vergessen haben solltest:",
|
"onForgottenPasswordClickLink": "Klicke bitte auf den folgenden Link, falls du dein Passwort vergessen haben solltest:",
|
||||||
"onForgottenPasswordCopyLink": "oder kopiere den obigen Link in dein Browserfenster.",
|
"onForgottenPasswordCopyLink": "oder kopiere den obigen Link in dein Browserfenster.",
|
||||||
"subject": "Gradido: Erneuter Registrierungsversuch mit deiner E-Mail"
|
"subject": "Gradido: Erneuter Registrierungsversuch mit deiner E-Mail"
|
||||||
@ -40,22 +40,25 @@
|
|||||||
"yourGradidoTeam": "dein Gradido-Team"
|
"yourGradidoTeam": "dein Gradido-Team"
|
||||||
},
|
},
|
||||||
"resetPassword": {
|
"resetPassword": {
|
||||||
"duration": "Der Link hat eine Gültigkeit von {hours} Stunden und {minutes} Minuten. Sollte die Gültigkeit des Links bereits abgelaufen sein, kannst du dir hier einen neuen Link schicken lassen, in dem du deine E-Mail-Adresse eingibst:",
|
"duration": "Der Link hat eine Gültigkeit von {hours} Stunden und {minutes} Minuten. Sollte die Gültigkeit des Links bereits abgelaufen sein, kannst du dir hier einen neuen Link schicken lassen:",
|
||||||
"pleaseClickLink": "Wenn du es warst, klicke bitte auf den Link:",
|
"pleaseClickLink": "Wenn du es warst, klicke bitte auf den Link:",
|
||||||
"subject": "Gradido: Passwort zurücksetzen",
|
"subject": "Gradido: Passwort zurücksetzen",
|
||||||
"youOrSomeoneResetPassword": "du, oder jemand anderes, hast für dieses Konto ein Zurücksetzen des Passworts angefordert."
|
"youOrSomeoneResetPassword": "du, oder jemand anderes, hast für dieses Konto ein Zurücksetzen des Passworts angefordert."
|
||||||
},
|
},
|
||||||
"transactionLinkRedeemed": {
|
"transactionLinkRedeemed": {
|
||||||
"hasRedeemedYourLink": "{senderFirstName} {senderLastName} ({senderEmail}) hat soeben deinen Link eingelöst.",
|
"hasRedeemedYourLink": "{senderFirstName} {senderLastName} ({senderEmail}) hat soeben deinen Link eingelöst.",
|
||||||
"memo": "Memo: {transactionMemo}",
|
"memo": "Nachricht: {transactionMemo}",
|
||||||
"subject": "Gradido: Dein Gradido-Link wurde eingelöst"
|
"subject": "Gradido: Dein Gradido-Link wurde eingelöst"
|
||||||
},
|
},
|
||||||
"transactionReceived": {
|
"transactionReceived": {
|
||||||
"haveReceivedAmountGDDFrom": "du hast soeben {transactionAmount} GDD von {senderFirstName} {senderLastName} ({senderEmail}) erhalten.",
|
"haveReceivedAmountGDDFrom": "du hast soeben {transactionAmount} GDD von {senderFirstName} {senderLastName} ({senderEmail}) erhalten.",
|
||||||
"subject": "Gradido: Du hast Gradidos erhalten"
|
"subject": "Gradido: {senderFirstName} {senderLastName} hat dir {transactionAmount} Gradido gesendet"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"general": {
|
"general": {
|
||||||
"decimalSeparator": ","
|
"decimalSeparator": ",",
|
||||||
|
"imprint": "Gradido-Akademie\nInstitut für Wirtschaftsbionik\nPfarrweg 2\n74653 Künzelsau\nDeutschland",
|
||||||
|
"imprintImageAlt": "Gradido-Akademie Logo",
|
||||||
|
"imprintImageURL": "https://gdd.gradido.net/img/brand/green.png"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -6,7 +6,7 @@
|
|||||||
"toSeeAndAnswerMessage": "To view and reply to the message, go to the “Community” menu in your Gradido account and click on the “My contributions to the common good” tab!"
|
"toSeeAndAnswerMessage": "To view and reply to the message, go to the “Community” menu in your Gradido account and click on the “My contributions to the common good” tab!"
|
||||||
},
|
},
|
||||||
"accountActivation": {
|
"accountActivation": {
|
||||||
"duration": "The link has a validity of {hours} hours and {minutes} minutes. If the validity of the link has already expired, you can have a new link sent to you here by entering your email address:",
|
"duration": "The link has a validity of {hours} hours and {minutes} minutes. If the validity of the link has already expired, you can have a new link sent to you here:",
|
||||||
"emailRegistered": "Your email address has just been registered with Gradido.",
|
"emailRegistered": "Your email address has just been registered with Gradido.",
|
||||||
"pleaseClickLink": "Please click on this link to complete the registration and activate your Gradido account:",
|
"pleaseClickLink": "Please click on this link to complete the registration and activate your Gradido account:",
|
||||||
"subject": "Gradido: Email Verification"
|
"subject": "Gradido: Email Verification"
|
||||||
@ -40,22 +40,25 @@
|
|||||||
"yourGradidoTeam": "your Gradido team"
|
"yourGradidoTeam": "your Gradido team"
|
||||||
},
|
},
|
||||||
"resetPassword": {
|
"resetPassword": {
|
||||||
"duration": "The link has a validity of {hours} hours and {minutes} minutes. If the validity of the link has already expired, you can have a new link sent to you here by entering your email address:",
|
"duration": "The link has a validity of {hours} hours and {minutes} minutes. If the validity of the link has already expired, you can have a new link sent to you here:",
|
||||||
"pleaseClickLink": "If it was you, please click on the link:",
|
"pleaseClickLink": "If it was you, please click on the link:",
|
||||||
"subject": "Gradido: Reset password",
|
"subject": "Gradido: Reset password",
|
||||||
"youOrSomeoneResetPassword": "You, or someone else, requested a password reset for this account."
|
"youOrSomeoneResetPassword": "You, or someone else, requested a password reset for this account."
|
||||||
},
|
},
|
||||||
"transactionLinkRedeemed": {
|
"transactionLinkRedeemed": {
|
||||||
"hasRedeemedYourLink": "{senderFirstName} {senderLastName} ({senderEmail}) has just redeemed your link.",
|
"hasRedeemedYourLink": "{senderFirstName} {senderLastName} ({senderEmail}) has just redeemed your link.",
|
||||||
"memo": "Memo: {transactionMemo}",
|
"memo": "Message: {transactionMemo}",
|
||||||
"subject": "Gradido: Your Gradido link has been redeemed"
|
"subject": "Gradido: Your Gradido link has been redeemed"
|
||||||
},
|
},
|
||||||
"transactionReceived": {
|
"transactionReceived": {
|
||||||
"haveReceivedAmountGDDFrom": "You have just received {transactionAmount} GDD from {senderFirstName} {senderLastName} ({senderEmail}).",
|
"haveReceivedAmountGDDFrom": "You have just received {transactionAmount} GDD from {senderFirstName} {senderLastName} ({senderEmail}).",
|
||||||
"subject": "Gradido: You have received Gradidos"
|
"subject": "Gradido: {senderFirstName} {senderLastName} has sent you {transactionAmount} Gradido"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"general": {
|
"general": {
|
||||||
"decimalSeparator": "."
|
"decimalSeparator": ".",
|
||||||
|
"imprint": "Gradido-Akademie\nInstitut für Wirtschaftsbionik\nPfarrweg 2\n74653 Künzelsau\nDeutschland",
|
||||||
|
"imprintImageAlt": "Gradido-Akademie Logo",
|
||||||
|
"imprintImageURL": "https://gdd.gradido.net/img/brand/green.png"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -75,10 +75,7 @@ const run = async () => {
|
|||||||
|
|
||||||
// create GDD
|
// create GDD
|
||||||
for (let i = 0; i < creations.length; i++) {
|
for (let i = 0; i < creations.length; i++) {
|
||||||
const now = new Date().getTime() // we have to wait a little! quick fix for account sum problem of bob@baumeister.de, (see https://github.com/gradido/gradido/issues/1886)
|
|
||||||
await creationFactory(seedClient, creations[i])
|
await creationFactory(seedClient, creations[i])
|
||||||
// eslint-disable-next-line no-empty
|
|
||||||
while (new Date().getTime() < now + 1000) {} // we have to wait a little! quick fix for account sum problem of bob@baumeister.de, (see https://github.com/gradido/gradido/issues/1886)
|
|
||||||
}
|
}
|
||||||
logger.info('##seed## seeding all creations successful...')
|
logger.info('##seed## seeding all creations successful...')
|
||||||
|
|
||||||
|
|||||||
@ -4,21 +4,6 @@ import { User as DbUser } from '@entity/User'
|
|||||||
|
|
||||||
@EntityRepository(DbUser)
|
@EntityRepository(DbUser)
|
||||||
export class UserRepository extends Repository<DbUser> {
|
export class UserRepository extends Repository<DbUser> {
|
||||||
async findByPubkeyHex(pubkeyHex: string): Promise<DbUser> {
|
|
||||||
const dbUser = await this.createQueryBuilder('user')
|
|
||||||
.leftJoinAndSelect('user.emailContact', 'emailContact')
|
|
||||||
.where('hex(user.pubKey) = :pubkeyHex', { pubkeyHex })
|
|
||||||
.getOneOrFail()
|
|
||||||
/*
|
|
||||||
const dbUser = await this.findOneOrFail(`hex(user.pubKey) = { pubkeyHex }`)
|
|
||||||
const emailContact = await this.query(
|
|
||||||
`SELECT * from user_contacts where id = { dbUser.emailId }`,
|
|
||||||
)
|
|
||||||
dbUser.emailContact = emailContact
|
|
||||||
*/
|
|
||||||
return dbUser
|
|
||||||
}
|
|
||||||
|
|
||||||
async findBySearchCriteriaPagedFiltered(
|
async findBySearchCriteriaPagedFiltered(
|
||||||
select: string[],
|
select: string[],
|
||||||
searchCriteria: string,
|
searchCriteria: string,
|
||||||
|
|||||||
4
backend/src/util/TRANSACTIONS_LOCK.ts
Normal file
4
backend/src/util/TRANSACTIONS_LOCK.ts
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
import { Semaphore } from 'await-semaphore'
|
||||||
|
|
||||||
|
const CONCURRENT_TRANSACTIONS = 1
|
||||||
|
export const TRANSACTIONS_LOCK = new Semaphore(CONCURRENT_TRANSACTIONS)
|
||||||
@ -16,8 +16,6 @@ const communityDbUser: dbUser = {
|
|||||||
emailId: -1,
|
emailId: -1,
|
||||||
firstName: 'Gradido',
|
firstName: 'Gradido',
|
||||||
lastName: 'Akademie',
|
lastName: 'Akademie',
|
||||||
pubKey: Buffer.from(''),
|
|
||||||
privKey: Buffer.from(''),
|
|
||||||
deletedAt: null,
|
deletedAt: null,
|
||||||
password: BigInt(0),
|
password: BigInt(0),
|
||||||
// emailHash: Buffer.from(''),
|
// emailHash: Buffer.from(''),
|
||||||
@ -26,7 +24,6 @@ const communityDbUser: dbUser = {
|
|||||||
language: '',
|
language: '',
|
||||||
isAdmin: null,
|
isAdmin: null,
|
||||||
publisherId: 0,
|
publisherId: 0,
|
||||||
passphrase: '',
|
|
||||||
// default password encryption type
|
// default password encryption type
|
||||||
passwordEncryptionType: PasswordEncryptionType.NO_PASSWORD,
|
passwordEncryptionType: PasswordEncryptionType.NO_PASSWORD,
|
||||||
hasId: function (): boolean {
|
hasId: function (): boolean {
|
||||||
|
|||||||
@ -14,17 +14,13 @@ function isStringBoolean(value: string): boolean {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
function isHexPublicKey(publicKey: string): boolean {
|
|
||||||
return /^[0-9A-Fa-f]{64}$/i.test(publicKey)
|
|
||||||
}
|
|
||||||
|
|
||||||
async function calculateBalance(
|
async function calculateBalance(
|
||||||
userId: number,
|
userId: number,
|
||||||
amount: Decimal,
|
amount: Decimal,
|
||||||
time: Date,
|
time: Date,
|
||||||
transactionLink?: dbTransactionLink | null,
|
transactionLink?: dbTransactionLink | null,
|
||||||
): Promise<{ balance: Decimal; decay: Decay; lastTransactionId: number } | null> {
|
): Promise<{ balance: Decimal; decay: Decay; lastTransactionId: number } | null> {
|
||||||
const lastTransaction = await Transaction.findOne({ userId }, { order: { balanceDate: 'DESC' } })
|
const lastTransaction = await Transaction.findOne({ userId }, { order: { id: 'DESC' } })
|
||||||
if (!lastTransaction) return null
|
if (!lastTransaction) return null
|
||||||
|
|
||||||
const decay = calculateDecay(lastTransaction.balance, lastTransaction.balanceDate, time)
|
const decay = calculateDecay(lastTransaction.balance, lastTransaction.balanceDate, time)
|
||||||
@ -45,4 +41,4 @@ async function calculateBalance(
|
|||||||
return { balance, lastTransactionId: lastTransaction.id, decay }
|
return { balance, lastTransactionId: lastTransaction.id, decay }
|
||||||
}
|
}
|
||||||
|
|
||||||
export { isHexPublicKey, calculateBalance, isStringBoolean }
|
export { calculateBalance, isStringBoolean }
|
||||||
|
|||||||
@ -1643,6 +1643,11 @@ asynckit@^0.4.0:
|
|||||||
resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79"
|
resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79"
|
||||||
integrity sha1-x57Zf380y48robyXkLzDZkdLS3k=
|
integrity sha1-x57Zf380y48robyXkLzDZkdLS3k=
|
||||||
|
|
||||||
|
await-semaphore@^0.1.3:
|
||||||
|
version "0.1.3"
|
||||||
|
resolved "https://registry.yarnpkg.com/await-semaphore/-/await-semaphore-0.1.3.tgz#2b88018cc8c28e06167ae1cdff02504f1f9688d3"
|
||||||
|
integrity sha512-d1W2aNSYcz/sxYO4pMGX9vq65qOTu0P800epMud+6cYYX0QcT7zyqcxec3VWzpgvdXo57UWmVbZpLMjX2m1I7Q==
|
||||||
|
|
||||||
axios@^0.21.1:
|
axios@^0.21.1:
|
||||||
version "0.21.4"
|
version "0.21.4"
|
||||||
resolved "https://registry.yarnpkg.com/axios/-/axios-0.21.4.tgz#c67b90dc0568e5c1cf2b0b858c43ba28e2eda575"
|
resolved "https://registry.yarnpkg.com/axios/-/axios-0.21.4.tgz#c67b90dc0568e5c1cf2b0b858c43ba28e2eda575"
|
||||||
|
|||||||
112
database/entity/0057-clear_old_password_junk/User.ts
Normal file
112
database/entity/0057-clear_old_password_junk/User.ts
Normal file
@ -0,0 +1,112 @@
|
|||||||
|
import {
|
||||||
|
BaseEntity,
|
||||||
|
Entity,
|
||||||
|
PrimaryGeneratedColumn,
|
||||||
|
Column,
|
||||||
|
DeleteDateColumn,
|
||||||
|
OneToMany,
|
||||||
|
JoinColumn,
|
||||||
|
OneToOne,
|
||||||
|
} from 'typeorm'
|
||||||
|
import { Contribution } from '../Contribution'
|
||||||
|
import { ContributionMessage } from '../ContributionMessage'
|
||||||
|
import { UserContact } from '../UserContact'
|
||||||
|
|
||||||
|
@Entity('users', { engine: 'InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci' })
|
||||||
|
export class User extends BaseEntity {
|
||||||
|
@PrimaryGeneratedColumn('increment', { unsigned: true })
|
||||||
|
id: number
|
||||||
|
|
||||||
|
@Column({
|
||||||
|
name: 'gradido_id',
|
||||||
|
length: 36,
|
||||||
|
nullable: false,
|
||||||
|
collation: 'utf8mb4_unicode_ci',
|
||||||
|
})
|
||||||
|
gradidoID: string
|
||||||
|
|
||||||
|
@Column({
|
||||||
|
name: 'alias',
|
||||||
|
length: 20,
|
||||||
|
nullable: true,
|
||||||
|
default: null,
|
||||||
|
collation: 'utf8mb4_unicode_ci',
|
||||||
|
})
|
||||||
|
alias: string
|
||||||
|
|
||||||
|
@OneToOne(() => UserContact, (emailContact: UserContact) => emailContact.user)
|
||||||
|
@JoinColumn({ name: 'email_id' })
|
||||||
|
emailContact: UserContact
|
||||||
|
|
||||||
|
@Column({ name: 'email_id', type: 'int', unsigned: true, nullable: true, default: null })
|
||||||
|
emailId: number | null
|
||||||
|
|
||||||
|
@Column({
|
||||||
|
name: 'first_name',
|
||||||
|
length: 255,
|
||||||
|
nullable: true,
|
||||||
|
default: null,
|
||||||
|
collation: 'utf8mb4_unicode_ci',
|
||||||
|
})
|
||||||
|
firstName: string
|
||||||
|
|
||||||
|
@Column({
|
||||||
|
name: 'last_name',
|
||||||
|
length: 255,
|
||||||
|
nullable: true,
|
||||||
|
default: null,
|
||||||
|
collation: 'utf8mb4_unicode_ci',
|
||||||
|
})
|
||||||
|
lastName: string
|
||||||
|
|
||||||
|
@Column({ name: 'created_at', default: () => 'CURRENT_TIMESTAMP', nullable: false })
|
||||||
|
createdAt: Date
|
||||||
|
|
||||||
|
@DeleteDateColumn({ name: 'deleted_at', nullable: true })
|
||||||
|
deletedAt: Date | null
|
||||||
|
|
||||||
|
@Column({ type: 'bigint', default: 0, unsigned: true })
|
||||||
|
password: BigInt
|
||||||
|
|
||||||
|
@Column({
|
||||||
|
name: 'password_encryption_type',
|
||||||
|
type: 'int',
|
||||||
|
unsigned: true,
|
||||||
|
nullable: false,
|
||||||
|
default: 0,
|
||||||
|
})
|
||||||
|
passwordEncryptionType: number
|
||||||
|
|
||||||
|
@Column({ length: 4, default: 'de', collation: 'utf8mb4_unicode_ci', nullable: false })
|
||||||
|
language: string
|
||||||
|
|
||||||
|
@Column({ name: 'is_admin', type: 'datetime', nullable: true, default: null })
|
||||||
|
isAdmin: Date | null
|
||||||
|
|
||||||
|
@Column({ name: 'referrer_id', type: 'int', unsigned: true, nullable: true, default: null })
|
||||||
|
referrerId?: number | null
|
||||||
|
|
||||||
|
@Column({
|
||||||
|
name: 'contribution_link_id',
|
||||||
|
type: 'int',
|
||||||
|
unsigned: true,
|
||||||
|
nullable: true,
|
||||||
|
default: null,
|
||||||
|
})
|
||||||
|
contributionLinkId?: number | null
|
||||||
|
|
||||||
|
@Column({ name: 'publisher_id', default: 0 })
|
||||||
|
publisherId: number
|
||||||
|
|
||||||
|
@OneToMany(() => Contribution, (contribution) => contribution.user)
|
||||||
|
@JoinColumn({ name: 'user_id' })
|
||||||
|
contributions?: Contribution[]
|
||||||
|
|
||||||
|
@OneToMany(() => ContributionMessage, (message) => message.user)
|
||||||
|
@JoinColumn({ name: 'user_id' })
|
||||||
|
messages?: ContributionMessage[]
|
||||||
|
|
||||||
|
@OneToMany(() => UserContact, (userContact: UserContact) => userContact.user)
|
||||||
|
@JoinColumn({ name: 'user_id' })
|
||||||
|
userContacts?: UserContact[]
|
||||||
|
}
|
||||||
57
database/entity/0057-clear_old_password_junk/UserContact.ts
Normal file
57
database/entity/0057-clear_old_password_junk/UserContact.ts
Normal file
@ -0,0 +1,57 @@
|
|||||||
|
import {
|
||||||
|
BaseEntity,
|
||||||
|
Entity,
|
||||||
|
PrimaryGeneratedColumn,
|
||||||
|
Column,
|
||||||
|
DeleteDateColumn,
|
||||||
|
OneToOne,
|
||||||
|
} from 'typeorm'
|
||||||
|
import { User } from './User'
|
||||||
|
|
||||||
|
@Entity('user_contacts', { engine: 'InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci' })
|
||||||
|
export class UserContact extends BaseEntity {
|
||||||
|
@PrimaryGeneratedColumn('increment', { unsigned: true })
|
||||||
|
id: number
|
||||||
|
|
||||||
|
@Column({
|
||||||
|
name: 'type',
|
||||||
|
length: 100,
|
||||||
|
nullable: true,
|
||||||
|
default: null,
|
||||||
|
collation: 'utf8mb4_unicode_ci',
|
||||||
|
})
|
||||||
|
type: string
|
||||||
|
|
||||||
|
@OneToOne(() => User, (user) => user.emailContact)
|
||||||
|
user: User
|
||||||
|
|
||||||
|
@Column({ name: 'user_id', type: 'int', unsigned: true, nullable: false })
|
||||||
|
userId: number
|
||||||
|
|
||||||
|
@Column({ length: 255, unique: true, nullable: false, collation: 'utf8mb4_unicode_ci' })
|
||||||
|
email: string
|
||||||
|
|
||||||
|
@Column({ name: 'email_verification_code', type: 'bigint', unsigned: true, unique: true })
|
||||||
|
emailVerificationCode: BigInt
|
||||||
|
|
||||||
|
@Column({ name: 'email_opt_in_type_id' })
|
||||||
|
emailOptInTypeId: number
|
||||||
|
|
||||||
|
@Column({ name: 'email_resend_count' })
|
||||||
|
emailResendCount: number
|
||||||
|
|
||||||
|
@Column({ name: 'email_checked', type: 'bool', nullable: false, default: false })
|
||||||
|
emailChecked: boolean
|
||||||
|
|
||||||
|
@Column({ length: 255, unique: false, nullable: true, collation: 'utf8mb4_unicode_ci' })
|
||||||
|
phone: string
|
||||||
|
|
||||||
|
@Column({ name: 'created_at', default: () => 'CURRENT_TIMESTAMP', nullable: false })
|
||||||
|
createdAt: Date
|
||||||
|
|
||||||
|
@Column({ name: 'updated_at', nullable: true, default: null, type: 'datetime' })
|
||||||
|
updatedAt: Date | null
|
||||||
|
|
||||||
|
@DeleteDateColumn({ name: 'deleted_at', nullable: true })
|
||||||
|
deletedAt: Date | null
|
||||||
|
}
|
||||||
@ -1 +1 @@
|
|||||||
export { Community } from './0056-add_communities_table/Community'
|
export { Community } from './0058-add_communities_table/Community'
|
||||||
|
|||||||
@ -1 +1 @@
|
|||||||
export { User } from './0053-change_password_encryption/User'
|
export { User } from './0057-clear_old_password_junk/User'
|
||||||
|
|||||||
@ -1 +1 @@
|
|||||||
export { UserContact } from './0053-change_password_encryption/UserContact'
|
export { UserContact } from './0057-clear_old_password_junk/UserContact'
|
||||||
|
|||||||
39
database/migrations/0056-consistent_transactions_table.ts
Normal file
39
database/migrations/0056-consistent_transactions_table.ts
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
/* MIGRATION TO add users that have a transaction but do not exist */
|
||||||
|
|
||||||
|
/* eslint-disable @typescript-eslint/explicit-module-boundary-types */
|
||||||
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||||
|
import { v4 as uuidv4 } from 'uuid'
|
||||||
|
import { OkPacket } from 'mysql'
|
||||||
|
|
||||||
|
export async function upgrade(queryFn: (query: string, values?: any[]) => Promise<Array<any>>) {
|
||||||
|
const missingUserIds = await queryFn(`
|
||||||
|
SELECT user_id FROM transactions
|
||||||
|
WHERE NOT EXISTS (SELECT id FROM users WHERE id = user_id) GROUP BY user_id;`)
|
||||||
|
|
||||||
|
for (let i = 0; i < missingUserIds.length; i++) {
|
||||||
|
let gradidoId = ''
|
||||||
|
let countIds: any[] = []
|
||||||
|
do {
|
||||||
|
gradidoId = uuidv4()
|
||||||
|
countIds = await queryFn(
|
||||||
|
`SELECT COUNT(*) FROM \`users\` WHERE \`gradido_id\` = "${gradidoId}"`,
|
||||||
|
)
|
||||||
|
} while (countIds[0] > 0)
|
||||||
|
|
||||||
|
const userContact = (await queryFn(`
|
||||||
|
INSERT INTO user_contacts
|
||||||
|
(type, user_id, email, email_checked, created_at, deleted_at)
|
||||||
|
VALUES
|
||||||
|
('EMAIL', ${missingUserIds[i].user_id}, 'deleted.user${missingUserIds[i].user_id}@gradido.net', 0, NOW(), NOW());`)) as unknown as OkPacket
|
||||||
|
|
||||||
|
await queryFn(`
|
||||||
|
INSERT INTO users
|
||||||
|
(id, gradido_id, email_id, first_name, last_name, deleted_at, password_encryption_type, created_at, language)
|
||||||
|
VALUES
|
||||||
|
(${missingUserIds[i].user_id}, '${gradidoId}', ${userContact.insertId}, 'DELETED', 'USER', NOW(), 0, NOW(), 'de');`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* eslint-disable @typescript-eslint/no-empty-function */
|
||||||
|
/* eslint-disable-next-line @typescript-eslint/no-unused-vars */
|
||||||
|
export async function downgrade(queryFn: (query: string, values?: any[]) => Promise<Array<any>>) {}
|
||||||
16
database/migrations/0057-clear_old_password_junk.ts
Normal file
16
database/migrations/0057-clear_old_password_junk.ts
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
/* eslint-disable @typescript-eslint/explicit-module-boundary-types */
|
||||||
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||||
|
|
||||||
|
export async function upgrade(queryFn: (query: string, values?: any[]) => Promise<Array<any>>) {
|
||||||
|
await queryFn('ALTER TABLE users DROP COLUMN public_key;')
|
||||||
|
await queryFn('ALTER TABLE users DROP COLUMN privkey;')
|
||||||
|
await queryFn('ALTER TABLE users DROP COLUMN email_hash;')
|
||||||
|
await queryFn('ALTER TABLE users DROP COLUMN passphrase;')
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function downgrade(queryFn: (query: string, values?: any[]) => Promise<Array<any>>) {
|
||||||
|
await queryFn('ALTER TABLE users ADD COLUMN public_key binary(32) DEFAULT NULL;')
|
||||||
|
await queryFn('ALTER TABLE users ADD COLUMN privkey binary(80) DEFAULT NULL;')
|
||||||
|
await queryFn('ALTER TABLE users ADD COLUMN email_hash binary(32) DEFAULT NULL;')
|
||||||
|
await queryFn('ALTER TABLE users ADD COLUMN passphrase text DEFAULT NULL;')
|
||||||
|
}
|
||||||
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "gradido-database",
|
"name": "gradido-database",
|
||||||
"version": "1.15.0",
|
"version": "1.16.0",
|
||||||
"description": "Gradido Database Tool to execute database migrations",
|
"description": "Gradido Database Tool to execute database migrations",
|
||||||
"main": "src/index.ts",
|
"main": "src/index.ts",
|
||||||
"repository": "https://github.com/gradido/gradido/database",
|
"repository": "https://github.com/gradido/gradido/database",
|
||||||
|
|||||||
@ -24,9 +24,10 @@ COMMUNITY_REGISTER_URL=https://stage1.gradido.net/register
|
|||||||
COMMUNITY_REDEEM_URL=https://stage1.gradido.net/redeem/{code}
|
COMMUNITY_REDEEM_URL=https://stage1.gradido.net/redeem/{code}
|
||||||
COMMUNITY_REDEEM_CONTRIBUTION_URL=https://stage1.gradido.net/redeem/CL-{code}
|
COMMUNITY_REDEEM_CONTRIBUTION_URL=https://stage1.gradido.net/redeem/CL-{code}
|
||||||
COMMUNITY_DESCRIPTION="Gradido Development Stage1 Test Community"
|
COMMUNITY_DESCRIPTION="Gradido Development Stage1 Test Community"
|
||||||
|
COMMUNITY_SUPPORT_MAIL=support@supportmail.com
|
||||||
|
|
||||||
# backend
|
# backend
|
||||||
BACKEND_CONFIG_VERSION=v12.2022-11-10
|
BACKEND_CONFIG_VERSION=v13.2022-12-20
|
||||||
|
|
||||||
JWT_EXPIRES_IN=10m
|
JWT_EXPIRES_IN=10m
|
||||||
GDT_API_URL=https://gdt.gradido.net
|
GDT_API_URL=https://gdt.gradido.net
|
||||||
@ -69,7 +70,7 @@ EVENT_PROTOCOL_DISABLED=false
|
|||||||
DATABASE_CONFIG_VERSION=v1.2022-03-18
|
DATABASE_CONFIG_VERSION=v1.2022-03-18
|
||||||
|
|
||||||
# frontend
|
# frontend
|
||||||
FRONTEND_CONFIG_VERSION=v3.2022-09-16
|
FRONTEND_CONFIG_VERSION=v4.2022-12-20
|
||||||
|
|
||||||
GRAPHQL_URI=https://stage1.gradido.net/graphql
|
GRAPHQL_URI=https://stage1.gradido.net/graphql
|
||||||
ADMIN_AUTH_URL=https://stage1.gradido.net/admin/authenticate?token={token}
|
ADMIN_AUTH_URL=https://stage1.gradido.net/admin/authenticate?token={token}
|
||||||
@ -85,8 +86,6 @@ META_KEYWORDS_DE="Grundeinkommen, Währung, Dankbarkeit, Schenk-Ökonomie, Natü
|
|||||||
META_KEYWORDS_EN="Basic Income, Currency, Gratitude, Gift Economy, Natural Economy of Life, Economy, Ecology, Potential Development, Giving and Thanking, Cycle of Life, Monetary System"
|
META_KEYWORDS_EN="Basic Income, Currency, Gratitude, Gift Economy, Natural Economy of Life, Economy, Ecology, Potential Development, Giving and Thanking, Cycle of Life, Monetary System"
|
||||||
META_AUTHOR="Bernd Hückstädt - Gradido-Akademie"
|
META_AUTHOR="Bernd Hückstädt - Gradido-Akademie"
|
||||||
|
|
||||||
SUPPORT_MAIL=support@supportmail.com
|
|
||||||
|
|
||||||
# admin
|
# admin
|
||||||
ADMIN_CONFIG_VERSION=v1.2022-03-18
|
ADMIN_CONFIG_VERSION=v1.2022-03-18
|
||||||
|
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
CONFIG_VERSION=v3.2022-09-16
|
CONFIG_VERSION=v4.2022-12-20
|
||||||
|
|
||||||
# Environment
|
# Environment
|
||||||
DEFAULT_PUBLISHER_ID=2896
|
DEFAULT_PUBLISHER_ID=2896
|
||||||
@ -12,6 +12,7 @@ COMMUNITY_NAME=Gradido Entwicklung
|
|||||||
COMMUNITY_URL=http://localhost/
|
COMMUNITY_URL=http://localhost/
|
||||||
COMMUNITY_REGISTER_URL=http://localhost/register
|
COMMUNITY_REGISTER_URL=http://localhost/register
|
||||||
COMMUNITY_DESCRIPTION=Die lokale Entwicklungsumgebung von Gradido.
|
COMMUNITY_DESCRIPTION=Die lokale Entwicklungsumgebung von Gradido.
|
||||||
|
COMMUNITY_SUPPORT_MAIL=support@supportmail.com
|
||||||
|
|
||||||
# Meta
|
# Meta
|
||||||
META_URL=http://localhost
|
META_URL=http://localhost
|
||||||
@ -22,6 +23,3 @@ META_DESCRIPTION_EN="Gratitude is the currency of the new age. More and more peo
|
|||||||
META_KEYWORDS_DE="Grundeinkommen, Währung, Dankbarkeit, Schenk-Ökonomie, Natürliche Ökonomie des Lebens, Ökonomie, Ökologie, Potenzialentfaltung, Schenken und Danken, Kreislauf des Lebens, Geldsystem"
|
META_KEYWORDS_DE="Grundeinkommen, Währung, Dankbarkeit, Schenk-Ökonomie, Natürliche Ökonomie des Lebens, Ökonomie, Ökologie, Potenzialentfaltung, Schenken und Danken, Kreislauf des Lebens, Geldsystem"
|
||||||
META_KEYWORDS_EN="Basic Income, Currency, Gratitude, Gift Economy, Natural Economy of Life, Economy, Ecology, Potential Development, Giving and Thanking, Cycle of Life, Monetary System"
|
META_KEYWORDS_EN="Basic Income, Currency, Gratitude, Gift Economy, Natural Economy of Life, Economy, Ecology, Potential Development, Giving and Thanking, Cycle of Life, Monetary System"
|
||||||
META_AUTHOR="Bernd Hückstädt - Gradido-Akademie"
|
META_AUTHOR="Bernd Hückstädt - Gradido-Akademie"
|
||||||
|
|
||||||
# Support Mail
|
|
||||||
SUPPORT_MAIL=support@supportmail.com
|
|
||||||
@ -12,6 +12,7 @@ COMMUNITY_NAME=$COMMUNITY_NAME
|
|||||||
COMMUNITY_URL=$COMMUNITY_URL
|
COMMUNITY_URL=$COMMUNITY_URL
|
||||||
COMMUNITY_REGISTER_URL=$COMMUNITY_REGISTER_URL
|
COMMUNITY_REGISTER_URL=$COMMUNITY_REGISTER_URL
|
||||||
COMMUNITY_DESCRIPTION=$COMMUNITY_DESCRIPTION
|
COMMUNITY_DESCRIPTION=$COMMUNITY_DESCRIPTION
|
||||||
|
COMMUNITY_SUPPORT_MAIL=$COMMUNITY_SUPPORT_MAIL
|
||||||
|
|
||||||
# Meta
|
# Meta
|
||||||
META_URL=$META_URL
|
META_URL=$META_URL
|
||||||
@ -22,6 +23,3 @@ META_DESCRIPTION_EN=$META_DESCRIPTION_EN
|
|||||||
META_KEYWORDS_DE=$META_KEYWORDS_DE
|
META_KEYWORDS_DE=$META_KEYWORDS_DE
|
||||||
META_KEYWORDS_EN=$META_KEYWORDS_EN
|
META_KEYWORDS_EN=$META_KEYWORDS_EN
|
||||||
META_AUTHOR=$META_AUTHOR
|
META_AUTHOR=$META_AUTHOR
|
||||||
|
|
||||||
# Support Mail
|
|
||||||
SUPPORT_MAIL=$SUPPORT_MAIL
|
|
||||||
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "bootstrap-vue-gradido-wallet",
|
"name": "bootstrap-vue-gradido-wallet",
|
||||||
"version": "1.15.0",
|
"version": "1.16.0",
|
||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"start": "node run/server.js",
|
"start": "node run/server.js",
|
||||||
|
|||||||
@ -8,7 +8,7 @@ const constants = {
|
|||||||
DECAY_START_TIME: new Date('2021-05-13 17:46:31-0000'), // GMT+0
|
DECAY_START_TIME: new Date('2021-05-13 17:46:31-0000'), // GMT+0
|
||||||
CONFIG_VERSION: {
|
CONFIG_VERSION: {
|
||||||
DEFAULT: 'DEFAULT',
|
DEFAULT: 'DEFAULT',
|
||||||
EXPECTED: 'v3.2022-09-16',
|
EXPECTED: 'v4.2022-12-20',
|
||||||
CURRENT: '',
|
CURRENT: '',
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@ -39,6 +39,7 @@ const community = {
|
|||||||
COMMUNITY_REGISTER_URL: process.env.COMMUNITY_REGISTER_URL || 'http://localhost/register',
|
COMMUNITY_REGISTER_URL: process.env.COMMUNITY_REGISTER_URL || 'http://localhost/register',
|
||||||
COMMUNITY_DESCRIPTION:
|
COMMUNITY_DESCRIPTION:
|
||||||
process.env.COMMUNITY_DESCRIPTION || 'Die lokale Entwicklungsumgebung von Gradido.',
|
process.env.COMMUNITY_DESCRIPTION || 'Die lokale Entwicklungsumgebung von Gradido.',
|
||||||
|
COMMUNITY_SUPPORT_MAIL: process.env.COMMUNITY_SUPPORT_MAIL || 'support@supportmail.com',
|
||||||
}
|
}
|
||||||
|
|
||||||
const meta = {
|
const meta = {
|
||||||
@ -60,10 +61,6 @@ const meta = {
|
|||||||
META_AUTHOR: process.env.META_AUTHOR || 'Bernd Hückstädt - Gradido-Akademie',
|
META_AUTHOR: process.env.META_AUTHOR || 'Bernd Hückstädt - Gradido-Akademie',
|
||||||
}
|
}
|
||||||
|
|
||||||
const supportmail = {
|
|
||||||
SUPPORT_MAIL: process.env.SUPPORT_MAIL || 'support@supportmail.com',
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check config version
|
// Check config version
|
||||||
constants.CONFIG_VERSION.CURRENT = process.env.CONFIG_VERSION || constants.CONFIG_VERSION.DEFAULT
|
constants.CONFIG_VERSION.CURRENT = process.env.CONFIG_VERSION || constants.CONFIG_VERSION.DEFAULT
|
||||||
if (
|
if (
|
||||||
@ -83,7 +80,6 @@ const CONFIG = {
|
|||||||
...endpoints,
|
...endpoints,
|
||||||
...community,
|
...community,
|
||||||
...meta,
|
...meta,
|
||||||
...supportmail,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = CONFIG
|
module.exports = CONFIG
|
||||||
|
|||||||
@ -89,7 +89,7 @@ export default {
|
|||||||
countAdminUser: null,
|
countAdminUser: null,
|
||||||
itemsContributionLinks: [],
|
itemsContributionLinks: [],
|
||||||
itemsAdminUser: [],
|
itemsAdminUser: [],
|
||||||
supportMail: CONFIG.SUPPORT_MAIL,
|
supportMail: CONFIG.COMMUNITY_SUPPORT_MAIL,
|
||||||
membersCount: '1203',
|
membersCount: '1203',
|
||||||
totalUsers: null,
|
totalUsers: null,
|
||||||
totalGradidoCreated: null,
|
totalGradidoCreated: null,
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "gradido",
|
"name": "gradido",
|
||||||
"version": "1.15.0",
|
"version": "1.16.0",
|
||||||
"description": "Gradido",
|
"description": "Gradido",
|
||||||
"main": "index.js",
|
"main": "index.js",
|
||||||
"repository": "git@github.com:gradido/gradido.git",
|
"repository": "git@github.com:gradido/gradido.git",
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user