From 37562c0dcd7ad2b8544e5d513d13764d4407745a Mon Sep 17 00:00:00 2001 From: Frank Date: Mon, 5 Aug 2024 18:21:50 +0200 Subject: [PATCH] feat: added protected mutations --- .../modules/document-drive/drives-resolver.ts | 50 ++-- api/src/modules/document-drive/utils.ts | 15 + .../modules/real-world-assets/resolvers.ts | 279 +++++++++--------- 3 files changed, 192 insertions(+), 152 deletions(-) create mode 100644 api/src/modules/document-drive/utils.ts diff --git a/api/src/modules/document-drive/drives-resolver.ts b/api/src/modules/document-drive/drives-resolver.ts index 01caf3a3..70967878 100644 --- a/api/src/modules/document-drive/drives-resolver.ts +++ b/api/src/modules/document-drive/drives-resolver.ts @@ -11,7 +11,7 @@ import { DocumentDriveStateObject } from './drive-resolver'; import { Context } from '../../graphql/server/drive/context'; import logger from '../../logger'; import DocumentDriveError from '../../errors/DocumentDriveError'; -import { UpdateStatus } from "document-drive"; +import { checkUserIsAdmin } from './utils'; export const DocumentDriveLocalState = objectType({ name: 'DocumentDriveLocalState', @@ -39,10 +39,10 @@ export const DocumentDriveStateInput = inputObjectType({ }); export const SetDriveIconInput = inputObjectType({ - name: "SetDriveIconInput", + name: 'SetDriveIconInput', definition(t) { - t.nonNull.string("icon") - } + t.nonNull.string('icon'); + }, }); export const getDrives = queryField('drives', { @@ -61,7 +61,7 @@ export const getDrives = queryField('drives', { export const getDriveBySlug = queryField('driveIdBySlug', { type: 'String', args: { - slug: stringArg() + slug: stringArg(), }, resolve: async (_parent, args, ctx: Context) => { try { @@ -78,7 +78,7 @@ const addDriveResponseDefinition = objectType({ name: 'AddDriveResponse', definition(t) { t.nonNull.field('global', { - type: DocumentDriveStateObject + type: DocumentDriveStateObject, }); t.nonNull.field('local', { type: DocumentDriveLocalState, @@ -86,7 +86,7 @@ const addDriveResponseDefinition = objectType({ }, }); - +// protected routes export const addDrive = mutationField('addDrive', { type: addDriveResponseDefinition, args: { @@ -94,14 +94,21 @@ export const addDrive = mutationField('addDrive', { local: nonNull(DocumentDriveLocalStateInput), }, resolve: async (_parent, { global, local }, ctx: Context) => { + await checkUserIsAdmin(ctx); try { const drive = await ctx.prisma.document.addDrive({ - global: { id: global.id, name: global.name, icon: global.icon ?? null, slug: global.slug ?? null }, - local: { availableOffline: local.availableOffline, sharingType: local.sharingType ?? null, listeners: [], triggers: [] }, + global: { + id: global.id, name: global.name, icon: global.icon ?? null, slug: global.slug ?? null, + }, + local: { + availableOffline: local.availableOffline, sharingType: local.sharingType ?? null, listeners: [], triggers: [], + }, }); return drive.state; } catch (e: any) { - throw new DocumentDriveError({ code: 500, message: e.message ?? "Failed to add drive", logging: true, context: e }) + throw new DocumentDriveError({ + code: 500, message: e.message ?? 'Failed to add drive', logging: true, context: e, + }); } }, }); @@ -112,10 +119,13 @@ export const deleteDrive = mutationField('deleteDrive', { id: nonNull('String'), }, resolve: async (_parent, { id }, ctx: Context) => { + await checkUserIsAdmin(ctx); try { await ctx.prisma.document.deleteDrive(id); } catch (e: any) { - throw new DocumentDriveError({ code: 500, message: e.message ?? "Failed to delete drive", logging: true, context: e }) + throw new DocumentDriveError({ + code: 500, message: e.message ?? 'Failed to delete drive', logging: true, context: e, + }); } return true; @@ -129,17 +139,18 @@ export const setDriveIcon = mutationField('setDriveIcon', { icon: nonNull('String'), }, resolve: async (_parent, { id, icon }, ctx: Context) => { + await checkUserIsAdmin(ctx); const result = await ctx.prisma.document.setDriveIcon(id, icon); - if (result.status !== "SUCCESS") { + if (result.status !== 'SUCCESS') { if (result.error) { const { message } = result.error; - throw new DocumentDriveError({ code: 500, message, logging: true }) + throw new DocumentDriveError({ code: 500, message, logging: true }); } - throw new DocumentDriveError({ code: 500, message: "Failed to set drive icon", logging: true }) + throw new DocumentDriveError({ code: 500, message: 'Failed to set drive icon', logging: true }); } return true; - } + }, }); export const setDriveName = mutationField('setDriveName', { @@ -149,15 +160,16 @@ export const setDriveName = mutationField('setDriveName', { name: nonNull('String'), }, resolve: async (_parent, { id, name }, ctx: Context) => { + await checkUserIsAdmin(ctx); const result = await ctx.prisma.document.setDriveName(id, name); - if (result.status !== "SUCCESS") { + if (result.status !== 'SUCCESS') { if (result.error) { const { message } = result.error; - throw new DocumentDriveError({ code: 500, message, logging: true }) + throw new DocumentDriveError({ code: 500, message, logging: true }); } - throw new DocumentDriveError({ code: 500, message: "Failed to set drive icon", logging: true }) + throw new DocumentDriveError({ code: 500, message: 'Failed to set drive icon', logging: true }); } return true; - } + }, }); diff --git a/api/src/modules/document-drive/utils.ts b/api/src/modules/document-drive/utils.ts new file mode 100644 index 00000000..13b6bc0e --- /dev/null +++ b/api/src/modules/document-drive/utils.ts @@ -0,0 +1,15 @@ +import { GraphQLError } from 'graphql'; +import { Context } from '../../graphql/server'; + +export function isAdmin(user: string) { + const { ADMIN_USERS } = process.env; + return ADMIN_USERS?.split(',').includes(user); +} + +export async function checkUserIsAdmin(ctx: Context) { + const { revokedAt, createdBy } = await ctx.getSession(); + console.log(createdBy, revokedAt) + if (revokedAt || !createdBy || !isAdmin(createdBy)) { + throw new GraphQLError('Access denied'); + } +} diff --git a/api/src/modules/real-world-assets/resolvers.ts b/api/src/modules/real-world-assets/resolvers.ts index 225672f2..382a1de7 100644 --- a/api/src/modules/real-world-assets/resolvers.ts +++ b/api/src/modules/real-world-assets/resolvers.ts @@ -1,166 +1,183 @@ -import { arg, enumType, interfaceType, list, objectType, queryField, stringArg, unionType } from 'nexus'; +import { + arg, enumType, interfaceType, list, objectType, queryField, stringArg, unionType, +} from 'nexus'; +import { string } from 'zod'; import { GQLDateBase } from '../system'; import { documentModelInterface } from '../document'; import { getChildLogger } from '../../logger'; -import { string } from 'zod'; const logger = getChildLogger({ msgPrefix: 'REAL WORLD ASSETS RESOLVER' }); export const Account = objectType({ - name: "Account", + name: 'Account', definition(t) { - t.nonNull.id("id") - t.nonNull.string("reference") - t.string("label") - } -}) + t.nonNull.id('id'); + t.nonNull.string('reference'); + t.string('label'); + }, +}); export const BaseTransaction = objectType({ - name: "BaseTransaction", + name: 'BaseTransaction', definition(t) { - t.nonNull.id("id") - t.nonNull.field("assetType", { type: AssetType }) - t.nonNull.id("assetId") - t.nonNull.float("amount") - t.nonNull.field("entryTime", { type: GQLDateBase }) - t.field("tradeTime", { type: GQLDateBase }) - t.field("settlementTime", { type: GQLDateBase }) - t.id("accountId") - t.id("counterPartyAccountId") - } -}) + t.nonNull.id('id'); + t.nonNull.field('assetType', { type: AssetType }); + t.nonNull.id('assetId'); + t.nonNull.float('amount'); + t.nonNull.field('entryTime', { type: GQLDateBase }); + t.field('tradeTime', { type: GQLDateBase }); + t.field('settlementTime', { type: GQLDateBase }); + t.id('accountId'); + t.id('counterPartyAccountId'); + }, +}); export const Cash = objectType({ - name: "Cash", + name: 'Cash', definition(t) { - t.nonNull.id("id") - t.id("spvId") - t.field("spv", { type: Spv }) - t.nonNull.string("currency") - t.nonNull.float("balance") - } -}) -export const FixedIncome = objectType({ - name: "FixedIncome", - definition(t) { - t.id("id") - t.nonNull.field("type", { type: AssetType }) - t.id("fixedIncomeTypeId") - t.field("fixedIncomeType", { type: FixedIncomeType }) - t.string("name") - t.id("spvId") - t.field("spv", { type: Spv }) - t.field("purchaseDate", { type: GQLDateBase }) - t.float("notional") - t.float("assetProceeds") - t.float("purchaseProceeds") - t.float("salesProceeds") - t.float("purchasePrice") - t.float("totalDiscount") - t.float("realizedSurplus") - t.field("maturity", { type: GQLDateBase }) - t.string("ISIN") - t.string("CUSIP") - t.float("coupon") - } -}) + t.nonNull.id('id'); + t.id('spvId'); + t.field('spv', { type: Spv }); + t.nonNull.string('currency'); + t.nonNull.float('balance'); + }, +}); + export const FixedIncomeType = objectType({ - name: "FixedIncomeType", + name: 'FixedIncomeType', + definition(t) { + t.nonNull.id('id'); + t.nonNull.string('name'); + }, +}); + +export const AssetType = enumType({ + name: 'AssetType', + members: ['Cash', 'FixedIncome'], +}); + +export const Spv = objectType({ + name: 'Spv', definition(t) { - t.nonNull.id("id") - t.nonNull.string("name") - } -}) + t.nonNull.id('id'); + t.nonNull.string('name'); + }, +}); + +export const FixedIncome = objectType({ + name: 'FixedIncome', + definition(t) { + t.id('id'); + t.nonNull.field('type', { type: AssetType }); + t.id('fixedIncomeTypeId'); + t.field('fixedIncomeType', { type: FixedIncomeType }); + t.string('name'); + t.id('spvId'); + t.field('spv', { type: Spv }); + t.field('purchaseDate', { type: GQLDateBase }); + t.float('notional'); + t.float('assetProceeds'); + t.float('purchaseProceeds'); + t.float('salesProceeds'); + t.float('purchasePrice'); + t.float('totalDiscount'); + t.float('realizedSurplus'); + t.field('maturity', { type: GQLDateBase }); + t.string('ISIN'); + t.string('CUSIP'); + t.float('coupon'); + }, +}); + export const GroupTransaction = objectType({ - name: "GroupTransaction", + name: 'GroupTransaction', definition(t) { - t.nonNull.id("id") - t.nonNull.field("type", { type: GroupTransactionType }) - t.nonNull.field("entryTime", { type: GQLDateBase }) - t.nonNull.float("cashBalanceChange") - t.float("unitPrice") - t.list.nonNull.field("fees", { type: TransactionFee }) - t.nonNull.field("cashTransaction", { type: BaseTransaction }) - t.field("fixedIncomeTransaction", { type: BaseTransaction }) - t.id("serviceProviderFeeTypeId") - t.string("txRef") - } -}) + t.nonNull.id('id'); + t.nonNull.field('type', { type: GroupTransactionType }); + t.nonNull.field('entryTime', { type: GQLDateBase }); + t.nonNull.float('cashBalanceChange'); + t.float('unitPrice'); + t.list.nonNull.field('fees', { type: TransactionFee }); + t.nonNull.field('cashTransaction', { type: BaseTransaction }); + t.field('fixedIncomeTransaction', { type: BaseTransaction }); + t.id('serviceProviderFeeTypeId'); + t.string('txRef'); + }, +}); + export const RealWorldAssetsStateInterface = interfaceType({ - name: "IRealWorldAssetsState", + name: 'IRealWorldAssetsState', definition(t) { - t.nonNull.list.nonNull.field("accounts", { type: Account }) - t.nonNull.id("principalLenderAccountId") - t.nonNull.list.nonNull.field("spvs", { type: Spv }) - t.nonNull.list.nonNull.field("serviceProviderFeeTypes", { type: ServiceProviderFeeType }) - t.nonNull.list.nonNull.field("fixedIncomeTypes", { type: FixedIncomeType }) - t.nonNull.list.nonNull.field("portfolio", { type: Asset }) - t.nonNull.list.nonNull.field("transactions", { type: GroupTransaction }) - }, resolveType: () => "RealWorldAssetsState" -}) + t.nonNull.list.nonNull.field('accounts', { type: Account }); + t.nonNull.id('principalLenderAccountId'); + t.nonNull.list.nonNull.field('spvs', { type: Spv }); + t.nonNull.list.nonNull.field('serviceProviderFeeTypes', { type: ServiceProviderFeeType }); + t.nonNull.list.nonNull.field('fixedIncomeTypes', { type: FixedIncomeType }); + t.nonNull.list.nonNull.field('portfolio', { type: Asset }); + t.nonNull.list.nonNull.field('transactions', { type: GroupTransaction }); + }, + resolveType: () => 'RealWorldAssetsState', +}); export const RealWorldAssetsState = objectType({ - name: "RealWorldAssetsState", + name: 'RealWorldAssetsState', definition(t) { - t.implements(RealWorldAssetsStateInterface) - } -}) + t.implements(RealWorldAssetsStateInterface); + }, +}); export const ServiceProviderFeeType = objectType({ - name: "ServiceProviderFeeType", - definition(t) { - t.nonNull.id("id") - t.nonNull.string("name") - t.nonNull.string("feeType") - t.nonNull.id("accountId") - } -}) -export const Spv = objectType({ - name: "Spv", + name: 'ServiceProviderFeeType', definition(t) { - t.nonNull.id("id") - t.nonNull.string("name") - } -}) + t.nonNull.id('id'); + t.nonNull.string('name'); + t.nonNull.string('feeType'); + t.nonNull.id('accountId'); + }, +}); + export const TransactionFee = objectType({ - name: "TransactionFee", + name: 'TransactionFee', definition(t) { - t.nonNull.id("id") - t.nonNull.id("serviceProviderFeeTypeId") - t.nonNull.float("amount") - } -}) - + t.nonNull.id('id'); + t.nonNull.id('serviceProviderFeeTypeId'); + t.nonNull.float('amount'); + }, +}); export const Asset = unionType({ - name: "Asset", + name: 'Asset', definition(t) { - t.members(FixedIncome, Cash) + t.members(FixedIncome, Cash); }, - resolveType: (asset) => { + resolveType: (asset: any) => { if (asset.name) { - return "FixedIncome"; + return 'FixedIncome'; } - return "Cash" - } -}); - -export const AssetType = enumType({ - name: "AssetType", - members: ['Cash', 'FixedIncome'], + return 'Cash'; + }, }); export const GroupTransactionType = enumType({ - name: "GroupTransactionType", - members: ['PrincipalDraw', 'PrincipalReturn', 'AssetPurchase', 'AssetSale', 'InterestIncome', 'InterestPayment', 'FeesIncome', 'FeesPayment'], + name: 'GroupTransactionType', + members: [ + 'PrincipalDraw', + 'PrincipalReturn', + 'AssetPurchase', + 'AssetSale', + 'InterestIncome', + 'InterestPayment', + 'FeesIncome', + 'FeesPayment', + ], }); export const RealWorldAssetsPortfolio = objectType({ - name: "RealWorldAssetsPortfolio", + name: 'RealWorldAssetsPortfolio', definition(t) { - t.nonNull.id("id") - t.implements(RealWorldAssetsStateInterface) - } -}) + t.nonNull.id('id'); + t.implements(RealWorldAssetsStateInterface); + }, +}); export const RealWorldAssetsDocument = objectType({ name: 'RealWorldAssets', @@ -176,16 +193,12 @@ export const rwaQuery = queryField('rwaPortfolios', { id: stringArg(), }, resolve: async (_root, { id }, ctx) => { - try { - const docs = await ctx.prisma.rWAPortfolio.findRWAPortfolios({ driveId: ctx.driveId }); - if (id) { - return docs.filter(e => e.id === id); - } - - return docs; - } catch (e: any) { - logger.error({ msg: e.message }); + logger.info('Fetching RWA portfolios'); + const docs = await ctx.prisma.rWAPortfolio.findRWAPortfolios({ driveId: ctx.driveId }); + if (id) { + return docs.filter((e) => e.id === id); } + + return docs as any[]; }, }); -