diff --git a/bruno/collections/Rafiki/Rafiki Admin APIs/Create Tenant.bru b/bruno/collections/Rafiki/Rafiki Admin APIs/Create Tenant.bru new file mode 100644 index 0000000000..c078d15371 --- /dev/null +++ b/bruno/collections/Rafiki/Rafiki Admin APIs/Create Tenant.bru @@ -0,0 +1,50 @@ +meta { + name: Create Tenant + type: graphql + seq: 54 +} + +post { + url: {{RafikiGraphqlHost}}/graphql + body: graphql + auth: none +} + +body:graphql { + mutation CreateTenant($input: CreateTenantInput!) { + createTenant(input:$input) { + tenant { + id + email + apiSecret + idpConsentUrl + idpSecret + } + } + } +} + +body:graphql:vars { + { + "input": { + "email": "example@example.com", + "apiSecret": "test-secret", + "idpConsentUrl": "https://example.com/consent", + "idpSecret": "test-idp-secret" + } + } +} + +script:pre-request { + const scripts = require('./scripts'); + + scripts.addApiSignatureHeader(); +} + +script:post-response { + const body = res.getBody(); + + if (body?.data) { + bru.setEnvVar("tenantId", body.data.createTenant.tenant?.id); + } +} diff --git a/bruno/collections/Rafiki/Rafiki Admin APIs/Delete Tenant.bru b/bruno/collections/Rafiki/Rafiki Admin APIs/Delete Tenant.bru new file mode 100644 index 0000000000..6c664049f7 --- /dev/null +++ b/bruno/collections/Rafiki/Rafiki Admin APIs/Delete Tenant.bru @@ -0,0 +1,31 @@ +meta { + name: Delete Tenant + type: graphql + seq: 56 +} + +post { + url: {{RafikiGraphqlHost}}/graphql + body: graphql + auth: none +} + +body:graphql { + mutation DeleteTenant($id: String!) { + deleteTenant(id:$id) { + success + } + } +} + +body:graphql:vars { + { + "id": "{{tenantId}}" + } +} + +script:pre-request { + const scripts = require('./scripts'); + + scripts.addApiSignatureHeader(); +} diff --git a/bruno/collections/Rafiki/Rafiki Admin APIs/Update Tenant.bru b/bruno/collections/Rafiki/Rafiki Admin APIs/Update Tenant.bru new file mode 100644 index 0000000000..b3f71689d8 --- /dev/null +++ b/bruno/collections/Rafiki/Rafiki Admin APIs/Update Tenant.bru @@ -0,0 +1,43 @@ +meta { + name: Update Tenant + type: graphql + seq: 55 +} + +post { + url: {{RafikiGraphqlHost}}/graphql + body: graphql + auth: none +} + +body:graphql { + mutation UpdateTenant($input: UpdateTenantInput!) { + updateTenant(input:$input) { + tenant { + id + email + apiSecret + idpConsentUrl + idpSecret + } + } + } +} + +body:graphql:vars { + { + "input": { + "id": "{{tenantId}}", + "email": "updated@example.com", + "apiSecret": "updated-test-secret", + "idpConsentUrl": "https://example.com/consent-updated", + "idpSecret": "updated-test-idp-secret" + } + } +} + +script:pre-request { + const scripts = require('./scripts'); + + scripts.addApiSignatureHeader(); +} diff --git a/bruno/collections/Rafiki/environments/Local Playground.bru b/bruno/collections/Rafiki/environments/Local Playground.bru index 6cb1033ff6..24924232e4 100644 --- a/bruno/collections/Rafiki/environments/Local Playground.bru +++ b/bruno/collections/Rafiki/environments/Local Playground.bru @@ -30,4 +30,5 @@ vars { assetIdTigerBeetle: 1 assetCode: USD assetScale: 2 + senderTenantId: 438fa74a-fa7d-4317-9ced-dde32ece1787 } diff --git a/bruno/collections/Rafiki/scripts.js b/bruno/collections/Rafiki/scripts.js index 8ea6b7a614..ef03f9ab1d 100644 --- a/bruno/collections/Rafiki/scripts.js +++ b/bruno/collections/Rafiki/scripts.js @@ -127,6 +127,7 @@ const scripts = { signature = this.generateBackendApiSignature(formattedBody) } req.setHeader('signature', signature) + req.setHeader('tenant-id', bru.getEnvVar('senderTenantId')) }, addHostHeader: function (hostVarName) { diff --git a/localenv/mock-account-servicing-entity/generated/graphql.ts b/localenv/mock-account-servicing-entity/generated/graphql.ts index 46dad2dedb..a9a4b15bc0 100644 --- a/localenv/mock-account-servicing-entity/generated/graphql.ts +++ b/localenv/mock-account-servicing-entity/generated/graphql.ts @@ -364,6 +364,19 @@ export type CreateReceiverResponse = { receiver?: Maybe; }; +export type CreateTenantInput = { + /** Secret used to secure requests made for this tenant. */ + apiSecret: Scalars['String']['input']; + /** Contact email of the tenant owner. */ + email?: InputMaybe; + /** URL of the tenant's identity provider's consent screen. */ + idpConsentUrl?: InputMaybe; + /** Secret used to secure requests from the tenant's identity provider. */ + idpSecret?: InputMaybe; + /** Public name for the tenant. */ + publicName?: InputMaybe; +}; + export type CreateWalletAddressInput = { /** Additional properties associated with the wallet address. */ additionalProperties?: InputMaybe>; @@ -440,6 +453,11 @@ export type DeletePeerMutationResponse = { success: Scalars['Boolean']['output']; }; +export type DeleteTenantMutationResponse = { + __typename?: 'DeleteTenantMutationResponse'; + success: Scalars['Boolean']['output']; +}; + export type DepositAssetLiquidityInput = { /** Amount of liquidity to deposit. */ amount: Scalars['UInt64']['input']; @@ -721,6 +739,8 @@ export type Mutation = { createQuote: QuoteResponse; /** Create an internal or external Open Payments incoming payment. The receiver has a wallet address on either this or another Open Payments resource server. */ createReceiver: CreateReceiverResponse; + /** As an operator, create a tenant. */ + createTenant: TenantMutationResponse; /** Create a new wallet address. */ createWalletAddress: CreateWalletAddressMutationResponse; /** Add a public key to a wallet address that is used to verify Open Payments requests. */ @@ -731,6 +751,8 @@ export type Mutation = { deleteAsset: DeleteAssetMutationResponse; /** Delete a peer. */ deletePeer: DeletePeerMutationResponse; + /** Delete a tenant. */ + deleteTenant: DeleteTenantMutationResponse; /** Deposit asset liquidity. */ depositAssetLiquidity?: Maybe; /** @@ -756,6 +778,8 @@ export type Mutation = { updateIncomingPayment: IncomingPaymentResponse; /** Update an existing peer. */ updatePeer: UpdatePeerMutationResponse; + /** Update a tenant. */ + updateTenant: TenantMutationResponse; /** Update an existing wallet address. */ updateWalletAddress: UpdateWalletAddressMutationResponse; /** Void liquidity withdrawal. Withdrawals are two-phase commits and are rolled back via this mutation. */ @@ -843,6 +867,11 @@ export type MutationCreateReceiverArgs = { }; +export type MutationCreateTenantArgs = { + input: CreateTenantInput; +}; + + export type MutationCreateWalletAddressArgs = { input: CreateWalletAddressInput; }; @@ -868,6 +897,11 @@ export type MutationDeletePeerArgs = { }; +export type MutationDeleteTenantArgs = { + id: Scalars['String']['input']; +}; + + export type MutationDepositAssetLiquidityArgs = { input: DepositAssetLiquidityInput; }; @@ -923,6 +957,11 @@ export type MutationUpdatePeerArgs = { }; +export type MutationUpdateTenantArgs = { + input: UpdateTenantInput; +}; + + export type MutationUpdateWalletAddressArgs = { input: UpdateWalletAddressInput; }; @@ -1150,6 +1189,10 @@ export type Query = { quote?: Maybe; /** Retrieve an Open Payments incoming payment by receiver ID. The receiver's wallet address can be hosted on this server or a remote Open Payments resource server. */ receiver?: Maybe; + /** Retrieve a tenant of the instance. */ + tenant: Tenant; + /** As an operator, fetch a paginated list of tenants on the instance. */ + tenants: TenantsConnection; /** Fetch a wallet address by its ID. */ walletAddress?: Maybe; /** Get a wallet address by its url if it exists */ @@ -1158,6 +1201,8 @@ export type Query = { walletAddresses: WalletAddressesConnection; /** Fetch a paginated list of webhook events. */ webhookEvents: WebhookEventsConnection; + /** Determine if the requester has operator permissions */ + whoami: WhoamiResponse; }; @@ -1247,6 +1292,20 @@ export type QueryReceiverArgs = { }; +export type QueryTenantArgs = { + id: Scalars['String']['input']; +}; + + +export type QueryTenantsArgs = { + after?: InputMaybe; + before?: InputMaybe; + first?: InputMaybe; + last?: InputMaybe; + sortOrder?: InputMaybe; +}; + + export type QueryWalletAddressArgs = { id: Scalars['String']['input']; }; @@ -1376,6 +1435,47 @@ export enum SortOrder { Desc = 'DESC' } +export type Tenant = Model & { + __typename?: 'Tenant'; + /** Secret used to secure requests made for this tenant. */ + apiSecret: Scalars['String']['output']; + /** The date and time that this tenant was created. */ + createdAt: Scalars['String']['output']; + /** The date and time that this tenant was deleted. */ + deletedAt?: Maybe; + /** Contact email of the tenant owner. */ + email?: Maybe; + /** Unique identifier of the tenant. */ + id: Scalars['ID']['output']; + /** URL of the tenant's identity provider's consent screen. */ + idpConsentUrl?: Maybe; + /** Secret used to secure requests from the tenant's identity provider. */ + idpSecret?: Maybe; + /** Public name for the tenant. */ + publicName?: Maybe; +}; + +export type TenantEdge = { + __typename?: 'TenantEdge'; + /** A cursor for paginating through the tenants. */ + cursor: Scalars['String']['output']; + /** A tenant node in the list. */ + node: Tenant; +}; + +export type TenantMutationResponse = { + __typename?: 'TenantMutationResponse'; + tenant: Tenant; +}; + +export type TenantsConnection = { + __typename?: 'TenantsConnection'; + /** A list of edges representing tenants and cursors for pagination. */ + edges: Array; + /** Information to aid in pagination. */ + pageInfo: PageInfo; +}; + export enum TransferState { /** The accounting transfer is pending */ Pending = 'PENDING', @@ -1448,6 +1548,21 @@ export type UpdatePeerMutationResponse = { peer?: Maybe; }; +export type UpdateTenantInput = { + /** Secret used to secure requests made for this tenant. */ + apiSecret?: InputMaybe; + /** Contact email of the tenant owner. */ + email?: InputMaybe; + /** Unique identifier of the tenant. */ + id: Scalars['ID']['input']; + /** URL of the tenant's identity provider's consent screen. */ + idpConsentUrl?: InputMaybe; + /** Secret used to secure requests from the tenant's identity provider. */ + idpSecret?: InputMaybe; + /** Public name for the tenant. */ + publicName?: InputMaybe; +}; + export type UpdateWalletAddressInput = { /** Additional properties associated with this wallet address. */ additionalProperties?: InputMaybe>; @@ -1640,6 +1755,12 @@ export type WebhookEventsEdge = { node: WebhookEvent; }; +export type WhoamiResponse = { + __typename?: 'WhoamiResponse'; + id: Scalars['String']['output']; + isOperator: Scalars['Boolean']['output']; +}; + export type WithdrawEventLiquidityInput = { /** Unique identifier of the event to withdraw liquidity from. */ eventId: Scalars['String']['input']; @@ -1718,7 +1839,7 @@ export type DirectiveResolverFn> = { BasePayment: ( Partial ) | ( Partial ) | ( Partial ); - Model: ( Partial ) | ( Partial ) | ( Partial ) | ( Partial ) | ( Partial ) | ( Partial ) | ( Partial ) | ( Partial ) | ( Partial ) | ( Partial ); + Model: ( Partial ) | ( Partial ) | ( Partial ) | ( Partial ) | ( Partial ) | ( Partial ) | ( Partial ) | ( Partial ) | ( Partial ) | ( Partial ) | ( Partial ); }; /** Mapping between all available schema types and the resolvers types */ @@ -1756,6 +1877,7 @@ export type ResolversTypes = { CreateQuoteInput: ResolverTypeWrapper>; CreateReceiverInput: ResolverTypeWrapper>; CreateReceiverResponse: ResolverTypeWrapper>; + CreateTenantInput: ResolverTypeWrapper>; CreateWalletAddressInput: ResolverTypeWrapper>; CreateWalletAddressKeyInput: ResolverTypeWrapper>; CreateWalletAddressKeyMutationResponse: ResolverTypeWrapper>; @@ -1766,6 +1888,7 @@ export type ResolversTypes = { DeleteAssetMutationResponse: ResolverTypeWrapper>; DeletePeerInput: ResolverTypeWrapper>; DeletePeerMutationResponse: ResolverTypeWrapper>; + DeleteTenantMutationResponse: ResolverTypeWrapper>; DepositAssetLiquidityInput: ResolverTypeWrapper>; DepositEventLiquidityInput: ResolverTypeWrapper>; DepositOutgoingPaymentLiquidityInput: ResolverTypeWrapper>; @@ -1825,6 +1948,10 @@ export type ResolversTypes = { SetFeeResponse: ResolverTypeWrapper>; SortOrder: ResolverTypeWrapper>; String: ResolverTypeWrapper>; + Tenant: ResolverTypeWrapper>; + TenantEdge: ResolverTypeWrapper>; + TenantMutationResponse: ResolverTypeWrapper>; + TenantsConnection: ResolverTypeWrapper>; TransferState: ResolverTypeWrapper>; TransferType: ResolverTypeWrapper>; TriggerWalletAddressEventsInput: ResolverTypeWrapper>; @@ -1835,6 +1962,7 @@ export type ResolversTypes = { UpdateIncomingPaymentInput: ResolverTypeWrapper>; UpdatePeerInput: ResolverTypeWrapper>; UpdatePeerMutationResponse: ResolverTypeWrapper>; + UpdateTenantInput: ResolverTypeWrapper>; UpdateWalletAddressInput: ResolverTypeWrapper>; UpdateWalletAddressMutationResponse: ResolverTypeWrapper>; VoidLiquidityWithdrawalInput: ResolverTypeWrapper>; @@ -1851,6 +1979,7 @@ export type ResolversTypes = { WebhookEventFilter: ResolverTypeWrapper>; WebhookEventsConnection: ResolverTypeWrapper>; WebhookEventsEdge: ResolverTypeWrapper>; + WhoamiResponse: ResolverTypeWrapper>; WithdrawEventLiquidityInput: ResolverTypeWrapper>; }; @@ -1888,6 +2017,7 @@ export type ResolversParentTypes = { CreateQuoteInput: Partial; CreateReceiverInput: Partial; CreateReceiverResponse: Partial; + CreateTenantInput: Partial; CreateWalletAddressInput: Partial; CreateWalletAddressKeyInput: Partial; CreateWalletAddressKeyMutationResponse: Partial; @@ -1897,6 +2027,7 @@ export type ResolversParentTypes = { DeleteAssetMutationResponse: Partial; DeletePeerInput: Partial; DeletePeerMutationResponse: Partial; + DeleteTenantMutationResponse: Partial; DepositAssetLiquidityInput: Partial; DepositEventLiquidityInput: Partial; DepositOutgoingPaymentLiquidityInput: Partial; @@ -1949,6 +2080,10 @@ export type ResolversParentTypes = { SetFeeInput: Partial; SetFeeResponse: Partial; String: Partial; + Tenant: Partial; + TenantEdge: Partial; + TenantMutationResponse: Partial; + TenantsConnection: Partial; TriggerWalletAddressEventsInput: Partial; TriggerWalletAddressEventsMutationResponse: Partial; UInt8: Partial; @@ -1957,6 +2092,7 @@ export type ResolversParentTypes = { UpdateIncomingPaymentInput: Partial; UpdatePeerInput: Partial; UpdatePeerMutationResponse: Partial; + UpdateTenantInput: Partial; UpdateWalletAddressInput: Partial; UpdateWalletAddressMutationResponse: Partial; VoidLiquidityWithdrawalInput: Partial; @@ -1972,6 +2108,7 @@ export type ResolversParentTypes = { WebhookEventFilter: Partial; WebhookEventsConnection: Partial; WebhookEventsEdge: Partial; + WhoamiResponse: Partial; WithdrawEventLiquidityInput: Partial; }; @@ -2093,6 +2230,11 @@ export type DeletePeerMutationResponseResolvers; }; +export type DeleteTenantMutationResponseResolvers = { + success?: Resolver; + __isTypeOf?: IsTypeOfResolverFn; +}; + export type FeeResolvers = { assetId?: Resolver; basisPoints?: Resolver; @@ -2176,7 +2318,7 @@ export type LiquidityMutationResponseResolvers = { - __resolveType: TypeResolveFn<'AccountingTransfer' | 'Asset' | 'Fee' | 'IncomingPayment' | 'OutgoingPayment' | 'Payment' | 'Peer' | 'WalletAddress' | 'WalletAddressKey' | 'WebhookEvent', ParentType, ContextType>; + __resolveType: TypeResolveFn<'AccountingTransfer' | 'Asset' | 'Fee' | 'IncomingPayment' | 'OutgoingPayment' | 'Payment' | 'Peer' | 'Tenant' | 'WalletAddress' | 'WalletAddressKey' | 'WebhookEvent', ParentType, ContextType>; createdAt?: Resolver; id?: Resolver; }; @@ -2197,11 +2339,13 @@ export type MutationResolvers, ParentType, ContextType, RequireFields>; createQuote?: Resolver>; createReceiver?: Resolver>; + createTenant?: Resolver>; createWalletAddress?: Resolver>; createWalletAddressKey?: Resolver, ParentType, ContextType, RequireFields>; createWalletAddressWithdrawal?: Resolver, ParentType, ContextType, RequireFields>; deleteAsset?: Resolver>; deletePeer?: Resolver>; + deleteTenant?: Resolver>; depositAssetLiquidity?: Resolver, ParentType, ContextType, RequireFields>; depositEventLiquidity?: Resolver, ParentType, ContextType, RequireFields>; depositOutgoingPaymentLiquidity?: Resolver, ParentType, ContextType, RequireFields>; @@ -2213,6 +2357,7 @@ export type MutationResolvers>; updateIncomingPayment?: Resolver>; updatePeer?: Resolver>; + updateTenant?: Resolver>; updateWalletAddress?: Resolver>; voidLiquidityWithdrawal?: Resolver, ParentType, ContextType, RequireFields>; withdrawEventLiquidity?: Resolver, ParentType, ContextType, RequireFields>; @@ -2325,10 +2470,13 @@ export type QueryResolvers>; quote?: Resolver, ParentType, ContextType, RequireFields>; receiver?: Resolver, ParentType, ContextType, RequireFields>; + tenant?: Resolver>; + tenants?: Resolver>; walletAddress?: Resolver, ParentType, ContextType, RequireFields>; walletAddressByUrl?: Resolver, ParentType, ContextType, RequireFields>; walletAddresses?: Resolver>; webhookEvents?: Resolver>; + whoami?: Resolver; }; export type QuoteResolvers = { @@ -2383,6 +2531,35 @@ export type SetFeeResponseResolvers; }; +export type TenantResolvers = { + apiSecret?: Resolver; + createdAt?: Resolver; + deletedAt?: Resolver, ParentType, ContextType>; + email?: Resolver, ParentType, ContextType>; + id?: Resolver; + idpConsentUrl?: Resolver, ParentType, ContextType>; + idpSecret?: Resolver, ParentType, ContextType>; + publicName?: Resolver, ParentType, ContextType>; + __isTypeOf?: IsTypeOfResolverFn; +}; + +export type TenantEdgeResolvers = { + cursor?: Resolver; + node?: Resolver; + __isTypeOf?: IsTypeOfResolverFn; +}; + +export type TenantMutationResponseResolvers = { + tenant?: Resolver; + __isTypeOf?: IsTypeOfResolverFn; +}; + +export type TenantsConnectionResolvers = { + edges?: Resolver, ParentType, ContextType>; + pageInfo?: Resolver; + __isTypeOf?: IsTypeOfResolverFn; +}; + export type TriggerWalletAddressEventsMutationResponseResolvers = { count?: Resolver, ParentType, ContextType>; __isTypeOf?: IsTypeOfResolverFn; @@ -2487,6 +2664,12 @@ export type WebhookEventsEdgeResolvers; }; +export type WhoamiResponseResolvers = { + id?: Resolver; + isOperator?: Resolver; + __isTypeOf?: IsTypeOfResolverFn; +}; + export type Resolvers = { AccountingTransfer?: AccountingTransferResolvers; AccountingTransferConnection?: AccountingTransferConnectionResolvers; @@ -2506,6 +2689,7 @@ export type Resolvers = { CreateWalletAddressMutationResponse?: CreateWalletAddressMutationResponseResolvers; DeleteAssetMutationResponse?: DeleteAssetMutationResponseResolvers; DeletePeerMutationResponse?: DeletePeerMutationResponseResolvers; + DeleteTenantMutationResponse?: DeleteTenantMutationResponseResolvers; Fee?: FeeResolvers; FeeEdge?: FeeEdgeResolvers; FeesConnection?: FeesConnectionResolvers; @@ -2539,6 +2723,10 @@ export type Resolvers = { Receiver?: ReceiverResolvers; RevokeWalletAddressKeyMutationResponse?: RevokeWalletAddressKeyMutationResponseResolvers; SetFeeResponse?: SetFeeResponseResolvers; + Tenant?: TenantResolvers; + TenantEdge?: TenantEdgeResolvers; + TenantMutationResponse?: TenantMutationResponseResolvers; + TenantsConnection?: TenantsConnectionResolvers; TriggerWalletAddressEventsMutationResponse?: TriggerWalletAddressEventsMutationResponseResolvers; UInt8?: GraphQLScalarType; UInt64?: GraphQLScalarType; @@ -2555,5 +2743,6 @@ export type Resolvers = { WebhookEvent?: WebhookEventResolvers; WebhookEventsConnection?: WebhookEventsConnectionResolvers; WebhookEventsEdge?: WebhookEventsEdgeResolvers; + WhoamiResponse?: WhoamiResponseResolvers; }; diff --git a/packages/auth/migrations/20241125233415_create_tenants_table.js b/packages/auth/migrations/20241125233415_create_tenants_table.js index 9112108977..4846a07ce9 100644 --- a/packages/auth/migrations/20241125233415_create_tenants_table.js +++ b/packages/auth/migrations/20241125233415_create_tenants_table.js @@ -5,8 +5,8 @@ exports.up = function (knex) { return knex.schema.createTable('tenants', function (table) { table.uuid('id').notNullable().primary() - table.string('idpConsentUrl').notNullable() - table.string('idpSecret').notNullable() + table.string('idpConsentUrl') + table.string('idpSecret') table.timestamp('createdAt').defaultTo(knex.fn.now()) table.timestamp('updatedAt').defaultTo(knex.fn.now()) diff --git a/packages/auth/src/tenant/model.ts b/packages/auth/src/tenant/model.ts index b135541c34..412422e217 100644 --- a/packages/auth/src/tenant/model.ts +++ b/packages/auth/src/tenant/model.ts @@ -5,8 +5,8 @@ export class Tenant extends BaseModel { return 'tenants' } - public idpConsentUrl!: string - public idpSecret!: string + public idpConsentUrl?: string + public idpSecret?: string public deletedAt?: Date } diff --git a/packages/auth/src/tenant/routes.ts b/packages/auth/src/tenant/routes.ts index 0ffe7c5940..895e07cf2c 100644 --- a/packages/auth/src/tenant/routes.ts +++ b/packages/auth/src/tenant/routes.ts @@ -22,8 +22,8 @@ type TenantContext = Exclude< interface CreateTenantBody { id: string - idpConsentUrl: string - idpSecret: string + idpConsentUrl?: string + idpSecret?: string } type UpdateTenantBody = Partial> @@ -34,8 +34,8 @@ interface TenantParams { interface TenantResponse { id: string - idpConsentUrl: string - idpSecret: string + idpConsentUrl?: string + idpSecret?: string } export type GetContext = TenantContext diff --git a/packages/auth/src/tenant/service.ts b/packages/auth/src/tenant/service.ts index d4f3cc336a..27e562e397 100644 --- a/packages/auth/src/tenant/service.ts +++ b/packages/auth/src/tenant/service.ts @@ -4,8 +4,8 @@ import { Tenant } from './model' export interface CreateOptions { id: string - idpConsentUrl: string - idpSecret: string + idpConsentUrl?: string + idpSecret?: string } export interface TenantService { diff --git a/packages/backend/src/auth-service-client/client.ts b/packages/backend/src/auth-service-client/client.ts index da2e0a9a72..402435446f 100644 --- a/packages/backend/src/auth-service-client/client.ts +++ b/packages/backend/src/auth-service-client/client.ts @@ -1,7 +1,7 @@ interface Tenant { id: string - idpConsentUrl: string - idpSecret: string + idpConsentUrl?: string + idpSecret?: string } export class AuthServiceClientError extends Error { diff --git a/packages/backend/src/graphql/generated/graphql.schema.json b/packages/backend/src/graphql/generated/graphql.schema.json index 1745966aa2..ed648e8445 100644 --- a/packages/backend/src/graphql/generated/graphql.schema.json +++ b/packages/backend/src/graphql/generated/graphql.schema.json @@ -2105,6 +2105,81 @@ "enumValues": null, "possibleTypes": null }, + { + "kind": "INPUT_OBJECT", + "name": "CreateTenantInput", + "description": null, + "fields": null, + "inputFields": [ + { + "name": "apiSecret", + "description": "Secret used to secure requests made for this tenant.", + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "String", + "ofType": null + } + }, + "defaultValue": null, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "email", + "description": "Contact email of the tenant owner.", + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + }, + "defaultValue": null, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "idpConsentUrl", + "description": "URL of the tenant's identity provider's consent screen.", + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + }, + "defaultValue": null, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "idpSecret", + "description": "Secret used to secure requests from the tenant's identity provider.", + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + }, + "defaultValue": null, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "publicName", + "description": "Public name for the tenant.", + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + }, + "defaultValue": null, + "isDeprecated": false, + "deprecationReason": null + } + ], + "interfaces": null, + "enumValues": null, + "possibleTypes": null + }, { "kind": "INPUT_OBJECT", "name": "CreateWalletAddressInput", @@ -2513,6 +2588,33 @@ "enumValues": null, "possibleTypes": null }, + { + "kind": "OBJECT", + "name": "DeleteTenantMutationResponse", + "description": null, + "fields": [ + { + "name": "success", + "description": null, + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "Boolean", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + } + ], + "inputFields": null, + "interfaces": [], + "enumValues": null, + "possibleTypes": null + }, { "kind": "INPUT_OBJECT", "name": "DepositAssetLiquidityInput", @@ -3988,6 +4090,11 @@ "name": "Peer", "ofType": null }, + { + "kind": "OBJECT", + "name": "Tenant", + "ofType": null + }, { "kind": "OBJECT", "name": "WalletAddress", @@ -4489,6 +4596,39 @@ "isDeprecated": false, "deprecationReason": null }, + { + "name": "createTenant", + "description": "As an operator, create a tenant.", + "args": [ + { + "name": "input", + "description": null, + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "INPUT_OBJECT", + "name": "CreateTenantInput", + "ofType": null + } + }, + "defaultValue": null, + "isDeprecated": false, + "deprecationReason": null + } + ], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "OBJECT", + "name": "TenantMutationResponse", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + }, { "name": "createWalletAddress", "description": "Create a new wallet address.", @@ -4646,6 +4786,39 @@ "isDeprecated": false, "deprecationReason": null }, + { + "name": "deleteTenant", + "description": "Delete a tenant.", + "args": [ + { + "name": "id", + "description": null, + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "String", + "ofType": null + } + }, + "defaultValue": null, + "isDeprecated": false, + "deprecationReason": null + } + ], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "OBJECT", + "name": "DeleteTenantMutationResponse", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + }, { "name": "depositAssetLiquidity", "description": "Deposit asset liquidity.", @@ -4985,6 +5158,39 @@ "isDeprecated": false, "deprecationReason": null }, + { + "name": "updateTenant", + "description": "Update a tenant.", + "args": [ + { + "name": "input", + "description": null, + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "INPUT_OBJECT", + "name": "UpdateTenantInput", + "ofType": null + } + }, + "defaultValue": null, + "isDeprecated": false, + "deprecationReason": null + } + ], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "OBJECT", + "name": "TenantMutationResponse", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + }, { "name": "updateWalletAddress", "description": "Update an existing wallet address.", @@ -6811,12 +7017,12 @@ "deprecationReason": null }, { - "name": "walletAddress", - "description": "Fetch a wallet address by its ID.", + "name": "tenant", + "description": "Retrieve a tenant of the instance.", "args": [ { "name": "id", - "description": "Unique identifier of the wallet address.", + "description": "Unique identifier of the tenant.", "type": { "kind": "NON_NULL", "name": null, @@ -6832,49 +7038,24 @@ } ], "type": { - "kind": "OBJECT", - "name": "WalletAddress", - "ofType": null - }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "walletAddressByUrl", - "description": "Get a wallet address by its url if it exists", - "args": [ - { - "name": "url", - "description": "Wallet Address URL.", - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { - "kind": "SCALAR", - "name": "String", - "ofType": null - } - }, - "defaultValue": null, - "isDeprecated": false, - "deprecationReason": null + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "OBJECT", + "name": "Tenant", + "ofType": null } - ], - "type": { - "kind": "OBJECT", - "name": "WalletAddress", - "ofType": null }, "isDeprecated": false, "deprecationReason": null }, { - "name": "walletAddresses", - "description": "Fetch a paginated list of wallet addresses.", + "name": "tenants", + "description": "As an operator, fetch a paginated list of tenants on the instance.", "args": [ { "name": "after", - "description": "Forward pagination: Cursor (wallet address ID) to start retrieving wallet addresses after this point.", + "description": "Forward pagination: Cursor (tenant ID) to start retrieving tenants after this point.", "type": { "kind": "SCALAR", "name": "String", @@ -6886,7 +7067,7 @@ }, { "name": "before", - "description": "Backward pagination: Cursor (wallet address ID) to start retrieving wallet addresses before this point.", + "description": "Backward pagination: Cursor (tenant ID) to start retrieving tenants before this point.", "type": { "kind": "SCALAR", "name": "String", @@ -6898,7 +7079,7 @@ }, { "name": "first", - "description": "Forward pagination: Limit the result to the first **n** wallet addresses after the `after` cursor.", + "description": "Forward pagination: Limit the result to the first **n** tenants after the `after` cursor.", "type": { "kind": "SCALAR", "name": "Int", @@ -6910,7 +7091,7 @@ }, { "name": "last", - "description": "Backward pagination: Limit the result to the last **n** wallet addresses before the `before` cursor.", + "description": "Backward pagination: Limit the result to the last **n** tenants before the `before` cursor.", "type": { "kind": "SCALAR", "name": "Int", @@ -6922,7 +7103,7 @@ }, { "name": "sortOrder", - "description": "Specify the sort order of wallet addresses based on their creation date, either ascending or descending.", + "description": "Specify the sort order of tenants based on their creation date, either ascending or descending.", "type": { "kind": "ENUM", "name": "SortOrder", @@ -6938,7 +7119,7 @@ "name": null, "ofType": { "kind": "OBJECT", - "name": "WalletAddressesConnection", + "name": "TenantsConnection", "ofType": null } }, @@ -6946,28 +7127,163 @@ "deprecationReason": null }, { - "name": "webhookEvents", - "description": "Fetch a paginated list of webhook events.", + "name": "walletAddress", + "description": "Fetch a wallet address by its ID.", "args": [ { - "name": "after", - "description": "Forward pagination: Cursor (webhook event ID) to start retrieving webhook events after this point.", + "name": "id", + "description": "Unique identifier of the wallet address.", "type": { - "kind": "SCALAR", - "name": "String", - "ofType": null + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "String", + "ofType": null + } }, "defaultValue": null, "isDeprecated": false, "deprecationReason": null - }, - { - "name": "before", - "description": "Backward pagination: Cursor (webhook event ID) to start retrieving webhook events before this point.", - "type": { - "kind": "SCALAR", - "name": "String", - "ofType": null + } + ], + "type": { + "kind": "OBJECT", + "name": "WalletAddress", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "walletAddressByUrl", + "description": "Get a wallet address by its url if it exists", + "args": [ + { + "name": "url", + "description": "Wallet Address URL.", + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "String", + "ofType": null + } + }, + "defaultValue": null, + "isDeprecated": false, + "deprecationReason": null + } + ], + "type": { + "kind": "OBJECT", + "name": "WalletAddress", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "walletAddresses", + "description": "Fetch a paginated list of wallet addresses.", + "args": [ + { + "name": "after", + "description": "Forward pagination: Cursor (wallet address ID) to start retrieving wallet addresses after this point.", + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + }, + "defaultValue": null, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "before", + "description": "Backward pagination: Cursor (wallet address ID) to start retrieving wallet addresses before this point.", + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + }, + "defaultValue": null, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "first", + "description": "Forward pagination: Limit the result to the first **n** wallet addresses after the `after` cursor.", + "type": { + "kind": "SCALAR", + "name": "Int", + "ofType": null + }, + "defaultValue": null, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "last", + "description": "Backward pagination: Limit the result to the last **n** wallet addresses before the `before` cursor.", + "type": { + "kind": "SCALAR", + "name": "Int", + "ofType": null + }, + "defaultValue": null, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "sortOrder", + "description": "Specify the sort order of wallet addresses based on their creation date, either ascending or descending.", + "type": { + "kind": "ENUM", + "name": "SortOrder", + "ofType": null + }, + "defaultValue": null, + "isDeprecated": false, + "deprecationReason": null + } + ], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "OBJECT", + "name": "WalletAddressesConnection", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "webhookEvents", + "description": "Fetch a paginated list of webhook events.", + "args": [ + { + "name": "after", + "description": "Forward pagination: Cursor (webhook event ID) to start retrieving webhook events after this point.", + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + }, + "defaultValue": null, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "before", + "description": "Backward pagination: Cursor (webhook event ID) to start retrieving webhook events before this point.", + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null }, "defaultValue": null, "isDeprecated": false, @@ -7033,6 +7349,22 @@ }, "isDeprecated": false, "deprecationReason": null + }, + { + "name": "whoami", + "description": "Determine if the requester has operator permissions", + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "OBJECT", + "name": "WhoamiResponse", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null } ], "inputFields": null, @@ -7624,6 +7956,252 @@ "enumValues": null, "possibleTypes": null }, + { + "kind": "OBJECT", + "name": "Tenant", + "description": null, + "fields": [ + { + "name": "apiSecret", + "description": "Secret used to secure requests made for this tenant.", + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "String", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "createdAt", + "description": "The date and time that this tenant was created.", + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "String", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "deletedAt", + "description": "The date and time that this tenant was deleted.", + "args": [], + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "email", + "description": "Contact email of the tenant owner.", + "args": [], + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "id", + "description": "Unique identifier of the tenant.", + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "ID", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "idpConsentUrl", + "description": "URL of the tenant's identity provider's consent screen.", + "args": [], + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "idpSecret", + "description": "Secret used to secure requests from the tenant's identity provider.", + "args": [], + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "publicName", + "description": "Public name for the tenant.", + "args": [], + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + } + ], + "inputFields": null, + "interfaces": [ + { + "kind": "INTERFACE", + "name": "Model", + "ofType": null + } + ], + "enumValues": null, + "possibleTypes": null + }, + { + "kind": "OBJECT", + "name": "TenantEdge", + "description": null, + "fields": [ + { + "name": "cursor", + "description": "A cursor for paginating through the tenants.", + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "String", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "node", + "description": "A tenant node in the list.", + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "OBJECT", + "name": "Tenant", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + } + ], + "inputFields": null, + "interfaces": [], + "enumValues": null, + "possibleTypes": null + }, + { + "kind": "OBJECT", + "name": "TenantMutationResponse", + "description": null, + "fields": [ + { + "name": "tenant", + "description": null, + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "OBJECT", + "name": "Tenant", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + } + ], + "inputFields": null, + "interfaces": [], + "enumValues": null, + "possibleTypes": null + }, + { + "kind": "OBJECT", + "name": "TenantsConnection", + "description": null, + "fields": [ + { + "name": "edges", + "description": "A list of edges representing tenants and cursors for pagination.", + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "LIST", + "name": null, + "ofType": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "OBJECT", + "name": "TenantEdge", + "ofType": null + } + } + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "pageInfo", + "description": "Information to aid in pagination.", + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "OBJECT", + "name": "PageInfo", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + } + ], + "inputFields": null, + "interfaces": [], + "enumValues": null, + "possibleTypes": null + }, { "kind": "ENUM", "name": "TransferState", @@ -7992,6 +8570,93 @@ "enumValues": null, "possibleTypes": null }, + { + "kind": "INPUT_OBJECT", + "name": "UpdateTenantInput", + "description": null, + "fields": null, + "inputFields": [ + { + "name": "apiSecret", + "description": "Secret used to secure requests made for this tenant.", + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + }, + "defaultValue": null, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "email", + "description": "Contact email of the tenant owner.", + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + }, + "defaultValue": null, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "id", + "description": "Unique identifier of the tenant.", + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "ID", + "ofType": null + } + }, + "defaultValue": null, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "idpConsentUrl", + "description": "URL of the tenant's identity provider's consent screen.", + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + }, + "defaultValue": null, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "idpSecret", + "description": "Secret used to secure requests from the tenant's identity provider.", + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + }, + "defaultValue": null, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "publicName", + "description": "Public name for the tenant.", + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + }, + "defaultValue": null, + "isDeprecated": false, + "deprecationReason": null + } + ], + "interfaces": null, + "enumValues": null, + "possibleTypes": null + }, { "kind": "INPUT_OBJECT", "name": "UpdateWalletAddressInput", @@ -9158,6 +9823,49 @@ "enumValues": null, "possibleTypes": null }, + { + "kind": "OBJECT", + "name": "WhoamiResponse", + "description": null, + "fields": [ + { + "name": "id", + "description": null, + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "String", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "isOperator", + "description": null, + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "Boolean", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + } + ], + "inputFields": null, + "interfaces": [], + "enumValues": null, + "possibleTypes": null + }, { "kind": "INPUT_OBJECT", "name": "WithdrawEventLiquidityInput", diff --git a/packages/backend/src/graphql/generated/graphql.ts b/packages/backend/src/graphql/generated/graphql.ts index 46dad2dedb..a9a4b15bc0 100644 --- a/packages/backend/src/graphql/generated/graphql.ts +++ b/packages/backend/src/graphql/generated/graphql.ts @@ -364,6 +364,19 @@ export type CreateReceiverResponse = { receiver?: Maybe; }; +export type CreateTenantInput = { + /** Secret used to secure requests made for this tenant. */ + apiSecret: Scalars['String']['input']; + /** Contact email of the tenant owner. */ + email?: InputMaybe; + /** URL of the tenant's identity provider's consent screen. */ + idpConsentUrl?: InputMaybe; + /** Secret used to secure requests from the tenant's identity provider. */ + idpSecret?: InputMaybe; + /** Public name for the tenant. */ + publicName?: InputMaybe; +}; + export type CreateWalletAddressInput = { /** Additional properties associated with the wallet address. */ additionalProperties?: InputMaybe>; @@ -440,6 +453,11 @@ export type DeletePeerMutationResponse = { success: Scalars['Boolean']['output']; }; +export type DeleteTenantMutationResponse = { + __typename?: 'DeleteTenantMutationResponse'; + success: Scalars['Boolean']['output']; +}; + export type DepositAssetLiquidityInput = { /** Amount of liquidity to deposit. */ amount: Scalars['UInt64']['input']; @@ -721,6 +739,8 @@ export type Mutation = { createQuote: QuoteResponse; /** Create an internal or external Open Payments incoming payment. The receiver has a wallet address on either this or another Open Payments resource server. */ createReceiver: CreateReceiverResponse; + /** As an operator, create a tenant. */ + createTenant: TenantMutationResponse; /** Create a new wallet address. */ createWalletAddress: CreateWalletAddressMutationResponse; /** Add a public key to a wallet address that is used to verify Open Payments requests. */ @@ -731,6 +751,8 @@ export type Mutation = { deleteAsset: DeleteAssetMutationResponse; /** Delete a peer. */ deletePeer: DeletePeerMutationResponse; + /** Delete a tenant. */ + deleteTenant: DeleteTenantMutationResponse; /** Deposit asset liquidity. */ depositAssetLiquidity?: Maybe; /** @@ -756,6 +778,8 @@ export type Mutation = { updateIncomingPayment: IncomingPaymentResponse; /** Update an existing peer. */ updatePeer: UpdatePeerMutationResponse; + /** Update a tenant. */ + updateTenant: TenantMutationResponse; /** Update an existing wallet address. */ updateWalletAddress: UpdateWalletAddressMutationResponse; /** Void liquidity withdrawal. Withdrawals are two-phase commits and are rolled back via this mutation. */ @@ -843,6 +867,11 @@ export type MutationCreateReceiverArgs = { }; +export type MutationCreateTenantArgs = { + input: CreateTenantInput; +}; + + export type MutationCreateWalletAddressArgs = { input: CreateWalletAddressInput; }; @@ -868,6 +897,11 @@ export type MutationDeletePeerArgs = { }; +export type MutationDeleteTenantArgs = { + id: Scalars['String']['input']; +}; + + export type MutationDepositAssetLiquidityArgs = { input: DepositAssetLiquidityInput; }; @@ -923,6 +957,11 @@ export type MutationUpdatePeerArgs = { }; +export type MutationUpdateTenantArgs = { + input: UpdateTenantInput; +}; + + export type MutationUpdateWalletAddressArgs = { input: UpdateWalletAddressInput; }; @@ -1150,6 +1189,10 @@ export type Query = { quote?: Maybe; /** Retrieve an Open Payments incoming payment by receiver ID. The receiver's wallet address can be hosted on this server or a remote Open Payments resource server. */ receiver?: Maybe; + /** Retrieve a tenant of the instance. */ + tenant: Tenant; + /** As an operator, fetch a paginated list of tenants on the instance. */ + tenants: TenantsConnection; /** Fetch a wallet address by its ID. */ walletAddress?: Maybe; /** Get a wallet address by its url if it exists */ @@ -1158,6 +1201,8 @@ export type Query = { walletAddresses: WalletAddressesConnection; /** Fetch a paginated list of webhook events. */ webhookEvents: WebhookEventsConnection; + /** Determine if the requester has operator permissions */ + whoami: WhoamiResponse; }; @@ -1247,6 +1292,20 @@ export type QueryReceiverArgs = { }; +export type QueryTenantArgs = { + id: Scalars['String']['input']; +}; + + +export type QueryTenantsArgs = { + after?: InputMaybe; + before?: InputMaybe; + first?: InputMaybe; + last?: InputMaybe; + sortOrder?: InputMaybe; +}; + + export type QueryWalletAddressArgs = { id: Scalars['String']['input']; }; @@ -1376,6 +1435,47 @@ export enum SortOrder { Desc = 'DESC' } +export type Tenant = Model & { + __typename?: 'Tenant'; + /** Secret used to secure requests made for this tenant. */ + apiSecret: Scalars['String']['output']; + /** The date and time that this tenant was created. */ + createdAt: Scalars['String']['output']; + /** The date and time that this tenant was deleted. */ + deletedAt?: Maybe; + /** Contact email of the tenant owner. */ + email?: Maybe; + /** Unique identifier of the tenant. */ + id: Scalars['ID']['output']; + /** URL of the tenant's identity provider's consent screen. */ + idpConsentUrl?: Maybe; + /** Secret used to secure requests from the tenant's identity provider. */ + idpSecret?: Maybe; + /** Public name for the tenant. */ + publicName?: Maybe; +}; + +export type TenantEdge = { + __typename?: 'TenantEdge'; + /** A cursor for paginating through the tenants. */ + cursor: Scalars['String']['output']; + /** A tenant node in the list. */ + node: Tenant; +}; + +export type TenantMutationResponse = { + __typename?: 'TenantMutationResponse'; + tenant: Tenant; +}; + +export type TenantsConnection = { + __typename?: 'TenantsConnection'; + /** A list of edges representing tenants and cursors for pagination. */ + edges: Array; + /** Information to aid in pagination. */ + pageInfo: PageInfo; +}; + export enum TransferState { /** The accounting transfer is pending */ Pending = 'PENDING', @@ -1448,6 +1548,21 @@ export type UpdatePeerMutationResponse = { peer?: Maybe; }; +export type UpdateTenantInput = { + /** Secret used to secure requests made for this tenant. */ + apiSecret?: InputMaybe; + /** Contact email of the tenant owner. */ + email?: InputMaybe; + /** Unique identifier of the tenant. */ + id: Scalars['ID']['input']; + /** URL of the tenant's identity provider's consent screen. */ + idpConsentUrl?: InputMaybe; + /** Secret used to secure requests from the tenant's identity provider. */ + idpSecret?: InputMaybe; + /** Public name for the tenant. */ + publicName?: InputMaybe; +}; + export type UpdateWalletAddressInput = { /** Additional properties associated with this wallet address. */ additionalProperties?: InputMaybe>; @@ -1640,6 +1755,12 @@ export type WebhookEventsEdge = { node: WebhookEvent; }; +export type WhoamiResponse = { + __typename?: 'WhoamiResponse'; + id: Scalars['String']['output']; + isOperator: Scalars['Boolean']['output']; +}; + export type WithdrawEventLiquidityInput = { /** Unique identifier of the event to withdraw liquidity from. */ eventId: Scalars['String']['input']; @@ -1718,7 +1839,7 @@ export type DirectiveResolverFn> = { BasePayment: ( Partial ) | ( Partial ) | ( Partial ); - Model: ( Partial ) | ( Partial ) | ( Partial ) | ( Partial ) | ( Partial ) | ( Partial ) | ( Partial ) | ( Partial ) | ( Partial ) | ( Partial ); + Model: ( Partial ) | ( Partial ) | ( Partial ) | ( Partial ) | ( Partial ) | ( Partial ) | ( Partial ) | ( Partial ) | ( Partial ) | ( Partial ) | ( Partial ); }; /** Mapping between all available schema types and the resolvers types */ @@ -1756,6 +1877,7 @@ export type ResolversTypes = { CreateQuoteInput: ResolverTypeWrapper>; CreateReceiverInput: ResolverTypeWrapper>; CreateReceiverResponse: ResolverTypeWrapper>; + CreateTenantInput: ResolverTypeWrapper>; CreateWalletAddressInput: ResolverTypeWrapper>; CreateWalletAddressKeyInput: ResolverTypeWrapper>; CreateWalletAddressKeyMutationResponse: ResolverTypeWrapper>; @@ -1766,6 +1888,7 @@ export type ResolversTypes = { DeleteAssetMutationResponse: ResolverTypeWrapper>; DeletePeerInput: ResolverTypeWrapper>; DeletePeerMutationResponse: ResolverTypeWrapper>; + DeleteTenantMutationResponse: ResolverTypeWrapper>; DepositAssetLiquidityInput: ResolverTypeWrapper>; DepositEventLiquidityInput: ResolverTypeWrapper>; DepositOutgoingPaymentLiquidityInput: ResolverTypeWrapper>; @@ -1825,6 +1948,10 @@ export type ResolversTypes = { SetFeeResponse: ResolverTypeWrapper>; SortOrder: ResolverTypeWrapper>; String: ResolverTypeWrapper>; + Tenant: ResolverTypeWrapper>; + TenantEdge: ResolverTypeWrapper>; + TenantMutationResponse: ResolverTypeWrapper>; + TenantsConnection: ResolverTypeWrapper>; TransferState: ResolverTypeWrapper>; TransferType: ResolverTypeWrapper>; TriggerWalletAddressEventsInput: ResolverTypeWrapper>; @@ -1835,6 +1962,7 @@ export type ResolversTypes = { UpdateIncomingPaymentInput: ResolverTypeWrapper>; UpdatePeerInput: ResolverTypeWrapper>; UpdatePeerMutationResponse: ResolverTypeWrapper>; + UpdateTenantInput: ResolverTypeWrapper>; UpdateWalletAddressInput: ResolverTypeWrapper>; UpdateWalletAddressMutationResponse: ResolverTypeWrapper>; VoidLiquidityWithdrawalInput: ResolverTypeWrapper>; @@ -1851,6 +1979,7 @@ export type ResolversTypes = { WebhookEventFilter: ResolverTypeWrapper>; WebhookEventsConnection: ResolverTypeWrapper>; WebhookEventsEdge: ResolverTypeWrapper>; + WhoamiResponse: ResolverTypeWrapper>; WithdrawEventLiquidityInput: ResolverTypeWrapper>; }; @@ -1888,6 +2017,7 @@ export type ResolversParentTypes = { CreateQuoteInput: Partial; CreateReceiverInput: Partial; CreateReceiverResponse: Partial; + CreateTenantInput: Partial; CreateWalletAddressInput: Partial; CreateWalletAddressKeyInput: Partial; CreateWalletAddressKeyMutationResponse: Partial; @@ -1897,6 +2027,7 @@ export type ResolversParentTypes = { DeleteAssetMutationResponse: Partial; DeletePeerInput: Partial; DeletePeerMutationResponse: Partial; + DeleteTenantMutationResponse: Partial; DepositAssetLiquidityInput: Partial; DepositEventLiquidityInput: Partial; DepositOutgoingPaymentLiquidityInput: Partial; @@ -1949,6 +2080,10 @@ export type ResolversParentTypes = { SetFeeInput: Partial; SetFeeResponse: Partial; String: Partial; + Tenant: Partial; + TenantEdge: Partial; + TenantMutationResponse: Partial; + TenantsConnection: Partial; TriggerWalletAddressEventsInput: Partial; TriggerWalletAddressEventsMutationResponse: Partial; UInt8: Partial; @@ -1957,6 +2092,7 @@ export type ResolversParentTypes = { UpdateIncomingPaymentInput: Partial; UpdatePeerInput: Partial; UpdatePeerMutationResponse: Partial; + UpdateTenantInput: Partial; UpdateWalletAddressInput: Partial; UpdateWalletAddressMutationResponse: Partial; VoidLiquidityWithdrawalInput: Partial; @@ -1972,6 +2108,7 @@ export type ResolversParentTypes = { WebhookEventFilter: Partial; WebhookEventsConnection: Partial; WebhookEventsEdge: Partial; + WhoamiResponse: Partial; WithdrawEventLiquidityInput: Partial; }; @@ -2093,6 +2230,11 @@ export type DeletePeerMutationResponseResolvers; }; +export type DeleteTenantMutationResponseResolvers = { + success?: Resolver; + __isTypeOf?: IsTypeOfResolverFn; +}; + export type FeeResolvers = { assetId?: Resolver; basisPoints?: Resolver; @@ -2176,7 +2318,7 @@ export type LiquidityMutationResponseResolvers = { - __resolveType: TypeResolveFn<'AccountingTransfer' | 'Asset' | 'Fee' | 'IncomingPayment' | 'OutgoingPayment' | 'Payment' | 'Peer' | 'WalletAddress' | 'WalletAddressKey' | 'WebhookEvent', ParentType, ContextType>; + __resolveType: TypeResolveFn<'AccountingTransfer' | 'Asset' | 'Fee' | 'IncomingPayment' | 'OutgoingPayment' | 'Payment' | 'Peer' | 'Tenant' | 'WalletAddress' | 'WalletAddressKey' | 'WebhookEvent', ParentType, ContextType>; createdAt?: Resolver; id?: Resolver; }; @@ -2197,11 +2339,13 @@ export type MutationResolvers, ParentType, ContextType, RequireFields>; createQuote?: Resolver>; createReceiver?: Resolver>; + createTenant?: Resolver>; createWalletAddress?: Resolver>; createWalletAddressKey?: Resolver, ParentType, ContextType, RequireFields>; createWalletAddressWithdrawal?: Resolver, ParentType, ContextType, RequireFields>; deleteAsset?: Resolver>; deletePeer?: Resolver>; + deleteTenant?: Resolver>; depositAssetLiquidity?: Resolver, ParentType, ContextType, RequireFields>; depositEventLiquidity?: Resolver, ParentType, ContextType, RequireFields>; depositOutgoingPaymentLiquidity?: Resolver, ParentType, ContextType, RequireFields>; @@ -2213,6 +2357,7 @@ export type MutationResolvers>; updateIncomingPayment?: Resolver>; updatePeer?: Resolver>; + updateTenant?: Resolver>; updateWalletAddress?: Resolver>; voidLiquidityWithdrawal?: Resolver, ParentType, ContextType, RequireFields>; withdrawEventLiquidity?: Resolver, ParentType, ContextType, RequireFields>; @@ -2325,10 +2470,13 @@ export type QueryResolvers>; quote?: Resolver, ParentType, ContextType, RequireFields>; receiver?: Resolver, ParentType, ContextType, RequireFields>; + tenant?: Resolver>; + tenants?: Resolver>; walletAddress?: Resolver, ParentType, ContextType, RequireFields>; walletAddressByUrl?: Resolver, ParentType, ContextType, RequireFields>; walletAddresses?: Resolver>; webhookEvents?: Resolver>; + whoami?: Resolver; }; export type QuoteResolvers = { @@ -2383,6 +2531,35 @@ export type SetFeeResponseResolvers; }; +export type TenantResolvers = { + apiSecret?: Resolver; + createdAt?: Resolver; + deletedAt?: Resolver, ParentType, ContextType>; + email?: Resolver, ParentType, ContextType>; + id?: Resolver; + idpConsentUrl?: Resolver, ParentType, ContextType>; + idpSecret?: Resolver, ParentType, ContextType>; + publicName?: Resolver, ParentType, ContextType>; + __isTypeOf?: IsTypeOfResolverFn; +}; + +export type TenantEdgeResolvers = { + cursor?: Resolver; + node?: Resolver; + __isTypeOf?: IsTypeOfResolverFn; +}; + +export type TenantMutationResponseResolvers = { + tenant?: Resolver; + __isTypeOf?: IsTypeOfResolverFn; +}; + +export type TenantsConnectionResolvers = { + edges?: Resolver, ParentType, ContextType>; + pageInfo?: Resolver; + __isTypeOf?: IsTypeOfResolverFn; +}; + export type TriggerWalletAddressEventsMutationResponseResolvers = { count?: Resolver, ParentType, ContextType>; __isTypeOf?: IsTypeOfResolverFn; @@ -2487,6 +2664,12 @@ export type WebhookEventsEdgeResolvers; }; +export type WhoamiResponseResolvers = { + id?: Resolver; + isOperator?: Resolver; + __isTypeOf?: IsTypeOfResolverFn; +}; + export type Resolvers = { AccountingTransfer?: AccountingTransferResolvers; AccountingTransferConnection?: AccountingTransferConnectionResolvers; @@ -2506,6 +2689,7 @@ export type Resolvers = { CreateWalletAddressMutationResponse?: CreateWalletAddressMutationResponseResolvers; DeleteAssetMutationResponse?: DeleteAssetMutationResponseResolvers; DeletePeerMutationResponse?: DeletePeerMutationResponseResolvers; + DeleteTenantMutationResponse?: DeleteTenantMutationResponseResolvers; Fee?: FeeResolvers; FeeEdge?: FeeEdgeResolvers; FeesConnection?: FeesConnectionResolvers; @@ -2539,6 +2723,10 @@ export type Resolvers = { Receiver?: ReceiverResolvers; RevokeWalletAddressKeyMutationResponse?: RevokeWalletAddressKeyMutationResponseResolvers; SetFeeResponse?: SetFeeResponseResolvers; + Tenant?: TenantResolvers; + TenantEdge?: TenantEdgeResolvers; + TenantMutationResponse?: TenantMutationResponseResolvers; + TenantsConnection?: TenantsConnectionResolvers; TriggerWalletAddressEventsMutationResponse?: TriggerWalletAddressEventsMutationResponseResolvers; UInt8?: GraphQLScalarType; UInt64?: GraphQLScalarType; @@ -2555,5 +2743,6 @@ export type Resolvers = { WebhookEvent?: WebhookEventResolvers; WebhookEventsConnection?: WebhookEventsConnectionResolvers; WebhookEventsEdge?: WebhookEventsEdgeResolvers; + WhoamiResponse?: WhoamiResponseResolvers; }; diff --git a/packages/backend/src/graphql/resolvers/index.ts b/packages/backend/src/graphql/resolvers/index.ts index 2c191b30e8..343c07a475 100644 --- a/packages/backend/src/graphql/resolvers/index.ts +++ b/packages/backend/src/graphql/resolvers/index.ts @@ -77,6 +77,14 @@ import { GraphQLJSONObject } from 'graphql-scalars' import { getCombinedPayments } from './combined_payments' import { createOrUpdatePeerByUrl } from './auto-peering' import { getAccountingTransfers } from './accounting_transfer' +import { + whoami, + createTenant, + updateTenant, + deleteTenant, + getTenant, + getTenants +} from './tenant' export const resolvers: Resolvers = { UInt8: GraphQLUInt8, @@ -92,6 +100,7 @@ export const resolvers: Resolvers = { liquidity: getPeerLiquidity }, Query: { + whoami, walletAddress: getWalletAddress, walletAddressByUrl: getWalletAddressByUrl, walletAddresses: getWalletAddresses, @@ -108,7 +117,9 @@ export const resolvers: Resolvers = { webhookEvents: getWebhookEvents, payments: getCombinedPayments, accountingTransfers: getAccountingTransfers, - receiver: getReceiver + receiver: getReceiver, + tenant: getTenant, + tenants: getTenants }, WalletAddress: { liquidity: getWalletAddressLiquidity, @@ -161,6 +172,9 @@ export const resolvers: Resolvers = { createIncomingPaymentWithdrawal, createOutgoingPaymentWithdrawal, setFee, - updateIncomingPayment + updateIncomingPayment, + createTenant, + updateTenant, + deleteTenant } } diff --git a/packages/backend/src/graphql/resolvers/tenant.test.ts b/packages/backend/src/graphql/resolvers/tenant.test.ts new file mode 100644 index 0000000000..177ed85015 --- /dev/null +++ b/packages/backend/src/graphql/resolvers/tenant.test.ts @@ -0,0 +1,503 @@ +import { IocContract } from '@adonisjs/fold' +import { AppServices } from '../../app' +import { createTestApp, TestContainer } from '../../tests/app' +import { + DeleteTenantMutationResponse, + Tenant, + TenantMutationResponse, + TenantsConnection, + WhoamiResponse +} from '../generated/graphql' +import { initIocContainer } from '../..' +import { Config, IAppConfig } from '../../config/app' +import { createTenant, generateTenantInput } from '../../tests/tenant' +import { ApolloError, gql, NormalizedCacheObject } from '@apollo/client' +import { getPageTests } from './page.test' +import { truncateTables } from '../../tests/tableManager' +import { + createHttpLink, + ApolloLink, + ApolloClient, + InMemoryCache +} from '@apollo/client' +import { setContext } from '@apollo/client/link/context' +import { GraphQLErrorCode } from '../errors' +import { Tenant as TenantModel } from '../../tenants/model' + +function createTenantedApolloClient( + appContainer: TestContainer, + tenantId: string +): ApolloClient { + const httpLink = createHttpLink({ + uri: `http://localhost:${appContainer.app.getAdminPort()}/graphql`, + fetch + }) + const authLink = setContext((_, { headers }) => { + return { + headers: { + ...headers, + 'tenant-id': tenantId + } + } + }) + + const link = ApolloLink.from([authLink, httpLink]) + + return new ApolloClient({ + cache: new InMemoryCache({}), + link: link, + defaultOptions: { + query: { + fetchPolicy: 'no-cache' + }, + mutate: { + fetchPolicy: 'no-cache' + }, + watchQuery: { + fetchPolicy: 'no-cache' + } + } + }) +} + +describe('Tenant Resolvers', (): void => { + let deps: IocContract + let appContainer: TestContainer + let config: IAppConfig + + beforeAll(async (): Promise => { + deps = await initIocContainer({ + ...Config, + dbSchema: 'tenant_service_test_schema' + }) + appContainer = await createTestApp(deps) + config = await deps.use('config') + const authServiceClient = await deps.use('authServiceClient') + jest + .spyOn(authServiceClient.tenant, 'create') + .mockImplementation(async () => undefined) + jest + .spyOn(authServiceClient.tenant, 'update') + .mockImplementation(async () => undefined) + jest + .spyOn(authServiceClient.tenant, 'delete') + .mockImplementation(async () => undefined) + }) + + afterEach(async (): Promise => { + await truncateTables(appContainer.knex, true) + }) + afterAll(async (): Promise => { + await appContainer.apolloClient.stop() + await appContainer.shutdown() + }) + + describe('whoami', (): void => { + test.each` + isOperator | description + ${true} | ${'operator'} + ${false} | ${'tenant'} + `('whoami query as $description', async ({ isOperator }): Promise => { + const tenant = await createTenant(deps) + const client = isOperator + ? appContainer.apolloClient + : createTenantedApolloClient(appContainer, tenant.id) + + const result = await client + .query({ + query: gql` + query Whoami { + whoami { + id + isOperator + } + } + ` + }) + .then((query): WhoamiResponse => query.data?.whoami) + + expect(result).toEqual({ + id: isOperator ? config.operatorTenantId : tenant.id, + isOperator, + __typename: 'WhoamiResponse' + }) + }) + }) + + describe('Query.tenant', (): void => { + describe('page tests', (): void => { + getPageTests({ + getClient: () => appContainer.apolloClient, + createModel: () => createTenant(deps), + pagedQuery: 'tenants' + }) + + test('Cannot get page as non-operator', async (): Promise => { + const tenant = await createTenant(deps) + const apolloClient = createTenantedApolloClient(appContainer, tenant.id) + try { + expect.assertions(2) + await apolloClient + .query({ + query: gql` + query GetTenants { + tenants { + edges { + node { + id + } + } + } + } + ` + }) + .then((query): TenantsConnection => query.data?.tenants) + } catch (error) { + expect(error).toBeInstanceOf(ApolloError) + expect((error as ApolloError).graphQLErrors).toContainEqual( + expect.objectContaining({ + message: 'cannot get tenants page', + extensions: expect.objectContaining({ + code: GraphQLErrorCode.Forbidden + }) + }) + ) + } + }) + }) + + test('can get tenant as operator', async (): Promise => { + const tenant = await createTenant(deps) + + const query = await appContainer.apolloClient + .query({ + query: gql` + query Tenant($id: String!) { + tenant(id: $id) { + id + email + } + } + `, + variables: { + id: tenant.id + } + }) + .then((query): Tenant => query.data?.tenant) + + expect(query).toEqual({ + id: tenant.id, + email: tenant.email, + __typename: 'Tenant' + }) + }) + + test('can get own tenant', async (): Promise => { + const tenant = await createTenant(deps) + const apolloClient = createTenantedApolloClient(appContainer, tenant.id) + + const query = await apolloClient + .query({ + query: gql` + query Tenant($id: String!) { + tenant(id: $id) { + id + email + } + } + `, + variables: { + id: tenant.id + } + }) + .then((query): Tenant => query.data?.tenant) + + expect(query).toEqual({ + id: tenant.id, + email: tenant.email, + __typename: 'Tenant' + }) + }) + + test('cannot get other tenant as non-operator', async (): Promise => { + const firstTenant = await createTenant(deps) + const secondTenant = await createTenant(deps) + + const apolloClient = createTenantedApolloClient( + appContainer, + firstTenant.id + ) + + try { + expect.assertions(2) + await apolloClient + .query({ + query: gql` + query Tenant($id: String!) { + tenant(id: $id) { + id + email + } + } + `, + variables: { + id: secondTenant.id + } + }) + .then((query): Tenant => query.data?.tenant) + } catch (error) { + expect(error).toBeInstanceOf(ApolloError) + expect((error as ApolloError).graphQLErrors).toContainEqual( + expect.objectContaining({ + message: 'tenant does not exist', + extensions: expect.objectContaining({ + code: GraphQLErrorCode.NotFound + }) + }) + ) + } + }) + }) + + describe('Mutations', (): void => { + describe('Create', (): void => { + test('can create a tenant', async (): Promise => { + const input = generateTenantInput() + + const mutation = await appContainer.apolloClient + .mutate({ + mutation: gql` + mutation CreateTenant($input: CreateTenantInput!) { + createTenant(input: $input) { + tenant { + id + email + apiSecret + idpConsentUrl + idpSecret + publicName + } + } + } + `, + variables: { + input + } + }) + .then((query): TenantMutationResponse => query.data?.createTenant) + + expect(mutation.tenant).toEqual({ + ...input, + id: expect.any(String), + __typename: 'Tenant' + }) + }) + + test('cannot create tenant as non-operator', async (): Promise => { + const input = generateTenantInput() + const tenant = await createTenant(deps) + const apolloClient = createTenantedApolloClient(appContainer, tenant.id) + + try { + expect.assertions(2) + await apolloClient + .mutate({ + mutation: gql` + mutation CreateTenant($input: CreateTenantInput!) { + createTenant(input: $input) { + tenant { + id + email + apiSecret + idpConsentUrl + idpSecret + publicName + } + } + } + `, + variables: { + input + } + }) + .then((query): TenantMutationResponse => query.data?.createTenant) + } catch (error) { + expect(error).toBeInstanceOf(ApolloError) + expect((error as ApolloError).graphQLErrors).toContainEqual( + expect.objectContaining({ + message: 'permission denied', + extensions: expect.objectContaining({ + code: GraphQLErrorCode.Forbidden + }) + }) + ) + } + }) + }) + describe('Update', (): void => { + let tenantedApolloClient: ApolloClient + let tenant: TenantModel + beforeEach(async (): Promise => { + tenant = await createTenant(deps) + tenantedApolloClient = createTenantedApolloClient( + appContainer, + tenant.id + ) + }) + + afterEach(async (): Promise => { + await truncateTables(appContainer.knex) + }) + + test.each` + isOperator | description + ${true} | ${'operator'} + ${false} | ${'tenant'} + `( + 'can update a tenant as $description', + async ({ isOperator }): Promise => { + const client = isOperator + ? appContainer.apolloClient + : tenantedApolloClient + const updateInput = { + ...generateTenantInput(), + id: tenant.id + } + + const mutation = await client + .mutate({ + mutation: gql` + mutation UpdateTenant($input: UpdateTenantInput!) { + updateTenant(input: $input) { + tenant { + id + email + apiSecret + idpConsentUrl + idpSecret + publicName + } + } + } + `, + variables: { + input: updateInput + } + }) + .then((query): TenantMutationResponse => query.data?.updateTenant) + + expect(mutation.tenant).toEqual({ + ...updateInput, + __typename: 'Tenant' + }) + } + ) + test('Cannot update other tenant as non-operator', async (): Promise => { + const firstTenant = await createTenant(deps) + const secondTenant = await createTenant(deps) + + const updateInput = { + ...generateTenantInput(), + id: secondTenant.id + } + const client = createTenantedApolloClient(appContainer, firstTenant.id) + try { + expect.assertions(2) + await client + .mutate({ + mutation: gql` + mutation UpdateTenant($input: UpdateTenantInput!) { + updateTenant(input: $input) { + tenant { + id + email + apiSecret + idpConsentUrl + idpSecret + publicName + } + } + } + `, + variables: { + input: updateInput + } + }) + .then((query): TenantMutationResponse => query.data?.updateTenant) + } catch (error) { + expect(error).toBeInstanceOf(ApolloError) + expect((error as ApolloError).graphQLErrors).toContainEqual( + expect.objectContaining({ + message: 'tenant does not exist', + extensions: expect.objectContaining({ + code: GraphQLErrorCode.NotFound + }) + }) + ) + } + }) + }) + + describe('Delete', (): void => { + test('Can delete a tenant as operator', async (): Promise => { + const tenant = await createTenant(deps) + + const mutation = await appContainer.apolloClient + .mutate({ + mutation: gql` + mutation DeleteTenant($id: String!) { + deleteTenant(id: $id) { + success + } + } + `, + variables: { + id: tenant.id + } + }) + .then( + (query): DeleteTenantMutationResponse => query.data?.deleteTenant + ) + + expect(mutation.success).toBe(true) + }) + + test('Cannot delete tenant as non-operator', async (): Promise => { + const firstTenant = await createTenant(deps) + const secondTenant = await createTenant(deps) + + const client = createTenantedApolloClient(appContainer, secondTenant.id) + + try { + expect.assertions(2) + await client + .mutate({ + mutation: gql` + mutation DeleteTenant($id: String!) { + deleteTenant(id: $id) { + success + } + } + `, + variables: { + id: firstTenant.id + } + }) + .then( + (query): DeleteTenantMutationResponse => query.data?.deleteTenant + ) + } catch (error) { + expect(error).toBeInstanceOf(ApolloError) + expect((error as ApolloError).graphQLErrors).toContainEqual( + expect.objectContaining({ + message: 'permission denied', + extensions: expect.objectContaining({ + code: GraphQLErrorCode.Forbidden + }) + }) + ) + } + }) + }) + }) +}) diff --git a/packages/backend/src/graphql/resolvers/tenant.ts b/packages/backend/src/graphql/resolvers/tenant.ts new file mode 100644 index 0000000000..19e86bd077 --- /dev/null +++ b/packages/backend/src/graphql/resolvers/tenant.ts @@ -0,0 +1,179 @@ +import { GraphQLError } from 'graphql' +import { TenantedApolloContext } from '../../app' +import { + MutationResolvers, + QueryResolvers, + ResolversTypes, + Tenant as SchemaTenant +} from '../generated/graphql' +import { GraphQLErrorCode } from '../errors' +import { Tenant } from '../../tenants/model' +import { Pagination, SortOrder } from '../../shared/baseModel' +import { getPageInfo } from '../../shared/pagination' + +export const whoami: QueryResolvers['whoami'] = async ( + parent, + args, + ctx +): Promise => { + const { tenant, isOperator } = ctx + + return { + id: tenant.id, + isOperator + } +} + +export const getTenant: QueryResolvers['tenant'] = + async (parent, args, ctx): Promise => { + const { tenant: contextTenant, isOperator } = ctx + + // TODO: make this a util + // If the tenant that was authorized in the request is not the tenant being requested, + // or the requester is not the operator, return not found + if (args.id !== contextTenant.id && !isOperator) { + throw new GraphQLError('tenant does not exist', { + extensions: { + code: GraphQLErrorCode.NotFound + } + }) + } + + const tenantService = await ctx.container.use('tenantService') + const tenant = await tenantService.get(args.id) + if (!tenant) { + throw new GraphQLError('tenant does not exist', { + extensions: { + code: GraphQLErrorCode.NotFound + } + }) + } + + return tenantToGraphQl(tenant) + } + +export const getTenants: QueryResolvers['tenants'] = + async (parent, args, ctx): Promise => { + const { isOperator } = ctx + if (!isOperator) { + throw new GraphQLError('cannot get tenants page', { + extensions: { + code: GraphQLErrorCode.Forbidden + } + }) + } + + const { sortOrder, ...pagination } = args + const order = sortOrder === 'ASC' ? SortOrder.Asc : SortOrder.Desc + const tenantService = await ctx.container.use('tenantService') + + const tenants = await tenantService.getPage(pagination, order) + + const pageInfo = await getPageInfo({ + getPage: (pagination: Pagination, sortOrder?: SortOrder) => + tenantService.getPage(pagination, sortOrder), + page: tenants, + sortOrder: order + }) + return { + pageInfo, + edges: tenants.map((tenant: Tenant) => ({ + cursor: tenant.id, + node: tenantToGraphQl(tenant) + })) + } + } + +export const createTenant: MutationResolvers['createTenant'] = + async ( + parent, + args, + ctx + ): Promise => { + // createTenant is an operator-only resolver + const { isOperator } = ctx + if (!isOperator) { + throw new GraphQLError('permission denied', { + extensions: { + code: GraphQLErrorCode.Forbidden + } + }) + } + + const tenantService = await ctx.container.use('tenantService') + const tenant = await tenantService.create(args.input) + + return { tenant: tenantToGraphQl(tenant) } + } + +export const updateTenant: MutationResolvers['updateTenant'] = + async ( + parent, + args, + ctx + ): Promise => { + const { tenant: contextTenant, isOperator } = ctx + // TODO: make this a util + if (args.input.id !== contextTenant.id && !isOperator) { + throw new GraphQLError('tenant does not exist', { + extensions: { + code: GraphQLErrorCode.NotFound + } + }) + } + + const tenantService = await ctx.container.use('tenantService') + try { + const updatedTenant = await tenantService.update(args.input) + return { tenant: tenantToGraphQl(updatedTenant) } + } catch (err) { + throw new GraphQLError('failed to update tenant', { + extensions: { + code: GraphQLErrorCode.NotFound + } + }) + } + } + +export const deleteTenant: MutationResolvers['deleteTenant'] = + async ( + parent, + args, + ctx + ): Promise => { + const { isOperator } = ctx + if (!isOperator) { + throw new GraphQLError('permission denied', { + extensions: { + code: GraphQLErrorCode.Forbidden + } + }) + } + + const tenantService = await ctx.container.use('tenantService') + try { + await tenantService.delete(args.id) + return { success: true } + } catch (err) { + throw new GraphQLError('failed to delete tenant', { + extensions: { + code: GraphQLErrorCode.NotFound + } + }) + } + } + +export function tenantToGraphQl(tenant: Tenant): SchemaTenant { + return { + id: tenant.id, + email: tenant.email, + apiSecret: tenant.apiSecret, + idpConsentUrl: tenant.idpConsentUrl, + idpSecret: tenant.idpSecret, + publicName: tenant.publicName, + createdAt: new Date(+tenant.createdAt).toISOString(), + deletedAt: tenant.deletedAt + ? new Date(+tenant.deletedAt).toISOString() + : null + } +} diff --git a/packages/backend/src/graphql/schema.graphql b/packages/backend/src/graphql/schema.graphql index f8286e14d4..fe1e4354c5 100644 --- a/packages/backend/src/graphql/schema.graphql +++ b/packages/backend/src/graphql/schema.graphql @@ -148,6 +148,26 @@ type Query { "Unique identifier of the receiver (incoming payment URL)." id: String! ): Receiver + + "Retrieve a tenant of the instance." + tenant("Unique identifier of the tenant." id: String!): Tenant! + + "As an operator, fetch a paginated list of tenants on the instance." + tenants( + "Forward pagination: Cursor (tenant ID) to start retrieving tenants after this point." + after: String + "Backward pagination: Cursor (tenant ID) to start retrieving tenants before this point." + before: String + "Forward pagination: Limit the result to the first **n** tenants after the `after` cursor." + first: Int + "Backward pagination: Limit the result to the last **n** tenants before the `before` cursor." + last: Int + "Specify the sort order of tenants based on their creation date, either ascending or descending." + sortOrder: SortOrder + ): TenantsConnection! + + "Determine if the requester has operator permissions" + whoami: WhoamiResponse! } type Mutation { @@ -306,6 +326,15 @@ type Mutation { cancelIncomingPayment( input: CancelIncomingPaymentInput! ): CancelIncomingPaymentResponse! + + "As an operator, create a tenant." + createTenant(input: CreateTenantInput!): TenantMutationResponse! + + "Update a tenant." + updateTenant(input: UpdateTenantInput!): TenantMutationResponse! + + "Delete a tenant." + deleteTenant(id: String!): DeleteTenantMutationResponse! } type PageInfo { @@ -319,6 +348,11 @@ type PageInfo { startCursor: String } +type WhoamiResponse { + id: String! + isOperator: Boolean! +} + type AssetsConnection { "Information to aid in pagination." pageInfo: PageInfo! @@ -1493,6 +1527,75 @@ type CancelIncomingPaymentResponse { payment: IncomingPayment } +type Tenant implements Model { + "Unique identifier of the tenant." + id: ID! + "Contact email of the tenant owner." + email: String + "Secret used to secure requests made for this tenant." + apiSecret: String! + "URL of the tenant's identity provider's consent screen." + idpConsentUrl: String + "Secret used to secure requests from the tenant's identity provider." + idpSecret: String + "Public name for the tenant." + publicName: String + "The date and time that this tenant was created." + createdAt: String! + "The date and time that this tenant was deleted." + deletedAt: String +} + +type TenantsConnection { + "Information to aid in pagination." + pageInfo: PageInfo! + "A list of edges representing tenants and cursors for pagination." + edges: [TenantEdge!]! +} + +type TenantEdge { + "A tenant node in the list." + node: Tenant! + "A cursor for paginating through the tenants." + cursor: String! +} + +input CreateTenantInput { + "Contact email of the tenant owner." + email: String + "Secret used to secure requests made for this tenant." + apiSecret: String! + "URL of the tenant's identity provider's consent screen." + idpConsentUrl: String + "Secret used to secure requests from the tenant's identity provider." + idpSecret: String + "Public name for the tenant." + publicName: String +} + +input UpdateTenantInput { + "Unique identifier of the tenant." + id: ID! + "Contact email of the tenant owner." + email: String + "Secret used to secure requests made for this tenant." + apiSecret: String + "URL of the tenant's identity provider's consent screen." + idpConsentUrl: String + "Secret used to secure requests from the tenant's identity provider." + idpSecret: String + "Public name for the tenant." + publicName: String +} + +type TenantMutationResponse { + tenant: Tenant! +} + +type DeleteTenantMutationResponse { + success: Boolean! +} + """ The `UInt8` scalar type represents unsigned 8-bit whole numeric values, ranging from 0 to 255. """ diff --git a/packages/backend/src/tenants/service.ts b/packages/backend/src/tenants/service.ts index 947881619f..11eedc73ca 100644 --- a/packages/backend/src/tenants/service.ts +++ b/packages/backend/src/tenants/service.ts @@ -60,10 +60,10 @@ async function getTenantPage( } interface CreateTenantOptions { - email: string + email?: string apiSecret: string - idpSecret: string - idpConsentUrl: string + idpSecret?: string + idpConsentUrl?: string publicName?: string } diff --git a/packages/backend/src/tests/tenant.ts b/packages/backend/src/tests/tenant.ts index 579735b73d..d8874b4573 100644 --- a/packages/backend/src/tests/tenant.ts +++ b/packages/backend/src/tests/tenant.ts @@ -11,6 +11,16 @@ interface CreateOptions { idpSecret: string } +export function generateTenantInput() { + return { + email: faker.internet.email(), + apiSecret: faker.string.alphanumeric(8), + idpConsentUrl: faker.internet.url(), + idpSecret: faker.string.alphanumeric(8), + publicName: faker.company.name() + } +} + export async function createTenant( deps: IocContract, options?: CreateOptions diff --git a/packages/frontend/app/generated/graphql.ts b/packages/frontend/app/generated/graphql.ts index 1116595860..b8fd92871d 100644 --- a/packages/frontend/app/generated/graphql.ts +++ b/packages/frontend/app/generated/graphql.ts @@ -364,6 +364,19 @@ export type CreateReceiverResponse = { receiver?: Maybe; }; +export type CreateTenantInput = { + /** Secret used to secure requests made for this tenant. */ + apiSecret: Scalars['String']['input']; + /** Contact email of the tenant owner. */ + email?: InputMaybe; + /** URL of the tenant's identity provider's consent screen. */ + idpConsentUrl?: InputMaybe; + /** Secret used to secure requests from the tenant's identity provider. */ + idpSecret?: InputMaybe; + /** Public name for the tenant. */ + publicName?: InputMaybe; +}; + export type CreateWalletAddressInput = { /** Additional properties associated with the wallet address. */ additionalProperties?: InputMaybe>; @@ -440,6 +453,11 @@ export type DeletePeerMutationResponse = { success: Scalars['Boolean']['output']; }; +export type DeleteTenantMutationResponse = { + __typename?: 'DeleteTenantMutationResponse'; + success: Scalars['Boolean']['output']; +}; + export type DepositAssetLiquidityInput = { /** Amount of liquidity to deposit. */ amount: Scalars['UInt64']['input']; @@ -721,6 +739,8 @@ export type Mutation = { createQuote: QuoteResponse; /** Create an internal or external Open Payments incoming payment. The receiver has a wallet address on either this or another Open Payments resource server. */ createReceiver: CreateReceiverResponse; + /** As an operator, create a tenant. */ + createTenant: TenantMutationResponse; /** Create a new wallet address. */ createWalletAddress: CreateWalletAddressMutationResponse; /** Add a public key to a wallet address that is used to verify Open Payments requests. */ @@ -731,6 +751,8 @@ export type Mutation = { deleteAsset: DeleteAssetMutationResponse; /** Delete a peer. */ deletePeer: DeletePeerMutationResponse; + /** Delete a tenant. */ + deleteTenant: DeleteTenantMutationResponse; /** Deposit asset liquidity. */ depositAssetLiquidity?: Maybe; /** @@ -756,6 +778,8 @@ export type Mutation = { updateIncomingPayment: IncomingPaymentResponse; /** Update an existing peer. */ updatePeer: UpdatePeerMutationResponse; + /** Update a tenant. */ + updateTenant: TenantMutationResponse; /** Update an existing wallet address. */ updateWalletAddress: UpdateWalletAddressMutationResponse; /** Void liquidity withdrawal. Withdrawals are two-phase commits and are rolled back via this mutation. */ @@ -843,6 +867,11 @@ export type MutationCreateReceiverArgs = { }; +export type MutationCreateTenantArgs = { + input: CreateTenantInput; +}; + + export type MutationCreateWalletAddressArgs = { input: CreateWalletAddressInput; }; @@ -868,6 +897,11 @@ export type MutationDeletePeerArgs = { }; +export type MutationDeleteTenantArgs = { + id: Scalars['String']['input']; +}; + + export type MutationDepositAssetLiquidityArgs = { input: DepositAssetLiquidityInput; }; @@ -923,6 +957,11 @@ export type MutationUpdatePeerArgs = { }; +export type MutationUpdateTenantArgs = { + input: UpdateTenantInput; +}; + + export type MutationUpdateWalletAddressArgs = { input: UpdateWalletAddressInput; }; @@ -1150,6 +1189,10 @@ export type Query = { quote?: Maybe; /** Retrieve an Open Payments incoming payment by receiver ID. The receiver's wallet address can be hosted on this server or a remote Open Payments resource server. */ receiver?: Maybe; + /** Retrieve a tenant of the instance. */ + tenant: Tenant; + /** As an operator, fetch a paginated list of tenants on the instance. */ + tenants: TenantsConnection; /** Fetch a wallet address by its ID. */ walletAddress?: Maybe; /** Get a wallet address by its url if it exists */ @@ -1158,6 +1201,8 @@ export type Query = { walletAddresses: WalletAddressesConnection; /** Fetch a paginated list of webhook events. */ webhookEvents: WebhookEventsConnection; + /** Determine if the requester has operator permissions */ + whoami: WhoamiResponse; }; @@ -1247,6 +1292,20 @@ export type QueryReceiverArgs = { }; +export type QueryTenantArgs = { + id: Scalars['String']['input']; +}; + + +export type QueryTenantsArgs = { + after?: InputMaybe; + before?: InputMaybe; + first?: InputMaybe; + last?: InputMaybe; + sortOrder?: InputMaybe; +}; + + export type QueryWalletAddressArgs = { id: Scalars['String']['input']; }; @@ -1376,6 +1435,47 @@ export enum SortOrder { Desc = 'DESC' } +export type Tenant = Model & { + __typename?: 'Tenant'; + /** Secret used to secure requests made for this tenant. */ + apiSecret: Scalars['String']['output']; + /** The date and time that this tenant was created. */ + createdAt: Scalars['String']['output']; + /** The date and time that this tenant was deleted. */ + deletedAt?: Maybe; + /** Contact email of the tenant owner. */ + email?: Maybe; + /** Unique identifier of the tenant. */ + id: Scalars['ID']['output']; + /** URL of the tenant's identity provider's consent screen. */ + idpConsentUrl?: Maybe; + /** Secret used to secure requests from the tenant's identity provider. */ + idpSecret?: Maybe; + /** Public name for the tenant. */ + publicName?: Maybe; +}; + +export type TenantEdge = { + __typename?: 'TenantEdge'; + /** A cursor for paginating through the tenants. */ + cursor: Scalars['String']['output']; + /** A tenant node in the list. */ + node: Tenant; +}; + +export type TenantMutationResponse = { + __typename?: 'TenantMutationResponse'; + tenant: Tenant; +}; + +export type TenantsConnection = { + __typename?: 'TenantsConnection'; + /** A list of edges representing tenants and cursors for pagination. */ + edges: Array; + /** Information to aid in pagination. */ + pageInfo: PageInfo; +}; + export enum TransferState { /** The accounting transfer is pending */ Pending = 'PENDING', @@ -1448,6 +1548,21 @@ export type UpdatePeerMutationResponse = { peer?: Maybe; }; +export type UpdateTenantInput = { + /** Secret used to secure requests made for this tenant. */ + apiSecret?: InputMaybe; + /** Contact email of the tenant owner. */ + email?: InputMaybe; + /** Unique identifier of the tenant. */ + id: Scalars['ID']['input']; + /** URL of the tenant's identity provider's consent screen. */ + idpConsentUrl?: InputMaybe; + /** Secret used to secure requests from the tenant's identity provider. */ + idpSecret?: InputMaybe; + /** Public name for the tenant. */ + publicName?: InputMaybe; +}; + export type UpdateWalletAddressInput = { /** Additional properties associated with this wallet address. */ additionalProperties?: InputMaybe>; @@ -1640,6 +1755,12 @@ export type WebhookEventsEdge = { node: WebhookEvent; }; +export type WhoamiResponse = { + __typename?: 'WhoamiResponse'; + id: Scalars['String']['output']; + isOperator: Scalars['Boolean']['output']; +}; + export type WithdrawEventLiquidityInput = { /** Unique identifier of the event to withdraw liquidity from. */ eventId: Scalars['String']['input']; @@ -1718,7 +1839,7 @@ export type DirectiveResolverFn> = { BasePayment: ( Partial ) | ( Partial ) | ( Partial ); - Model: ( Partial ) | ( Partial ) | ( Partial ) | ( Partial ) | ( Partial ) | ( Partial ) | ( Partial ) | ( Partial ) | ( Partial ) | ( Partial ); + Model: ( Partial ) | ( Partial ) | ( Partial ) | ( Partial ) | ( Partial ) | ( Partial ) | ( Partial ) | ( Partial ) | ( Partial ) | ( Partial ) | ( Partial ); }; /** Mapping between all available schema types and the resolvers types */ @@ -1756,6 +1877,7 @@ export type ResolversTypes = { CreateQuoteInput: ResolverTypeWrapper>; CreateReceiverInput: ResolverTypeWrapper>; CreateReceiverResponse: ResolverTypeWrapper>; + CreateTenantInput: ResolverTypeWrapper>; CreateWalletAddressInput: ResolverTypeWrapper>; CreateWalletAddressKeyInput: ResolverTypeWrapper>; CreateWalletAddressKeyMutationResponse: ResolverTypeWrapper>; @@ -1766,6 +1888,7 @@ export type ResolversTypes = { DeleteAssetMutationResponse: ResolverTypeWrapper>; DeletePeerInput: ResolverTypeWrapper>; DeletePeerMutationResponse: ResolverTypeWrapper>; + DeleteTenantMutationResponse: ResolverTypeWrapper>; DepositAssetLiquidityInput: ResolverTypeWrapper>; DepositEventLiquidityInput: ResolverTypeWrapper>; DepositOutgoingPaymentLiquidityInput: ResolverTypeWrapper>; @@ -1825,6 +1948,10 @@ export type ResolversTypes = { SetFeeResponse: ResolverTypeWrapper>; SortOrder: ResolverTypeWrapper>; String: ResolverTypeWrapper>; + Tenant: ResolverTypeWrapper>; + TenantEdge: ResolverTypeWrapper>; + TenantMutationResponse: ResolverTypeWrapper>; + TenantsConnection: ResolverTypeWrapper>; TransferState: ResolverTypeWrapper>; TransferType: ResolverTypeWrapper>; TriggerWalletAddressEventsInput: ResolverTypeWrapper>; @@ -1835,6 +1962,7 @@ export type ResolversTypes = { UpdateIncomingPaymentInput: ResolverTypeWrapper>; UpdatePeerInput: ResolverTypeWrapper>; UpdatePeerMutationResponse: ResolverTypeWrapper>; + UpdateTenantInput: ResolverTypeWrapper>; UpdateWalletAddressInput: ResolverTypeWrapper>; UpdateWalletAddressMutationResponse: ResolverTypeWrapper>; VoidLiquidityWithdrawalInput: ResolverTypeWrapper>; @@ -1851,6 +1979,7 @@ export type ResolversTypes = { WebhookEventFilter: ResolverTypeWrapper>; WebhookEventsConnection: ResolverTypeWrapper>; WebhookEventsEdge: ResolverTypeWrapper>; + WhoamiResponse: ResolverTypeWrapper>; WithdrawEventLiquidityInput: ResolverTypeWrapper>; }; @@ -1888,6 +2017,7 @@ export type ResolversParentTypes = { CreateQuoteInput: Partial; CreateReceiverInput: Partial; CreateReceiverResponse: Partial; + CreateTenantInput: Partial; CreateWalletAddressInput: Partial; CreateWalletAddressKeyInput: Partial; CreateWalletAddressKeyMutationResponse: Partial; @@ -1897,6 +2027,7 @@ export type ResolversParentTypes = { DeleteAssetMutationResponse: Partial; DeletePeerInput: Partial; DeletePeerMutationResponse: Partial; + DeleteTenantMutationResponse: Partial; DepositAssetLiquidityInput: Partial; DepositEventLiquidityInput: Partial; DepositOutgoingPaymentLiquidityInput: Partial; @@ -1949,6 +2080,10 @@ export type ResolversParentTypes = { SetFeeInput: Partial; SetFeeResponse: Partial; String: Partial; + Tenant: Partial; + TenantEdge: Partial; + TenantMutationResponse: Partial; + TenantsConnection: Partial; TriggerWalletAddressEventsInput: Partial; TriggerWalletAddressEventsMutationResponse: Partial; UInt8: Partial; @@ -1957,6 +2092,7 @@ export type ResolversParentTypes = { UpdateIncomingPaymentInput: Partial; UpdatePeerInput: Partial; UpdatePeerMutationResponse: Partial; + UpdateTenantInput: Partial; UpdateWalletAddressInput: Partial; UpdateWalletAddressMutationResponse: Partial; VoidLiquidityWithdrawalInput: Partial; @@ -1972,6 +2108,7 @@ export type ResolversParentTypes = { WebhookEventFilter: Partial; WebhookEventsConnection: Partial; WebhookEventsEdge: Partial; + WhoamiResponse: Partial; WithdrawEventLiquidityInput: Partial; }; @@ -2093,6 +2230,11 @@ export type DeletePeerMutationResponseResolvers; }; +export type DeleteTenantMutationResponseResolvers = { + success?: Resolver; + __isTypeOf?: IsTypeOfResolverFn; +}; + export type FeeResolvers = { assetId?: Resolver; basisPoints?: Resolver; @@ -2176,7 +2318,7 @@ export type LiquidityMutationResponseResolvers = { - __resolveType: TypeResolveFn<'AccountingTransfer' | 'Asset' | 'Fee' | 'IncomingPayment' | 'OutgoingPayment' | 'Payment' | 'Peer' | 'WalletAddress' | 'WalletAddressKey' | 'WebhookEvent', ParentType, ContextType>; + __resolveType: TypeResolveFn<'AccountingTransfer' | 'Asset' | 'Fee' | 'IncomingPayment' | 'OutgoingPayment' | 'Payment' | 'Peer' | 'Tenant' | 'WalletAddress' | 'WalletAddressKey' | 'WebhookEvent', ParentType, ContextType>; createdAt?: Resolver; id?: Resolver; }; @@ -2197,11 +2339,13 @@ export type MutationResolvers, ParentType, ContextType, RequireFields>; createQuote?: Resolver>; createReceiver?: Resolver>; + createTenant?: Resolver>; createWalletAddress?: Resolver>; createWalletAddressKey?: Resolver, ParentType, ContextType, RequireFields>; createWalletAddressWithdrawal?: Resolver, ParentType, ContextType, RequireFields>; deleteAsset?: Resolver>; deletePeer?: Resolver>; + deleteTenant?: Resolver>; depositAssetLiquidity?: Resolver, ParentType, ContextType, RequireFields>; depositEventLiquidity?: Resolver, ParentType, ContextType, RequireFields>; depositOutgoingPaymentLiquidity?: Resolver, ParentType, ContextType, RequireFields>; @@ -2213,6 +2357,7 @@ export type MutationResolvers>; updateIncomingPayment?: Resolver>; updatePeer?: Resolver>; + updateTenant?: Resolver>; updateWalletAddress?: Resolver>; voidLiquidityWithdrawal?: Resolver, ParentType, ContextType, RequireFields>; withdrawEventLiquidity?: Resolver, ParentType, ContextType, RequireFields>; @@ -2325,10 +2470,13 @@ export type QueryResolvers>; quote?: Resolver, ParentType, ContextType, RequireFields>; receiver?: Resolver, ParentType, ContextType, RequireFields>; + tenant?: Resolver>; + tenants?: Resolver>; walletAddress?: Resolver, ParentType, ContextType, RequireFields>; walletAddressByUrl?: Resolver, ParentType, ContextType, RequireFields>; walletAddresses?: Resolver>; webhookEvents?: Resolver>; + whoami?: Resolver; }; export type QuoteResolvers = { @@ -2383,6 +2531,35 @@ export type SetFeeResponseResolvers; }; +export type TenantResolvers = { + apiSecret?: Resolver; + createdAt?: Resolver; + deletedAt?: Resolver, ParentType, ContextType>; + email?: Resolver, ParentType, ContextType>; + id?: Resolver; + idpConsentUrl?: Resolver, ParentType, ContextType>; + idpSecret?: Resolver, ParentType, ContextType>; + publicName?: Resolver, ParentType, ContextType>; + __isTypeOf?: IsTypeOfResolverFn; +}; + +export type TenantEdgeResolvers = { + cursor?: Resolver; + node?: Resolver; + __isTypeOf?: IsTypeOfResolverFn; +}; + +export type TenantMutationResponseResolvers = { + tenant?: Resolver; + __isTypeOf?: IsTypeOfResolverFn; +}; + +export type TenantsConnectionResolvers = { + edges?: Resolver, ParentType, ContextType>; + pageInfo?: Resolver; + __isTypeOf?: IsTypeOfResolverFn; +}; + export type TriggerWalletAddressEventsMutationResponseResolvers = { count?: Resolver, ParentType, ContextType>; __isTypeOf?: IsTypeOfResolverFn; @@ -2487,6 +2664,12 @@ export type WebhookEventsEdgeResolvers; }; +export type WhoamiResponseResolvers = { + id?: Resolver; + isOperator?: Resolver; + __isTypeOf?: IsTypeOfResolverFn; +}; + export type Resolvers = { AccountingTransfer?: AccountingTransferResolvers; AccountingTransferConnection?: AccountingTransferConnectionResolvers; @@ -2506,6 +2689,7 @@ export type Resolvers = { CreateWalletAddressMutationResponse?: CreateWalletAddressMutationResponseResolvers; DeleteAssetMutationResponse?: DeleteAssetMutationResponseResolvers; DeletePeerMutationResponse?: DeletePeerMutationResponseResolvers; + DeleteTenantMutationResponse?: DeleteTenantMutationResponseResolvers; Fee?: FeeResolvers; FeeEdge?: FeeEdgeResolvers; FeesConnection?: FeesConnectionResolvers; @@ -2539,6 +2723,10 @@ export type Resolvers = { Receiver?: ReceiverResolvers; RevokeWalletAddressKeyMutationResponse?: RevokeWalletAddressKeyMutationResponseResolvers; SetFeeResponse?: SetFeeResponseResolvers; + Tenant?: TenantResolvers; + TenantEdge?: TenantEdgeResolvers; + TenantMutationResponse?: TenantMutationResponseResolvers; + TenantsConnection?: TenantsConnectionResolvers; TriggerWalletAddressEventsMutationResponse?: TriggerWalletAddressEventsMutationResponseResolvers; UInt8?: GraphQLScalarType; UInt64?: GraphQLScalarType; @@ -2555,6 +2743,7 @@ export type Resolvers = { WebhookEvent?: WebhookEventResolvers; WebhookEventsConnection?: WebhookEventsConnectionResolvers; WebhookEventsEdge?: WebhookEventsEdgeResolvers; + WhoamiResponse?: WhoamiResponseResolvers; }; diff --git a/packages/mock-account-service-lib/src/generated/graphql.ts b/packages/mock-account-service-lib/src/generated/graphql.ts index 46dad2dedb..a9a4b15bc0 100644 --- a/packages/mock-account-service-lib/src/generated/graphql.ts +++ b/packages/mock-account-service-lib/src/generated/graphql.ts @@ -364,6 +364,19 @@ export type CreateReceiverResponse = { receiver?: Maybe; }; +export type CreateTenantInput = { + /** Secret used to secure requests made for this tenant. */ + apiSecret: Scalars['String']['input']; + /** Contact email of the tenant owner. */ + email?: InputMaybe; + /** URL of the tenant's identity provider's consent screen. */ + idpConsentUrl?: InputMaybe; + /** Secret used to secure requests from the tenant's identity provider. */ + idpSecret?: InputMaybe; + /** Public name for the tenant. */ + publicName?: InputMaybe; +}; + export type CreateWalletAddressInput = { /** Additional properties associated with the wallet address. */ additionalProperties?: InputMaybe>; @@ -440,6 +453,11 @@ export type DeletePeerMutationResponse = { success: Scalars['Boolean']['output']; }; +export type DeleteTenantMutationResponse = { + __typename?: 'DeleteTenantMutationResponse'; + success: Scalars['Boolean']['output']; +}; + export type DepositAssetLiquidityInput = { /** Amount of liquidity to deposit. */ amount: Scalars['UInt64']['input']; @@ -721,6 +739,8 @@ export type Mutation = { createQuote: QuoteResponse; /** Create an internal or external Open Payments incoming payment. The receiver has a wallet address on either this or another Open Payments resource server. */ createReceiver: CreateReceiverResponse; + /** As an operator, create a tenant. */ + createTenant: TenantMutationResponse; /** Create a new wallet address. */ createWalletAddress: CreateWalletAddressMutationResponse; /** Add a public key to a wallet address that is used to verify Open Payments requests. */ @@ -731,6 +751,8 @@ export type Mutation = { deleteAsset: DeleteAssetMutationResponse; /** Delete a peer. */ deletePeer: DeletePeerMutationResponse; + /** Delete a tenant. */ + deleteTenant: DeleteTenantMutationResponse; /** Deposit asset liquidity. */ depositAssetLiquidity?: Maybe; /** @@ -756,6 +778,8 @@ export type Mutation = { updateIncomingPayment: IncomingPaymentResponse; /** Update an existing peer. */ updatePeer: UpdatePeerMutationResponse; + /** Update a tenant. */ + updateTenant: TenantMutationResponse; /** Update an existing wallet address. */ updateWalletAddress: UpdateWalletAddressMutationResponse; /** Void liquidity withdrawal. Withdrawals are two-phase commits and are rolled back via this mutation. */ @@ -843,6 +867,11 @@ export type MutationCreateReceiverArgs = { }; +export type MutationCreateTenantArgs = { + input: CreateTenantInput; +}; + + export type MutationCreateWalletAddressArgs = { input: CreateWalletAddressInput; }; @@ -868,6 +897,11 @@ export type MutationDeletePeerArgs = { }; +export type MutationDeleteTenantArgs = { + id: Scalars['String']['input']; +}; + + export type MutationDepositAssetLiquidityArgs = { input: DepositAssetLiquidityInput; }; @@ -923,6 +957,11 @@ export type MutationUpdatePeerArgs = { }; +export type MutationUpdateTenantArgs = { + input: UpdateTenantInput; +}; + + export type MutationUpdateWalletAddressArgs = { input: UpdateWalletAddressInput; }; @@ -1150,6 +1189,10 @@ export type Query = { quote?: Maybe; /** Retrieve an Open Payments incoming payment by receiver ID. The receiver's wallet address can be hosted on this server or a remote Open Payments resource server. */ receiver?: Maybe; + /** Retrieve a tenant of the instance. */ + tenant: Tenant; + /** As an operator, fetch a paginated list of tenants on the instance. */ + tenants: TenantsConnection; /** Fetch a wallet address by its ID. */ walletAddress?: Maybe; /** Get a wallet address by its url if it exists */ @@ -1158,6 +1201,8 @@ export type Query = { walletAddresses: WalletAddressesConnection; /** Fetch a paginated list of webhook events. */ webhookEvents: WebhookEventsConnection; + /** Determine if the requester has operator permissions */ + whoami: WhoamiResponse; }; @@ -1247,6 +1292,20 @@ export type QueryReceiverArgs = { }; +export type QueryTenantArgs = { + id: Scalars['String']['input']; +}; + + +export type QueryTenantsArgs = { + after?: InputMaybe; + before?: InputMaybe; + first?: InputMaybe; + last?: InputMaybe; + sortOrder?: InputMaybe; +}; + + export type QueryWalletAddressArgs = { id: Scalars['String']['input']; }; @@ -1376,6 +1435,47 @@ export enum SortOrder { Desc = 'DESC' } +export type Tenant = Model & { + __typename?: 'Tenant'; + /** Secret used to secure requests made for this tenant. */ + apiSecret: Scalars['String']['output']; + /** The date and time that this tenant was created. */ + createdAt: Scalars['String']['output']; + /** The date and time that this tenant was deleted. */ + deletedAt?: Maybe; + /** Contact email of the tenant owner. */ + email?: Maybe; + /** Unique identifier of the tenant. */ + id: Scalars['ID']['output']; + /** URL of the tenant's identity provider's consent screen. */ + idpConsentUrl?: Maybe; + /** Secret used to secure requests from the tenant's identity provider. */ + idpSecret?: Maybe; + /** Public name for the tenant. */ + publicName?: Maybe; +}; + +export type TenantEdge = { + __typename?: 'TenantEdge'; + /** A cursor for paginating through the tenants. */ + cursor: Scalars['String']['output']; + /** A tenant node in the list. */ + node: Tenant; +}; + +export type TenantMutationResponse = { + __typename?: 'TenantMutationResponse'; + tenant: Tenant; +}; + +export type TenantsConnection = { + __typename?: 'TenantsConnection'; + /** A list of edges representing tenants and cursors for pagination. */ + edges: Array; + /** Information to aid in pagination. */ + pageInfo: PageInfo; +}; + export enum TransferState { /** The accounting transfer is pending */ Pending = 'PENDING', @@ -1448,6 +1548,21 @@ export type UpdatePeerMutationResponse = { peer?: Maybe; }; +export type UpdateTenantInput = { + /** Secret used to secure requests made for this tenant. */ + apiSecret?: InputMaybe; + /** Contact email of the tenant owner. */ + email?: InputMaybe; + /** Unique identifier of the tenant. */ + id: Scalars['ID']['input']; + /** URL of the tenant's identity provider's consent screen. */ + idpConsentUrl?: InputMaybe; + /** Secret used to secure requests from the tenant's identity provider. */ + idpSecret?: InputMaybe; + /** Public name for the tenant. */ + publicName?: InputMaybe; +}; + export type UpdateWalletAddressInput = { /** Additional properties associated with this wallet address. */ additionalProperties?: InputMaybe>; @@ -1640,6 +1755,12 @@ export type WebhookEventsEdge = { node: WebhookEvent; }; +export type WhoamiResponse = { + __typename?: 'WhoamiResponse'; + id: Scalars['String']['output']; + isOperator: Scalars['Boolean']['output']; +}; + export type WithdrawEventLiquidityInput = { /** Unique identifier of the event to withdraw liquidity from. */ eventId: Scalars['String']['input']; @@ -1718,7 +1839,7 @@ export type DirectiveResolverFn> = { BasePayment: ( Partial ) | ( Partial ) | ( Partial ); - Model: ( Partial ) | ( Partial ) | ( Partial ) | ( Partial ) | ( Partial ) | ( Partial ) | ( Partial ) | ( Partial ) | ( Partial ) | ( Partial ); + Model: ( Partial ) | ( Partial ) | ( Partial ) | ( Partial ) | ( Partial ) | ( Partial ) | ( Partial ) | ( Partial ) | ( Partial ) | ( Partial ) | ( Partial ); }; /** Mapping between all available schema types and the resolvers types */ @@ -1756,6 +1877,7 @@ export type ResolversTypes = { CreateQuoteInput: ResolverTypeWrapper>; CreateReceiverInput: ResolverTypeWrapper>; CreateReceiverResponse: ResolverTypeWrapper>; + CreateTenantInput: ResolverTypeWrapper>; CreateWalletAddressInput: ResolverTypeWrapper>; CreateWalletAddressKeyInput: ResolverTypeWrapper>; CreateWalletAddressKeyMutationResponse: ResolverTypeWrapper>; @@ -1766,6 +1888,7 @@ export type ResolversTypes = { DeleteAssetMutationResponse: ResolverTypeWrapper>; DeletePeerInput: ResolverTypeWrapper>; DeletePeerMutationResponse: ResolverTypeWrapper>; + DeleteTenantMutationResponse: ResolverTypeWrapper>; DepositAssetLiquidityInput: ResolverTypeWrapper>; DepositEventLiquidityInput: ResolverTypeWrapper>; DepositOutgoingPaymentLiquidityInput: ResolverTypeWrapper>; @@ -1825,6 +1948,10 @@ export type ResolversTypes = { SetFeeResponse: ResolverTypeWrapper>; SortOrder: ResolverTypeWrapper>; String: ResolverTypeWrapper>; + Tenant: ResolverTypeWrapper>; + TenantEdge: ResolverTypeWrapper>; + TenantMutationResponse: ResolverTypeWrapper>; + TenantsConnection: ResolverTypeWrapper>; TransferState: ResolverTypeWrapper>; TransferType: ResolverTypeWrapper>; TriggerWalletAddressEventsInput: ResolverTypeWrapper>; @@ -1835,6 +1962,7 @@ export type ResolversTypes = { UpdateIncomingPaymentInput: ResolverTypeWrapper>; UpdatePeerInput: ResolverTypeWrapper>; UpdatePeerMutationResponse: ResolverTypeWrapper>; + UpdateTenantInput: ResolverTypeWrapper>; UpdateWalletAddressInput: ResolverTypeWrapper>; UpdateWalletAddressMutationResponse: ResolverTypeWrapper>; VoidLiquidityWithdrawalInput: ResolverTypeWrapper>; @@ -1851,6 +1979,7 @@ export type ResolversTypes = { WebhookEventFilter: ResolverTypeWrapper>; WebhookEventsConnection: ResolverTypeWrapper>; WebhookEventsEdge: ResolverTypeWrapper>; + WhoamiResponse: ResolverTypeWrapper>; WithdrawEventLiquidityInput: ResolverTypeWrapper>; }; @@ -1888,6 +2017,7 @@ export type ResolversParentTypes = { CreateQuoteInput: Partial; CreateReceiverInput: Partial; CreateReceiverResponse: Partial; + CreateTenantInput: Partial; CreateWalletAddressInput: Partial; CreateWalletAddressKeyInput: Partial; CreateWalletAddressKeyMutationResponse: Partial; @@ -1897,6 +2027,7 @@ export type ResolversParentTypes = { DeleteAssetMutationResponse: Partial; DeletePeerInput: Partial; DeletePeerMutationResponse: Partial; + DeleteTenantMutationResponse: Partial; DepositAssetLiquidityInput: Partial; DepositEventLiquidityInput: Partial; DepositOutgoingPaymentLiquidityInput: Partial; @@ -1949,6 +2080,10 @@ export type ResolversParentTypes = { SetFeeInput: Partial; SetFeeResponse: Partial; String: Partial; + Tenant: Partial; + TenantEdge: Partial; + TenantMutationResponse: Partial; + TenantsConnection: Partial; TriggerWalletAddressEventsInput: Partial; TriggerWalletAddressEventsMutationResponse: Partial; UInt8: Partial; @@ -1957,6 +2092,7 @@ export type ResolversParentTypes = { UpdateIncomingPaymentInput: Partial; UpdatePeerInput: Partial; UpdatePeerMutationResponse: Partial; + UpdateTenantInput: Partial; UpdateWalletAddressInput: Partial; UpdateWalletAddressMutationResponse: Partial; VoidLiquidityWithdrawalInput: Partial; @@ -1972,6 +2108,7 @@ export type ResolversParentTypes = { WebhookEventFilter: Partial; WebhookEventsConnection: Partial; WebhookEventsEdge: Partial; + WhoamiResponse: Partial; WithdrawEventLiquidityInput: Partial; }; @@ -2093,6 +2230,11 @@ export type DeletePeerMutationResponseResolvers; }; +export type DeleteTenantMutationResponseResolvers = { + success?: Resolver; + __isTypeOf?: IsTypeOfResolverFn; +}; + export type FeeResolvers = { assetId?: Resolver; basisPoints?: Resolver; @@ -2176,7 +2318,7 @@ export type LiquidityMutationResponseResolvers = { - __resolveType: TypeResolveFn<'AccountingTransfer' | 'Asset' | 'Fee' | 'IncomingPayment' | 'OutgoingPayment' | 'Payment' | 'Peer' | 'WalletAddress' | 'WalletAddressKey' | 'WebhookEvent', ParentType, ContextType>; + __resolveType: TypeResolveFn<'AccountingTransfer' | 'Asset' | 'Fee' | 'IncomingPayment' | 'OutgoingPayment' | 'Payment' | 'Peer' | 'Tenant' | 'WalletAddress' | 'WalletAddressKey' | 'WebhookEvent', ParentType, ContextType>; createdAt?: Resolver; id?: Resolver; }; @@ -2197,11 +2339,13 @@ export type MutationResolvers, ParentType, ContextType, RequireFields>; createQuote?: Resolver>; createReceiver?: Resolver>; + createTenant?: Resolver>; createWalletAddress?: Resolver>; createWalletAddressKey?: Resolver, ParentType, ContextType, RequireFields>; createWalletAddressWithdrawal?: Resolver, ParentType, ContextType, RequireFields>; deleteAsset?: Resolver>; deletePeer?: Resolver>; + deleteTenant?: Resolver>; depositAssetLiquidity?: Resolver, ParentType, ContextType, RequireFields>; depositEventLiquidity?: Resolver, ParentType, ContextType, RequireFields>; depositOutgoingPaymentLiquidity?: Resolver, ParentType, ContextType, RequireFields>; @@ -2213,6 +2357,7 @@ export type MutationResolvers>; updateIncomingPayment?: Resolver>; updatePeer?: Resolver>; + updateTenant?: Resolver>; updateWalletAddress?: Resolver>; voidLiquidityWithdrawal?: Resolver, ParentType, ContextType, RequireFields>; withdrawEventLiquidity?: Resolver, ParentType, ContextType, RequireFields>; @@ -2325,10 +2470,13 @@ export type QueryResolvers>; quote?: Resolver, ParentType, ContextType, RequireFields>; receiver?: Resolver, ParentType, ContextType, RequireFields>; + tenant?: Resolver>; + tenants?: Resolver>; walletAddress?: Resolver, ParentType, ContextType, RequireFields>; walletAddressByUrl?: Resolver, ParentType, ContextType, RequireFields>; walletAddresses?: Resolver>; webhookEvents?: Resolver>; + whoami?: Resolver; }; export type QuoteResolvers = { @@ -2383,6 +2531,35 @@ export type SetFeeResponseResolvers; }; +export type TenantResolvers = { + apiSecret?: Resolver; + createdAt?: Resolver; + deletedAt?: Resolver, ParentType, ContextType>; + email?: Resolver, ParentType, ContextType>; + id?: Resolver; + idpConsentUrl?: Resolver, ParentType, ContextType>; + idpSecret?: Resolver, ParentType, ContextType>; + publicName?: Resolver, ParentType, ContextType>; + __isTypeOf?: IsTypeOfResolverFn; +}; + +export type TenantEdgeResolvers = { + cursor?: Resolver; + node?: Resolver; + __isTypeOf?: IsTypeOfResolverFn; +}; + +export type TenantMutationResponseResolvers = { + tenant?: Resolver; + __isTypeOf?: IsTypeOfResolverFn; +}; + +export type TenantsConnectionResolvers = { + edges?: Resolver, ParentType, ContextType>; + pageInfo?: Resolver; + __isTypeOf?: IsTypeOfResolverFn; +}; + export type TriggerWalletAddressEventsMutationResponseResolvers = { count?: Resolver, ParentType, ContextType>; __isTypeOf?: IsTypeOfResolverFn; @@ -2487,6 +2664,12 @@ export type WebhookEventsEdgeResolvers; }; +export type WhoamiResponseResolvers = { + id?: Resolver; + isOperator?: Resolver; + __isTypeOf?: IsTypeOfResolverFn; +}; + export type Resolvers = { AccountingTransfer?: AccountingTransferResolvers; AccountingTransferConnection?: AccountingTransferConnectionResolvers; @@ -2506,6 +2689,7 @@ export type Resolvers = { CreateWalletAddressMutationResponse?: CreateWalletAddressMutationResponseResolvers; DeleteAssetMutationResponse?: DeleteAssetMutationResponseResolvers; DeletePeerMutationResponse?: DeletePeerMutationResponseResolvers; + DeleteTenantMutationResponse?: DeleteTenantMutationResponseResolvers; Fee?: FeeResolvers; FeeEdge?: FeeEdgeResolvers; FeesConnection?: FeesConnectionResolvers; @@ -2539,6 +2723,10 @@ export type Resolvers = { Receiver?: ReceiverResolvers; RevokeWalletAddressKeyMutationResponse?: RevokeWalletAddressKeyMutationResponseResolvers; SetFeeResponse?: SetFeeResponseResolvers; + Tenant?: TenantResolvers; + TenantEdge?: TenantEdgeResolvers; + TenantMutationResponse?: TenantMutationResponseResolvers; + TenantsConnection?: TenantsConnectionResolvers; TriggerWalletAddressEventsMutationResponse?: TriggerWalletAddressEventsMutationResponseResolvers; UInt8?: GraphQLScalarType; UInt64?: GraphQLScalarType; @@ -2555,5 +2743,6 @@ export type Resolvers = { WebhookEvent?: WebhookEventResolvers; WebhookEventsConnection?: WebhookEventsConnectionResolvers; WebhookEventsEdge?: WebhookEventsEdgeResolvers; + WhoamiResponse?: WhoamiResponseResolvers; }; diff --git a/test/integration/lib/generated/graphql.ts b/test/integration/lib/generated/graphql.ts index 46dad2dedb..a9a4b15bc0 100644 --- a/test/integration/lib/generated/graphql.ts +++ b/test/integration/lib/generated/graphql.ts @@ -364,6 +364,19 @@ export type CreateReceiverResponse = { receiver?: Maybe; }; +export type CreateTenantInput = { + /** Secret used to secure requests made for this tenant. */ + apiSecret: Scalars['String']['input']; + /** Contact email of the tenant owner. */ + email?: InputMaybe; + /** URL of the tenant's identity provider's consent screen. */ + idpConsentUrl?: InputMaybe; + /** Secret used to secure requests from the tenant's identity provider. */ + idpSecret?: InputMaybe; + /** Public name for the tenant. */ + publicName?: InputMaybe; +}; + export type CreateWalletAddressInput = { /** Additional properties associated with the wallet address. */ additionalProperties?: InputMaybe>; @@ -440,6 +453,11 @@ export type DeletePeerMutationResponse = { success: Scalars['Boolean']['output']; }; +export type DeleteTenantMutationResponse = { + __typename?: 'DeleteTenantMutationResponse'; + success: Scalars['Boolean']['output']; +}; + export type DepositAssetLiquidityInput = { /** Amount of liquidity to deposit. */ amount: Scalars['UInt64']['input']; @@ -721,6 +739,8 @@ export type Mutation = { createQuote: QuoteResponse; /** Create an internal or external Open Payments incoming payment. The receiver has a wallet address on either this or another Open Payments resource server. */ createReceiver: CreateReceiverResponse; + /** As an operator, create a tenant. */ + createTenant: TenantMutationResponse; /** Create a new wallet address. */ createWalletAddress: CreateWalletAddressMutationResponse; /** Add a public key to a wallet address that is used to verify Open Payments requests. */ @@ -731,6 +751,8 @@ export type Mutation = { deleteAsset: DeleteAssetMutationResponse; /** Delete a peer. */ deletePeer: DeletePeerMutationResponse; + /** Delete a tenant. */ + deleteTenant: DeleteTenantMutationResponse; /** Deposit asset liquidity. */ depositAssetLiquidity?: Maybe; /** @@ -756,6 +778,8 @@ export type Mutation = { updateIncomingPayment: IncomingPaymentResponse; /** Update an existing peer. */ updatePeer: UpdatePeerMutationResponse; + /** Update a tenant. */ + updateTenant: TenantMutationResponse; /** Update an existing wallet address. */ updateWalletAddress: UpdateWalletAddressMutationResponse; /** Void liquidity withdrawal. Withdrawals are two-phase commits and are rolled back via this mutation. */ @@ -843,6 +867,11 @@ export type MutationCreateReceiverArgs = { }; +export type MutationCreateTenantArgs = { + input: CreateTenantInput; +}; + + export type MutationCreateWalletAddressArgs = { input: CreateWalletAddressInput; }; @@ -868,6 +897,11 @@ export type MutationDeletePeerArgs = { }; +export type MutationDeleteTenantArgs = { + id: Scalars['String']['input']; +}; + + export type MutationDepositAssetLiquidityArgs = { input: DepositAssetLiquidityInput; }; @@ -923,6 +957,11 @@ export type MutationUpdatePeerArgs = { }; +export type MutationUpdateTenantArgs = { + input: UpdateTenantInput; +}; + + export type MutationUpdateWalletAddressArgs = { input: UpdateWalletAddressInput; }; @@ -1150,6 +1189,10 @@ export type Query = { quote?: Maybe; /** Retrieve an Open Payments incoming payment by receiver ID. The receiver's wallet address can be hosted on this server or a remote Open Payments resource server. */ receiver?: Maybe; + /** Retrieve a tenant of the instance. */ + tenant: Tenant; + /** As an operator, fetch a paginated list of tenants on the instance. */ + tenants: TenantsConnection; /** Fetch a wallet address by its ID. */ walletAddress?: Maybe; /** Get a wallet address by its url if it exists */ @@ -1158,6 +1201,8 @@ export type Query = { walletAddresses: WalletAddressesConnection; /** Fetch a paginated list of webhook events. */ webhookEvents: WebhookEventsConnection; + /** Determine if the requester has operator permissions */ + whoami: WhoamiResponse; }; @@ -1247,6 +1292,20 @@ export type QueryReceiverArgs = { }; +export type QueryTenantArgs = { + id: Scalars['String']['input']; +}; + + +export type QueryTenantsArgs = { + after?: InputMaybe; + before?: InputMaybe; + first?: InputMaybe; + last?: InputMaybe; + sortOrder?: InputMaybe; +}; + + export type QueryWalletAddressArgs = { id: Scalars['String']['input']; }; @@ -1376,6 +1435,47 @@ export enum SortOrder { Desc = 'DESC' } +export type Tenant = Model & { + __typename?: 'Tenant'; + /** Secret used to secure requests made for this tenant. */ + apiSecret: Scalars['String']['output']; + /** The date and time that this tenant was created. */ + createdAt: Scalars['String']['output']; + /** The date and time that this tenant was deleted. */ + deletedAt?: Maybe; + /** Contact email of the tenant owner. */ + email?: Maybe; + /** Unique identifier of the tenant. */ + id: Scalars['ID']['output']; + /** URL of the tenant's identity provider's consent screen. */ + idpConsentUrl?: Maybe; + /** Secret used to secure requests from the tenant's identity provider. */ + idpSecret?: Maybe; + /** Public name for the tenant. */ + publicName?: Maybe; +}; + +export type TenantEdge = { + __typename?: 'TenantEdge'; + /** A cursor for paginating through the tenants. */ + cursor: Scalars['String']['output']; + /** A tenant node in the list. */ + node: Tenant; +}; + +export type TenantMutationResponse = { + __typename?: 'TenantMutationResponse'; + tenant: Tenant; +}; + +export type TenantsConnection = { + __typename?: 'TenantsConnection'; + /** A list of edges representing tenants and cursors for pagination. */ + edges: Array; + /** Information to aid in pagination. */ + pageInfo: PageInfo; +}; + export enum TransferState { /** The accounting transfer is pending */ Pending = 'PENDING', @@ -1448,6 +1548,21 @@ export type UpdatePeerMutationResponse = { peer?: Maybe; }; +export type UpdateTenantInput = { + /** Secret used to secure requests made for this tenant. */ + apiSecret?: InputMaybe; + /** Contact email of the tenant owner. */ + email?: InputMaybe; + /** Unique identifier of the tenant. */ + id: Scalars['ID']['input']; + /** URL of the tenant's identity provider's consent screen. */ + idpConsentUrl?: InputMaybe; + /** Secret used to secure requests from the tenant's identity provider. */ + idpSecret?: InputMaybe; + /** Public name for the tenant. */ + publicName?: InputMaybe; +}; + export type UpdateWalletAddressInput = { /** Additional properties associated with this wallet address. */ additionalProperties?: InputMaybe>; @@ -1640,6 +1755,12 @@ export type WebhookEventsEdge = { node: WebhookEvent; }; +export type WhoamiResponse = { + __typename?: 'WhoamiResponse'; + id: Scalars['String']['output']; + isOperator: Scalars['Boolean']['output']; +}; + export type WithdrawEventLiquidityInput = { /** Unique identifier of the event to withdraw liquidity from. */ eventId: Scalars['String']['input']; @@ -1718,7 +1839,7 @@ export type DirectiveResolverFn> = { BasePayment: ( Partial ) | ( Partial ) | ( Partial ); - Model: ( Partial ) | ( Partial ) | ( Partial ) | ( Partial ) | ( Partial ) | ( Partial ) | ( Partial ) | ( Partial ) | ( Partial ) | ( Partial ); + Model: ( Partial ) | ( Partial ) | ( Partial ) | ( Partial ) | ( Partial ) | ( Partial ) | ( Partial ) | ( Partial ) | ( Partial ) | ( Partial ) | ( Partial ); }; /** Mapping between all available schema types and the resolvers types */ @@ -1756,6 +1877,7 @@ export type ResolversTypes = { CreateQuoteInput: ResolverTypeWrapper>; CreateReceiverInput: ResolverTypeWrapper>; CreateReceiverResponse: ResolverTypeWrapper>; + CreateTenantInput: ResolverTypeWrapper>; CreateWalletAddressInput: ResolverTypeWrapper>; CreateWalletAddressKeyInput: ResolverTypeWrapper>; CreateWalletAddressKeyMutationResponse: ResolverTypeWrapper>; @@ -1766,6 +1888,7 @@ export type ResolversTypes = { DeleteAssetMutationResponse: ResolverTypeWrapper>; DeletePeerInput: ResolverTypeWrapper>; DeletePeerMutationResponse: ResolverTypeWrapper>; + DeleteTenantMutationResponse: ResolverTypeWrapper>; DepositAssetLiquidityInput: ResolverTypeWrapper>; DepositEventLiquidityInput: ResolverTypeWrapper>; DepositOutgoingPaymentLiquidityInput: ResolverTypeWrapper>; @@ -1825,6 +1948,10 @@ export type ResolversTypes = { SetFeeResponse: ResolverTypeWrapper>; SortOrder: ResolverTypeWrapper>; String: ResolverTypeWrapper>; + Tenant: ResolverTypeWrapper>; + TenantEdge: ResolverTypeWrapper>; + TenantMutationResponse: ResolverTypeWrapper>; + TenantsConnection: ResolverTypeWrapper>; TransferState: ResolverTypeWrapper>; TransferType: ResolverTypeWrapper>; TriggerWalletAddressEventsInput: ResolverTypeWrapper>; @@ -1835,6 +1962,7 @@ export type ResolversTypes = { UpdateIncomingPaymentInput: ResolverTypeWrapper>; UpdatePeerInput: ResolverTypeWrapper>; UpdatePeerMutationResponse: ResolverTypeWrapper>; + UpdateTenantInput: ResolverTypeWrapper>; UpdateWalletAddressInput: ResolverTypeWrapper>; UpdateWalletAddressMutationResponse: ResolverTypeWrapper>; VoidLiquidityWithdrawalInput: ResolverTypeWrapper>; @@ -1851,6 +1979,7 @@ export type ResolversTypes = { WebhookEventFilter: ResolverTypeWrapper>; WebhookEventsConnection: ResolverTypeWrapper>; WebhookEventsEdge: ResolverTypeWrapper>; + WhoamiResponse: ResolverTypeWrapper>; WithdrawEventLiquidityInput: ResolverTypeWrapper>; }; @@ -1888,6 +2017,7 @@ export type ResolversParentTypes = { CreateQuoteInput: Partial; CreateReceiverInput: Partial; CreateReceiverResponse: Partial; + CreateTenantInput: Partial; CreateWalletAddressInput: Partial; CreateWalletAddressKeyInput: Partial; CreateWalletAddressKeyMutationResponse: Partial; @@ -1897,6 +2027,7 @@ export type ResolversParentTypes = { DeleteAssetMutationResponse: Partial; DeletePeerInput: Partial; DeletePeerMutationResponse: Partial; + DeleteTenantMutationResponse: Partial; DepositAssetLiquidityInput: Partial; DepositEventLiquidityInput: Partial; DepositOutgoingPaymentLiquidityInput: Partial; @@ -1949,6 +2080,10 @@ export type ResolversParentTypes = { SetFeeInput: Partial; SetFeeResponse: Partial; String: Partial; + Tenant: Partial; + TenantEdge: Partial; + TenantMutationResponse: Partial; + TenantsConnection: Partial; TriggerWalletAddressEventsInput: Partial; TriggerWalletAddressEventsMutationResponse: Partial; UInt8: Partial; @@ -1957,6 +2092,7 @@ export type ResolversParentTypes = { UpdateIncomingPaymentInput: Partial; UpdatePeerInput: Partial; UpdatePeerMutationResponse: Partial; + UpdateTenantInput: Partial; UpdateWalletAddressInput: Partial; UpdateWalletAddressMutationResponse: Partial; VoidLiquidityWithdrawalInput: Partial; @@ -1972,6 +2108,7 @@ export type ResolversParentTypes = { WebhookEventFilter: Partial; WebhookEventsConnection: Partial; WebhookEventsEdge: Partial; + WhoamiResponse: Partial; WithdrawEventLiquidityInput: Partial; }; @@ -2093,6 +2230,11 @@ export type DeletePeerMutationResponseResolvers; }; +export type DeleteTenantMutationResponseResolvers = { + success?: Resolver; + __isTypeOf?: IsTypeOfResolverFn; +}; + export type FeeResolvers = { assetId?: Resolver; basisPoints?: Resolver; @@ -2176,7 +2318,7 @@ export type LiquidityMutationResponseResolvers = { - __resolveType: TypeResolveFn<'AccountingTransfer' | 'Asset' | 'Fee' | 'IncomingPayment' | 'OutgoingPayment' | 'Payment' | 'Peer' | 'WalletAddress' | 'WalletAddressKey' | 'WebhookEvent', ParentType, ContextType>; + __resolveType: TypeResolveFn<'AccountingTransfer' | 'Asset' | 'Fee' | 'IncomingPayment' | 'OutgoingPayment' | 'Payment' | 'Peer' | 'Tenant' | 'WalletAddress' | 'WalletAddressKey' | 'WebhookEvent', ParentType, ContextType>; createdAt?: Resolver; id?: Resolver; }; @@ -2197,11 +2339,13 @@ export type MutationResolvers, ParentType, ContextType, RequireFields>; createQuote?: Resolver>; createReceiver?: Resolver>; + createTenant?: Resolver>; createWalletAddress?: Resolver>; createWalletAddressKey?: Resolver, ParentType, ContextType, RequireFields>; createWalletAddressWithdrawal?: Resolver, ParentType, ContextType, RequireFields>; deleteAsset?: Resolver>; deletePeer?: Resolver>; + deleteTenant?: Resolver>; depositAssetLiquidity?: Resolver, ParentType, ContextType, RequireFields>; depositEventLiquidity?: Resolver, ParentType, ContextType, RequireFields>; depositOutgoingPaymentLiquidity?: Resolver, ParentType, ContextType, RequireFields>; @@ -2213,6 +2357,7 @@ export type MutationResolvers>; updateIncomingPayment?: Resolver>; updatePeer?: Resolver>; + updateTenant?: Resolver>; updateWalletAddress?: Resolver>; voidLiquidityWithdrawal?: Resolver, ParentType, ContextType, RequireFields>; withdrawEventLiquidity?: Resolver, ParentType, ContextType, RequireFields>; @@ -2325,10 +2470,13 @@ export type QueryResolvers>; quote?: Resolver, ParentType, ContextType, RequireFields>; receiver?: Resolver, ParentType, ContextType, RequireFields>; + tenant?: Resolver>; + tenants?: Resolver>; walletAddress?: Resolver, ParentType, ContextType, RequireFields>; walletAddressByUrl?: Resolver, ParentType, ContextType, RequireFields>; walletAddresses?: Resolver>; webhookEvents?: Resolver>; + whoami?: Resolver; }; export type QuoteResolvers = { @@ -2383,6 +2531,35 @@ export type SetFeeResponseResolvers; }; +export type TenantResolvers = { + apiSecret?: Resolver; + createdAt?: Resolver; + deletedAt?: Resolver, ParentType, ContextType>; + email?: Resolver, ParentType, ContextType>; + id?: Resolver; + idpConsentUrl?: Resolver, ParentType, ContextType>; + idpSecret?: Resolver, ParentType, ContextType>; + publicName?: Resolver, ParentType, ContextType>; + __isTypeOf?: IsTypeOfResolverFn; +}; + +export type TenantEdgeResolvers = { + cursor?: Resolver; + node?: Resolver; + __isTypeOf?: IsTypeOfResolverFn; +}; + +export type TenantMutationResponseResolvers = { + tenant?: Resolver; + __isTypeOf?: IsTypeOfResolverFn; +}; + +export type TenantsConnectionResolvers = { + edges?: Resolver, ParentType, ContextType>; + pageInfo?: Resolver; + __isTypeOf?: IsTypeOfResolverFn; +}; + export type TriggerWalletAddressEventsMutationResponseResolvers = { count?: Resolver, ParentType, ContextType>; __isTypeOf?: IsTypeOfResolverFn; @@ -2487,6 +2664,12 @@ export type WebhookEventsEdgeResolvers; }; +export type WhoamiResponseResolvers = { + id?: Resolver; + isOperator?: Resolver; + __isTypeOf?: IsTypeOfResolverFn; +}; + export type Resolvers = { AccountingTransfer?: AccountingTransferResolvers; AccountingTransferConnection?: AccountingTransferConnectionResolvers; @@ -2506,6 +2689,7 @@ export type Resolvers = { CreateWalletAddressMutationResponse?: CreateWalletAddressMutationResponseResolvers; DeleteAssetMutationResponse?: DeleteAssetMutationResponseResolvers; DeletePeerMutationResponse?: DeletePeerMutationResponseResolvers; + DeleteTenantMutationResponse?: DeleteTenantMutationResponseResolvers; Fee?: FeeResolvers; FeeEdge?: FeeEdgeResolvers; FeesConnection?: FeesConnectionResolvers; @@ -2539,6 +2723,10 @@ export type Resolvers = { Receiver?: ReceiverResolvers; RevokeWalletAddressKeyMutationResponse?: RevokeWalletAddressKeyMutationResponseResolvers; SetFeeResponse?: SetFeeResponseResolvers; + Tenant?: TenantResolvers; + TenantEdge?: TenantEdgeResolvers; + TenantMutationResponse?: TenantMutationResponseResolvers; + TenantsConnection?: TenantsConnectionResolvers; TriggerWalletAddressEventsMutationResponse?: TriggerWalletAddressEventsMutationResponseResolvers; UInt8?: GraphQLScalarType; UInt64?: GraphQLScalarType; @@ -2555,5 +2743,6 @@ export type Resolvers = { WebhookEvent?: WebhookEventResolvers; WebhookEventsConnection?: WebhookEventsConnectionResolvers; WebhookEventsEdge?: WebhookEventsEdgeResolvers; + WhoamiResponse?: WhoamiResponseResolvers; };