Merge remote-tracking branch 'origin/master' into

3237-feature-connect-gms-api-with-a-new-rest-client
This commit is contained in:
Claus-Peter Huebner 2024-02-08 00:57:45 +01:00
commit a3c18408af
294 changed files with 6433 additions and 1603 deletions

View File

@ -131,7 +131,7 @@ Each component (frontend, admin, backend and database) has its own `.env` file.
Each component has a `.env.dist` file. This file contains all environment variables used by the component and can be used as pattern. If you want to use a local `.env`, copy the `.env.dist` and adjust the variables accordingly.
Each component has a `.env.template` file. These files are very important on deploy.
Each component has a `.env.template` file. These files are very important on deploy. They use COMMUNITY_HOST instead of different urls for different modules because in deploy using nginx is expected for routing incoming request to the correct module
There is one `.env.dist` in the `deployment/bare_metal/` folder. This `.env.dist` contains all variables used by the components, e.g. unites all `.env.dist` from the components. On deploy, we copy this `.env.dist` to `.env` and set all variables in this new file. The deploy script loads this variables and provides them by the `.env.templates` of each component, creating an `.env` for each component (see in `deployment/bare_metal/start.sh` the `envsubst`).

View File

@ -1,4 +1,6 @@
GRAPHQL_URI=http://localhost:4000/graphql
WALLET_AUTH_URL=http://localhost/authenticate?token={token}
WALLET_URL=http://localhost/login
GRAPHQL_URL=http://localhost:4000
GRAPHQL_PATH=/graphql
WALLET_URL=http://localhost
WALLET_AUTH_PATH=/authenticate?token={token}
WALLET_LOGIN_PATH=/login
DEBUG_DISABLE_AUTH=false

View File

@ -1,6 +1,8 @@
CONFIG_VERSION=$ADMIN_CONFIG_VERSION
GRAPHQL_URI=$GRAPHQL_URI
WALLET_AUTH_URL=$WALLET_AUTH_URL
WALLET_URL=$WALLET_URL
DEBUG_DISABLE_AUTH=false
COMMUNITY_HOST=$COMMUNITY_HOST
URL_PROTOCOL=$URL_PROTOCOL
WALLET_AUTH_PATH=$WALLET_AUTH_PATH
WALLET_LOGIN_PATH=$WALLET_LOGIN_PATH
GRAPHQL_PATH=$GRAPHQL_PATH
DEBUG_DISABLE_AUTH=false

View File

@ -38,8 +38,8 @@ export default {
name: 'navbar',
methods: {
async logout() {
window.location.assign(CONFIG.WALLET_URL)
// window.location = CONFIG.WALLET_URL
window.location.assign(CONFIG.WALLET_LOGIN_URL)
// window.location = CONFIG.WALLET_LOGIN_URL
this.$store.dispatch('logout')
await this.$apollo.mutate({
mutation: logout,

View File

@ -7,37 +7,45 @@ const pkg = require('../../package')
const constants = {
CONFIG_VERSION: {
DEFAULT: 'DEFAULT',
EXPECTED: 'v1.2022-03-18',
EXPECTED: 'v2.2024-01-04',
CURRENT: '',
},
}
const version = {
APP_VERSION: pkg.version,
BUILD_COMMIT: process.env.BUILD_COMMIT || null,
BUILD_COMMIT: process.env.BUILD_COMMIT ?? null,
// self reference of `version.BUILD_COMMIT` is not possible at this point, hence the duplicate code
BUILD_COMMIT_SHORT: (process.env.BUILD_COMMIT || '0000000').slice(0, 7),
PORT: process.env.PORT || 8080,
BUILD_COMMIT_SHORT: (process.env.BUILD_COMMIT ?? '0000000').slice(0, 7),
PORT: process.env.PORT ?? 8080,
}
const environment = {
NODE_ENV: process.env.NODE_ENV,
DEBUG: process.env.NODE_ENV !== 'production' || false,
PRODUCTION: process.env.NODE_ENV === 'production' || false,
DEBUG: process.env.NODE_ENV !== 'production' ?? false,
PRODUCTION: process.env.NODE_ENV === 'production' ?? false,
}
const COMMUNITY_HOST = process.env.COMMUNITY_HOST ?? undefined
const URL_PROTOCOL = process.env.URL_PROTOCOL ?? 'http'
const COMMUNITY_URL =
COMMUNITY_HOST && URL_PROTOCOL ? URL_PROTOCOL + '://' + COMMUNITY_HOST : undefined
const WALLET_URL = process.env.WALLET_URL ?? COMMUNITY_URL ?? 'http://localhost'
const endpoints = {
GRAPHQL_URI: process.env.GRAPHQL_URI || 'http://localhost:4000/graphql',
WALLET_AUTH_URL: process.env.WALLET_AUTH_URL || 'http://localhost/authenticate?token={token}',
WALLET_URL: process.env.WALLET_URL || 'http://localhost/login',
GRAPHQL_URL:
(process.env.GRAPHQL_URL ?? COMMUNITY_URL ?? 'http://localhost:4000') +
process.env.GRAPHQL_PATH ?? '/graphql',
WALLET_AUTH_URL: WALLET_URL + (process.env.WALLET_AUTH_PATH ?? '/authenticate?token={token}'),
WALLET_LOGIN_URL: WALLET_URL + (process.env.WALLET_LOGIN_PATH ?? '/login'),
}
const debug = {
DEBUG_DISABLE_AUTH: process.env.DEBUG_DISABLE_AUTH === 'true' || false,
DEBUG_DISABLE_AUTH: process.env.DEBUG_DISABLE_AUTH === 'true' ?? false,
}
// 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 (
![constants.CONFIG_VERSION.EXPECTED, constants.CONFIG_VERSION.DEFAULT].includes(
constants.CONFIG_VERSION.CURRENT,

View File

@ -16,7 +16,7 @@ const authLink = new ApolloLink((operation, forward) => {
return forward(operation).map((response) => {
if (response.errors && response.errors[0].message === '403.13 - Client certificate revoked') {
store.dispatch('logout', null)
window.location.assign(CONFIG.WALLET_URL)
window.location.assign(CONFIG.WALLET_LOGIN_URL)
return response
}
const newToken = operation.getContext().response.headers.get('token')

View File

@ -27,10 +27,10 @@ DLT_CONNECTOR_URL=http://localhost:6010
# Community
COMMUNITY_NAME=Gradido Entwicklung
COMMUNITY_URL=http://localhost/
COMMUNITY_REGISTER_URL=http://localhost/register
COMMUNITY_REDEEM_URL=http://localhost/redeem/{code}
COMMUNITY_REDEEM_CONTRIBUTION_URL=http://localhost/redeem/CL-{code}
COMMUNITY_URL=http://localhost
COMMUNITY_REGISTER_PATH=/register
COMMUNITY_REDEEM_PATH=/redeem/{code}
COMMUNITY_REDEEM_CONTRIBUTION_PATH=/redeem/CL-{code}
COMMUNITY_DESCRIPTION=Die lokale Entwicklungsumgebung von Gradido.
COMMUNITY_SUPPORT_MAIL=support@supportmail.com
@ -47,10 +47,10 @@ EMAIL_SENDER=info@gradido.net
EMAIL_PASSWORD=xxx
EMAIL_SMTP_URL=gmail.com
EMAIL_SMTP_PORT=587
EMAIL_LINK_VERIFICATION=http://localhost/checkEmail/{optin}{code}
EMAIL_LINK_SETPASSWORD=http://localhost/reset-password/{optin}
EMAIL_LINK_FORGOTPASSWORD=http://localhost/forgot-password
EMAIL_LINK_OVERVIEW=http://localhost/overview
EMAIL_LINK_VERIFICATION_PATH=/checkEmail/{optin}{code}
EMAIL_LINK_SETPASSWORD_PATH=/reset-password/{optin}
EMAIL_LINK_FORGOTPASSWORD_PATH=/forgot-password
EMAIL_LINK_OVERVIEW_PATH=/overview
EMAIL_CODE_VALID_TIME=1440
EMAIL_CODE_REQUEST_TIME=10
@ -63,6 +63,7 @@ WEBHOOK_ELOPAGE_SECRET=secret
# Federation
FEDERATION_VALIDATE_COMMUNITY_TIMER=60000
FEDERATION_XCOM_SENDCOINS_ENABLED=false
# GMS
# GMS_ACTIVE=true

View File

@ -1,5 +1,5 @@
# must match the CONFIG_VERSION.EXPECTED definition in scr/config/index.ts
CONFIG_VERSION=v21.2023-11-15
CONFIG_VERSION=$BACKEND_CONFIG_VERSION
# Server
JWT_SECRET=$JWT_SECRET
@ -25,14 +25,15 @@ KLICKTIPP_APIKEY_EN=$KLICKTIPP_APIKEY_EN
# DltConnector
DLT_CONNECTOR=$DLT_CONNECTOR
DLT_CONNECTOR_URL=$DLT_CONNECTOR_URL
DLT_CONNECTOR_PORT=$DLT_CONNECTOR_PORT
# Community
COMMUNITY_HOST=$COMMUNITY_HOST
URL_PROTOCOL=$URL_PROTOCOL
COMMUNITY_NAME=$COMMUNITY_NAME
COMMUNITY_URL=$COMMUNITY_URL
COMMUNITY_REGISTER_URL=$COMMUNITY_REGISTER_URL
COMMUNITY_REDEEM_URL=$COMMUNITY_REDEEM_URL
COMMUNITY_REDEEM_CONTRIBUTION_URL=$COMMUNITY_REDEEM_CONTRIBUTION_URL
COMMUNITY_REGISTER_PATH=$COMMUNITY_REGISTER_PATH
COMMUNITY_REDEEM_PATH=$COMMUNITY_REDEEM_PATH
COMMUNITY_REDEEM_CONTRIBUTION_PATH=$COMMUNITY_REDEEM_CONTRIBUTION_PATH
COMMUNITY_DESCRIPTION=$COMMUNITY_DESCRIPTION
COMMUNITY_SUPPORT_MAIL=$COMMUNITY_SUPPORT_MAIL
@ -48,12 +49,12 @@ EMAIL_USERNAME=$EMAIL_USERNAME
EMAIL_SENDER=$EMAIL_SENDER
EMAIL_PASSWORD=$EMAIL_PASSWORD
EMAIL_SMTP_URL=$EMAIL_SMTP_URL
EMAIL_SMTP_PORT=587
EMAIL_LINK_VERIFICATION=$EMAIL_LINK_VERIFICATION
EMAIL_LINK_SETPASSWORD=$EMAIL_LINK_SETPASSWORD
EMAIL_LINK_FORGOTPASSWORD=$EMAIL_LINK_FORGOTPASSWORD
EMAIL_LINK_OVERVIEW=$EMAIL_LINK_OVERVIEW
EMAIL_CODE_VALID_TIME=$EMAIL_CODE_VALID_TIME
EMAIL_SMTP_PORT=$EMAIL_SMTP_PORT
EMAIL_LINK_VERIFICATION_PATH=$EMAIL_LINK_VERIFICATION_PATH
EMAIL_LINK_SETPASSWORD_PATH=$EMAIL_LINK_SETPASSWORD_PATH
EMAIL_LINK_FORGOTPASSWORD_PATH=$EMAIL_LINK_FORGOTPASSWORD_PATH
EMAIL_LINK_OVERVIEW_PATH=$EMAIL_LINK_OVERVIEW_PATH
EMAIL_CODE_VALID_TIME=$EMAIL_CODE_VALID_TIME_PATH
EMAIL_CODE_REQUEST_TIME=$EMAIL_CODE_REQUEST_TIME
# Webhook
@ -61,6 +62,7 @@ WEBHOOK_ELOPAGE_SECRET=$WEBHOOK_ELOPAGE_SECRET
# Federation
FEDERATION_VALIDATE_COMMUNITY_TIMER=$FEDERATION_VALIDATE_COMMUNITY_TIMER
FEDERATION_XCOM_SENDCOINS_ENABLED=$FEDERATION_XCOM_SENDCOINS_ENABLED
# GMS
GMS_ACTIVE=$GMS_ACTIVE

View File

@ -16,6 +16,7 @@ module.exports = {
moduleNameMapper: {
'@/(.*)': '<rootDir>/src/$1',
'@arg/(.*)': '<rootDir>/src/graphql/arg/$1',
'@dltConnector/(.*)': '<rootDir>/src/apis/dltConnector/$1',
'@enum/(.*)': '<rootDir>/src/graphql/enum/$1',
'@model/(.*)': '<rootDir>/src/graphql/model/$1',
'@union/(.*)': '<rootDir>/src/graphql/union/$1',
@ -27,6 +28,11 @@ module.exports = {
process.env.NODE_ENV === 'development'
? '<rootDir>/../database/entity/$1'
: '<rootDir>/../database/build/entity/$1',
'@logging/(.*)':
// eslint-disable-next-line n/no-process-env
process.env.NODE_ENV === 'development'
? '<rootDir>/../database/logging/$1'
: '<rootDir>/../database/build/logging/$1',
'@dbTools/(.*)':
// eslint-disable-next-line n/no-process-env
process.env.NODE_ENV === 'development'

View File

@ -31,9 +31,11 @@
"dotenv": "^10.0.0",
"email-templates": "^10.0.1",
"express": "^4.17.1",
"express-slow-down": "^2.0.1",
"gradido-database": "file:../database",
"graphql": "^15.5.1",
"graphql-request": "5.0.0",
"helmet": "^5.1.1",
"i18n": "^0.15.1",
"jose": "^4.14.4",
"lodash.clonedeep": "^4.5.0",

View File

@ -25,8 +25,6 @@ let testEnv: {
jest.mock('graphql-request', () => {
const originalModule = jest.requireActual('graphql-request')
let testCursor = 0
return {
__esModule: true,
...originalModule,
@ -38,30 +36,11 @@ jest.mock('graphql-request', () => {
// why not using mockResolvedValueOnce or mockReturnValueOnce?
// I have tried, but it didn't work and return every time the first value
request: jest.fn().mockImplementation(() => {
testCursor++
if (testCursor === 4) {
return Promise.resolve(
// invalid, is 33 Bytes long as binary
{
transmitTransaction: {
dltTransactionIdHex:
'723e3fab62c5d3e2f62fd72ba4e622bcd53eff35262e3f3526327fe41bc516212A',
},
},
)
} else if (testCursor === 5) {
throw Error('Connection error')
} else {
return Promise.resolve(
// valid, is 32 Bytes long as binary
{
transmitTransaction: {
dltTransactionIdHex:
'723e3fab62c5d3e2f62fd72ba4e622bcd53eff35262e3f3526327fe41bc51621',
},
},
)
}
return Promise.resolve({
transmitTransaction: {
succeed: true,
},
})
}),
}
}),

View File

@ -6,6 +6,9 @@ import { TransactionTypeId } from '@/graphql/enum/TransactionTypeId'
import { LogError } from '@/server/LogError'
import { backendLogger as logger } from '@/server/logger'
import { TransactionResult } from './model/TransactionResult'
import { UserIdentifier } from './model/UserIdentifier'
const sendTransaction = gql`
mutation ($input: TransactionInput!) {
sendTransaction(data: $input) {
@ -78,32 +81,42 @@ export class DltConnectorClient {
* transmit transaction via dlt-connector to iota
* and update dltTransactionId of transaction in db with iota message id
*/
public async transmitTransaction(
transaction: DbTransaction,
senderCommunityUuid?: string,
recipientCommunityUuid?: string,
): Promise<string> {
public async transmitTransaction(transaction: DbTransaction): Promise<boolean> {
const typeString = getTransactionTypeString(transaction.typeId)
const amountString = transaction.amount.toString()
// no negative values in dlt connector, gradido concept don't use negative values so the code don't use it too
const amountString = transaction.amount.abs().toString()
const params = {
input: {
user: {
uuid: transaction.userGradidoID,
communityUuid: transaction.userCommunityUuid,
} as UserIdentifier,
linkedUser: {
uuid: transaction.linkedUserGradidoID,
communityUuid: transaction.linkedUserCommunityUuid,
} as UserIdentifier,
amount: amountString,
type: typeString,
createdAt: transaction.balanceDate.toISOString(),
backendTransactionId: transaction.id,
targetDate: transaction.creationDate?.toISOString(),
},
}
try {
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
const { data } = await this.client.rawRequest(sendTransaction, {
input: {
senderUser: {
uuid: transaction.userGradidoID,
communityUuid: senderCommunityUuid,
},
recipientUser: {
uuid: transaction.linkedUserGradidoID,
communityUuid: recipientCommunityUuid,
},
amount: amountString,
type: typeString,
createdAt: transaction.balanceDate.toString(),
// TODO: add account nr for user after they have also more than one account in backend
logger.debug('transmit transaction to dlt connector', params)
const {
data: {
sendTransaction: { error, succeed },
},
})
// eslint-disable-next-line @typescript-eslint/no-unsafe-return, @typescript-eslint/no-unsafe-member-access
return data.sendTransaction.dltTransactionIdHex
} = await this.client.rawRequest<{ sendTransaction: TransactionResult }>(
sendTransaction,
params,
)
if (error) {
throw new Error(error.message)
}
return succeed
} catch (e) {
throw new LogError('Error send sending transaction to dlt-connector: ', e)
}

View File

@ -0,0 +1,14 @@
/**
* Error Types for dlt-connector graphql responses
*/
export enum TransactionErrorType {
NOT_IMPLEMENTED_YET = 'Not Implemented yet',
MISSING_PARAMETER = 'Missing parameter',
ALREADY_EXIST = 'Already exist',
DB_ERROR = 'DB Error',
PROTO_DECODE_ERROR = 'Proto Decode Error',
PROTO_ENCODE_ERROR = 'Proto Encode Error',
INVALID_SIGNATURE = 'Invalid Signature',
LOGIC_ERROR = 'Logic Error',
NOT_FOUND = 'Not found',
}

View File

@ -0,0 +1,11 @@
/**
* Transaction Types on Blockchain
*/
export enum TransactionType {
GRADIDO_TRANSFER = 1,
GRADIDO_CREATION = 2,
GROUP_FRIENDS_UPDATE = 3,
REGISTER_ADDRESS = 4,
GRADIDO_DEFERRED_TRANSFER = 5,
COMMUNITY_ROOT = 6,
}

View File

@ -0,0 +1,7 @@
import { TransactionErrorType } from '@dltConnector/enum/TransactionErrorType'
export interface TransactionError {
type: TransactionErrorType
message: string
name: string
}

View File

@ -0,0 +1,8 @@
import { TransactionType } from '@dltConnector/enum/TransactionType'
export interface TransactionRecipe {
id: number
createdAt: string
type: TransactionType
topic: string
}

View File

@ -0,0 +1,8 @@
import { TransactionError } from './TransactionError'
import { TransactionRecipe } from './TransactionRecipe'
export interface TransactionResult {
error?: TransactionError
recipe?: TransactionRecipe
succeed: boolean
}

View File

@ -0,0 +1,5 @@
export interface UserIdentifier {
uuid: string
communityUuid: string
accountNr?: number
}

View File

@ -1,36 +0,0 @@
import { registerEnumType } from 'type-graphql'
export enum GmsPublishNameType {
GMS_PUBLISH_NAME_ALIAS_OR_INITALS = 0,
GMS_PUBLISH_NAME_INITIALS = 1,
GMS_PUBLISH_NAME_FIRST = 2,
GMS_PUBLISH_NAME_FIRST_INITIAL = 3,
GMS_PUBLISH_NAME_FULL = 4,
}
registerEnumType(GmsPublishNameType, {
name: 'GmsPublishNameType', // this one is mandatory
description: 'Type of name publishing', // this one is optional
})
export enum GmsPublishPhoneType {
GMS_PUBLISH_PHONE_NOTHING = 0,
GMS_PUBLISH_PHONE_COUNTRY = 1,
GMS_PUBLISH_PHONE_FULL = 2,
}
registerEnumType(GmsPublishPhoneType, {
name: 'GmsPublishPhoneType', // this one is mandatory
description: 'Type of Phone publishing', // this one is optional
})
export enum GmsLocationType {
GMS_LOCATION_TYPE_EXACT = 0,
GMS_LOCATION_TYPE_APPROXIMATE = 1,
GMS_LOCATION_TYPE_RANDOM = 2,
}
registerEnumType(GmsLocationType, {
name: 'GmsLocationType', // this one is mandatory
description: 'Type of location treatment in GMS', // this one is optional
})

View File

@ -1,6 +1,8 @@
import { User as dbUser } from '@entity/User'
import { GmsLocationType, GmsPublishNameType, GmsPublishPhoneType } from './GmsEnums'
import { GmsPublishLocationType } from '@/graphql/enum/GmsPublishLocationType'
import { GmsPublishNameType } from '@/graphql/enum/GmsPublishNameType'
import { GmsPublishPhoneType } from '@/graphql/enum/GmsPublishPhoneType'
export class GmsUser {
constructor(user: dbUser) {
@ -12,7 +14,7 @@ export class GmsUser {
this.firstName = this.getGmsFirstName(user)
this.lastName = this.getGmsLastName(user)
this.alias = this.getGmsAlias(user)
this.type = GmsLocationType.GMS_LOCATION_TYPE_RANDOM
this.type = GmsPublishLocationType.GMS_LOCATION_TYPE_RANDOM
this.location = null
}

View File

@ -12,14 +12,14 @@ Decimal.set({
})
const constants = {
DB_VERSION: '0079-introduce_gms_registration',
DB_VERSION: '0082-introduce_gms_registration',
DECAY_START_TIME: new Date('2021-05-13 17:46:31-0000'), // GMT+0
LOG4JS_CONFIG: 'log4js-config.json',
// default log level on production should be info
LOG_LEVEL: process.env.LOG_LEVEL ?? 'info',
CONFIG_VERSION: {
DEFAULT: 'DEFAULT',
EXPECTED: 'v21.2023-11-15',
EXPECTED: 'v21.2024-01-06',
CURRENT: '',
},
}
@ -51,18 +51,23 @@ const klicktipp = {
KLICKTIPP_APIKEY_EN: process.env.KLICKTIPP_APIKEY_EN ?? 'SomeFakeKeyEN',
}
const COMMUNITY_HOST = process.env.COMMUNITY_HOST ?? 'localhost'
const URL_PROTOCOL = process.env.URL_PROTOCOL ?? 'http'
const COMMUNITY_URL = process.env.COMMUNITY_URL ?? `${URL_PROTOCOL}://${COMMUNITY_HOST}`
const DLT_CONNECTOR_PORT = process.env.DLT_CONNECTOR_PORT ?? 6010
const dltConnector = {
DLT_CONNECTOR: process.env.DLT_CONNECTOR === 'true' || false,
DLT_CONNECTOR_URL: process.env.DLT_CONNECTOR_URL ?? 'http://localhost:6010',
DLT_CONNECTOR_URL: process.env.DLT_CONNECTOR_URL ?? `${COMMUNITY_URL}:${DLT_CONNECTOR_PORT}`,
}
const community = {
COMMUNITY_NAME: process.env.COMMUNITY_NAME ?? 'Gradido Entwicklung',
COMMUNITY_URL: process.env.COMMUNITY_URL ?? 'http://localhost/',
COMMUNITY_REGISTER_URL: process.env.COMMUNITY_REGISTER_URL ?? 'http://localhost/register',
COMMUNITY_REDEEM_URL: process.env.COMMUNITY_REDEEM_URL ?? 'http://localhost/redeem/{code}',
COMMUNITY_URL,
COMMUNITY_REGISTER_URL: COMMUNITY_URL + (process.env.COMMUNITY_REGISTER_PATH ?? '/register'),
COMMUNITY_REDEEM_URL: COMMUNITY_URL + (process.env.COMMUNITY_REDEEM_PATH ?? '/redeem/{code}'),
COMMUNITY_REDEEM_CONTRIBUTION_URL:
process.env.COMMUNITY_REDEEM_CONTRIBUTION_URL ?? 'http://localhost/redeem/CL-{code}',
COMMUNITY_URL + (process.env.COMMUNITY_REDEEM_CONTRIBUTION_PATH ?? '/redeem/CL-{code}'),
COMMUNITY_DESCRIPTION:
process.env.COMMUNITY_DESCRIPTION ?? 'Die lokale Entwicklungsumgebung von Gradido.',
COMMUNITY_SUPPORT_MAIL: process.env.COMMUNITY_SUPPORT_MAIL ?? 'support@supportmail.com',
@ -74,8 +79,8 @@ const loginServer = {
}
const email = {
EMAIL: process.env.EMAIL === 'true' || false,
EMAIL_TEST_MODUS: process.env.EMAIL_TEST_MODUS === 'true' || false,
EMAIL: process.env.EMAIL === 'true' ?? false,
EMAIL_TEST_MODUS: process.env.EMAIL_TEST_MODUS === 'true' ?? false,
EMAIL_TEST_RECEIVER: process.env.EMAIL_TEST_RECEIVER ?? 'stage1@gradido.net',
EMAIL_USERNAME: process.env.EMAIL_USERNAME ?? '',
EMAIL_SENDER: process.env.EMAIL_SENDER ?? 'info@gradido.net',
@ -85,19 +90,19 @@ const email = {
// eslint-disable-next-line no-unneeded-ternary
EMAIL_TLS: process.env.EMAIL_TLS === 'false' ? false : true,
EMAIL_LINK_VERIFICATION:
process.env.EMAIL_LINK_VERIFICATION ?? 'http://localhost/checkEmail/{optin}{code}',
COMMUNITY_URL + (process.env.EMAIL_LINK_VERIFICATION_PATH ?? '/checkEmail/{optin}{code}'),
EMAIL_LINK_SETPASSWORD:
process.env.EMAIL_LINK_SETPASSWORD ?? 'http://localhost/reset-password/{optin}',
COMMUNITY_URL + (process.env.EMAIL_LINK_SETPASSWORD_PATH ?? '/reset-password/{optin}'),
EMAIL_LINK_FORGOTPASSWORD:
process.env.EMAIL_LINK_FORGOTPASSWORD ?? 'http://localhost/forgot-password',
EMAIL_LINK_OVERVIEW: process.env.EMAIL_LINK_OVERVIEW ?? 'http://localhost/overview',
COMMUNITY_URL + (process.env.EMAIL_LINK_FORGOTPASSWORD_PATH ?? '/forgot-password'),
EMAIL_LINK_OVERVIEW: COMMUNITY_URL + (process.env.EMAIL_LINK_OVERVIEW_PATH ?? '/overview'),
// time in minutes a optin code is valid
EMAIL_CODE_VALID_TIME: process.env.EMAIL_CODE_VALID_TIME
? parseInt(process.env.EMAIL_CODE_VALID_TIME) || 1440
? parseInt(process.env.EMAIL_CODE_VALID_TIME) ?? 1440
: 1440,
// time in minutes that must pass to request a new optin code
EMAIL_CODE_REQUEST_TIME: process.env.EMAIL_CODE_REQUEST_TIME
? parseInt(process.env.EMAIL_CODE_REQUEST_TIME) || 10
? parseInt(process.env.EMAIL_CODE_REQUEST_TIME) ?? 10
: 10,
}
@ -124,9 +129,9 @@ if (
const federation = {
FEDERATION_BACKEND_SEND_ON_API: process.env.FEDERATION_BACKEND_SEND_ON_API ?? '1_0',
FEDERATION_VALIDATE_COMMUNITY_TIMER:
Number(process.env.FEDERATION_VALIDATE_COMMUNITY_TIMER) || 60000,
Number(process.env.FEDERATION_VALIDATE_COMMUNITY_TIMER) ?? 60000,
FEDERATION_XCOM_SENDCOINS_ENABLED:
process.env.FEDERATION_XCOM_SENDCOINS_ENABLED === 'true' || false,
process.env.FEDERATION_XCOM_SENDCOINS_ENABLED === 'true' ?? false,
// default value for community-uuid is equal uuid of stage-3
FEDERATION_XCOM_RECEIVER_COMMUNITY_UUID:
process.env.FEDERATION_XCOM_RECEIVER_COMMUNITY_UUID ?? '56a55482-909e-46a4-bfa2-cd025e894ebc',

View File

@ -9,6 +9,6 @@ block content
h2= t('emails.addedContributionMessage.readMessage')
div(class="p_content")= t('emails.addedContributionMessage.toSeeAndAnswerMessage')
a.button-3(href=`${communityURL}community/contributions`) #{t('emails.general.toAccount')}
a.button-3(href=`${communityURL}/community/contributions`) #{t('emails.general.toAccount')}
include ../includes/doNotReply.pug

View File

@ -1,7 +1,7 @@
//-
h2= t('emails.general.contributionDetails')
div(class="p_content")= t('emails.contribution.toSeeContributionsAndMessages')
a.button-3(href=`${communityURL}community/contributions`) #{t('emails.general.toAccount')}
a.button-3(href=`${communityURL}/community/contributions`) #{t('emails.general.toAccount')}
div(class="p_content")= t('emails.general.orCopyLink')
a.clink(href=`${communityURL}community/contributions`) #{`${communityURL}community/contributions`}
a.clink(href=`${communityURL}/community/contributions`) #{`${communityURL}/community/contributions`}

View File

@ -13,6 +13,6 @@ block content
br
= t('emails.general.detailsYouFindOnLinkToYourAccount')
a.button-3(href=`${communityURL}transactions`) #{t('emails.general.toAccount')}
a.button-3(href=`${communityURL}/transactions`) #{t('emails.general.toAccount')}
include ../includes/doNotReply.pug

View File

@ -9,7 +9,7 @@ block content
h2= t('emails.general.transactionDetails')
div(class="p_content")= t('emails.general.detailsYouFindOnLinkToYourAccount')
a.button-3(href=`${communityURL}transactions`) #{t('emails.general.toAccount')}
a.button-3(href=`${communityURL}/transactions`) #{t('emails.general.toAccount')}
include ../includes/doNotReply.pug

View File

@ -28,9 +28,9 @@ export class AuthenticationClient {
async openConnection(args: OpenConnectionArgs): Promise<boolean | undefined> {
logger.debug(`Authentication: openConnection at ${this.endpoint} for args:`, args)
try {
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
const { data } = await this.client.rawRequest(openConnection, { args })
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
const { data } = await this.client.rawRequest<{ openConnection: boolean }>(openConnection, {
args,
})
if (!data?.openConnection) {
logger.warn(
'Authentication: openConnection without response data from endpoint',

View File

@ -5,9 +5,10 @@ import { getPublicCommunityInfo } from '@/federation/client/1_0/query/getPublicC
import { getPublicKey } from '@/federation/client/1_0/query/getPublicKey'
import { backendLogger as logger } from '@/server/logger'
import { PublicCommunityInfoLoggingView } from './logging/PublicCommunityInfoLogging.view'
import { GetPublicKeyResult } from './model/GetPublicKeyResult'
import { PublicCommunityInfo } from './model/PublicCommunityInfo'
// eslint-disable-next-line camelcase
export class FederationClient {
dbCom: DbFederatedCommunity
endpoint: string
@ -27,12 +28,17 @@ export class FederationClient {
})
}
getEndpoint = () => {
return this.endpoint
}
getPublicKey = async (): Promise<string | undefined> => {
logger.debug('Federation: getPublicKey from endpoint', this.endpoint)
try {
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
const { data } = await this.client.rawRequest(getPublicKey, {})
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
const { data } = await this.client.rawRequest<{ getPublicKey: GetPublicKeyResult }>(
getPublicKey,
{},
)
if (!data?.getPublicKey?.publicKey) {
logger.warn('Federation: getPublicKey without response data from endpoint', this.endpoint)
return
@ -40,22 +46,25 @@ export class FederationClient {
logger.debug(
'Federation: getPublicKey successful from endpoint',
this.endpoint,
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
data.getPublicKey.publicKey,
)
// eslint-disable-next-line @typescript-eslint/no-unsafe-return, @typescript-eslint/no-unsafe-member-access
return data.getPublicKey.publicKey
} catch (err) {
logger.warn('Federation: getPublicKey failed for endpoint', this.endpoint)
const errorString = JSON.stringify(err)
logger.warn('Federation: getPublicKey failed for endpoint', {
endpoint: this.endpoint,
err: errorString.length <= 200 ? errorString : errorString.substring(0, 200) + '...',
})
}
}
getPublicCommunityInfo = async (): Promise<PublicCommunityInfo | undefined> => {
logger.debug(`Federation: getPublicCommunityInfo with endpoint='${this.endpoint}'...`)
try {
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
const { data } = await this.client.rawRequest(getPublicCommunityInfo, {})
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
const { data } = await this.client.rawRequest<{
getPublicCommunityInfo: PublicCommunityInfo
}>(getPublicCommunityInfo, {})
if (!data?.getPublicCommunityInfo?.name) {
logger.warn(
'Federation: getPublicCommunityInfo without response data from endpoint',
@ -64,12 +73,17 @@ export class FederationClient {
return
}
logger.debug(`Federation: getPublicCommunityInfo successful from endpoint=${this.endpoint}`)
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
logger.debug(`publicCommunityInfo:`, data.getPublicCommunityInfo)
// eslint-disable-next-line @typescript-eslint/no-unsafe-return, @typescript-eslint/no-unsafe-member-access
logger.debug(
`publicCommunityInfo:`,
new PublicCommunityInfoLoggingView(data.getPublicCommunityInfo),
)
return data.getPublicCommunityInfo
} catch (err) {
logger.warn('Federation: getPublicCommunityInfo failed for endpoint', this.endpoint)
const errorString = JSON.stringify(err)
logger.warn('Federation: getPublicCommunityInfo failed for endpoint', {
endpoint: this.endpoint,
err: errorString.length <= 200 ? errorString : errorString.substring(0, 200) + '...',
})
}
}
}

View File

@ -4,6 +4,8 @@ import { GraphQLClient } from 'graphql-request'
import { LogError } from '@/server/LogError'
import { backendLogger as logger } from '@/server/logger'
import { SendCoinsArgsLoggingView } from './logging/SendCoinsArgsLogging.view'
import { SendCoinsResultLoggingView } from './logging/SendCoinsResultLogging.view'
import { SendCoinsArgs } from './model/SendCoinsArgs'
import { SendCoinsResult } from './model/SendCoinsResult'
import { revertSendCoins as revertSendCoinsQuery } from './query/revertSendCoins'
@ -11,7 +13,6 @@ import { revertSettledSendCoins as revertSettledSendCoinsQuery } from './query/r
import { settleSendCoins as settleSendCoinsQuery } from './query/settleSendCoins'
import { voteForSendCoins as voteForSendCoinsQuery } from './query/voteForSendCoins'
// eslint-disable-next-line camelcase
export class SendCoinsClient {
dbCom: DbFederatedCommunity
endpoint: string
@ -34,26 +35,26 @@ export class SendCoinsClient {
async voteForSendCoins(args: SendCoinsArgs): Promise<SendCoinsResult> {
logger.debug('X-Com: voteForSendCoins against endpoint=', this.endpoint)
try {
logger.debug(`X-Com: SendCoinsClient: voteForSendCoins with args=`, args)
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
const { data } = await this.client.rawRequest(voteForSendCoinsQuery, { args })
logger.debug(`X-Com: SendCoinsClient: after rawRequest...data:`, data)
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
logger.debug(
`X-Com: SendCoinsClient: voteForSendCoins with args=`,
new SendCoinsArgsLoggingView(args),
)
const { data } = await this.client.rawRequest<{ voteForSendCoins: SendCoinsResult }>(
voteForSendCoinsQuery,
{ args },
)
const result = data.voteForSendCoins
if (!data?.voteForSendCoins?.vote) {
logger.debug('X-Com: voteForSendCoins failed with: ', data)
logger.debug(
'X-Com: voteForSendCoins failed with: ',
new SendCoinsResultLoggingView(result),
)
return new SendCoinsResult()
}
const result = new SendCoinsResult()
result.vote = true
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-assignment
result.recipGradidoID = data.voteForSendCoins.recipGradidoID
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-assignment
result.recipFirstName = data.voteForSendCoins.recipFirstName
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-assignment
result.recipLastName = data.voteForSendCoins.recipLastName
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-assignment
result.recipAlias = data.voteForSendCoins.recipAlias
logger.debug('X-Com: voteForSendCoins successful with result=', result)
logger.debug(
'X-Com: voteForSendCoins successful with result=',
new SendCoinsResultLoggingView(result),
)
return result
} catch (err) {
throw new LogError(`X-Com: voteForSendCoins failed for endpoint=${this.endpoint}:`, err)
@ -63,11 +64,15 @@ export class SendCoinsClient {
async revertSendCoins(args: SendCoinsArgs): Promise<boolean> {
logger.debug('X-Com: revertSendCoins against endpoint=', this.endpoint)
try {
logger.debug(`X-Com: SendCoinsClient: revertSendCoins with args=`, args)
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
const { data } = await this.client.rawRequest(revertSendCoinsQuery, { args })
logger.debug(
`X-Com: SendCoinsClient: revertSendCoins with args=`,
new SendCoinsArgsLoggingView(args),
)
const { data } = await this.client.rawRequest<{ revertSendCoins: boolean }>(
revertSendCoinsQuery,
{ args },
)
logger.debug(`X-Com: SendCoinsClient: after revertSendCoins: data=`, data)
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
if (!data?.revertSendCoins) {
logger.warn('X-Com: revertSendCoins without response data from endpoint', this.endpoint)
return false
@ -88,11 +93,15 @@ export class SendCoinsClient {
async settleSendCoins(args: SendCoinsArgs): Promise<boolean> {
logger.debug(`X-Com: settleSendCoins against endpoint='${this.endpoint}'...`)
try {
logger.debug(`X-Com: SendCoinsClient: settleSendCoins with args=`, args)
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
const { data } = await this.client.rawRequest(settleSendCoinsQuery, { args })
logger.debug(
`X-Com: SendCoinsClient: settleSendCoins with args=`,
new SendCoinsArgsLoggingView(args),
)
const { data } = await this.client.rawRequest<{ settleSendCoins: boolean }>(
settleSendCoinsQuery,
{ args },
)
logger.debug(`X-Com: SendCoinsClient: after settleSendCoins: data=`, data)
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
if (!data?.settleSendCoins) {
logger.warn(
'X-Com: SendCoinsClient: settleSendCoins without response data from endpoint',
@ -115,9 +124,14 @@ export class SendCoinsClient {
async revertSettledSendCoins(args: SendCoinsArgs): Promise<boolean> {
logger.debug(`X-Com: revertSettledSendCoins against endpoint='${this.endpoint}'...`)
try {
logger.debug(`X-Com: SendCoinsClient: revertSettledSendCoins with args=`, args)
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
const { data } = await this.client.rawRequest(revertSettledSendCoinsQuery, { args })
logger.debug(
`X-Com: SendCoinsClient: revertSettledSendCoins with args=`,
new SendCoinsArgsLoggingView(args),
)
const { data } = await this.client.rawRequest<{ revertSettledSendCoins: boolean }>(
revertSettledSendCoinsQuery,
{ args },
)
logger.debug(`X-Com: SendCoinsClient: after revertSettledSendCoins: data=`, data)
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
if (!data?.revertSettledSendCoins) {

View File

@ -0,0 +1,19 @@
import { AbstractLoggingView } from '@logging/AbstractLogging.view'
import { PublicCommunityInfo } from '@/federation/client/1_0/model/PublicCommunityInfo'
export class PublicCommunityInfoLoggingView extends AbstractLoggingView {
public constructor(private self: PublicCommunityInfo) {
super()
}
// eslint-disable-next-line @typescript-eslint/no-explicit-any
public toJSON(): any {
return {
name: this.self.name,
description: this.self.description,
creationDate: this.dateToString(this.self.creationDate),
publicKey: this.self.publicKey,
}
}
}

View File

@ -0,0 +1,24 @@
import { AbstractLoggingView } from '@logging/AbstractLogging.view'
import { SendCoinsArgs } from '@/federation/client/1_0/model/SendCoinsArgs'
export class SendCoinsArgsLoggingView extends AbstractLoggingView {
public constructor(private self: SendCoinsArgs) {
super()
}
// eslint-disable-next-line @typescript-eslint/no-explicit-any
public toJSON(): any {
return {
recipientCommunityUuid: this.self.recipientCommunityUuid,
recipientUserIdentifier: this.self.recipientUserIdentifier,
creationDate: this.self.creationDate,
amount: this.decimalToString(this.self.amount),
memoLength: this.self.memo.length,
senderCommunityUuid: this.self.senderCommunityUuid,
senderUserUuid: this.self.senderUserUuid,
senderUserName: this.self.senderUserName.substring(0, 3),
senderAlias: this.self.senderAlias?.substring(0, 3),
}
}
}

View File

@ -0,0 +1,20 @@
import { AbstractLoggingView } from '@logging/AbstractLogging.view'
import { SendCoinsResult } from '@/federation/client/1_0/model/SendCoinsResult'
export class SendCoinsResultLoggingView extends AbstractLoggingView {
public constructor(private self: SendCoinsResult) {
super()
}
// eslint-disable-next-line @typescript-eslint/no-explicit-any
public toJSON(): any {
return {
vote: this.self.vote,
recipGradidoID: this.self.recipGradidoID,
recipFirstName: this.self.recipFirstName?.substring(0, 3),
recipLastName: this.self.recipLastName?.substring(0, 3),
recipAlias: this.self.recipAlias?.substring(0, 3),
}
}
}

View File

@ -0,0 +1,13 @@
// eslint-disable-next-line @typescript-eslint/no-unused-vars
import { Field, ObjectType } from 'type-graphql'
@ObjectType()
// eslint-disable-next-line @typescript-eslint/no-unused-vars
export class GetPublicKeyResult {
constructor(pubKey: string) {
this.publicKey = pubKey
}
@Field(() => String)
publicKey: string
}

View File

@ -47,15 +47,25 @@ export class FederationClientFactory {
const instance = FederationClientFactory.instanceArray.find(
(instance) => instance.id === dbCom.id,
)
if (instance) {
// TODO: found a way to prevent double code with FederationClient::constructor
const endpoint = `${dbCom.endPoint.endsWith('/') ? dbCom.endPoint : dbCom.endPoint + '/'}${
dbCom.apiVersion
}/`
// check if endpoint is still the same and not changed meanwhile
if (instance && instance.client.getEndpoint() === endpoint) {
return instance.client
}
const client = FederationClientFactory.createFederationClient(dbCom)
if (client) {
FederationClientFactory.instanceArray.push({
id: dbCom.id,
client,
} as FederationClientInstance)
// only update instance if we already have one
if (instance) {
instance.client = client
} else {
FederationClientFactory.instanceArray.push({
id: dbCom.id,
client,
} as FederationClientInstance)
}
}
return client
}

View File

@ -68,7 +68,7 @@ describe('validate Communities', () => {
return { data: {} } as Response<unknown>
})
const variables1 = {
publicKey: Buffer.from('11111111111111111111111111111111'),
publicKey: Buffer.from('11111111111111111111111111111111', 'hex'),
apiVersion: '1_0',
endPoint: 'http//localhost:5001/api/',
lastAnnouncedAt: new Date(),
@ -113,7 +113,7 @@ describe('validate Communities', () => {
} as Response<unknown>
})
const variables1 = {
publicKey: Buffer.from('11111111111111111111111111111111'),
publicKey: Buffer.from('11111111111111111111111111111111', 'hex'),
apiVersion: '1_0',
endPoint: 'http//localhost:5001/api/',
lastAnnouncedAt: new Date(),
@ -195,7 +195,7 @@ describe('validate Communities', () => {
} as Response<unknown>
})
const variables1 = {
publicKey: Buffer.from('11111111111111111111111111111111'),
publicKey: Buffer.from('11111111111111111111111111111111', 'hex'),
apiVersion: '1_0',
endPoint: 'http//localhost:5001/api/',
lastAnnouncedAt: new Date(),
@ -315,7 +315,7 @@ describe('validate Communities', () => {
} as Response<unknown>
})
const variables3 = {
publicKey: Buffer.from('11111111111111111111111111111111'),
publicKey: Buffer.from('11111111111111111111111111111111', 'hex'),
apiVersion: '2_0',
endPoint: 'http//localhost:5001/api/',
lastAnnouncedAt: new Date(),

View File

@ -3,14 +3,15 @@
import { IsNull } from '@dbTools/typeorm'
import { Community as DbCommunity } from '@entity/Community'
import { FederatedCommunity as DbFederatedCommunity } from '@entity/FederatedCommunity'
import { FederatedCommunityLoggingView } from '@logging/FederatedCommunityLogging.view'
// eslint-disable-next-line camelcase
import { FederationClient as V1_0_FederationClient } from '@/federation/client/1_0/FederationClient'
import { PublicCommunityInfo } from '@/federation/client/1_0/model/PublicCommunityInfo'
import { FederationClientFactory } from '@/federation/client/FederationClientFactory'
import { backendLogger as logger } from '@/server/logger'
import { startCommunityAuthentication } from './authenticateCommunities'
import { PublicCommunityInfoLoggingView } from './client/1_0/logging/PublicCommunityInfoLogging.view'
import { ApiVersionType } from './enum/apiVersionType'
export async function startValidateCommunities(timerInterval: number): Promise<void> {
@ -37,7 +38,7 @@ export async function validateCommunities(): Promise<void> {
logger.debug(`Federation: found ${dbFederatedCommunities.length} dbCommunities`)
for (const dbCom of dbFederatedCommunities) {
logger.debug('Federation: dbCom', dbCom)
logger.debug('Federation: dbCom', new FederatedCommunityLoggingView(dbCom))
const apiValueStrings: string[] = Object.values(ApiVersionType)
logger.debug(`suppported ApiVersions=`, apiValueStrings)
if (!apiValueStrings.includes(dbCom.apiVersion)) {
@ -53,7 +54,7 @@ export async function validateCommunities(): Promise<void> {
// eslint-disable-next-line camelcase
if (client instanceof V1_0_FederationClient) {
const pubKey = await client.getPublicKey()
if (pubKey && pubKey === dbCom.publicKey.toString()) {
if (pubKey && pubKey === dbCom.publicKey.toString('hex')) {
await DbFederatedCommunity.update({ id: dbCom.id }, { verifiedAt: new Date() })
logger.debug(`Federation: verified community with:`, dbCom.endPoint)
const pubComInfo = await client.getPublicCommunityInfo()
@ -68,7 +69,7 @@ export async function validateCommunities(): Promise<void> {
logger.debug(
'Federation: received not matching publicKey:',
pubKey,
dbCom.publicKey.toString(),
dbCom.publicKey.toString('hex'),
)
}
}
@ -82,10 +83,11 @@ async function writeForeignCommunity(
dbCom: DbFederatedCommunity,
pubInfo: PublicCommunityInfo,
): Promise<void> {
if (!dbCom || !pubInfo || !(dbCom.publicKey.toString() === pubInfo.publicKey)) {
if (!dbCom || !pubInfo || !(dbCom.publicKey.toString('hex') === pubInfo.publicKey)) {
const pubInfoView = new PublicCommunityInfoLoggingView(pubInfo)
logger.error(
`Error in writeForeignCommunity: missmatching parameters or publicKey. pubInfo:${JSON.stringify(
pubInfo,
`Error in writeForeignCommunity: missmatching parameters or publicKey. pubInfo:${pubInfoView.toString(
true,
)}`,
)
} else {

View File

@ -3,11 +3,11 @@ import { ArgsType, Field } from 'type-graphql'
@ArgsType()
export class UserArgs {
@Field({ nullable: false })
@Field()
@IsString()
identifier: string
@Field({ nullable: true })
@Field()
@IsString()
communityIdentifier?: string
communityIdentifier: string
}

View File

@ -0,0 +1,12 @@
import { registerEnumType } from 'type-graphql'
export enum GmsPublishLocationType {
GMS_LOCATION_TYPE_EXACT = 0,
GMS_LOCATION_TYPE_APPROXIMATE = 1,
GMS_LOCATION_TYPE_RANDOM = 2,
}
registerEnumType(GmsPublishLocationType, {
name: 'GmsPublishLocationType', // this one is mandatory
description: 'Type of location treatment in GMS', // this one is optional
})

View File

@ -0,0 +1,14 @@
import { registerEnumType } from 'type-graphql'
export enum GmsPublishNameType {
GMS_PUBLISH_NAME_ALIAS_OR_INITALS = 0,
GMS_PUBLISH_NAME_INITIALS = 1,
GMS_PUBLISH_NAME_FIRST = 2,
GMS_PUBLISH_NAME_FIRST_INITIAL = 3,
GMS_PUBLISH_NAME_FULL = 4,
}
registerEnumType(GmsPublishNameType, {
name: 'GmsPublishNameType', // this one is mandatory
description: 'Type of name publishing', // this one is optional
})

View File

@ -0,0 +1,12 @@
import { registerEnumType } from 'type-graphql'
export enum GmsPublishPhoneType {
GMS_PUBLISH_PHONE_NOTHING = 0,
GMS_PUBLISH_PHONE_COUNTRY = 1,
GMS_PUBLISH_PHONE_FULL = 2,
}
registerEnumType(GmsPublishPhoneType, {
name: 'GmsPublishPhoneType', // this one is mandatory
description: 'Type of Phone publishing', // this one is optional
})

View File

@ -6,7 +6,7 @@ export class FederatedCommunity {
constructor(dbCom: DbFederatedCommunity) {
this.id = dbCom.id
this.foreign = dbCom.foreign
this.publicKey = dbCom.publicKey.toString()
this.publicKey = dbCom.publicKey.toString('hex')
this.url =
(dbCom.endPoint.endsWith('/') ? dbCom.endPoint : dbCom.endPoint + '/') + dbCom.apiVersion
this.lastAnnouncedAt = dbCom.lastAnnouncedAt

View File

@ -10,6 +10,9 @@ export class User {
this.id = user.id
this.foreign = user.foreign
this.communityUuid = user.communityUuid
if (user.community) {
this.communityName = user.community.name
}
this.gradidoID = user.gradidoID
this.alias = user.alias
if (user.emailContact) {

View File

@ -34,6 +34,60 @@ afterAll(async () => {
await con.close()
})
// real valid ed25519 key pairs
const ed25519KeyPairStaticHex = [
{
public: '264c1e88914d18166cc31e8d6c2111c03ac83f5910398eb45cd425c6c3836367',
private:
'0ddcafd5e2da92e171ccc974af22fee3ad8407475e330586c8f259837d4fedc6264c1e88914d18166cc31e8d6c2111c03ac83f5910398eb45cd425c6c3836367',
},
{
public: 'ac18a8754f725079f93d27b9054f2eff536109a2fd439f9755941abdd639baf0',
private:
'45325a0d0f22655095321d9d05999c65245da02130318ff51da1ee423b836117ac18a8754f725079f93d27b9054f2eff536109a2fd439f9755941abdd639baf0',
},
{
public: '6f7d4ccde610db1e1a33fabbb444d5400013c168296b03fd50bc686d4c1ad0ed',
private:
'8ab6d5da8b666ef5b3d754559c028806a1e2f8142a3e7ada411a8b6a3fe70eeb6f7d4ccde610db1e1a33fabbb444d5400013c168296b03fd50bc686d4c1ad0ed',
},
{
public: '85fbbce0763db24677cf7cb579a743013557a4fea0a9a624245f3ae8cd785e1d',
private:
'0369ea7c80c3134c2872c3cf77a68f12d57de57359145b550e3a0c4c8170a31785fbbce0763db24677cf7cb579a743013557a4fea0a9a624245f3ae8cd785e1d',
},
{
public: 'b099d023476ece01f231c269cbe496139ca73b3b4eb705816a511a1ca09661d0',
private:
'015ac650157b9e9bdbe718940606242daa318a251e8417b49440495e5afe3750b099d023476ece01f231c269cbe496139ca73b3b4eb705816a511a1ca09661d0',
},
{
public: '9f8dc17f1af9f71e9b9a1cd49ca295b89049863515a487578ad4f90b307abf39',
private:
'0c13e71c55a3c03bd5df05c92bbccde88ad4a47f3bac6bdc5383ef1ec946cfdc9f8dc17f1af9f71e9b9a1cd49ca295b89049863515a487578ad4f90b307abf39',
},
{
public: '34218b2f570d341370dd2db111d0ef2415c03a110c3bf3127c6b2337af71753a',
private:
'60f3479bba44d035886ac21c362bceece9f9ec81859c9b37f734b6442a06c93b34218b2f570d341370dd2db111d0ef2415c03a110c3bf3127c6b2337af71753a',
},
{
public: 'a447404f5e04ed4896ed64d0f704574ed780b52e90868d4b83e1afb8ea687ff6',
private:
'ea85ebb4332a52d87fe6f322dcd23ad4afc5eafb93dfff2216f3ffa9f0730e8aa447404f5e04ed4896ed64d0f704574ed780b52e90868d4b83e1afb8ea687ff6',
},
{
public: 'b8b987c55da62b30d929672520551033eb37abdd88f9ea104db5d107c19680b4',
private:
'29475dbbc96d694b3c653a1e143caf084f6daf2d35267522c4096c55b47e2b76b8b987c55da62b30d929672520551033eb37abdd88f9ea104db5d107c19680b4',
},
{
public: '40203d18a6ff8fb3c4c62d78e4807036fc9207782ce97a9bcf3be0755c236c37',
private:
'0b5c4d536d222e88b561ea495e15918fb8cba61a3f8c261ec9e587cca560804040203d18a6ff8fb3c4c62d78e4807036fc9207782ce97a9bcf3be0755c236c37',
},
]
describe('CommunityResolver', () => {
describe('getCommunities', () => {
let homeCom1: DbFederatedCommunity
@ -62,7 +116,7 @@ describe('CommunityResolver', () => {
homeCom1 = DbFederatedCommunity.create()
homeCom1.foreign = false
homeCom1.publicKey = Buffer.from('publicKey-HomeCommunity')
homeCom1.publicKey = Buffer.from(ed25519KeyPairStaticHex[0].public, 'hex')
homeCom1.apiVersion = '1_0'
homeCom1.endPoint = 'http://localhost/api'
homeCom1.createdAt = new Date()
@ -70,7 +124,7 @@ describe('CommunityResolver', () => {
homeCom2 = DbFederatedCommunity.create()
homeCom2.foreign = false
homeCom2.publicKey = Buffer.from('publicKey-HomeCommunity')
homeCom2.publicKey = Buffer.from(ed25519KeyPairStaticHex[1].public, 'hex')
homeCom2.apiVersion = '1_1'
homeCom2.endPoint = 'http://localhost/api'
homeCom2.createdAt = new Date()
@ -78,7 +132,7 @@ describe('CommunityResolver', () => {
homeCom3 = DbFederatedCommunity.create()
homeCom3.foreign = false
homeCom3.publicKey = Buffer.from('publicKey-HomeCommunity')
homeCom3.publicKey = Buffer.from(ed25519KeyPairStaticHex[2].public, 'hex')
homeCom3.apiVersion = '2_0'
homeCom3.endPoint = 'http://localhost/api'
homeCom3.createdAt = new Date()
@ -92,7 +146,7 @@ describe('CommunityResolver', () => {
{
id: 3,
foreign: homeCom3.foreign,
publicKey: expect.stringMatching('publicKey-HomeCommunity'),
publicKey: expect.stringMatching(ed25519KeyPairStaticHex[2].public),
url: expect.stringMatching('http://localhost/api/2_0'),
lastAnnouncedAt: null,
verifiedAt: null,
@ -103,7 +157,7 @@ describe('CommunityResolver', () => {
{
id: 2,
foreign: homeCom2.foreign,
publicKey: expect.stringMatching('publicKey-HomeCommunity'),
publicKey: expect.stringMatching(ed25519KeyPairStaticHex[1].public),
url: expect.stringMatching('http://localhost/api/1_1'),
lastAnnouncedAt: null,
verifiedAt: null,
@ -114,7 +168,7 @@ describe('CommunityResolver', () => {
{
id: 1,
foreign: homeCom1.foreign,
publicKey: expect.stringMatching('publicKey-HomeCommunity'),
publicKey: expect.stringMatching(ed25519KeyPairStaticHex[0].public),
url: expect.stringMatching('http://localhost/api/1_0'),
lastAnnouncedAt: null,
verifiedAt: null,
@ -134,7 +188,7 @@ describe('CommunityResolver', () => {
foreignCom1 = DbFederatedCommunity.create()
foreignCom1.foreign = true
foreignCom1.publicKey = Buffer.from('publicKey-ForeignCommunity')
foreignCom1.publicKey = Buffer.from(ed25519KeyPairStaticHex[3].public, 'hex')
foreignCom1.apiVersion = '1_0'
foreignCom1.endPoint = 'http://remotehost/api'
foreignCom1.createdAt = new Date()
@ -142,7 +196,7 @@ describe('CommunityResolver', () => {
foreignCom2 = DbFederatedCommunity.create()
foreignCom2.foreign = true
foreignCom2.publicKey = Buffer.from('publicKey-ForeignCommunity')
foreignCom2.publicKey = Buffer.from(ed25519KeyPairStaticHex[4].public, 'hex')
foreignCom2.apiVersion = '1_1'
foreignCom2.endPoint = 'http://remotehost/api'
foreignCom2.createdAt = new Date()
@ -150,7 +204,7 @@ describe('CommunityResolver', () => {
foreignCom3 = DbFederatedCommunity.create()
foreignCom3.foreign = true
foreignCom3.publicKey = Buffer.from('publicKey-ForeignCommunity')
foreignCom3.publicKey = Buffer.from(ed25519KeyPairStaticHex[5].public, 'hex')
foreignCom3.apiVersion = '1_2'
foreignCom3.endPoint = 'http://remotehost/api'
foreignCom3.createdAt = new Date()
@ -164,7 +218,7 @@ describe('CommunityResolver', () => {
{
id: 3,
foreign: homeCom3.foreign,
publicKey: expect.stringMatching('publicKey-HomeCommunity'),
publicKey: expect.stringMatching(ed25519KeyPairStaticHex[2].public),
url: expect.stringMatching('http://localhost/api/2_0'),
lastAnnouncedAt: null,
verifiedAt: null,
@ -175,7 +229,7 @@ describe('CommunityResolver', () => {
{
id: 2,
foreign: homeCom2.foreign,
publicKey: expect.stringMatching('publicKey-HomeCommunity'),
publicKey: expect.stringMatching(ed25519KeyPairStaticHex[1].public),
url: expect.stringMatching('http://localhost/api/1_1'),
lastAnnouncedAt: null,
verifiedAt: null,
@ -186,7 +240,7 @@ describe('CommunityResolver', () => {
{
id: 1,
foreign: homeCom1.foreign,
publicKey: expect.stringMatching('publicKey-HomeCommunity'),
publicKey: expect.stringMatching(ed25519KeyPairStaticHex[0].public),
url: expect.stringMatching('http://localhost/api/1_0'),
lastAnnouncedAt: null,
verifiedAt: null,
@ -197,7 +251,7 @@ describe('CommunityResolver', () => {
{
id: 6,
foreign: foreignCom3.foreign,
publicKey: expect.stringMatching('publicKey-ForeignCommunity'),
publicKey: expect.stringMatching(ed25519KeyPairStaticHex[5].public),
url: expect.stringMatching('http://remotehost/api/1_2'),
lastAnnouncedAt: null,
verifiedAt: null,
@ -208,7 +262,7 @@ describe('CommunityResolver', () => {
{
id: 5,
foreign: foreignCom2.foreign,
publicKey: expect.stringMatching('publicKey-ForeignCommunity'),
publicKey: expect.stringMatching(ed25519KeyPairStaticHex[4].public),
url: expect.stringMatching('http://remotehost/api/1_1'),
lastAnnouncedAt: null,
verifiedAt: null,
@ -219,7 +273,7 @@ describe('CommunityResolver', () => {
{
id: 4,
foreign: foreignCom1.foreign,
publicKey: expect.stringMatching('publicKey-ForeignCommunity'),
publicKey: expect.stringMatching(ed25519KeyPairStaticHex[3].public),
url: expect.stringMatching('http://remotehost/api/1_0'),
lastAnnouncedAt: null,
verifiedAt: null,
@ -264,8 +318,8 @@ describe('CommunityResolver', () => {
homeCom1 = DbCommunity.create()
homeCom1.foreign = false
homeCom1.url = 'http://localhost/api'
homeCom1.publicKey = Buffer.from('publicKey-HomeCommunity')
homeCom1.privateKey = Buffer.from('privateKey-HomeCommunity')
homeCom1.publicKey = Buffer.from(ed25519KeyPairStaticHex[0].public, 'hex')
homeCom1.privateKey = Buffer.from(ed25519KeyPairStaticHex[0].private, 'hex')
homeCom1.communityUuid = 'HomeCom-UUID'
homeCom1.authenticatedAt = new Date()
homeCom1.name = 'HomeCommunity-name'
@ -302,8 +356,8 @@ describe('CommunityResolver', () => {
homeCom1 = DbCommunity.create()
homeCom1.foreign = false
homeCom1.url = 'http://localhost/api'
homeCom1.publicKey = Buffer.from('publicKey-HomeCommunity')
homeCom1.privateKey = Buffer.from('privateKey-HomeCommunity')
homeCom1.publicKey = Buffer.from(ed25519KeyPairStaticHex[0].public, 'hex')
homeCom1.privateKey = Buffer.from(ed25519KeyPairStaticHex[0].private, 'hex')
homeCom1.communityUuid = 'HomeCom-UUID'
homeCom1.authenticatedAt = new Date()
homeCom1.name = 'HomeCommunity-name'
@ -314,8 +368,8 @@ describe('CommunityResolver', () => {
foreignCom1 = DbCommunity.create()
foreignCom1.foreign = true
foreignCom1.url = 'http://stage-2.gradido.net/api'
foreignCom1.publicKey = Buffer.from('publicKey-stage-2_Community')
foreignCom1.privateKey = Buffer.from('privateKey-stage-2_Community')
foreignCom1.publicKey = Buffer.from(ed25519KeyPairStaticHex[3].public, 'hex')
foreignCom1.privateKey = Buffer.from(ed25519KeyPairStaticHex[3].private, 'hex')
// foreignCom1.communityUuid = 'Stage2-Com-UUID'
// foreignCom1.authenticatedAt = new Date()
foreignCom1.name = 'Stage-2_Community-name'
@ -326,8 +380,8 @@ describe('CommunityResolver', () => {
foreignCom2 = DbCommunity.create()
foreignCom2.foreign = true
foreignCom2.url = 'http://stage-3.gradido.net/api'
foreignCom2.publicKey = Buffer.from('publicKey-stage-3_Community')
foreignCom2.privateKey = Buffer.from('privateKey-stage-3_Community')
foreignCom2.publicKey = Buffer.from(ed25519KeyPairStaticHex[4].public, 'hex')
foreignCom2.privateKey = Buffer.from(ed25519KeyPairStaticHex[4].private, 'hex')
foreignCom2.communityUuid = 'Stage3-Com-UUID'
foreignCom2.authenticatedAt = new Date()
foreignCom2.name = 'Stage-3_Community-name'

View File

@ -2609,7 +2609,7 @@ describe('ContributionResolver', () => {
expect(transaction[0].linkedTransactionId).toEqual(null)
expect(transaction[0].transactionLinkId).toEqual(null)
expect(transaction[0].previous).toEqual(null)
expect(transaction[0].linkedUserId).toEqual(null)
expect(transaction[0].linkedUserId).toEqual(admin.id)
expect(transaction[0].typeId).toEqual(1)
})

View File

@ -451,6 +451,11 @@ export class ContributionResolver {
transaction.userId = contribution.userId
transaction.userGradidoID = user.gradidoID
transaction.userName = fullName(user.firstName, user.lastName)
transaction.userCommunityUuid = user.communityUuid
transaction.linkedUserId = moderatorUser.id
transaction.linkedUserGradidoID = moderatorUser.gradidoID
transaction.linkedUserName = fullName(moderatorUser.firstName, moderatorUser.lastName)
transaction.linkedUserCommunityUuid = moderatorUser.communityUuid
transaction.previous = lastTransaction ? lastTransaction.id : null
transaction.amount = contribution.amount
transaction.creationDate = contribution.contributionDate

View File

@ -142,7 +142,11 @@ describe('send coins', () => {
})
it('logs the error thrown', () => {
expect(logger.error).toBeCalledWith('No user with this credentials', 'wrong@email.com')
expect(logger.error).toBeCalledWith(
'No user with this credentials',
'wrong@email.com',
homeCom.communityUuid,
)
})
describe('deleted recipient', () => {
@ -165,13 +169,17 @@ describe('send coins', () => {
}),
).toEqual(
expect.objectContaining({
errors: [new GraphQLError('No user to given contact')],
errors: [new GraphQLError('No user with this credentials')],
}),
)
})
it('logs the error thrown', () => {
expect(logger.error).toBeCalledWith('No user to given contact', 'stephen@hawking.uk')
expect(logger.error).toBeCalledWith(
'No user with this credentials',
'stephen@hawking.uk',
homeCom.communityUuid,
)
})
})
@ -204,6 +212,7 @@ describe('send coins', () => {
expect(logger.error).toBeCalledWith(
'No user with this credentials',
'garrick@ollivander.com',
homeCom.communityUuid,
)
})
})

View File

@ -109,9 +109,11 @@ export const executeTransaction = async (
transactionSend.userId = sender.id
transactionSend.userGradidoID = sender.gradidoID
transactionSend.userName = fullName(sender.firstName, sender.lastName)
transactionSend.userCommunityUuid = sender.communityUuid
transactionSend.linkedUserId = recipient.id
transactionSend.linkedUserGradidoID = recipient.gradidoID
transactionSend.linkedUserName = fullName(recipient.firstName, recipient.lastName)
transactionSend.linkedUserCommunityUuid = recipient.communityUuid
transactionSend.amount = amount.mul(-1)
transactionSend.balance = sendBalance.balance
transactionSend.balanceDate = receivedCallDate
@ -129,9 +131,11 @@ export const executeTransaction = async (
transactionReceive.userId = recipient.id
transactionReceive.userGradidoID = recipient.gradidoID
transactionReceive.userName = fullName(recipient.firstName, recipient.lastName)
transactionReceive.userCommunityUuid = recipient.communityUuid
transactionReceive.linkedUserId = sender.id
transactionReceive.linkedUserGradidoID = sender.gradidoID
transactionReceive.linkedUserName = fullName(sender.firstName, sender.lastName)
transactionReceive.linkedUserCommunityUuid = sender.communityUuid
transactionReceive.amount = amount
const receiveBalance = await calculateBalance(recipient.id, amount, receivedCallDate)
transactionReceive.balance = receiveBalance ? receiveBalance.balance : amount
@ -254,6 +258,9 @@ export class TransactionResolver {
// userTransactions.forEach((transaction: dbTransaction) => {
// use normal for loop because of timing problems with await in forEach-loop
for (const transaction of userTransactions) {
if (transaction.typeId === TransactionTypeId.CREATION) {
continue
}
if (transaction.linkedUserId && !involvedUserIds.includes(transaction.linkedUserId)) {
involvedUserIds.push(transaction.linkedUserId)
}
@ -425,7 +432,7 @@ export class TransactionResolver {
const senderUser = getUser(context)
if (!recipientCommunityIdentifier || (await isHomeCommunity(recipientCommunityIdentifier))) {
// processing sendCoins within sender and recepient are both in home community
// processing sendCoins within sender and recipient are both in home community
const recipientUser = await findUserByIdentifier(
recipientIdentifier,
recipientCommunityIdentifier,

View File

@ -2561,6 +2561,7 @@ describe('UserResolver', () => {
query: userQuery,
variables: {
identifier: 'identifier',
communityIdentifier: 'community identifier',
},
}),
).resolves.toEqual(
@ -2646,13 +2647,11 @@ describe('UserResolver', () => {
}),
).resolves.toEqual(
expect.objectContaining({
errors: [
new GraphQLError('Found user to given contact, but belongs to other community'),
],
errors: [new GraphQLError('No user with this credentials')],
}),
)
expect(logger.error).toBeCalledWith(
'Found user to given contact, but belongs to other community',
'No user with this credentials',
'bibi@bloxberg.de',
foreignCom1.communityUuid,
)

View File

@ -65,7 +65,7 @@ import random from 'random-bigint'
import { randombytes_random } from 'sodium-native'
import { FULL_CREATION_AVAILABLE } from './const/const'
import { getCommunityName, getHomeCommunity } from './util/communities'
import { getHomeCommunity } from './util/communities'
import { getUserCreations } from './util/creations'
import { findUserByIdentifier } from './util/findUserByIdentifier'
import { findUsers } from './util/findUsers'
@ -834,11 +834,6 @@ export class UserResolver {
): Promise<User> {
const foundDbUser = await findUserByIdentifier(identifier, communityIdentifier)
const modelUser = new User(foundDbUser)
if (!foundDbUser.communityUuid) {
modelUser.communityName = (await Promise.resolve(getHomeCommunity())).name
} else {
modelUser.communityName = await getCommunityName(foundDbUser.communityUuid)
}
return modelUser
}
}

View File

@ -6,6 +6,7 @@ import { Community as DbCommunity } from '@entity/Community'
import { ApolloServerTestClient } from 'apollo-server-testing'
import { Decimal } from 'decimal.js-light'
import { GraphQLError } from 'graphql'
import { v4 as uuidv4 } from 'uuid'
import { cleanDB, testEnvironment, contributionDateFormatter } from '@test/helpers'
@ -54,7 +55,7 @@ describe('semaphore', () => {
beforeAll(async () => {
const now = new Date()
homeCom = DbCommunity.create()
homeCom.communityUuid = 'homeCom-UUID'
homeCom.communityUuid = uuidv4()
homeCom.creationDate = new Date('2000-01-01')
homeCom.description = 'homeCom description'
homeCom.foreign = false

View File

@ -1,3 +1,5 @@
import { FindOptionsWhere } from '@dbTools/typeorm'
import { Community } from '@entity/Community'
import { User as DbUser } from '@entity/User'
import { UserContact as DbUserContact } from '@entity/UserContact'
import { validate, version } from 'uuid'
@ -6,15 +8,26 @@ import { LogError } from '@/server/LogError'
import { VALID_ALIAS_REGEX } from './validateAlias'
/**
*
* @param identifier could be gradidoID, alias or email of user
* @param communityIdentifier could be uuid or name of community
* @returns
*/
export const findUserByIdentifier = async (
identifier: string,
communityIdentifier?: string,
communityIdentifier: string,
): Promise<DbUser> => {
let user: DbUser | null
const communityWhere: FindOptionsWhere<Community> =
validate(communityIdentifier) && version(communityIdentifier) === 4
? { communityUuid: communityIdentifier }
: { name: communityIdentifier }
if (validate(identifier) && version(identifier) === 4) {
user = await DbUser.findOne({
where: { gradidoID: identifier, communityUuid: communityIdentifier },
relations: ['emailContact'],
where: { gradidoID: identifier, community: communityWhere },
relations: ['emailContact', 'community'],
})
if (!user) {
throw new LogError('No user found to given identifier(s)', identifier, communityIdentifier)
@ -24,28 +37,21 @@ export const findUserByIdentifier = async (
where: {
email: identifier,
emailChecked: true,
user: {
community: communityWhere,
},
},
relations: ['user'],
relations: { user: { community: true } },
})
if (!userContact) {
throw new LogError('No user with this credentials', identifier)
}
if (!userContact.user) {
throw new LogError('No user to given contact', identifier)
}
if (userContact.user.communityUuid !== communityIdentifier) {
throw new LogError(
'Found user to given contact, but belongs to other community',
identifier,
communityIdentifier,
)
throw new LogError('No user with this credentials', identifier, communityIdentifier)
}
user = userContact.user
user.emailContact = userContact
} else if (VALID_ALIAS_REGEX.exec(identifier)) {
user = await DbUser.findOne({
where: { alias: identifier, communityUuid: communityIdentifier },
relations: ['emailContact'],
where: { alias: identifier, community: communityWhere },
relations: ['emailContact', 'community'],
})
if (!user) {
throw new LogError('No user found to given identifier(s)', identifier, communityIdentifier)

View File

@ -0,0 +1,94 @@
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
/* eslint-disable @typescript-eslint/restrict-template-expressions */
import { Connection } from '@dbTools/typeorm'
import { Community as DbCommunity } from '@entity/Community'
import { User as DbUser } from '@entity/User'
import { ApolloServerTestClient } from 'apollo-server-testing'
import { cleanDB, testEnvironment } from '@test/helpers'
import { writeHomeCommunityEntry } from '@/seeds/community'
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 { findUserByIdentifier } from './findUserByIdentifier'
let con: Connection
let testEnv: {
mutate: ApolloServerTestClient['mutate']
query: ApolloServerTestClient['query']
con: Connection
}
beforeAll(async () => {
testEnv = await testEnvironment()
con = testEnv.con
await cleanDB()
})
afterAll(async () => {
await cleanDB()
await con.close()
})
describe('graphql/resolver/util/findUserByIdentifier', () => {
let homeCom: DbCommunity
let communityUuid: string
let communityName: string
let userBibi: DbUser
beforeAll(async () => {
homeCom = await writeHomeCommunityEntry()
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
communityUuid = homeCom.communityUuid!
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
communityName = homeCom.communityUuid!
userBibi = await userFactory(testEnv, bibiBloxberg)
await userFactory(testEnv, peterLustig)
await userFactory(testEnv, bobBaumeister)
})
describe('communityIdentifier is community uuid', () => {
it('userIdentifier is gradido id', async () => {
const user = await findUserByIdentifier(userBibi.gradidoID, communityUuid)
user.userRoles = []
expect(user).toMatchObject(userBibi)
})
it('userIdentifier is alias', async () => {
const user = await findUserByIdentifier(userBibi.alias, communityUuid)
user.userRoles = []
expect(user).toMatchObject(userBibi)
})
it('userIdentifier is email', async () => {
const user = await findUserByIdentifier(userBibi.emailContact.email, communityUuid)
user.userRoles = []
expect(user).toMatchObject(userBibi)
})
})
describe('communityIdentifier is community name', () => {
it('userIdentifier is gradido id', async () => {
const user = await findUserByIdentifier(userBibi.gradidoID, communityName)
user.userRoles = []
expect(user).toMatchObject(userBibi)
})
it('userIdentifier is alias', async () => {
const user = await findUserByIdentifier(userBibi.alias, communityName)
user.userRoles = []
expect(user).toMatchObject(userBibi)
})
it('userIdentifier is email', async () => {
const user = await findUserByIdentifier(userBibi.emailContact.email, communityName)
user.userRoles = []
expect(user).toMatchObject(userBibi)
})
})
})

View File

@ -22,6 +22,13 @@ import { logger, i18n as localization } from '@test/testSetup'
import { CONFIG } from '@/config'
import { TransactionTypeId } from '@/graphql/enum/TransactionTypeId'
import { creations } from '@/seeds/creation'
import { creationFactory } from '@/seeds/factory/creation'
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 { raeuberHotzenplotz } from '@/seeds/users/raeuber-hotzenplotz'
import { sendTransactionsToDltConnector } from './sendTransactionsToDltConnector'
@ -423,9 +430,17 @@ describe('create and send Transactions to DltConnector', () => {
describe('with 3 creations and active dlt-connector', () => {
it('found 3 dlt-transactions', async () => {
txCREATION1 = await createTxCREATION1(false)
txCREATION2 = await createTxCREATION2(false)
txCREATION3 = await createTxCREATION3(false)
await userFactory(testEnv, bibiBloxberg)
await userFactory(testEnv, peterLustig)
await userFactory(testEnv, raeuberHotzenplotz)
await userFactory(testEnv, bobBaumeister)
let count = 0
for (const creation of creations) {
await creationFactory(testEnv, creation)
count++
// we need only 3 for testing
if (count >= 3) break
}
await createHomeCommunity()
CONFIG.DLT_CONNECTOR = true
@ -435,10 +450,7 @@ describe('create and send Transactions to DltConnector', () => {
// eslint-disable-next-line @typescript-eslint/no-unsafe-return
return {
data: {
sendTransaction: {
dltTransactionIdHex:
'723e3fab62c5d3e2f62fd72ba4e622bcd53eff35262e3f3526327fe41bc51621',
},
sendTransaction: { succeed: true },
},
} as Response<unknown>
})
@ -464,7 +476,7 @@ describe('create and send Transactions to DltConnector', () => {
expect.objectContaining({
id: expect.any(Number),
transactionId: transactions[0].id,
messageId: '723e3fab62c5d3e2f62fd72ba4e622bcd53eff35262e3f3526327fe41bc51621',
messageId: 'sended',
verified: false,
createdAt: expect.any(Date),
verifiedAt: null,
@ -472,7 +484,7 @@ describe('create and send Transactions to DltConnector', () => {
expect.objectContaining({
id: expect.any(Number),
transactionId: transactions[1].id,
messageId: '723e3fab62c5d3e2f62fd72ba4e622bcd53eff35262e3f3526327fe41bc51621',
messageId: 'sended',
verified: false,
createdAt: expect.any(Date),
verifiedAt: null,
@ -480,7 +492,7 @@ describe('create and send Transactions to DltConnector', () => {
expect.objectContaining({
id: expect.any(Number),
transactionId: transactions[2].id,
messageId: '723e3fab62c5d3e2f62fd72ba4e622bcd53eff35262e3f3526327fe41bc51621',
messageId: 'sended',
verified: false,
createdAt: expect.any(Date),
verifiedAt: null,
@ -514,10 +526,7 @@ describe('create and send Transactions to DltConnector', () => {
// eslint-disable-next-line @typescript-eslint/no-unsafe-return
return {
data: {
sendTransaction: {
dltTransactionIdHex:
'723e3fab62c5d3e2f62fd72ba4e622bcd53eff35262e3f3526327fe41bc51621',
},
sendTransaction: { succeed: true },
},
} as Response<unknown>
})
@ -569,7 +578,7 @@ describe('create and send Transactions to DltConnector', () => {
expect.objectContaining({
id: expect.any(Number),
transactionId: txSEND1to2.id,
messageId: '723e3fab62c5d3e2f62fd72ba4e622bcd53eff35262e3f3526327fe41bc51621',
messageId: 'sended',
verified: false,
createdAt: expect.any(Date),
verifiedAt: null,
@ -577,7 +586,7 @@ describe('create and send Transactions to DltConnector', () => {
expect.objectContaining({
id: expect.any(Number),
transactionId: txRECEIVE2From1.id,
messageId: '723e3fab62c5d3e2f62fd72ba4e622bcd53eff35262e3f3526327fe41bc51621',
messageId: 'sended',
verified: false,
createdAt: expect.any(Date),
verifiedAt: null,

View File

@ -1,9 +1,9 @@
import { IsNull } from '@dbTools/typeorm'
import { Community } from '@entity/Community'
import { DltTransaction } from '@entity/DltTransaction'
import { Transaction } from '@entity/Transaction'
import { DltConnectorClient } from '@/apis/DltConnectorClient'
import { DltConnectorClient } from '@dltConnector/DltConnectorClient'
import { backendLogger as logger } from '@/server/logger'
import { Monitor, MonitorNames } from '@/util/Monitor'
@ -17,13 +17,6 @@ export async function sendTransactionsToDltConnector(): Promise<void> {
try {
await createDltTransactions()
const dltConnector = DltConnectorClient.getInstance()
// TODO: get actual communities from users
const homeCommunity = await Community.findOneOrFail({ where: { foreign: false } })
const senderCommunityUuid = homeCommunity.communityUuid
if (!senderCommunityUuid) {
throw new Error('Cannot find community uuid of home community')
}
const recipientCommunityUuid = ''
if (dltConnector) {
logger.debug('with sending to DltConnector...')
const dltTransactions = await DltTransaction.find({
@ -37,22 +30,14 @@ export async function sendTransactionsToDltConnector(): Promise<void> {
continue
}
try {
const messageId = await dltConnector.transmitTransaction(
dltTx.transaction,
senderCommunityUuid,
recipientCommunityUuid,
)
const dltMessageId = Buffer.from(messageId, 'hex')
if (dltMessageId.length !== 32) {
logger.error(
'Error dlt message id is invalid: %s, should by 32 Bytes long in binary after converting from hex',
dltMessageId,
)
return
const result = await dltConnector.transmitTransaction(dltTx.transaction)
// message id isn't known at this point of time, because transaction will not direct sended to iota,
// it will first go to db and then sended, if no transaction is in db before
if (result) {
dltTx.messageId = 'sended'
await DltTransaction.save(dltTx)
logger.info('store messageId=%s in dltTx=%d', dltTx.messageId, dltTx.id)
}
dltTx.messageId = dltMessageId.toString('hex')
await DltTransaction.save(dltTx)
logger.info('store messageId=%s in dltTx=%d', dltTx.messageId, dltTx.id)
} catch (e) {
logger.error(
`error while sending to dlt-connector or writing messageId of dltTx=${dltTx.id}`,

View File

@ -370,7 +370,7 @@ export const adminListContributionMessages = gql`
`
export const user = gql`
query ($identifier: String!, $communityIdentifier: String) {
query ($identifier: String!, $communityIdentifier: String!) {
user(identifier: $identifier, communityIdentifier: $communityIdentifier) {
firstName
lastName

View File

@ -4,6 +4,7 @@ export const bibiBloxberg: UserInterface = {
email: 'bibi@bloxberg.de',
firstName: 'Bibi',
lastName: 'Bloxberg',
alias: 'BBB',
// description: 'Hex Hex',
emailChecked: true,
language: 'de',

View File

@ -4,6 +4,8 @@
import { Connection as DbConnection } from '@dbTools/typeorm'
import { ApolloServer } from 'apollo-server-express'
import express, { Express, json, urlencoded } from 'express'
import { slowDown } from 'express-slow-down'
import helmet from 'helmet'
import { Logger } from 'log4js'
import { CONFIG } from '@/config'
@ -56,6 +58,28 @@ export const createServer = async (
// cors
app.use(cors)
// Helmet helps secure Express apps by setting HTTP response headers.
// eslint-disable-next-line @typescript-eslint/no-unsafe-call
app.use(helmet())
// rate limiter/ slow down to many requests
const limiter = slowDown({
windowMs: 1000, // 1 second
delayAfter: 10, // Allow 10 requests per 1 second.
delayMs: (hits) => hits * 50, // Add 100 ms of delay to every request after the 10th one.
/**
* So:
*
* - requests 1-10 are not delayed.
* - request 11 is delayed by 550ms
* - request 12 is delayed by 600ms
* - request 13 is delayed by 650ms
*
* and so on. After 1 seconds, the delay is reset to 0.
*/
})
app.use(limiter)
// bodyparser json
app.use(json())
// bodyparser urlencoded for elopage

View File

@ -50,6 +50,7 @@ const communityDbUser: dbUser = {
},
foreign: false,
communityUuid: '55555555-4444-4333-2222-11111111',
community: null,
gmsPublishName: 0,
gmsAllowed: false,
location: null,

View File

@ -49,6 +49,7 @@
"paths": { /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */
"@/*": ["src/*"],
"@arg/*": ["src/graphql/arg/*"],
"@dltConnector/*": ["src/apis/dltConnector/*"],
"@enum/*": ["src/graphql/enum/*"],
"@model/*": ["src/graphql/model/*"],
"@union/*": ["src/graphql/union/*"],
@ -57,7 +58,8 @@
"@test/*": ["test/*"],
/* external */
"@dbTools/*": ["../database/src/*", "../../database/build/src/*"],
"@entity/*": ["../database/entity/*", "../../database/build/entity/*"]
"@entity/*": ["../database/entity/*", "../../database/build/entity/*"],
"@logging/*": ["../database/logging/*", "../../database/build/logging/*"]
},
// "rootDirs": [], /* List of root folders whose combined content represents the structure of the project at runtime. */
"typeRoots": ["@types", "node_modules/@types"], /* List of folders to include type definitions from. */

View File

@ -3225,6 +3225,18 @@ expect@^27.2.5:
jest-message-util "^27.2.5"
jest-regex-util "^27.0.6"
express-rate-limit@7:
version "7.1.5"
resolved "https://registry.yarnpkg.com/express-rate-limit/-/express-rate-limit-7.1.5.tgz#af4c81143a945ea97f2599d13957440a0ddbfcfe"
integrity sha512-/iVogxu7ueadrepw1bS0X0kaRC/U0afwiYRSLg68Ts+p4Dc85Q5QKsOnPS/QUjPMHvOJQtBDrZgvkOzf8ejUYw==
express-slow-down@^2.0.1:
version "2.0.1"
resolved "https://registry.yarnpkg.com/express-slow-down/-/express-slow-down-2.0.1.tgz#60c4515467314675d89c54ec608e2d586aa30f87"
integrity sha512-zRogSZhNXJYKDBekhgFfFXGrOngH7Fub7Mx2g8OQ4RUBwSJP/3TVEKMgSGR/WlneT0mJ6NBUnidHhIELGVPe3w==
dependencies:
express-rate-limit "7"
express@^4.17.1:
version "4.17.1"
resolved "https://registry.yarnpkg.com/express/-/express-4.17.1.tgz#4491fc38605cf51f8629d39c2b5d026f98a4c134"
@ -3679,7 +3691,7 @@ graceful-fs@^4.1.6, graceful-fs@^4.2.0:
integrity sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==
"gradido-database@file:../database":
version "2.0.1"
version "2.1.1"
dependencies:
"@types/uuid" "^8.3.4"
cross-env "^7.0.3"
@ -3826,6 +3838,11 @@ he@1.2.0, he@^1.2.0:
resolved "https://registry.yarnpkg.com/he/-/he-1.2.0.tgz#84ae65fa7eafb165fddb61566ae14baf05664f0f"
integrity sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==
helmet@^5.1.1:
version "5.1.1"
resolved "https://registry.yarnpkg.com/helmet/-/helmet-5.1.1.tgz#609823c5c2e78aea62dd9afc8f544ca409da5e85"
integrity sha512-/yX0oVZBggA9cLJh8aw3PPCfedBnbd7J2aowjzsaWwZh7/UFY0nccn/aHAggIgWUFfnykX8GKd3a1pSbrmlcVQ==
highlight.js@^10.7.1:
version "10.7.3"
resolved "https://registry.yarnpkg.com/highlight.js/-/highlight.js-10.7.3.tgz#697272e3991356e40c3cac566a74eef681756531"

View File

@ -0,0 +1,70 @@
import {
BaseEntity,
Entity,
PrimaryGeneratedColumn,
Column,
CreateDateColumn,
UpdateDateColumn,
OneToMany,
JoinColumn,
} from 'typeorm'
import { User } from '../User'
@Entity('communities')
export class Community extends BaseEntity {
@PrimaryGeneratedColumn('increment', { unsigned: true })
id: number
@Column({ name: 'foreign', type: 'bool', nullable: false, default: true })
foreign: boolean
@Column({ name: 'url', length: 255, nullable: false })
url: string
@Column({ name: 'public_key', type: 'binary', length: 32, nullable: false })
publicKey: Buffer
@Column({ name: 'private_key', type: 'binary', length: 64, nullable: true })
privateKey: Buffer | null
@Column({
name: 'community_uuid',
type: 'char',
length: 36,
nullable: true,
collation: 'utf8mb4_unicode_ci',
})
communityUuid: string | null
@Column({ name: 'authenticated_at', type: 'datetime', nullable: true })
authenticatedAt: Date | null
@Column({ name: 'name', type: 'varchar', length: 40, nullable: true })
name: string | null
@Column({ name: 'description', type: 'varchar', length: 255, nullable: true })
description: string | null
@CreateDateColumn({ name: 'creation_date', type: 'datetime', nullable: true })
creationDate: Date | null
@CreateDateColumn({
name: 'created_at',
type: 'datetime',
default: () => 'CURRENT_TIMESTAMP(3)',
nullable: false,
})
createdAt: Date
@UpdateDateColumn({
name: 'updated_at',
type: 'datetime',
onUpdate: 'CURRENT_TIMESTAMP(3)',
nullable: true,
})
updatedAt: Date | null
@OneToMany(() => User, (user) => user.community)
@JoinColumn({ name: 'community_uuid', referencedColumnName: 'communityUuid' })
users: User[]
}

View File

@ -0,0 +1,138 @@
import {
BaseEntity,
Entity,
PrimaryGeneratedColumn,
Column,
DeleteDateColumn,
OneToMany,
JoinColumn,
OneToOne,
ManyToOne,
} from 'typeorm'
import { Contribution } from '../Contribution'
import { ContributionMessage } from '../ContributionMessage'
import { UserContact } from '../UserContact'
import { UserRole } from '../UserRole'
import { Community } from '../Community'
@Entity('users', { engine: 'InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci' })
export class User extends BaseEntity {
@PrimaryGeneratedColumn('increment', { unsigned: true })
id: number
@Column({ type: 'bool', default: false })
foreign: boolean
@Column({
name: 'gradido_id',
length: 36,
nullable: false,
collation: 'utf8mb4_unicode_ci',
})
gradidoID: string
@Column({
name: 'community_uuid',
type: 'char',
length: 36,
nullable: true,
collation: 'utf8mb4_unicode_ci',
})
communityUuid: string
@ManyToOne(() => Community, (community) => community.users)
@JoinColumn({ name: 'community_uuid', referencedColumnName: 'communityUuid' })
community: Community | null
@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(3)', 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({ type: 'bool', default: false })
hideAmountGDD: boolean
@Column({ type: 'bool', default: false })
hideAmountGDT: boolean
@OneToMany(() => UserRole, (userRole) => userRole.user)
@JoinColumn({ name: 'user_id' })
userRoles: UserRole[]
@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[]
}

View File

@ -5,7 +5,10 @@ import {
Column,
CreateDateColumn,
UpdateDateColumn,
OneToMany,
JoinColumn,
} from 'typeorm'
import { User } from '../User'
@Entity('communities')
export class Community extends BaseEntity {
@ -63,4 +66,8 @@ export class Community extends BaseEntity {
nullable: true,
})
updatedAt: Date | null
@OneToMany(() => User, (user) => user.community)
@JoinColumn({ name: 'community_uuid', referencedColumnName: 'communityUuid' })
users: User[]
}

View File

@ -8,11 +8,13 @@ import {
JoinColumn,
OneToOne,
Geometry,
ManyToOne,
} from 'typeorm'
import { Contribution } from '../Contribution'
import { ContributionMessage } from '../ContributionMessage'
import { UserContact } from '../UserContact'
import { UserRole } from '../UserRole'
import { Community } from '../Community'
@Entity('users', { engine: 'InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci' })
export class User extends BaseEntity {
@ -39,6 +41,10 @@ export class User extends BaseEntity {
})
communityUuid: string
@ManyToOne(() => Community, (community) => community.users)
@JoinColumn({ name: 'community_uuid', referencedColumnName: 'communityUuid' })
community: Community | null
@Column({
name: 'alias',
length: 20,

View File

@ -1 +1 @@
export { Community } from './0079-introduce_gms_registration/Community'
export { Community } from './0082-introduce_gms_registration/Community'

View File

@ -1 +1 @@
export { User } from './0079-introduce_gms_registration/User'
export { User } from './0082-introduce_gms_registration/User'

View File

@ -1 +1 @@
export { UserContact } from './0079-introduce_gms_registration/UserContact'
export { UserContact } from './0082-introduce_gms_registration/UserContact'

View File

@ -0,0 +1,38 @@
import util from 'util'
import { Decimal } from 'decimal.js-light'
export abstract class AbstractLoggingView {
// eslint-disable-next-line no-undef
protected bufferStringFormat: BufferEncoding = 'hex'
// This function gets called automatically when JSON.stringify() is called on this class instance
// eslint-disable-next-line @typescript-eslint/no-explicit-any
public abstract toJSON(): any
public toString(compact = false): string {
if (compact) {
return JSON.stringify(this.toJSON())
} else {
return JSON.stringify(this.toJSON(), null, 2)
}
}
// called form console.log or log4js logging functions
[util.inspect.custom](): string {
return this.toString()
}
public dateToString(date: Date | undefined | null): string | undefined {
if (date) {
return date.toISOString()
}
return undefined
}
public decimalToString(number: Decimal | undefined | null): string | undefined {
if (number) {
return number.toString()
}
return undefined
}
}

View File

@ -0,0 +1,26 @@
import { Community } from '../entity/Community'
import { AbstractLoggingView } from './AbstractLogging.view'
export class CommunityLoggingView extends AbstractLoggingView {
public constructor(private self: Community) {
super()
}
// eslint-disable-next-line @typescript-eslint/no-explicit-any
public toJSON(): any {
return {
id: this.self.id,
foreign: this.self.foreign,
url: this.self.url,
publicKey: this.self.publicKey.toString(this.bufferStringFormat),
communityUuid: this.self.communityUuid,
authenticatedAt: this.dateToString(this.self.authenticatedAt),
name: this.self.name,
description: this.self.description?.substring(0, 24),
creationDate: this.dateToString(this.self.creationDate),
createdAt: this.dateToString(this.self.createdAt),
updatedAt: this.dateToString(this.self.updatedAt),
}
}
}

View File

@ -0,0 +1,45 @@
import { Contribution } from '../entity/Contribution'
import { AbstractLoggingView } from './AbstractLogging.view'
import { ContributionMessageLoggingView } from './ContributionMessageLogging.view'
import { TransactionLoggingView } from './TransactionLogging.view'
import { UserLoggingView } from './UserLogging.view'
export class ContributionLoggingView extends AbstractLoggingView {
public constructor(private self: Contribution) {
super()
}
// eslint-disable-next-line @typescript-eslint/no-explicit-any
public toJSON(): any {
return {
id: this.self.id,
user: this.self.user
? new UserLoggingView(this.self.user).toJSON()
: { id: this.self.userId },
createdAt: this.dateToString(this.self.createdAt),
resubmissionAt: this.dateToString(this.self.resubmissionAt),
contributionDate: this.dateToString(this.self.contributionDate),
memoLength: this.self.memo.length,
amount: this.decimalToString(this.self.amount),
moderatorId: this.self.moderatorId,
contributionLinkId: this.self.contributionLinkId,
confirmedBy: this.self.confirmedBy,
confirmedAt: this.dateToString(this.self.confirmedAt),
deniedBy: this.self.deniedBy,
deniedAt: this.dateToString(this.self.deniedAt),
contributionType: this.self.contributionType,
contributionStatus: this.self.contributionStatus,
transactionId: this.self.transactionId,
updatedAt: this.dateToString(this.self.updatedAt),
updatedBy: this.self.updatedBy,
deletedAt: this.dateToString(this.self.deletedAt),
deletedBy: this.self.deletedBy,
messages: this.self.messages
? this.self.messages.map((message) => new ContributionMessageLoggingView(message).toJSON())
: undefined,
transaction: this.self.transaction
? new TransactionLoggingView(this.self.transaction).toJSON()
: { id: this.self.transactionId },
}
}
}

View File

@ -0,0 +1,30 @@
import { ContributionMessage } from '../entity/ContributionMessage'
import { AbstractLoggingView } from './AbstractLogging.view'
import { ContributionLoggingView } from './ContributionLogging.view'
import { UserLoggingView } from './UserLogging.view'
export class ContributionMessageLoggingView extends AbstractLoggingView {
public constructor(private self: ContributionMessage) {
super()
}
// eslint-disable-next-line @typescript-eslint/no-explicit-any
public toJSON(): any {
return {
id: this.self.id,
contribution: this.self.contribution
? new ContributionLoggingView(this.self.contribution).toJSON()
: { id: this.self.contributionId },
user: this.self.user
? new UserLoggingView(this.self.user).toJSON()
: { id: this.self.userId },
messageLength: this.self.message.length,
createdAt: this.dateToString(this.self.createdAt),
updatedAt: this.dateToString(this.self.updatedAt),
deletedAt: this.dateToString(this.self.deletedAt),
deletedBy: this.self.deletedBy,
type: this.self.type,
isModerator: this.self.isModerator,
}
}
}

View File

@ -0,0 +1,23 @@
import { DltTransaction } from '../entity/DltTransaction'
import { AbstractLoggingView } from './AbstractLogging.view'
import { TransactionLoggingView } from './TransactionLogging.view'
export class DltTransactionLoggingView extends AbstractLoggingView {
public constructor(private self: DltTransaction) {
super()
}
// eslint-disable-next-line @typescript-eslint/no-explicit-any
public toJSON(): any {
return {
id: this.self.id,
transaction: this.self.transaction
? new TransactionLoggingView(this.self.transaction).toJSON()
: { id: this.self.transactionId },
messageId: this.self.messageId,
verified: this.self.verified,
createdAt: this.dateToString(this.self.createdAt),
verifiedAt: this.dateToString(this.self.verifiedAt),
}
}
}

View File

@ -0,0 +1,24 @@
import { FederatedCommunity } from '../entity/FederatedCommunity'
import { AbstractLoggingView } from './AbstractLogging.view'
export class FederatedCommunityLoggingView extends AbstractLoggingView {
public constructor(private self: FederatedCommunity) {
super()
}
// eslint-disable-next-line @typescript-eslint/no-explicit-any
public toJSON(): any {
return {
id: this.self.id,
foreign: this.self.foreign,
publicKey: this.self.publicKey.toString(this.bufferStringFormat),
apiVersion: this.self.apiVersion,
endPoint: this.self.endPoint,
lastAnnouncedAt: this.dateToString(this.self.lastAnnouncedAt),
verifiedAt: this.self.verifiedAt,
lastErrorAt: this.self.lastErrorAt,
createdAt: this.dateToString(this.self.createdAt),
updatedAt: this.dateToString(this.self.updatedAt),
}
}
}

View File

@ -0,0 +1,27 @@
/* eslint-disable no-unused-vars */
import { PendingTransaction } from '../entity/PendingTransaction'
import { Transaction } from '../entity/Transaction'
import { AbstractLoggingView } from './AbstractLogging.view'
import { TransactionLoggingView } from './TransactionLogging.view'
// TODO: move enum into database, maybe rename database
enum PendingTransactionState {
NEW = 1,
PENDING = 2,
SETTLED = 3,
REVERTED = 4,
}
export class PendingTransactionLoggingView extends AbstractLoggingView {
public constructor(private self: PendingTransaction) {
super()
}
// eslint-disable-next-line @typescript-eslint/no-explicit-any
public toJSON(): any {
return {
...new TransactionLoggingView(this.self as Transaction).toJSON(),
state: PendingTransactionState[this.self.state],
}
}
}

View File

@ -0,0 +1,56 @@
/* eslint-disable no-unused-vars */
import { Transaction } from '../entity/Transaction'
import { AbstractLoggingView } from './AbstractLogging.view'
import { ContributionLoggingView } from './ContributionLogging.view'
import { DltTransactionLoggingView } from './DltTransactionLogging.view'
// TODO: move enum into database, maybe rename database
enum TransactionTypeId {
CREATION = 1,
SEND = 2,
RECEIVE = 3,
// This is a virtual property, never occurring on the database
DECAY = 4,
LINK_SUMMARY = 5,
}
export class TransactionLoggingView extends AbstractLoggingView {
public constructor(private self: Transaction) {
super()
}
// eslint-disable-next-line @typescript-eslint/no-explicit-any
public toJSON(): any {
return {
id: this.self.id,
previous: this.self.previous,
typeId: TransactionTypeId[this.self.typeId],
transactionLinkId: this.self.transactionLinkId,
amount: this.decimalToString(this.self.amount),
balance: this.decimalToString(this.self.balance),
balanceDate: this.dateToString(this.self.balanceDate),
decay: this.decimalToString(this.self.decay),
decayStart: this.dateToString(this.self.decayStart),
memoLength: this.self.memo.length,
creationDate: this.dateToString(this.self.creationDate),
userId: this.self.userId,
userCommunityUuid: this.self.userCommunityUuid,
userGradidoId: this.self.userGradidoID,
userName: this.self.userName?.substring(0, 3) + '...',
linkedUserId: this.self.linkedUserId,
linkedUserCommunityUuid: this.self.linkedUserCommunityUuid,
linkedUserGradidoID: this.self.linkedUserGradidoID,
linkedUserName: this.self.linkedUserName?.substring(0, 3) + '...',
linkedTransactionId: this.self.linkedTransactionId,
contribution: this.self.contribution
? new ContributionLoggingView(this.self.contribution)
: undefined,
dltTransaction: this.self.dltTransaction
? new DltTransactionLoggingView(this.self.dltTransaction).toJSON()
: undefined,
previousTransaction: this.self.previousTransaction
? new TransactionLoggingView(this.self.previousTransaction).toJSON()
: undefined,
}
}
}

View File

@ -0,0 +1,35 @@
/* eslint-disable no-unused-vars */
import { UserContact } from '../entity/UserContact'
import { AbstractLoggingView } from './AbstractLogging.view'
import { UserLoggingView } from './UserLogging.view'
enum OptInType {
EMAIL_OPT_IN_REGISTER = 1,
EMAIL_OPT_IN_RESET_PASSWORD = 2,
}
export class UserContactLoggingView extends AbstractLoggingView {
public constructor(private self: UserContact) {
super()
}
// eslint-disable-next-line @typescript-eslint/no-explicit-any
public toJSON(): any {
return {
id: this.self.id,
type: this.self.type,
user: this.self.user
? new UserLoggingView(this.self.user).toJSON()
: { id: this.self.userId },
email: this.self.email?.substring(0, 3) + '...',
emailVerificationCode: this.self.emailVerificationCode?.substring(0, 4) + '...',
emailOptInTypeId: OptInType[this.self.emailOptInTypeId],
emailResendCount: this.self.emailResendCount,
emailChecked: this.self.emailChecked,
phone: this.self.phone ? this.self.phone.substring(0, 3) + '...' : undefined,
createdAt: this.dateToString(this.self.createdAt),
updatedAt: this.dateToString(this.self.updatedAt),
deletedAt: this.dateToString(this.self.deletedAt),
}
}
}

View File

@ -0,0 +1,60 @@
/* eslint-disable no-unused-vars */
import { User } from '../entity/User'
import { AbstractLoggingView } from './AbstractLogging.view'
import { ContributionLoggingView } from './ContributionLogging.view'
import { ContributionMessageLoggingView } from './ContributionMessageLogging.view'
import { UserContactLoggingView } from './UserContactLogging.view'
import { UserRoleLoggingView } from './UserRoleLogging.view'
enum PasswordEncryptionType {
NO_PASSWORD = 0,
EMAIL = 1,
GRADIDO_ID = 2,
}
export class UserLoggingView extends AbstractLoggingView {
public constructor(private self: User) {
super()
}
// eslint-disable-next-line @typescript-eslint/no-explicit-any
public toJSON(): any {
return {
id: this.self.id,
foreign: this.self.foreign,
gradidoID: this.self.gradidoID,
communityUuid: this.self.communityUuid,
alias: this.self.alias?.substring(0, 3) + '...',
emailContact: this.self.emailContact
? new UserContactLoggingView(this.self.emailContact).toJSON()
: { id: this.self.emailId },
firstName: this.self.firstName?.substring(0, 3) + '...',
lastName: this.self.lastName?.substring(0, 3) + '...',
createdAt: this.dateToString(this.self.createdAt),
deletedAt: this.dateToString(this.self.deletedAt),
passwordEncryptionType: this.self.passwordEncryptionType as PasswordEncryptionType,
language: this.self.language,
hideAmountGDD: this.self.hideAmountGDD,
hideAmountGDT: this.self.hideAmountGDT,
userRoles: this.self.userRoles
? this.self.userRoles.map((userRole) => new UserRoleLoggingView(userRole).toJSON())
: undefined,
referrerId: this.self.referrerId,
contributionLinkId: this.self.contributionLinkId,
publisherId: this.self.publisherId,
contributions: this.self.contributions
? this.self.contributions.map((contribution) =>
new ContributionLoggingView(contribution).toJSON(),
)
: undefined,
messages: this.self.messages
? this.self.messages.map((message) => new ContributionMessageLoggingView(message).toJSON())
: undefined,
userContacts: this.self.userContacts
? this.self.userContacts.map((userContact) =>
new UserContactLoggingView(userContact).toJSON(),
)
: undefined,
}
}
}

View File

@ -0,0 +1,22 @@
import { UserRole } from '../entity/UserRole'
import { AbstractLoggingView } from './AbstractLogging.view'
import { UserLoggingView } from './UserLogging.view'
export class UserRoleLoggingView extends AbstractLoggingView {
public constructor(private self: UserRole) {
super()
}
// eslint-disable-next-line @typescript-eslint/no-explicit-any
public toJSON(): any {
return {
id: this.self.id,
user: this.self.user
? new UserLoggingView(this.self.user).toJSON()
: { id: this.self.userId },
role: this.self.role,
createdAt: this.dateToString(this.self.createdAt),
updatedAt: this.dateToString(this.self.updatedAt),
}
}
}

View File

@ -0,0 +1,13 @@
export async function upgrade(queryFn: (query: string, values?: any[]) => Promise<Array<any>>) {
await queryFn(
`UPDATE \`transactions\` AS t
JOIN \`contributions\` AS c ON t.id = c.transaction_id
SET t.linked_user_id = c.confirmed_by
WHERE t.type_id = ?`,
[1],
)
}
export async function downgrade(queryFn: (query: string, values?: any[]) => Promise<Array<any>>) {
await queryFn(`UPDATE \`transactions\` SET \`linked_user_id\` = NULL where \`type_id\` = ?;`, [1])
}

View File

@ -0,0 +1,34 @@
export async function upgrade(queryFn: (query: string, values?: any[]) => Promise<Array<any>>) {
await queryFn(
`UPDATE \`transactions\` AS t
JOIN \`contributions\` AS c ON t.id = c.transaction_id
JOIN \`users\` AS u ON u.id = c.confirmed_by
SET
t.linked_user_gradido_id = u.gradido_id,
t.linked_user_name = CONCAT(u.first_name, ' ', u.last_name),
t.linked_user_community_uuid = u.community_uuid
WHERE t.type_id = ?`,
[1],
)
// fill user community uuid fields in transactions
await queryFn(
`UPDATE \`transactions\` AS t
JOIN \`users\` AS u ON u.id = t.user_id
JOIN \`users\` AS lu ON lu.id = t.linked_user_id
SET
t.user_community_uuid = u.community_uuid,
t.linked_user_community_uuid = lu.community_uuid`,
)
}
export async function downgrade(queryFn: (query: string, values?: any[]) => Promise<Array<any>>) {
await queryFn(
`UPDATE \`transactions\` SET \`linked_user_gradido_id\` = NULL, \`linked_user_name\` = NULL where \`type_id\` = ?;`,
[1],
)
await queryFn(
`UPDATE \`transactions\` SET \`user_community_uuid\` = NULL, \`linked_user_community_uuid\` = NULL;`,
)
}

View File

@ -0,0 +1,11 @@
export async function upgrade(queryFn: (query: string, values?: any[]) => Promise<Array<any>>) {
await queryFn(
'ALTER TABLE users MODIFY community_uuid VARCHAR(36) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;',
)
}
export async function downgrade(queryFn: (query: string, values?: any[]) => Promise<Array<any>>) {
await queryFn(
'ALTER TABLE users MODIFY community_uuid VARCHAR(36) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;',
)
}

View File

@ -1,87 +1,102 @@
GRADIDO_LOG_PATH=/home/gradido/gradido/deployment/bare_metal/log
# Need to adjust!
COMMUNITY_NAME="Your community name"
COMMUNITY_DESCRIPTION="Short Description from your Community."
COMMUNITY_HOST=gddhost.tld
COMMUNITY_SUPPORT_MAIL=support@supportmail.com
# setup email account for sending gradido system messages to users
EMAIL=true
EMAIL_USERNAME=peter@lustig.de
EMAIL_SENDER=peter@lustig.de
EMAIL_PASSWORD=1234
EMAIL_SMTP_URL=smtp.lustig.de
EMAIL_SMTP_PORT=587
# if set to true allow sending gradidos to another communities
FEDERATION_XCOM_SENDCOINS_ENABLED=false
# how many minutes email verification code is valid
# also used for password reset code
EMAIL_CODE_VALID_TIME=1440
# how many minutes user must wait before he can request the email verification code again
# also used for password reset code
EMAIL_CODE_REQUEST_TIME=10
# Need to adjust by updates
# config versions
DATABASE_CONFIG_VERSION=v1.2022-03-18
BACKEND_CONFIG_VERSION=v21.2024-01-06
FRONTEND_CONFIG_VERSION=v5.2024-01-08
ADMIN_CONFIG_VERSION=v2.2024-01-04
FEDERATION_CONFIG_VERSION=v2.2023-08-24
FEDERATION_DHT_CONFIG_VERSION=v4.2024-01-17
FEDERATION_DHT_TOPIC=GRADIDO_HUB
# Need adjustments for test system
URL_PROTOCOL=https
# start script
# only for test server
DEPLOY_SEED_DATA=false
# test email
# if true all email will be send to EMAIL_TEST_RECEIVER instead of email address of user
EMAIL_TEST_MODUS=false
EMAIL_TEST_RECEIVER=test_team@gradido.net
# nginx
NGINX_REWRITE_LEGACY_URLS=true
NGINX_SSL=true
NGINX_SERVER_NAME=stage1.gradido.net
NGINX_SSL_CERTIFICATE=/etc/letsencrypt/live/stage1.gradido.net/fullchain.pem
NGINX_SSL_CERTIFICATE_KEY=/etc/letsencrypt/live/stage1.gradido.net/privkey.pem
NGINX_SSL_DHPARAM=/etc/letsencrypt/ssl-dhparams.pem
NGINX_SSL_INCLUDE=/etc/letsencrypt/options-ssl-nginx.conf
NGINX_UPDATE_PAGE_ROOT=/home/gradido/gradido/deployment/bare_metal/nginx/update-page
# Logging
LOG_LEVEL=INFO
GRADIDO_LOG_PATH=/home/gradido/gradido/deployment/bare_metal/log
TYPEORM_LOGGING_RELATIVE_PATH=../deployment/bare_metal/log/typeorm.backend.log
# webhook
WEBHOOK_GITHUB_SECRET=secret
WEBHOOK_GITHUB_BRANCH=master
# community
COMMUNITY_NAME="Gradido Development Stage1"
COMMUNITY_URL=https://stage1.gradido.net/
COMMUNITY_REGISTER_URL=https://stage1.gradido.net/register
COMMUNITY_REDEEM_URL=https://stage1.gradido.net/redeem/{code}
COMMUNITY_REDEEM_CONTRIBUTION_URL=https://stage1.gradido.net/redeem/CL-{code}
COMMUNITY_DESCRIPTION="Gradido Development Stage1 Test Community"
COMMUNITY_SUPPORT_MAIL=support@supportmail.com
# backend
BACKEND_CONFIG_VERSION=v17.2023-07-03
# frontend and admin paths, usually don't need changes
# used in nginx config and for links in emails
COMMUNITY_REGISTER_PATH=/register
COMMUNITY_REDEEM_PATH=/redeem/{code}
COMMUNITY_REDEEM_CONTRIBUTION_PATH=/redeem/CL-{code}
WALLET_LOGIN_PATH=/login
WALLET_AUTH_PATH=/authenticate?token={token}
EMAIL_LINK_VERIFICATION_PATH=/checkEmail/{optin}{code}
EMAIL_LINK_SETPASSWORD_PATH=/reset-password/{optin}
EMAIL_LINK_FORGOTPASSWORD_PATH=/forgot-password
EMAIL_LINK_OVERVIEW_PATH=/overview
ADMIN_AUTH_PATH=/admin/authenticate?token={token}
GRAPHQL_PATH=/graphql
# login expire time
JWT_EXPIRES_IN=10m
# Federation
# if you set the value of FEDERATION_DHT_TOPIC, the DHT hyperswarm will start to announce and listen
# on an hash created from this topic
# FEDERATION_DHT_TOPIC=GRADIDO_HUB
# FEDERATION_DHT_SEED=64ebcb0e3ad547848fef4197c6e2332f
# the api port is the baseport, which will be added with the api-version, e.g. 1_0 = 5010
FEDERATION_COMMUNITY_API_PORT=5000
FEDERATION_VALIDATE_COMMUNITY_TIMER=60000
# comma separated list of api-versions, which cause starting several federation modules
FEDERATION_COMMUNITY_APIS=1_0
# externe gradido services (more added in future)
GDT_API_URL=https://gdt.gradido.net
TYPEORM_LOGGING_RELATIVE_PATH=../deployment/bare_metal/log/typeorm.backend.log
# DLT-Connector (still in develop)
DLT_CONNECTOR=false
DLT_CONNECTOR_PORT=6010
# used for combining a newsletter on klicktipp with this gradido community
# if used, user will be subscribed on register and can unsubscribe in his account
KLICKTIPP=false
KLICKTIPP_USER=
KLICKTIPP_PASSWORD=
KLICKTIPP_APIKEY_DE=
KLICKTIPP_APIKEY_EN=
EMAIL=true
EMAIL_TEST_MODUS=false
EMAIL_TEST_RECEIVER=test_team@gradido.net
EMAIL_USERNAME=peter@lustig.de
EMAIL_SENDER=peter@lustig.de
EMAIL_PASSWORD=1234
EMAIL_SMTP_URL=smtp.lustig.de
EMAIL_LINK_VERIFICATION=https://stage1.gradido.net/checkEmail/{optin}{code}
EMAIL_LINK_SETPASSWORD=https://stage1.gradido.net/reset-password/{optin}
EMAIL_LINK_FORGOTPASSWORD=https://stage1.gradido.net/forgot-password
EMAIL_LINK_OVERVIEW=https://stage1.gradido.net/overview
EMAIL_CODE_VALID_TIME=1440
EMAIL_CODE_REQUEST_TIME=10
WEBHOOK_ELOPAGE_SECRET=secret
# Federation
FEDERATION_DHT_CONFIG_VERSION=v3.2023-04-26
# if you set the value of FEDERATION_DHT_TOPIC, the DHT hyperswarm will start to announce and listen
# on an hash created from this topic
# FEDERATION_DHT_TOPIC=GRADIDO_HUB
# FEDERATION_DHT_SEED=64ebcb0e3ad547848fef4197c6e2332f
FEDERATION_COMMUNITY_URL=http://stage1.gradido.net
# the api port is the baseport, which will be added with the api-version, e.g. 1_0 = 5010
FEDERATION_COMMUNITY_API_PORT=5000
FEDERATION_CONFIG_VERSION=v1.2023-01-09
# comma separated list of api-versions, which cause starting several federation modules
FEDERATION_COMMUNITY_APIS=1_0,1_1
# database
DATABASE_CONFIG_VERSION=v1.2022-03-18
# frontend
FRONTEND_CONFIG_VERSION=v4.2022-12-20
GRAPHQL_URI=https://stage1.gradido.net/graphql
ADMIN_AUTH_URL=https://stage1.gradido.net/admin/authenticate?token={token}
DEFAULT_PUBLISHER_ID=2896
META_URL=http://localhost
# Meta data in frontend pages, important when shared via facebook or twitter or for search engines
META_TITLE_DE="Gradido Dein Dankbarkeitskonto"
META_TITLE_EN="Gradido - Your gratitude account"
META_DESCRIPTION_DE="Dankbarkeit ist die Währung der neuen Zeit. Immer mehr Menschen entfalten ihr Potenzial und gestalten eine gute Zukunft für alle."
@ -90,8 +105,20 @@ 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_AUTHOR="Bernd Hückstädt - Gradido-Akademie"
# admin
ADMIN_CONFIG_VERSION=v1.2022-03-18
# update page shown while updating gradido
# page will be fed with status changes
NGINX_UPDATE_PAGE_ROOT=/home/gradido/gradido/deployment/bare_metal/nginx/update-page
# NGINX SSL Setup with certbot
# will be generated by start.sh with $COMMUNITY_HOST, only need to setup manual if setup differ from default
#NGINX_SSL_CERTIFICATE=/etc/letsencrypt/live/gddhost.tld/fullchain.pem
#NGINX_SSL_CERTIFICATE_KEY=/etc/letsencrypt/live/gddhost.tld/privkey.pem
NGINX_SSL_DHPARAM=/etc/letsencrypt/ssl-dhparams.pem
NGINX_SSL_INCLUDE=/etc/letsencrypt/options-ssl-nginx.conf
# LEGACY
NGINX_REWRITE_LEGACY_URLS=false
DEFAULT_PUBLISHER_ID=2896
WEBHOOK_ELOPAGE_SECRET=secret
WALLET_AUTH_URL=https://stage1.gradido.net/authenticate?token={token}
WALLET_URL=https://stage1.gradido.net/login

View File

@ -0,0 +1,118 @@
<mxfile host="65bd71144e">
<diagram id="q0c1bfTOSmR5BH1DDDeU" name="Page-1">
<mxGraphModel dx="874" dy="1662" grid="1" gridSize="10" guides="1" tooltips="1" connect="1" arrows="1" fold="1" page="1" pageScale="1" pageWidth="850" pageHeight="1100" math="0" shadow="0">
<root>
<mxCell id="0"/>
<mxCell id="1" parent="0"/>
<mxCell id="2" value="Physical Server" style="swimlane;whiteSpace=wrap;html=1;" vertex="1" parent="1">
<mxGeometry x="15" y="40" width="410" height="420" as="geometry"/>
</mxCell>
<mxCell id="3" value="mariadb&lt;br&gt;port: 3306" style="shape=datastore;whiteSpace=wrap;html=1;" vertex="1" parent="2">
<mxGeometry x="165" y="350" width="60" height="60" as="geometry"/>
</mxCell>
<mxCell id="4" value="nginx" style="swimlane;whiteSpace=wrap;html=1;startSize=23;" vertex="1" parent="2">
<mxGeometry x="60" y="20" width="230" height="110" as="geometry"/>
</mxCell>
<mxCell id="5" value="port 80: redirect to port 443" style="text;html=1;align=center;verticalAlign=middle;resizable=0;points=[];autosize=1;strokeColor=none;fillColor=none;" vertex="1" parent="4">
<mxGeometry x="-5" y="30" width="170" height="30" as="geometry"/>
</mxCell>
<mxCell id="6" value="port 443: using ssl encryption" style="text;html=1;align=center;verticalAlign=middle;resizable=0;points=[];autosize=1;strokeColor=none;fillColor=none;" vertex="1" parent="4">
<mxGeometry x="-5" y="60" width="180" height="30" as="geometry"/>
</mxCell>
<mxCell id="28" value="" style="endArrow=classic;html=1;entryX=0.5;entryY=0;entryDx=0;entryDy=0;" edge="1" parent="4" target="15">
<mxGeometry relative="1" as="geometry">
<mxPoint x="45" y="110" as="sourcePoint"/>
<mxPoint x="145" y="90" as="targetPoint"/>
</mxGeometry>
</mxCell>
<mxCell id="29" value="/&lt;span style=&quot;color: rgb(36, 41, 46); font-family: &amp;quot;Droid Sans Mono&amp;quot;, &amp;quot;monospace&amp;quot;, monospace; font-size: 14px;&quot;&gt;graphql&lt;/span&gt;" style="edgeLabel;resizable=0;html=1;align=center;verticalAlign=middle;" connectable="0" vertex="1" parent="28">
<mxGeometry relative="1" as="geometry"/>
</mxCell>
<mxCell id="41" style="edgeStyle=none;html=1;" edge="1" parent="2" source="15">
<mxGeometry relative="1" as="geometry">
<mxPoint x="185" y="350" as="targetPoint"/>
</mxGeometry>
</mxCell>
<mxCell id="15" value="Backend&lt;br&gt;runs on port: 4000" style="shape=process;whiteSpace=wrap;html=1;backgroundOutline=1;" vertex="1" parent="2">
<mxGeometry x="10" y="170" width="120" height="60" as="geometry"/>
</mxCell>
<mxCell id="17" value="frontend&lt;br&gt;static files server&lt;br&gt;port: 3000" style="shape=process;whiteSpace=wrap;html=1;backgroundOutline=1;" vertex="1" parent="2">
<mxGeometry x="125" y="210" width="120" height="40" as="geometry"/>
</mxCell>
<mxCell id="18" value="admin&lt;br&gt;static files server&lt;br&gt;port: 8080" style="shape=process;whiteSpace=wrap;html=1;backgroundOutline=1;" vertex="1" parent="2">
<mxGeometry x="290" y="190" width="120" height="60" as="geometry"/>
</mxCell>
<mxCell id="40" style="edgeStyle=none;html=1;" edge="1" parent="2" source="19">
<mxGeometry relative="1" as="geometry">
<mxPoint x="165" y="370" as="targetPoint"/>
</mxGeometry>
</mxCell>
<mxCell id="19" value="dht-node&lt;br&gt;use his own system" style="shape=process;whiteSpace=wrap;html=1;backgroundOutline=1;" vertex="1" parent="2">
<mxGeometry x="10" y="320" width="120" height="60" as="geometry"/>
</mxCell>
<mxCell id="39" style="edgeStyle=none;html=1;entryX=0.85;entryY=0.05;entryDx=0;entryDy=0;entryPerimeter=0;" edge="1" parent="2" source="21" target="3">
<mxGeometry relative="1" as="geometry"/>
</mxCell>
<mxCell id="21" value="Federation&lt;br&gt;on port per version" style="shape=process;whiteSpace=wrap;html=1;backgroundOutline=1;" vertex="1" parent="2">
<mxGeometry x="215" y="260" width="120" height="60" as="geometry"/>
</mxCell>
<mxCell id="30" value="/&lt;br&gt;" style="endArrow=classic;html=1;" edge="1" parent="2" target="17">
<mxGeometry relative="1" as="geometry">
<mxPoint x="155" y="130" as="sourcePoint"/>
<mxPoint x="305" y="150" as="targetPoint"/>
</mxGeometry>
</mxCell>
<mxCell id="31" value="/" style="edgeLabel;resizable=0;html=1;align=center;verticalAlign=middle;" connectable="0" vertex="1" parent="30">
<mxGeometry relative="1" as="geometry"/>
</mxCell>
<mxCell id="9" value="" style="endArrow=classic;html=1;" edge="1" parent="1" source="13" target="2">
<mxGeometry relative="1" as="geometry">
<mxPoint x="210" y="20" as="sourcePoint"/>
<mxPoint x="500" y="290" as="targetPoint"/>
</mxGeometry>
</mxCell>
<mxCell id="10" value="Extern Request&lt;br&gt;- webbrowser with frontend running&lt;br&gt;- webbrowser with admin running&lt;br&gt;- backend&lt;br&gt;- federation" style="edgeLabel;resizable=0;html=1;align=center;verticalAlign=middle;" connectable="0" vertex="1" parent="9">
<mxGeometry relative="1" as="geometry"/>
</mxCell>
<mxCell id="11" value="Source" style="edgeLabel;resizable=0;html=1;align=left;verticalAlign=bottom;" connectable="0" vertex="1" parent="9">
<mxGeometry x="-1" relative="1" as="geometry">
<mxPoint y="20" as="offset"/>
</mxGeometry>
</mxCell>
<mxCell id="12" value="Target" style="edgeLabel;resizable=0;html=1;align=right;verticalAlign=bottom;" connectable="0" vertex="1" parent="9">
<mxGeometry x="1" relative="1" as="geometry"/>
</mxCell>
<mxCell id="13" value="Internet" style="ellipse;shape=cloud;whiteSpace=wrap;html=1;" vertex="1" parent="1">
<mxGeometry x="100" y="-180" width="120" height="80" as="geometry"/>
</mxCell>
<mxCell id="33" value="" style="endArrow=classic;html=1;" edge="1" parent="1" target="18">
<mxGeometry relative="1" as="geometry">
<mxPoint x="270" y="170" as="sourcePoint"/>
<mxPoint x="470" y="50" as="targetPoint"/>
<Array as="points">
<mxPoint x="300" y="200"/>
</Array>
</mxGeometry>
</mxCell>
<mxCell id="34" value="/admin" style="edgeLabel;resizable=0;html=1;align=center;verticalAlign=middle;" connectable="0" vertex="1" parent="33">
<mxGeometry relative="1" as="geometry"/>
</mxCell>
<mxCell id="35" value="" style="endArrow=classic;html=1;entryX=0.633;entryY=-0.017;entryDx=0;entryDy=0;entryPerimeter=0;" edge="1" parent="1" target="21">
<mxGeometry relative="1" as="geometry">
<mxPoint x="220" y="170" as="sourcePoint"/>
<mxPoint x="470" y="170" as="targetPoint"/>
</mxGeometry>
</mxCell>
<mxCell id="36" value="/api/VERSION" style="edgeLabel;resizable=0;html=1;align=center;verticalAlign=middle;" connectable="0" vertex="1" parent="35">
<mxGeometry relative="1" as="geometry"/>
</mxCell>
<mxCell id="43" value="Legende:" style="text;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;spacingLeft=4;spacingRight=4;overflow=hidden;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;rotatable=0;whiteSpace=wrap;html=1;fontStyle=1;fontSize=14;" vertex="1" parent="1">
<mxGeometry x="550" y="130" width="80" height="30" as="geometry"/>
</mxCell>
<mxCell id="44" value="&lt;i style=&quot;&quot;&gt;Node JS&amp;nbsp;&lt;br&gt;Express Server&lt;/i&gt;" style="shape=process;whiteSpace=wrap;html=1;backgroundOutline=1;" vertex="1" parent="1">
<mxGeometry x="540" y="170" width="120" height="60" as="geometry"/>
</mxCell>
</root>
</mxGraphModel>
</diagram>
</mxfile>

View File

@ -0,0 +1,4 @@
limit_req_zone $binary_remote_addr zone=frontend:20m rate=5r/s;
limit_req_zone $binary_remote_addr zone=backend:25m rate=15r/s;
limit_req_zone $binary_remote_addr zone=api:5m rate=30r/s;
limit_conn_zone $binary_remote_addr zone=addr:10m;

View File

@ -1,5 +1,8 @@
location /api/$FEDERATION_APIVERSION {
limit_req zone=api burst=60 nodelay;
limit_conn addr 30;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade';

View File

@ -1,16 +1,18 @@
include /etc/nginx/common/limit_requests.conf;
server {
if ($host = $NGINX_SERVER_NAME) {
if ($host = $COMMUNITY_HOST) {
return 301 https://$host$request_uri;
}
server_name $NGINX_SERVER_NAME;
server_name $COMMUNITY_HOST;
listen 80;
listen [::]:80;
return 404;
}
server {
server_name $NGINX_SERVER_NAME;
server_name $COMMUNITY_HOST;
listen [::]:443 ssl ipv6only=on;
listen 443 ssl;
@ -22,6 +24,15 @@ server {
include /etc/nginx/common/protect.conf;
include /etc/nginx/common/protect_add_header.conf;
# protect from slow loris
client_body_timeout 10s;
client_header_timeout 10s;
# protect from range attack (in http header)
if ($http_range ~ "d{9,}") {
return 444;
}
#gzip_static on;
gzip on;
gzip_proxied any;
@ -42,6 +53,8 @@ server {
# Frontend (default)
location / {
limit_req zone=frontend burst=40 nodelay;
limit_conn addr 40;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade';
@ -58,6 +71,8 @@ server {
# Backend
location /graphql {
limit_req zone=backend burst=10 nodelay;
limit_conn addr 10;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade';
@ -74,6 +89,8 @@ server {
# Backend webhooks
location /hook {
limit_req zone=backend burst=10;
limit_conn addr 10;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade';
@ -90,6 +107,8 @@ server {
# Webhook reverse proxy
location /hooks/ {
limit_req zone=backend burst=10;
limit_conn addr 10;
proxy_pass http://127.0.0.1:9000/hooks/;
access_log $GRADIDO_LOG_PATH/nginx-access.hooks.log gradido_log;
@ -98,6 +117,8 @@ server {
# Admin Frontend
location /admin {
limit_req zone=frontend burst=30 nodelay;
limit_conn addr 40;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade';

View File

@ -1,5 +1,7 @@
include /etc/nginx/common/limit_requests.conf;
server {
server_name $NGINX_SERVER_NAME;
server_name $COMMUNITY_HOST;
listen 80;
listen [::]:80;
@ -7,6 +9,15 @@ server {
include /etc/nginx/common/protect.conf;
include /etc/nginx/common/protect_add_header.conf;
# protect from slow loris
client_body_timeout 10s;
client_header_timeout 10s;
# protect from range attack (in http header)
if ($http_range ~ "d{9,}") {
return 444;
}
#gzip_static on;
gzip on;
gzip_proxied any;
@ -27,6 +38,8 @@ server {
# Frontend (default)
location / {
limit_req zone=frontend burst=40 nodelay;
limit_conn addr 40;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade';
@ -43,6 +56,8 @@ server {
# Backend
location /graphql {
limit_req zone=backend burst=10 nodelay;
limit_conn addr 10;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade';
@ -59,6 +74,8 @@ server {
# Backend webhooks
location /hook {
limit_req zone=backend burst=10;
limit_conn addr 10;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade';
@ -66,7 +83,6 @@ server {
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header Host $host;
# no trailing slash to keep the hook/ prefix
proxy_pass http://127.0.0.1:4000/hook;
proxy_redirect off;
@ -76,6 +92,8 @@ server {
# Webhook reverse proxy
location /hooks/ {
limit_req zone=backend burst=10;
limit_conn addr 10;
proxy_pass http://127.0.0.1:9000/hooks/;
access_log $GRADIDO_LOG_PATH/nginx-access.hooks.log gradido_log;
@ -84,6 +102,8 @@ server {
# Admin Frontend
location /admin {
limit_req zone=frontend burst=30 nodelay;
limit_conn addr 40;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade';
@ -97,7 +117,7 @@ server {
access_log $GRADIDO_LOG_PATH/nginx-access.admin.log gradido_log;
error_log $GRADIDO_LOG_PATH/nginx-error.admin.log warn;
}
# Federation
$FEDERATION_NGINX_CONF

View File

@ -1,16 +1,17 @@
include /etc/nginx/common/limit_requests.conf;
server {
if ($host = $NGINX_SERVER_NAME) {
if ($host = $COMMUNITY_HOST) {
return 301 https://$host$request_uri;
}
server_name $NGINX_SERVER_NAME;
server_name $COMMUNITY_HOST;
listen 80;
listen [::]:80;
return 404;
}
server {
server_name $NGINX_SERVER_NAME;
server_name $COMMUNITY_HOST;
listen [::]:443 ssl ipv6only=on;
listen 443 ssl;
@ -22,12 +23,23 @@ server {
include /etc/nginx/common/protect.conf;
include /etc/nginx/common/protect_add_header.conf;
# protect from slow loris
client_body_timeout 10s;
client_header_timeout 10s;
# protect from range attack (in http header)
if ($http_range ~ "d{9,}") {
return 444;
}
gzip on;
root $NGINX_UPDATE_PAGE_ROOT;
index updating.html;
location / {
limit_req zone=frontend;
limit_conn addr 10;
try_files /updating.html =404;
}

View File

@ -1,18 +1,30 @@
include /etc/nginx/common/limit_requests.conf;
server {
server_name _;
server_name $COMMUNITY_HOST;
listen 80;
listen [::]:80;
include /etc/nginx/common/protect.conf;
include /etc/nginx/common/protect_add_header.conf;
# protect from slow loris
client_body_timeout 10s;
client_header_timeout 10s;
# protect from range attack (in http header)
if ($http_range ~ "d{9,}") {
return 444;
}
gzip on;
root $NGINX_UPDATE_PAGE_ROOT;
index updating.html;
location / {
limit_req zone=frontend;
limit_conn addr 10;
try_files /updating.html =404;
}

View File

@ -10,12 +10,17 @@ PROJECT_ROOT=$SCRIPT_DIR/../..
NGINX_CONFIG_DIR=$SCRIPT_DIR/nginx/sites-available
set +o allexport
# enable nvm
export NVM_DIR="$HOME/.nvm" && [ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh"
# NOTE: all config values will be in process.env when starting
# the services and will therefore take precedence over the .env
# We have to load the backend .env to get DB_USERNAME, DB_PASSWORD AND JWT_SECRET
# and the dht-node .env to get FEDERATION_DHT_SEED
export_var(){
export $1=$(grep -v '^#' $PROJECT_ROOT/backend/.env | grep -e "$1" | sed -e 's/.*=//')
export $1=$(grep -v '^#' $PROJECT_ROOT/dht-node/.env | grep -e "$1" | sed -e 's/.*=//')
}
if [ -f "$PROJECT_ROOT/backend/.env" ]; then
@ -24,6 +29,10 @@ if [ -f "$PROJECT_ROOT/backend/.env" ]; then
export_var 'JWT_SECRET'
fi
if [ -f "$PROJECT_ROOT/dht-node/.env" ]; then
export_var 'FEDERATION_DHT_SEED'
fi
# Load .env or .env.dist if not present
if [ -f "$SCRIPT_DIR/.env" ]; then
set -o allexport
@ -35,6 +44,14 @@ else
set +o allexport
fi
# set env variables dynamic if not already set in .env or .env.dist
: ${NGINX_SSL_CERTIFICATE:=/etc/letsencrypt/live/$COMMUNITY_HOST/fullchain.pem}
: ${NGINX_SSL_CERTIFICATE_KEY:=/etc/letsencrypt/live/$COMMUNITY_HOST/privkey.pem}
# export env variables
export NGINX_SSL_CERTIFICATE
export NGINX_SSL_CERTIFICATE_KEY
# lock start
if [ -f $LOCK_FILE ] ; then
echo "Already building!"
@ -54,8 +71,7 @@ exec > >(tee -a $UPDATE_HTML) 2>&1
# configure nginx for the update-page
echo 'Configuring nginx to serve the update-page' >> $UPDATE_HTML
rm /etc/nginx/sites-enabled/gradido.conf
ln -s /etc/nginx/sites-available/update-page.conf /etc/nginx/sites-enabled/
ln -sf $SCRIPT_DIR/nginx/sites-available/update-page.conf $SCRIPT_DIR/nginx/sites-enabled/default
sudo /etc/init.d/nginx restart
# stop all services
@ -100,9 +116,9 @@ export FEDERATION_NGINX_CONF=$(< $NGINX_CONFIG_DIR/gradido-federation.conf.locat
# *** 3rd generate gradido nginx config including federation modules per api-version
echo 'Generate new gradido nginx config' >> $UPDATE_HTML
case "$NGINX_SSL" in
true) TEMPLATE_FILE="gradido.conf.ssl.template" ;;
*) TEMPLATE_FILE="gradido.conf.template" ;;
case "$URL_PROTOCOL" in
'https') TEMPLATE_FILE="gradido.conf.ssl.template" ;;
*) TEMPLATE_FILE="gradido.conf.template" ;;
esac
envsubst '$FEDERATION_NGINX_CONF' < $NGINX_CONFIG_DIR/$TEMPLATE_FILE > $NGINX_CONFIG_DIR/gradido.conf.tmp
unset FEDERATION_NGINX_CONF
@ -112,9 +128,9 @@ rm $NGINX_CONFIG_DIR/gradido-federation.conf.locations
# Generate update-page.conf from template
echo 'Generate new update-page nginx config' >> $UPDATE_HTML
case "$NGINX_SSL" in
true) TEMPLATE_FILE="update-page.conf.ssl.template" ;;
*) TEMPLATE_FILE="update-page.conf.template" ;;
case "$URL_PROTOCOL" in
'https') TEMPLATE_FILE="update-page.conf.ssl.template" ;;
*) TEMPLATE_FILE="update-page.conf.template" ;;
esac
envsubst "$(env | sed -e 's/=.*//' -e 's/^/\$/g')" < $NGINX_CONFIG_DIR/$TEMPLATE_FILE > $NGINX_CONFIG_DIR/update-page.conf
@ -177,8 +193,7 @@ if [ "$DEPLOY_SEED_DATA" = "true" ]; then
fi
# TODO maybe handle this differently?
export NODE_ENV=production
pm2 start --name gradido-backend "yarn --cwd $PROJECT_ROOT/backend start" -l $GRADIDO_LOG_PATH/pm2.backend.$TODAY.log --log-date-format 'YYYY-MM-DD HH:mm:ss.SSS'
pm2 save
# Install & build frontend
echo 'Updating frontend' >> $UPDATE_HTML
@ -189,8 +204,6 @@ yarn install
yarn build
# TODO maybe handle this differently?
export NODE_ENV=production
pm2 start --name gradido-frontend "yarn --cwd $PROJECT_ROOT/frontend start" -l $GRADIDO_LOG_PATH/pm2.frontend.$TODAY.log --log-date-format 'YYYY-MM-DD HH:mm:ss.SSS'
pm2 save
# Install & build admin
echo 'Updating admin' >> $UPDATE_HTML
@ -201,8 +214,6 @@ yarn install
yarn build
# TODO maybe handle this differently?
export NODE_ENV=production
pm2 start --name gradido-admin "yarn --cwd $PROJECT_ROOT/admin start" -l $GRADIDO_LOG_PATH/pm2.admin.$TODAY.log --log-date-format 'YYYY-MM-DD HH:mm:ss.SSS'
pm2 save
# Install & build dht-node
echo 'Updating dht-node' >> $UPDATE_HTML
@ -213,15 +224,6 @@ yarn install
yarn build
# TODO maybe handle this differently?
export NODE_ENV=production
if [ ! -z $FEDERATION_DHT_TOPIC ]; then
pm2 start --name gradido-dht-node "yarn --cwd $PROJECT_ROOT/dht-node start" -l $GRADIDO_LOG_PATH/pm2.dht-node.$TODAY.log --log-date-format 'YYYY-MM-DD HH:mm:ss.SSS'
pm2 save
else
echo "=====================================================================" >> $UPDATE_HTML
echo "WARNING: FEDERATION_DHT_TOPIC not configured. DHT-Node not started..." >> $UPDATE_HTML
echo "=====================================================================" >> $UPDATE_HTML
fi
# Install & build federation
echo 'Updating federation' >> $UPDATE_HTML
@ -233,6 +235,20 @@ yarn build
# TODO maybe handle this differently?
export NODE_ENV=production
# start after building all to use up less ressources
pm2 start --name gradido-backend "yarn --cwd $PROJECT_ROOT/backend start" -l $GRADIDO_LOG_PATH/pm2.backend.$TODAY.log --log-date-format 'YYYY-MM-DD HH:mm:ss.SSS'
pm2 start --name gradido-frontend "yarn --cwd $PROJECT_ROOT/frontend start" -l $GRADIDO_LOG_PATH/pm2.frontend.$TODAY.log --log-date-format 'YYYY-MM-DD HH:mm:ss.SSS'
pm2 start --name gradido-admin "yarn --cwd $PROJECT_ROOT/admin start" -l $GRADIDO_LOG_PATH/pm2.admin.$TODAY.log --log-date-format 'YYYY-MM-DD HH:mm:ss.SSS'
pm2 save
if [ ! -z $FEDERATION_DHT_TOPIC ]; then
pm2 start --name gradido-dht-node "yarn --cwd $PROJECT_ROOT/dht-node start" -l $GRADIDO_LOG_PATH/pm2.dht-node.$TODAY.log --log-date-format 'YYYY-MM-DD HH:mm:ss.SSS'
pm2 save
else
echo "=====================================================================" >> $UPDATE_HTML
echo "WARNING: FEDERATION_DHT_TOPIC not configured. DHT-Node not started..." >> $UPDATE_HTML
echo "=====================================================================" >> $UPDATE_HTML
fi
# set FEDERATION_PORT from FEDERATION_COMMUNITY_APIS
IFS="," read -a API_ARRAY <<< $FEDERATION_COMMUNITY_APIS
for api in "${API_ARRAY[@]}"
@ -254,13 +270,9 @@ do
pm2 save
done
# let nginx showing gradido
echo 'Configuring nginx to serve gradido again' >> $UPDATE_HTML
ln -s /etc/nginx/sites-available/gradido.conf /etc/nginx/sites-enabled/
rm /etc/nginx/sites-enabled/update-page.conf
ln -sf $SCRIPT_DIR/nginx/sites-available/gradido.conf $SCRIPT_DIR/nginx/sites-enabled/default
sudo /etc/init.d/nginx restart
# keep the update log

View File

@ -0,0 +1,124 @@
# Setup on Hetzner Cloud Server
Suggested minimal Plan: CX41
4x vCPU, 16 GB Ram, 160 GB Disk Space, 20.71 € per month (04.01.2024)
Suggested OS:
Debian 12
For Hetzner Cloud Server a cloud config can be attached, which will be run before first start
https://community.hetzner.com/tutorials/basic-cloud-config/de
https://cloudinit.readthedocs.io/en/latest/reference/examples.html
You can use our [cloudConfig.yaml](./cloudConfig.yaml) but you must insert you own ssh public key,
like this:
```yaml
ssh_authorized_keys:
- ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIAkLGbzbG7KIGfkssKJBkc/0EVAzQ/8vjvVHzNdxhK8J yourname
```
## After Setup Cloud Server with cloudConfig.yaml
### setup your domain pointing on server ip address
### login to your new server as root
```bash
ssh -i /path/to/privKey root@gddhost.tld
```
### Change default shell
```bash
chsh -s /bin/bash
chsh -s /bin/bash gradido
```
### Set password for user `gradido`
```bash
$ passwd gradido
# enter new password twice
```
### Switch to the new user
```bash
su gradido
```
### Test authentication via SSH
If you logout from the server you can test authentication:
```bash
$ ssh -i /path/to/privKey gradido@gddhost.tld
# This should log you in and allow you to use sudo commands, which will require the user's password
```
### Disable password root login via ssh
```bash
sudo cp /etc/ssh/sshd_config /etc/ssh/sshd_config.org
sudo sed -i -e '/^\(#\|\)PermitRootLogin/s/^.*$/PermitRootLogin no/' /etc/ssh/sshd_config
sudo sed -i '$a AllowUsers gradido' /etc/ssh/sshd_config
sudo /etc/init.d/ssh restart
```
### Test SSH Access only, no root ssh access
```bash
$ ssh gradido@gddhost.tld
# Will result in in either a passphrase request for your key or the message 'Permission denied (publickey)'
$ ssh -i /path/to/privKey root@gddhost.tld
# Will result in 'Permission denied (publickey)'
$ ssh -i /path/to/privKey gradido@gddhost.tld
# Will succeed after entering the correct keys passphrase (if any)
```
### Install `Gradido` code
```bash
cd ~
git clone https://github.com/gradido/gradido.git
```
### Adjust the values in `.env`
***!!! Attention !!!***
*Don't forget this step!
All your following installations in `install.sh` will fail!*
*Notes:*
- *`;` cannot be part of any value!*
- *The GitHub secret is created on GitHub in Settings -> Webhooks.*
#### Create `.env` and set values
```bash
cd ~/gradido/deployment/bare_metal
cp .env.dist .env
nano .env
# adjust values accordingly
```
### Run `install.sh`
***!!! Attention !!!***
Don't use this script if you have custom config in /etc/nginx/conf.d, because this script
will remove it and ln ../bare_metal/nginx/conf.d
```bash
cd ~/gradido/deployment/hetzner_cloud
sudo ./install.sh
```
### Make yourself admin
- Create an account on your new gradido instance
- Click the link in the activation email
- go back to your ssh session and copy this command
```bash
sudo mysql -D gradido_community -e "insert into user_roles(user_id, role) values((select id from users order by id desc limit 1), 'ADMIN');"
```
- it will make last registered user admin
- login with you newly created user
- if you has a link to `Admin Area` it worked and you are admin

View File

@ -0,0 +1,47 @@
#cloud-config
users:
- name: gradido
groups: users, admin, sudo
sudo: ALL=(ALL) NOPASSWD:/etc/init.d/nginx start,/etc/init.d/nginx stop,/etc/init.d/nginx restart
shell: /bin/bash
ssh_authorized_keys:
- <public_ssh_key>
packages:
- fail2ban
- python3-systemd
- ufw
- git
- mariadb-server
- nginx
- curl
- build-essential
- gnupg
- certbot
- python3-certbot-nginx
- logrotate
- automysqlbackup
- expect
package_update: true
package_upgrade: true
runcmd:
- printf "[sshd]\nenabled = true\nbanaction = iptables-multiport" > /etc/fail2ban/jail.local
- systemctl enable fail2ban
- ufw allow OpenSSH
- ufw allow http
- ufw allow https
- ufw enable
- sed -i -e '/^\(#\|\)PasswordAuthentication/s/^.*$/PasswordAuthentication no/' /etc/ssh/sshd_config
- sed -i -e '/^\(#\|\)KbdInteractiveAuthentication/s/^.*$/KbdInteractiveAuthentication no/' /etc/ssh/sshd_config
- sed -i -e '/^\(#\|\)ChallengeResponseAuthentication/s/^.*$/ChallengeResponseAuthentication no/' /etc/ssh/sshd_config
- sed -i -e '/^\(#\|\)MaxAuthTries/s/^.*$/MaxAuthTries 3/' /etc/ssh/sshd_config
- sed -i -e '/^\(#\|\)AllowTcpForwarding/s/^.*$/AllowTcpForwarding no/' /etc/ssh/sshd_config
- sed -i -e '/^\(#\|\)X11Forwarding/s/^.*$/X11Forwarding no/' /etc/ssh/sshd_config
- sed -i -e '/^\(#\|\)AllowAgentForwarding/s/^.*$/AllowAgentForwarding no/' /etc/ssh/sshd_config
- sed -i -e '/^\(#\|\)AuthorizedKeysFile/s/^.*$/AuthorizedKeysFile .ssh\/authorized_keys/' /etc/ssh/sshd_config
- sed -i '$a AllowUsers gradido root' /etc/ssh/sshd_config
- reboot

View File

@ -0,0 +1,38 @@
# Edit this file to introduce tasks to be run by cron.
#
# Each task to run has to be defined through a single line
# indicating with different fields when the task will be run
# and what command to run for the task
#
# To define the time you can provide concrete values for
# minute (m), hour (h), day of month (dom), month (mon),
# and day of week (dow) or use '*' in these fields (for 'any').
#
# Notice that tasks will be started based on the cron's system
# daemon's notion of time and timezones.
#
# Output of the crontab jobs (including errors) is sent through
# email to the user the crontab file belongs to (unless redirected).
#
# For example, you can run a backup of all your user accounts
# at 5 a.m every week with:
# 0 5 * * 1 tar -zcf /var/backups/home.tgz /home/
#
# For more information see the manual pages of crontab(5) and cron(8)
#
# m h dom mon dow command
# `yarn` creates output in `/tmp` directory. This output is generated whenever `yarn start` is called.
# This is especially problematic on staging systems where instable versions are automatically deployed which can lead to an ever restarting,
# hence generating a lot of yarn output.
# the following hourly cron clean the /tmp folder
0 * * * * find /tmp -name "yarn--*" -exec rm -r {} \; > /dev/null
# cronjob for a daily db backup at 3:00am
0 3 * * * ~/gradido/deployment/bare_metal/backup.sh
# cronjob for a daily logfile clearance at 3:15
# remove all log files older than 30 days
15 3 * * * ~/gradido/deployment/bare_metal/removeLogFiles.sh

View File

@ -0,0 +1,161 @@
#!/bin/bash
# Note: This is needed - since there is Summer-Time included in the default server Setup - UTC is REQUIRED for production data
timedatectl set-timezone UTC
timedatectl set-ntp on
apt purge ntp
systemctl start systemd-timesyncd
set -o allexport
SCRIPT_PATH=$(realpath ../bare_metal)
SCRIPT_DIR=$(dirname $SCRIPT_PATH)
LOCAL_SCRIPT_PATH=$(realpath $0)
LOCAL_SCRIPT_DIR=$(dirname $LOCAL_SCRIPT_PATH)
PROJECT_ROOT=$SCRIPT_DIR/..
set +o allexport
# If install.sh will be called more than once
# We have to load the backend .env to get DB_USERNAME, DB_PASSWORD AND JWT_SECRET
# and the dht-node .env to get FEDERATION_DHT_SEED
export_var(){
export $1=$(grep -v '^#' $PROJECT_ROOT/backend/.env | grep -e "$1" | sed -e 's/.*=//')
export $1=$(grep -v '^#' $PROJECT_ROOT/dht-node/.env | grep -e "$1" | sed -e 's/.*=//')
}
if [ -f "$PROJECT_ROOT/backend/.env" ]; then
export_var 'DB_USER'
export_var 'DB_PASSWORD'
export_var 'JWT_SECRET'
fi
if [ -f "$PROJECT_ROOT/dht-node/.env" ]; then
export_var 'FEDERATION_DHT_SEED'
fi
# Load .env or .env.dist if not present
# NOTE: all config values will be in process.env when starting
# the services and will therefore take precedence over the .env
if [ -f "$SCRIPT_PATH/.env" ]; then
set -o allexport
source $SCRIPT_PATH/.env
set +o allexport
else
set -o allexport
source $SCRIPT_PATH/.env.dist
set +o allexport
fi
# Configure git
git config pull.ff only
# Secure mysql https://gist.github.com/Mins/4602864
SECURE_MYSQL=$(expect -c "
set timeout 10
spawn mysql_secure_installation
expect \"Enter current password for root (enter for none):\"
send \"\r\"
expect \"Switch to unix_socket authentication:\"
send \"Y\r\"
expect \"Change the root password?\"
send \"n\r\"
expect \"Remove anonymous users?\"
send \"y\r\"
expect \"Disallow root login remotely?\"
send \"y\r\"
expect \"Remove test database and access to it?\"
send \"y\r\"
expect \"Reload privilege tables now?\"
send \"y\r\"
expect eof
")
echo "$SECURE_MYSQL"
# Configure fail2ban, seems to not run out of the box on Debian 12
echo -e "[sshd]\nbackend = systemd" | tee /etc/fail2ban/jail.d/sshd.conf
# enable nginx-limit-req filter to block also user which exceed nginx request limiter
echo -e "[nginx-limit-req]\nenabled = true\nlogpath = $SCRIPT_PATH/log/nginx-error.*.log" | tee /etc/fail2ban/jail.d/nginx-limit-req.conf
# enable nginx bad request filter
echo -e "[nginx-bad-request]\nenabled = true\nlogpath = $SCRIPT_PATH/log/nginx-error.*.log" | tee /etc/fail2ban/jail.d/nginx-bad-request.conf
systemctl restart fail2ban
# Configure nginx
rm /etc/nginx/sites-enabled/default
envsubst "$(env | sed -e 's/=.*//' -e 's/^/\$/g')" < $SCRIPT_PATH/nginx/sites-available/gradido.conf.template > $SCRIPT_PATH/nginx/sites-available/gradido.conf
envsubst "$(env | sed -e 's/=.*//' -e 's/^/\$/g')" < $SCRIPT_PATH/nginx/sites-available/update-page.conf.template > $SCRIPT_PATH/nginx/sites-available/update-page.conf
mkdir $SCRIPT_PATH/nginx/sites-enabled
ln -s $SCRIPT_PATH/nginx/sites-available/update-page.conf $SCRIPT_PATH/nginx/sites-enabled/default
ln -s $SCRIPT_PATH/nginx/sites-enabled/default /etc/nginx/sites-enabled
ln -s $SCRIPT_PATH/nginx/common /etc/nginx/
rmdir /etc/nginx/conf.d
ln -s $SCRIPT_PATH/nginx/conf.d /etc/nginx/
# setup https with certbot
certbot certonly --nginx --non-interactive --agree-tos --domains $COMMUNITY_HOST --email $COMMUNITY_SUPPORT_MAIL
# Install node 16. with nvm, with nodesource is depracted
sudo -u gradido bash -c 'curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.7/install.sh | bash'
# Close and reopen your terminal to start using nvm or run the following to use it now:
sudo -u gradido bash -c 'export NVM_DIR="$HOME/.nvm" && [ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh"'
sudo -u gradido bash -c '. $HOME/.nvm/nvm.sh && nvm install 16' # first installed version will be set to default automatic
# Install yarn
sudo -u gradido bash -c '. $HOME/.nvm/nvm.sh && npm i -g yarn'
# Install pm2
sudo -u gradido bash -c '. $HOME/.nvm/nvm.sh && npm i -g pm2 && pm2 startup'
# Install logrotate
envsubst "$(env | sed -e 's/=.*//' -e 's/^/\$/g')" < $SCRIPT_PATH/logrotate/gradido.conf.template > $SCRIPT_PATH/logrotate/gradido.conf
cp $SCRIPT_PATH/logrotate/gradido.conf /etc/logrotate.d/gradido.conf
# create db user
export DB_USER=gradido
# create a new password only if it not already exist
if [ -z "${DB_PASSWORD}" ]; then
export DB_PASSWORD=$(< /dev/urandom tr -dc _A-Z-a-z-0-9 | head -c${1:-32};echo);
fi
mysql <<EOFMYSQL
CREATE USER IF NOT EXISTS '$DB_USER'@'localhost' IDENTIFIED BY '$DB_PASSWORD';
GRANT ALL PRIVILEGES ON *.* TO '$DB_USER'@'localhost';
FLUSH PRIVILEGES;
EOFMYSQL
# Configure database
envsubst "$(env | sed -e 's/=.*//' -e 's/^/\$/g')" < $PROJECT_ROOT/database/.env.template > $PROJECT_ROOT/database/.env
# Configure backend
export JWT_SECRET=$(< /dev/urandom tr -dc _A-Z-a-z-0-9 | head -c${1:-32};echo);
envsubst "$(env | sed -e 's/=.*//' -e 's/^/\$/g')" < $PROJECT_ROOT/backend/.env.template > $PROJECT_ROOT/backend/.env
# Configure frontend
envsubst "$(env | sed -e 's/=.*//' -e 's/^/\$/g')" < $PROJECT_ROOT/frontend/.env.template > $PROJECT_ROOT/frontend/.env
# Configure admin
envsubst "$(env | sed -e 's/=.*//' -e 's/^/\$/g')" < $PROJECT_ROOT/admin/.env.template > $PROJECT_ROOT/admin/.env
# Configure dht-node
export FEDERATION_DHT_SEED=$(< /dev/urandom tr -dc a-f0-9 | head -c 32;echo);
envsubst "$(env | sed -e 's/=.*//' -e 's/^/\$/g')" < $PROJECT_ROOT/dht-node/.env.template > $PROJECT_ROOT/dht-node/.env
# Configure federation
envsubst "$(env | sed -e 's/=.*//' -e 's/^/\$/g')" < $PROJECT_ROOT/federation/.env.template > $PROJECT_ROOT/federation/.env
# set all created or modified files back to belonging to gradido
chown -R gradido:gradido $PROJECT_ROOT
# create cronjob to delete yarn output in /tmp and for making backups regulary
sudo -u gradido crontab < $LOCAL_SCRIPT_DIR/crontabs.txt
# Start gradido
# Note: on first startup some errors will occur - nothing serious
sudo -u gradido $SCRIPT_PATH/start.sh

View File

@ -15,5 +15,5 @@ TYPEORM_LOGGING_RELATIVE_PATH=typeorm.dht-node.log
FEDERATION_DHT_TOPIC=GRADIDO_HUB
# FEDERATION_DHT_SEED=64ebcb0e3ad547848fef4197c6e2332f
FEDERATION_COMMUNITY_URL=http://localhost
# the api port is the dht baseport, which will be added with the supported api-versions, e.g. 1_0 = 5010
FEDERATION_COMMUNITY_API_PORT=5000
# comma separated values, which apis should be announced
FEDERATION_COMMUNITY_APIS=1_0

View File

@ -1,5 +1,5 @@
# must match the CONFIG_VERSION.EXPECTED definition in scr/config/index.ts
CONFIG_VERSION=v3.2023-04-26
CONFIG_VERSION=$FEDERATION_DHT_CONFIG_VERSION
# Database
DB_HOST=localhost
@ -19,5 +19,7 @@ FEDERATION_DHT_CONFIG_VERSION=$FEDERATION_DHT_CONFIG_VERSION
# on an hash created from this topic
FEDERATION_DHT_TOPIC=$FEDERATION_DHT_TOPIC
FEDERATION_DHT_SEED=$FEDERATION_DHT_SEED
FEDERATION_COMMUNITY_URL=$FEDERATION_COMMUNITY_URL
FEDERATION_COMMUNITY_API_PORT=$FEDERATION_COMMUNITY_API_PORT
# comma separated values, which apis should be announced
FEDERATION_COMMUNITY_APIS=$FEDERATION_COMMUNITY_APIS
COMMUNITY_HOST=$COMMUNITY_HOST
URL_PROTOCOL=$URL_PROTOCOL

View File

@ -7,7 +7,7 @@ module.exports = {
collectCoverageFrom: ['src/**/*.ts', '!**/node_modules/**', '!src/seeds/**', '!build/**'],
coverageThreshold: {
global: {
lines: 83,
lines: 82,
},
},
setupFiles: ['<rootDir>/test/testSetup.ts'],
@ -21,6 +21,11 @@ module.exports = {
process.env.NODE_ENV === 'development'
? '<rootDir>/../database/entity/$1'
: '<rootDir>/../database/build/entity/$1',
'@logging/(.*)':
// eslint-disable-next-line n/no-process-env
process.env.NODE_ENV === 'development'
? '<rootDir>/../database/logging/$1'
: '<rootDir>/../database/build/logging/$1',
'@dbTools/(.*)':
// eslint-disable-next-line n/no-process-env
process.env.NODE_ENV === 'development'

View File

@ -4,46 +4,50 @@ import dotenv from 'dotenv'
dotenv.config()
const constants = {
DB_VERSION: '0079-introduce_gms_registration',
DB_VERSION: '0082-introduce_gms_registration',
LOG4JS_CONFIG: 'log4js-config.json',
// default log level on production should be info
LOG_LEVEL: process.env.LOG_LEVEL || 'info',
LOG_LEVEL: process.env.LOG_LEVEL ?? 'info',
CONFIG_VERSION: {
DEFAULT: 'DEFAULT',
EXPECTED: 'v3.2023-04-26',
EXPECTED: 'v4.2024-01-17',
CURRENT: '',
},
}
const server = {
PRODUCTION: process.env.NODE_ENV === 'production' || false,
PRODUCTION: process.env.NODE_ENV === 'production' ?? false,
}
const database = {
DB_HOST: process.env.DB_HOST || 'localhost',
DB_HOST: process.env.DB_HOST ?? 'localhost',
DB_PORT: process.env.DB_PORT ? parseInt(process.env.DB_PORT) : 3306,
DB_USER: process.env.DB_USER || 'root',
DB_PASSWORD: process.env.DB_PASSWORD || '',
DB_DATABASE: process.env.DB_DATABASE || 'gradido_community',
DB_USER: process.env.DB_USER ?? 'root',
DB_PASSWORD: process.env.DB_PASSWORD ?? '',
DB_DATABASE: process.env.DB_DATABASE ?? 'gradido_community',
TYPEORM_LOGGING_RELATIVE_PATH:
process.env.TYPEORM_LOGGING_RELATIVE_PATH || 'typeorm.dht-node.log',
process.env.TYPEORM_LOGGING_RELATIVE_PATH ?? 'typeorm.dht-node.log',
}
const community = {
COMMUNITY_NAME: process.env.COMMUNITY_NAME || 'Gradido Entwicklung',
COMMUNITY_NAME: process.env.COMMUNITY_NAME ?? 'Gradido Entwicklung',
COMMUNITY_DESCRIPTION:
process.env.COMMUNITY_DESCRIPTION || 'Gradido-Community einer lokalen Entwicklungsumgebung.',
process.env.COMMUNITY_DESCRIPTION ?? 'Gradido-Community einer lokalen Entwicklungsumgebung.',
}
const COMMUNITY_HOST = process.env.COMMUNITY_HOST ?? 'localhost'
const URL_PROTOCOL = process.env.URL_PROTOCOL ?? 'http'
const COMMUNITY_URL = process.env.COMMUNITY_URL ?? `${URL_PROTOCOL}://${COMMUNITY_HOST}`
const federation = {
FEDERATION_DHT_TOPIC: process.env.FEDERATION_DHT_TOPIC || 'GRADIDO_HUB',
FEDERATION_DHT_SEED: process.env.FEDERATION_DHT_SEED || null,
FEDERATION_COMMUNITY_URL: process.env.FEDERATION_COMMUNITY_URL || 'http://localhost',
FEDERATION_COMMUNITY_API_PORT: process.env.FEDERATION_COMMUNITY_API_PORT || '5000',
FEDERATION_DHT_TOPIC: process.env.FEDERATION_DHT_TOPIC ?? 'GRADIDO_HUB',
FEDERATION_DHT_SEED: process.env.FEDERATION_DHT_SEED ?? null,
FEDERATION_COMMUNITY_URL: process.env.FEDERATION_COMMUNITY_URL ?? COMMUNITY_URL,
FEDERATION_COMMUNITY_APIS: process.env.FEDERATION_COMMUNITY_APIS ?? '1_0',
}
// 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 (
![constants.CONFIG_VERSION.EXPECTED, constants.CONFIG_VERSION.DEFAULT].includes(
constants.CONFIG_VERSION.CURRENT,

View File

@ -0,0 +1,5 @@
export enum ApiVersionType {
V1_0 = '1_0',
V1_1 = '1_1', // currently no changes
V2_0 = '2_0', // not exist
}

Some files were not shown because too many files have changed in this diff Show More