From 4255c1fdfa86609dace0f36cb00b10fd22e92bd4 Mon Sep 17 00:00:00 2001 From: einhornimmond Date: Tue, 20 Feb 2024 12:47:40 +0100 Subject: [PATCH] dlt-connector use jwt token for authentication at backend --- backend/src/auth/ADMIN_RIGHTS.ts | 1 + backend/src/auth/DLT_CONNECTOR_RIGHTS.ts | 3 + backend/src/auth/RIGHTS.ts | 1 + backend/src/auth/ROLES.ts | 3 + backend/src/graphql/directive/isAuthorized.ts | 58 +++++++++++-------- backend/src/graphql/enum/RoleNames.ts | 1 + dlt-connector/.env.dist | 5 +- dlt-connector/.env.template | 2 + dlt-connector/package.json | 1 + dlt-connector/src/client/BackendClient.ts | 14 +++++ dlt-connector/src/config/index.ts | 3 +- .../community/HomeCommunity.role.ts | 15 ++++- dlt-connector/yarn.lock | 5 ++ 13 files changed, 84 insertions(+), 28 deletions(-) create mode 100644 backend/src/auth/DLT_CONNECTOR_RIGHTS.ts diff --git a/backend/src/auth/ADMIN_RIGHTS.ts b/backend/src/auth/ADMIN_RIGHTS.ts index 79006a1de..db0b2a4d1 100644 --- a/backend/src/auth/ADMIN_RIGHTS.ts +++ b/backend/src/auth/ADMIN_RIGHTS.ts @@ -6,4 +6,5 @@ export const ADMIN_RIGHTS = [ RIGHTS.UNDELETE_USER, RIGHTS.COMMUNITY_UPDATE, RIGHTS.COMMUNITY_BY_UUID, + RIGHTS.COMMUNITY_BY_IDENTIFIER, ] diff --git a/backend/src/auth/DLT_CONNECTOR_RIGHTS.ts b/backend/src/auth/DLT_CONNECTOR_RIGHTS.ts new file mode 100644 index 000000000..3186082b8 --- /dev/null +++ b/backend/src/auth/DLT_CONNECTOR_RIGHTS.ts @@ -0,0 +1,3 @@ +import { RIGHTS } from './RIGHTS' + +export const DLT_CONNECTOR_RIGHTS = [RIGHTS.COMMUNITY_BY_IDENTIFIER] diff --git a/backend/src/auth/RIGHTS.ts b/backend/src/auth/RIGHTS.ts index c95aa18fd..670302e1d 100644 --- a/backend/src/auth/RIGHTS.ts +++ b/backend/src/auth/RIGHTS.ts @@ -59,5 +59,6 @@ export enum RIGHTS { DELETE_USER = 'DELETE_USER', UNDELETE_USER = 'UNDELETE_USER', COMMUNITY_BY_UUID = 'COMMUNITY_BY_UUID', + COMMUNITY_BY_IDENTIFIER = 'COMMUNITY_BY_IDENTIFIER', COMMUNITY_UPDATE = 'COMMUNITY_UPDATE', } diff --git a/backend/src/auth/ROLES.ts b/backend/src/auth/ROLES.ts index 15ba7b263..58b127626 100644 --- a/backend/src/auth/ROLES.ts +++ b/backend/src/auth/ROLES.ts @@ -1,6 +1,7 @@ import { RoleNames } from '@/graphql/enum/RoleNames' import { ADMIN_RIGHTS } from './ADMIN_RIGHTS' +import { DLT_CONNECTOR_RIGHTS } from './DLT_CONNECTOR_RIGHTS' import { INALIENABLE_RIGHTS } from './INALIENABLE_RIGHTS' import { MODERATOR_RIGHTS } from './MODERATOR_RIGHTS' import { Role } from './Role' @@ -20,5 +21,7 @@ export const ROLE_ADMIN = new Role(RoleNames.ADMIN, [ ...ADMIN_RIGHTS, ]) +export const ROLE_DLT_CONNECTOR = new Role(RoleNames.DLT_CONNECTOR_ROLE, DLT_CONNECTOR_RIGHTS) + // TODO from database export const ROLES = [ROLE_UNAUTHORIZED, ROLE_USER, ROLE_MODERATOR, ROLE_ADMIN] diff --git a/backend/src/graphql/directive/isAuthorized.ts b/backend/src/graphql/directive/isAuthorized.ts index 59309c91e..f3d03a539 100644 --- a/backend/src/graphql/directive/isAuthorized.ts +++ b/backend/src/graphql/directive/isAuthorized.ts @@ -6,7 +6,13 @@ import { RoleNames } from '@enum/RoleNames' import { INALIENABLE_RIGHTS } from '@/auth/INALIENABLE_RIGHTS' import { decode, encode } from '@/auth/JWT' import { RIGHTS } from '@/auth/RIGHTS' -import { ROLE_UNAUTHORIZED, ROLE_USER, ROLE_ADMIN, ROLE_MODERATOR } from '@/auth/ROLES' +import { + ROLE_UNAUTHORIZED, + ROLE_USER, + ROLE_ADMIN, + ROLE_MODERATOR, + ROLE_DLT_CONNECTOR, +} from '@/auth/ROLES' import { Context } from '@/server/context' import { LogError } from '@/server/LogError' @@ -30,31 +36,35 @@ export const isAuthorized: AuthChecker = async ({ context }, rights) => // Set context gradidoID context.gradidoID = decoded.gradidoID - // TODO - load from database dynamically & admin - maybe encode this in the token to prevent many database requests - // TODO this implementation is bullshit - two database queries cause our user identifiers are not aligned and vary between email, id and pubKey - try { - const user = await User.findOneOrFail({ - where: { gradidoID: decoded.gradidoID }, - withDeleted: true, - relations: ['emailContact', 'userRoles'], - }) - context.user = user - context.role = ROLE_USER - if (user.userRoles?.length > 0) { - switch (user.userRoles[0].role) { - case RoleNames.ADMIN: - context.role = ROLE_ADMIN - break - case RoleNames.MODERATOR: - context.role = ROLE_MODERATOR - break - default: - context.role = ROLE_USER + if (context.gradidoID === 'dlt-connector') { + context.role = ROLE_DLT_CONNECTOR + } else { + // TODO - load from database dynamically & admin - maybe encode this in the token to prevent many database requests + // TODO this implementation is bullshit - two database queries cause our user identifiers are not aligned and vary between email, id and pubKey + try { + const user = await User.findOneOrFail({ + where: { gradidoID: decoded.gradidoID }, + withDeleted: true, + relations: ['emailContact', 'userRoles'], + }) + context.user = user + context.role = ROLE_USER + if (user.userRoles?.length > 0) { + switch (user.userRoles[0].role) { + case RoleNames.ADMIN: + context.role = ROLE_ADMIN + break + case RoleNames.MODERATOR: + context.role = ROLE_MODERATOR + break + default: + context.role = ROLE_USER + } } + } catch { + // in case the database query fails (user deleted) + throw new LogError('401 Unauthorized') } - } catch { - // in case the database query fails (user deleted) - throw new LogError('401 Unauthorized') } // check for correct rights diff --git a/backend/src/graphql/enum/RoleNames.ts b/backend/src/graphql/enum/RoleNames.ts index c4a9b25cc..431154524 100644 --- a/backend/src/graphql/enum/RoleNames.ts +++ b/backend/src/graphql/enum/RoleNames.ts @@ -5,6 +5,7 @@ export enum RoleNames { USER = 'USER', MODERATOR = 'MODERATOR', ADMIN = 'ADMIN', + DLT_CONNECTOR_ROLE = 'DLT_CONNECTOR_ROLE', } registerEnumType(RoleNames, { diff --git a/dlt-connector/.env.dist b/dlt-connector/.env.dist index 6f2511c7e..50e9fe8e1 100644 --- a/dlt-connector/.env.dist +++ b/dlt-connector/.env.dist @@ -1,4 +1,4 @@ -CONFIG_VERSION=v4.2023-09-12 +CONFIG_VERSION=v6.2024-02-20 # SET LOG LEVEL AS NEEDED IN YOUR .ENV # POSSIBLE VALUES: all | trace | debug | info | warn | error | fatal @@ -22,4 +22,5 @@ TYPEORM_LOGGING_RELATIVE_PATH=typeorm.backend.log DLT_CONNECTOR_PORT=6010 # Route to Backend -BACKEND_SERVER_URL=http://localhost:4000 \ No newline at end of file +BACKEND_SERVER_URL=http://localhost:4000 +JWT_SECRET=secret123 \ No newline at end of file diff --git a/dlt-connector/.env.template b/dlt-connector/.env.template index a7cbcd6ba..2e123ca81 100644 --- a/dlt-connector/.env.template +++ b/dlt-connector/.env.template @@ -1,5 +1,7 @@ CONFIG_VERSION=$DLT_CONNECTOR_CONFIG_VERSION +JWT_SECRET=$JWT_SECRET + #IOTA IOTA_API_URL=$IOTA_API_URL IOTA_COMMUNITY_ALIAS=$IOTA_COMMUNITY_ALIAS diff --git a/dlt-connector/package.json b/dlt-connector/package.json index 146a096e1..b491a7c22 100644 --- a/dlt-connector/package.json +++ b/dlt-connector/package.json @@ -34,6 +34,7 @@ "graphql-request": "^6.1.0", "graphql-scalars": "^1.22.2", "helmet": "^7.1.0", + "jose": "^5.2.2", "log4js": "^6.7.1", "nodemon": "^2.0.20", "protobufjs": "^7.2.5", diff --git a/dlt-connector/src/client/BackendClient.ts b/dlt-connector/src/client/BackendClient.ts index 2228fdaf8..0a460d116 100644 --- a/dlt-connector/src/client/BackendClient.ts +++ b/dlt-connector/src/client/BackendClient.ts @@ -6,6 +6,7 @@ import { CONFIG } from '@/config' import { CommunityDraft } from '@/graphql/input/CommunityDraft' import { logger } from '@/logging/logger' import { LogError } from '@/server/LogError' +import { SignJWT } from 'jose' const communityByForeign = gql` query ($foreign: Boolean) { @@ -73,6 +74,7 @@ export class BackendClient { public async homeCommunityUUid(): Promise { logger.info('check home community on backend') + this.client.setHeader('token', await this.createJWTToken()) const { data, errors } = await this.client.rawRequest(communityByForeign, { foreign: false, }) @@ -85,4 +87,16 @@ export class BackendClient { communityDraft.createdAt = data.community.creationDate return communityDraft } + + private async createJWTToken(): Promise { + const secret = new TextEncoder().encode(CONFIG.JWT_SECRET) + const token = await new SignJWT({ gradidoID: 'dlt-connector', 'urn:gradido:claim': true }) + .setProtectedHeader({ alg: 'HS256' }) + .setIssuedAt() + .setIssuer('urn:gradido:issuer') + .setAudience('urn:gradido:audience') + .setExpirationTime('1m') + .sign(secret) + return token + } } diff --git a/dlt-connector/src/config/index.ts b/dlt-connector/src/config/index.ts index d39623c04..0a37d6413 100644 --- a/dlt-connector/src/config/index.ts +++ b/dlt-connector/src/config/index.ts @@ -9,13 +9,14 @@ const constants = { LOG_LEVEL: process.env.LOG_LEVEL ?? 'info', CONFIG_VERSION: { DEFAULT: 'DEFAULT', - EXPECTED: 'v5.2024-02-24', + EXPECTED: 'v6.2024-02-20', CURRENT: '', }, } const server = { PRODUCTION: process.env.NODE_ENV === 'production' ?? false, + JWT_SECRET: process.env.JWT_SECRET ?? 'secret123', } const database = { diff --git a/dlt-connector/src/interactions/backendToDb/community/HomeCommunity.role.ts b/dlt-connector/src/interactions/backendToDb/community/HomeCommunity.role.ts index 7a4798368..bdee42ae4 100644 --- a/dlt-connector/src/interactions/backendToDb/community/HomeCommunity.role.ts +++ b/dlt-connector/src/interactions/backendToDb/community/HomeCommunity.role.ts @@ -10,6 +10,7 @@ import { CommunityDraft } from '@/graphql/input/CommunityDraft' import { TransactionError } from '@/graphql/model/TransactionError' import { CommunityLoggingView } from '@/logging/CommunityLogging.view' import { logger } from '@/logging/logger' +import { LogError } from '@/server/LogError' import { getDataSource } from '@/typeorm/DataSource' import { CreateTransactionRecipeContext } from '../transaction/CreateTransationRecipe.context' @@ -22,7 +23,19 @@ export class HomeCommunityRole extends CommunityRole { public async create(communityDraft: CommunityDraft, topic: string): Promise { super.create(communityDraft, topic) // generate key pair for signing transactions and deriving all keys for community - const keyPair = new KeyPair(new Mnemonic(CONFIG.IOTA_HOME_COMMUNITY_SEED ?? undefined)) + let mnemonic: Mnemonic + try { + mnemonic = new Mnemonic(CONFIG.IOTA_HOME_COMMUNITY_SEED ?? undefined) + } catch (e) { + throw new LogError( + 'error creating mnemonic for home community, please fill IOTA_HOME_COMMUNITY_SEED in .env', + { + IOTA_HOME_COMMUNITY_SEED: CONFIG.IOTA_HOME_COMMUNITY_SEED, + error: e, + }, + ) + } + const keyPair = new KeyPair(mnemonic) keyPair.fillInCommunityKeys(this.self) // create auf account and gmw account diff --git a/dlt-connector/yarn.lock b/dlt-connector/yarn.lock index 7fbe1646f..81e904cf3 100644 --- a/dlt-connector/yarn.lock +++ b/dlt-connector/yarn.lock @@ -4281,6 +4281,11 @@ jiti@^1.19.3: resolved "https://registry.yarnpkg.com/jiti/-/jiti-1.20.0.tgz#2d823b5852ee8963585c8dd8b7992ffc1ae83b42" integrity sha512-3TV69ZbrvV6U5DfQimop50jE9Dl6J8O1ja1dvBbMba/sZ3YBEQqJ2VZRoQPVnhlzjNtU1vaXRZVrVjU4qtm8yA== +jose@^5.2.2: + version "5.2.2" + resolved "https://registry.yarnpkg.com/jose/-/jose-5.2.2.tgz#b91170e9ba6dbe609b0c0a86568f9a1fbe4335c0" + integrity sha512-/WByRr4jDcsKlvMd1dRJnPfS1GVO3WuKyaurJ/vvXcOaUQO8rnNObCQMlv/5uCceVQIq5Q4WLF44ohsdiTohdg== + js-tokens@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499"