From 2b8b086f6c94110e45680e6d95ebb614707e4719 Mon Sep 17 00:00:00 2001 From: Claus-Peter Huebner Date: Fri, 15 Mar 2024 01:16:33 +0100 Subject: [PATCH] first draft of gms authentication --- backend/src/apis/gms/GmsClient.ts | 41 +++++++++++++++++++ backend/src/auth/RIGHTS.ts | 1 + backend/src/auth/USER_RIGHTS.ts | 1 + backend/src/config/index.ts | 4 +- backend/src/graphql/resolver/UserResolver.ts | 16 ++++++++ .../util/authenticateGmsUserSearch.ts | 9 ++++ backend/src/server/createServer.ts | 5 +++ backend/src/webhook/gms.ts | 32 +++++++++++++++ 8 files changed, 108 insertions(+), 1 deletion(-) create mode 100644 backend/src/graphql/resolver/util/authenticateGmsUserSearch.ts create mode 100644 backend/src/webhook/gms.ts diff --git a/backend/src/apis/gms/GmsClient.ts b/backend/src/apis/gms/GmsClient.ts index 46fa64006..44980fec2 100644 --- a/backend/src/apis/gms/GmsClient.ts +++ b/backend/src/apis/gms/GmsClient.ts @@ -144,3 +144,44 @@ export async function createGmsUser(apiKey: string, user: GmsUser): Promise { + const baseUrl = CONFIG.GMS_URL.endsWith('/') ? CONFIG.GMS_URL : CONFIG.GMS_URL.concat('/') + const service = 'verify-auth-token' + const config = { + headers: { + accept: 'application/json', + language: 'en', + timezone: 'UTC', + connection: 'keep-alive', + // authorization: apiKey, + }, + } + const data = { + uuid: communityUuid, + token: token, + } + try { + const result = await axios.get(baseUrl.concat(service), data, config) + logger.debug('GET-Response of verify-auth-token:', result) + if (result.status !== 200) { + throw new LogError( + 'HTTP Status Error in verify-auth-token:', + result.status, + result.statusText, + ) + } + logger.debug('responseData:', result.data.responseData) + // eslint-disable-next-line @typescript-eslint/no-unsafe-call + const playgroundUri: string = JSON.parse(result.data.responseData.data) + logger.debug('verifyAuthToken=', playgroundUri) + return playgroundUri + } catch (error: any) { + logger.error('Error in verifyAuthToken:', error) + throw new LogError(error.message) + } +} diff --git a/backend/src/auth/RIGHTS.ts b/backend/src/auth/RIGHTS.ts index c8f02976b..c7a23c13b 100644 --- a/backend/src/auth/RIGHTS.ts +++ b/backend/src/auth/RIGHTS.ts @@ -37,6 +37,7 @@ export enum RIGHTS { LIST_ALL_CONTRIBUTION_MESSAGES = 'LIST_ALL_CONTRIBUTION_MESSAGES', OPEN_CREATIONS = 'OPEN_CREATIONS', USER = 'USER', + GMS_USER_PLAYGROUND = 'GMS_USER_PLAYGROUND', // Moderator SEARCH_USERS = 'SEARCH_USERS', ADMIN_CREATE_CONTRIBUTION = 'ADMIN_CREATE_CONTRIBUTION', diff --git a/backend/src/auth/USER_RIGHTS.ts b/backend/src/auth/USER_RIGHTS.ts index 9bf9fee93..0c56b0d02 100644 --- a/backend/src/auth/USER_RIGHTS.ts +++ b/backend/src/auth/USER_RIGHTS.ts @@ -29,4 +29,5 @@ export const USER_RIGHTS = [ RIGHTS.LIST_ALL_CONTRIBUTION_MESSAGES, RIGHTS.OPEN_CREATIONS, RIGHTS.USER, + RIGHTS.GMS_USER_PLAYGROUND, ] diff --git a/backend/src/config/index.ts b/backend/src/config/index.ts index 0dbc0ea5a..43e97b920 100644 --- a/backend/src/config/index.ts +++ b/backend/src/config/index.ts @@ -19,7 +19,7 @@ const constants = { LOG_LEVEL: process.env.LOG_LEVEL ?? 'info', CONFIG_VERSION: { DEFAULT: 'DEFAULT', - EXPECTED: 'v21.2024-01-06', + EXPECTED: 'v22.2024-03-14', CURRENT: '', }, } @@ -145,6 +145,8 @@ const gms = { GMS_ACTIVE: process.env.GMS_ACTIVE === 'true' || false, // koordinates of Illuminz-instance of GMS GMS_URL: process.env.GMS_HOST ?? 'http://localhost:4044/', + // used as secret postfix attached at the gms community-auth-url endpoint ('/hook/gms/' + 'secret') + GMS_WEBHOOK_SECRET: process.env.GMS_WEBHOOK_SECRET ?? 'secret', } export const CONFIG = { diff --git a/backend/src/graphql/resolver/UserResolver.ts b/backend/src/graphql/resolver/UserResolver.ts index 56bb5d0fc..791ed19dc 100644 --- a/backend/src/graphql/resolver/UserResolver.ts +++ b/backend/src/graphql/resolver/UserResolver.ts @@ -75,6 +75,7 @@ import { Location2Point } from './util/Location2Point' import { setUserRole, deleteUserRole } from './util/modifyUserRole' import { sendUserToGms } from './util/sendUserToGms' import { validateAlias } from './util/validateAlias' +import { authenticateGmsUserSearch } from './util/authenticateGmsUserSearch' const LANGUAGES = ['de', 'en', 'es', 'fr', 'nl'] const DEFAULT_LANGUAGE = 'de' @@ -655,6 +656,21 @@ export class UserResolver { return elopageBuys } + @Authorized([RIGHTS.GMS_USER_PLAYGROUND]) + @Query(() => String) + async authUserForGmsUserSearch(@Ctx() context: Context): Promise { + logger.info(`authUserForGmsUserSearch()...`) + const dbUser = getUser(context) + let gmsPlaygroundUri: string + if (context.token) { + gmsPlaygroundUri = await authenticateGmsUserSearch(context.token, dbUser) + logger.debug('authUserForGmsUserSearch=', gmsPlaygroundUri) + } else { + throw new LogError('authUserForGmsUserSearch without token') + } + return gmsPlaygroundUri + } + @Authorized([RIGHTS.SEARCH_ADMIN_USERS]) @Query(() => SearchAdminUsersResult) async searchAdminUsers( diff --git a/backend/src/graphql/resolver/util/authenticateGmsUserSearch.ts b/backend/src/graphql/resolver/util/authenticateGmsUserSearch.ts new file mode 100644 index 000000000..27bd98a91 --- /dev/null +++ b/backend/src/graphql/resolver/util/authenticateGmsUserSearch.ts @@ -0,0 +1,9 @@ +import { User as DbUser } from '@entity/User' + +import { verifyAuthToken } from '@/apis/gms/GmsClient' + +export async function authenticateGmsUserSearch(token: string, dbUser: DbUser): Promise { + const gmsPlaygroundUri = await verifyAuthToken(dbUser.communityUuid, token) + + return gmsPlaygroundUri +} diff --git a/backend/src/server/createServer.ts b/backend/src/server/createServer.ts index 3f02b0afc..01c106737 100644 --- a/backend/src/server/createServer.ts +++ b/backend/src/server/createServer.ts @@ -13,6 +13,7 @@ import { schema } from '@/graphql/schema' import { Connection } from '@/typeorm/connection' import { checkDBVersion } from '@/typeorm/DBVersion' import { elopageWebhook } from '@/webhook/elopage' +import { gmsWebhook } from '@/webhook/gms' import { context as serverContext } from './context' import { cors } from './cors' @@ -94,6 +95,10 @@ export const createServer = async ( // eslint-disable-next-line @typescript-eslint/no-misused-promises app.post('/hook/elopage/' + CONFIG.WEBHOOK_ELOPAGE_SECRET, elopageWebhook) + // GMS Webhook + // eslint-disable-next-line @typescript-eslint/no-misused-promises + app.post('/hook/gms/' + CONFIG.GMS_WEBHOOK_SECRET, gmsWebhook) + // Apollo Server const apollo = new ApolloServer({ schema: await schema(), diff --git a/backend/src/webhook/gms.ts b/backend/src/webhook/gms.ts new file mode 100644 index 000000000..b6d92fa25 --- /dev/null +++ b/backend/src/webhook/gms.ts @@ -0,0 +1,32 @@ +/* eslint-disable @typescript-eslint/no-unsafe-call */ +/* eslint-disable @typescript-eslint/no-unsafe-member-access */ +/* eslint-disable @typescript-eslint/no-unsafe-assignment */ +/* eslint-disable @typescript-eslint/no-explicit-any */ +/* eslint-disable @typescript-eslint/no-unsafe-argument */ +import { User as DbUser } from '@entity/User' + +import { decode } from '@/auth/JWT' +import { backendLogger as logger } from '@/server/logger' + +export const gmsWebhook = async (req: any, res: any): Promise => { + logger.info('GMS Hook received', req.body) + const { token } = req.body + + if (!token) { + logger.warn('gmsWebhook: missing token') + res.status(400).json({ message: 'false' }) + return + } + const payload = await decode(token) + if (payload) { + const user = await DbUser.findOne({ where: { gradidoID: payload.gradidoID } }) + if (!user) { + logger.warn('gmsWebhook: missing user') + res.status(400).json({ message: 'false' }) + return + } + logger.info('gmsWebhook: authenticate user=', user) + } + logger.info('gmsWebhook: authentication successful') + res.status(200).json({ message: 'true' }) +}